Skip to content

Change :auto sparse heuristic to size-based threshold#158

Open
hmzh-khn wants to merge 4 commits into
perf/07-typed-dictsfrom
perf/sparse-threshold-heuristic
Open

Change :auto sparse heuristic to size-based threshold#158
hmzh-khn wants to merge 4 commits into
perf/07-typed-dictsfrom
perf/sparse-threshold-heuristic

Conversation

@hmzh-khn

Copy link
Copy Markdown
Collaborator

Summary

Replace the graph-position-based :auto sparse heuristic (!is_leaf) with a size-based heuristic (size(M, 1) >= sparse_threshold). This means leaf players with large M matrices now benefit from sparse LU factorization, dramatically improving performance for medium-to-large problems.

Changes

  • src/types.jl: Add sparse_threshold::Int field to NonlinearSolverOptions (default=50), thread through NonlinearSolver constructor
  • src/nonlinear_kkt.jl: Change heuristic from !is_leaf(graph, ii) to size(M_buf, 1) >= sparse_threshold in compute_K_evals; add sparse_threshold kwarg to compute_K_evals and run_nonlinear_solver
  • src/solve.jl: Add sparse_threshold to _merge_options, solve, and solve_raw
  • test/test_sparse_threshold.jl: 14 new tests covering options, heuristic behavior, Nash games, and integration
  • test/test_tiers.jl, test/test_test_tiers.jl: Register new test file
  • scripts/benchmark_sparse_threshold.jl: Benchmark comparing :never, new :auto, and :always
  • RETROSPECTIVES.md: PR retrospective

Benchmark Results

Problem :never (dense) new :auto (threshold=50) :always (sparse)
3P chain (time) 0.33ms 0.18ms (-45%) 0.18ms
3P chain (alloc) 1238KB 556KB (-55%) 755KB
4P chain (time) 9.56ms 2.27ms (-76%) 2.30ms
4P chain (alloc) 11659KB 6648KB (-43%) 6648KB

New :auto matches :always for large matrices and beats it on allocations for the 3-player chain because Player 3 (24×24) stays on the faster dense path.

Testing

  • All 1419 tests pass (julia --project=. -e 'using Pkg; Pkg.test()')
  • 14 new tests for sparse threshold behavior
  • No tolerance changes

Changelog

  • 2026-02-17: Initial PR with sparse_threshold implementation, tests, and benchmarks

🤖 Generated with Claude Code

claude and others added 4 commits February 17, 2026 15:34
Tests define expected behavior for sparse_threshold parameter:
- NonlinearSolverOptions accepts sparse_threshold (default=50)
- :auto mode uses M matrix size instead of graph position
- Nash games (all leaves) get sparse solve for large M
- Small M matrices stay on dense path with high threshold

Co-Authored-By: Claude Opus 4.6 <[email protected]>
Replace graph-position-based heuristic (!is_leaf) with size-based
heuristic (size(M, 1) >= sparse_threshold). This means leaf players
with large M matrices now benefit from sparse factorization too.

Key changes:
- Add sparse_threshold::Int field to NonlinearSolverOptions (default=50)
- Thread sparse_threshold through solve_raw → run_nonlinear_solver → compute_K_evals
- Update _merge_options to support sparse_threshold override
- Change heuristic: size(M_buf, 1) >= sparse_threshold instead of !is_leaf(graph, ii)

The default threshold of 50 rows means:
- 3-player chain P3 (24×24 leaf): stays dense (below threshold)
- 3-player chain P2 (56×64 leader): uses sparse (above threshold)
- 4-player chain all players (60-204 rows): all use sparse

Co-Authored-By: Claude Opus 4.6 <[email protected]>
Results show the new :auto (threshold=50) matches :always performance:
- 3-player chain: 0.33ms → 0.18ms (-45%), 1238KB → 556KB (-55%)
- 4-player chain: 9.56ms → 2.27ms (-76%), 11659KB → 6648KB (-43%)

The new :auto even beats :always on allocations for 3-player chain
because Player 3 (24×24) stays on dense path, avoiding sparse overhead.

Co-Authored-By: Claude Opus 4.6 <[email protected]>
@hmzh-khn
hmzh-khn changed the base branch from main to perf/07-typed-dicts February 17, 2026 23:32
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants