Skip to content

Add technical report + switch base IP solver to KLU - #56

Merged
dfridovi merged 42 commits into
mainfrom
docs/technical-report
Aug 4, 2026
Merged

Add technical report + switch base IP solver to KLU#56
dfridovi merged 42 commits into
mainfrom
docs/technical-report

Conversation

@dfridovi

@dfridovi dfridovi commented Aug 4, 2026

Copy link
Copy Markdown
Member

Summary

  • Technical report (docs/paper/): a short IEEEtran two-column write-up of the package — MCP/KKT background, the interior-point algorithm, batched CPU/GPU solving, differentiable solving (implicit-function-theorem sensitivities), and an experiments section benchmarking against PATH across single-instance reliability, thread scaling, and GPU-vs-CPU batched throughput. Compiled PDF and all figure-generation scripts/data are included (benchmark/results/), along with a verify_paper_numbers.jl script that recomputes every cited number from raw data and checks it against the paper's claims. README.md now links the arXiv preprint and includes a citation entry.
  • KLU as the default linear solver in the (unbatched) interior-point method (src/solver.jl): replaces UMFPACKFactorization() as the default linear_solve_algorithm, giving ~4-6x faster per-iteration solves at identical reliability by reusing KLU's symbolic factorization/pivot ordering across Newton steps (matching what the batched CPU path already does). README.md's performance-status blurb is updated to reflect current, more nuanced GPU-vs-CPU numbers (GPU wins on large dense per-instance systems / large batches; CPU wins on the trajectory game at all tested horizons due to its active-set skip).

Test plan

  • verify_paper_numbers.jl passes (60/60 checks against raw benchmark data)
  • Existing test suite unaffected by the KLU default change (interior-point solver correctness is solver-agnostic)
  • Reviewer: skim docs/paper/main.pdf for the rendered report

dfridovi and others added 30 commits July 23, 2026 16:40
First draft: abstract, teaser figure placeholder, and full introduction for
a short report advertising the batched interior-point solver against PATH,
with a focus on trajectory-game benchmarks. Vendors IEEEtran.cls/.bst locally
since neither is available via this machine's TeX Live install, and adds a
Makefile wrapping the pdflatex/bibtex build sequence.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…etric view)

Adds Sec. II with four subsections: the general box-form MCP definition,
the KKT-to-primal-dual-MCP reduction that sets up notation for the solver
design section, the generalization to noncooperative games/GNEPs (with the
lane-change trajectory game introduced as the running example tied to the
teaser figure), and the parametric view motivating batching/differentiability.
Two \todo{add reference} placeholders remain for citations.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- New paragraph deriving the concatenated (G, H) for the N-agent game
  case, spelling out how shared multipliers lambda_sh/mu_sh (single
  vectors, not per-agent copies) couple every agent's stationarity
  block -- cross-checked against src/game.jl's actual construction.
- New `runningexample` environment (bold title, light grey colorbox,
  no extra package deps) wrapping the lane-change trajectory game
  paragraph, for reuse in the batched-solving section.
- Consistency fixes: unify x_{-i}/x_{\neg i} notation, and align the
  KKT subsection header (which had picked up "and noncooperative
  games" without the matching content) with the games subsection that
  follows it.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- Adds the primal-dual interior-point pseudocode as a proper
  algorithm2e float (alg:solver), with vertical scoping bars,
  line numbers, and a ruled caption box.
- Fills in the two \todo line-reference pointers left in the prose:
  turns out cleveref's extended tracking is broken for algorithm2e's
  per-line counter (every \cref resolved to "Line 1" regardless of
  actual position, confirmed via the .aux file), so these use plain
  \ref instead, which is what the original placeholder text already
  assumed.
- Gives explicit rho/epsilon tightening and loosening formulas and
  introduces the inner-loop iteration counter k, both lifted directly
  from src/solver.jl's actual update rule (tightening_rate/
  loosening_rate, max_inner_iters) rather than invented generically.
- Fixes a stray \lamdba typo that was breaking compilation.
- Needed three more TeX packages not present in this machine's minimal
  TeX Live install (algorithm2e, ifoddpage, relsize); installed via
  `tlmgr --usermode`, which needs no sudo.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Covers the device/strategy-agnostic verb set (residual!/jacobian!/
factorize!/ldiv!) that lets Algorithm 1 run unchanged on CPU or GPU, the
three changes needed to batch it (per-instance rho/epsilon, closed-form
batched linesearch, stall-freezing), and the CPU (KLU) / GPU (cuDSS)
backends, grounded in the actual BatchedInteriorPoint implementation and
README. Extends the running-example contentbox to the batched setting and
adds a Remark (new theorem environment) flagging BatchedDense/SparseSingle
as not-yet-implemented future strategies.
Algorithm 2 makes per-instance stall detection explicit (status_b,
stall_b), grounded in the actual BatchedInteriorPoint loop. Fills in the
Backends subsection with why CPU and GPU treat frozen instances
differently: CPU's independent per-instance KLU factorization lets it
literally skip inactive instances (cost scales with active count), while
cuDSS's batched GPU solver processes the whole batch every round
regardless (confirmed via ext/MixedComplementarityProblemsCUDSSExt.jl,
where `active` is accepted but ignored). Also fixes a stray math-mode
\: used in text mode that broke compilation.
Player index vs. batch index share the same subscript convention, and
batched iterates are capitalized while batched stepsizes aren't. Left as
a \todo in the Batched section rather than fixing now.
Fill in the previously-stubbed Section VII with the full four-axis
structure agreed on: (i) single-instance reliability/speed vs PATH,
(ii) batched throughput vs PATH, (iii) CPU thread scaling, and (iv) the
regime-dependent GPU-vs-CPU crossover.

All numbers, tables, and figures are left as \todo placeholders to be
populated from a fresh benchmark run. Notable open items baked into the
TODOs:
  - single-instance baseline includes an InteriorPoint (KLU) row, to test
    whether swapping the unbatched solver's UMFPACK factorization for KLU
    (as the batched CPU path uses) closes the gap to PATH.
  - GPU crossover framed honestly (GPU wins only once per-instance size is
    large enough to amortize cuDSS's batched-factorization overhead), with
    the solved-fraction and QP-sweep confounds flagged as caveats.

Adds \usepackage{multirow} for the new tables.

Note for the benchmark step: this branch is behind origin/main by the
PR #55 fixes (trajectory-game solved-fraction fix, cuDSS algo1 tuning),
which must be merged before regenerating numbers.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The unbatched InteriorPoint solver factorized ∇F with UMFPACK. Switch the
default to KLU (LinearSolve's KLUFactorization, whose default reuse_symbolic
reuses the symbolic analysis and pivot ordering across Newton steps and
refactorizes in place). ∇F is always sparse (SparseFunction) with a fixed
pattern, so this is safe and makes each per-iteration solve ~4-6x faster at
identical reliability -- the same reason the batched CPU path already uses KLU.

Verified: full test suite passes (555/555); single-instance timing in
benchmark/results/table1_summary.txt (per-solve median QP 0.58ms vs UMFPACK
2.49ms; game 0.75ms vs 4.43ms; same solved fractions).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Adds a `use_initial_guess` kwarg to `benchmark_throughput` so the batched solve
can be forced to cold-start (matching the always-cold PATH and unbatched-IP
baselines) for apples-to-apples comparison, or warm-started (the game's
zero-input rollout) for the realistic receding-horizon regime. Both are reported.

Adds benchmark/results/ with the raw data behind the technical report's
Experiments section: throughput vs PATH, GPU-vs-CPU scaling, per-instance
distributions (bimodal from infeasible QP draws), the GPU/CPU per-call kernel
crossover and active-set-skip mechanism, and the Table I KLU-vs-UMFPACK
single-instance comparison. See benchmark/results/README.md for the data
dictionary, findings (incl. the correction to PR #55's GPU-beats-CPU headline),
and reproduction commands. Driver + analysis scripts in results/scripts/.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The previous "GPU beats 32 CPU threads 2.5-2.8x on the trajectory game" claim
(from PRs #54/#55) was wrong for end-to-end throughput: that ratio had the GPU
in the numerator, i.e. the GPU is ~2.5x slower, mislabeled as GPU-favorable.

Re-measured (median-of-N, RTX 4090): the CPU is faster than the GPU end-to-end
on the game at every tested horizon; the GPU wins end-to-end only on large dense
per-instance systems (>=128-primal QPs, 2-3x) or large batches. Both backends
still clear a batch far faster than sequential PATH. The GPU factorization kernel
does cross over CPU at large d, but the CPU's active-set skip (it factorizes only
the still-active instances each Newton step, while cuDSS always does the whole
batch) keeps the CPU ahead end-to-end. Full data in benchmark/results/.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Populate Section VII (setup, single-instance, batched throughput, GPU-vs-CPU)
with the fresh benchmark numbers, and correct the GPU-vs-CPU narrative: the
CPU batched backend beats the GPU end-to-end on the trajectory game at every
tested horizon; the GPU pulls ahead only on large, dense per-instance systems
(large-n_x QPs). The per-call factorization kernel does cross over in the GPU's
favor at large KKT dimension, but the active-set skip -- the CPU factorizes only
still-active instances each step while cuDSS always does the whole batch -- keeps
the CPU ahead end-to-end. Abstract and intro updated to match.

The single-instance section reflects the new KLU default: with KLU the unbatched
solver is per-solve competitive with, and often faster than, PATH.

Remaining (left as TODOs): the thread-scaling subsection numbers, the three
plots (teaser (b), thread scaling, GPU vs CPU), the teaser schematic, and the
Discussion section.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
throughput_klu.csv: clean single-run throughput (PATH, sequential IP, batched
CPU) with KLU as the default linear solver -- source for Table II.
thread_scaling.csv: batched throughput vs CPU thread count (1-32) for QP and
game. Plus the generating scripts and README data-dictionary entries.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
dfridovi and others added 12 commits July 30, 2026 16:33
Report batched throughput vs CPU thread count: sublinear scaling with
diminishing returns; the trajectory game peaks at the 16 physical cores and
regresses under simultaneous multithreading, while the larger QP batch gains
marginally out to 32 threads. Figure TODO notes the game should be re-run at
N=1024 for a cleaner curve (current N=256 run is noisy at low thread counts).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Both the QP and trajectory game now use a fixed batch of N=1024, removing
the earlier N=256 game artifact (noisy super-linear low-thread points).
Clean result: throughput gains concentrate in the first ~4 threads, then
both curves plateau around physical-core saturation with no systematic SMT
gain. Update the prose, figure caption, and drop the stale "re-run at
N=1024" todo note.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Replace the stub with three paragraphs grounded in the experiments:
batching (not hardware) is the decisive speedup and the verb-set
abstraction makes it portable; the CPU-vs-GPU choice is regime-dependent
(active-set skip), with an active-set-aware cuDSS call as the natural GPU
fix; limitations (narrow benchmark families, long-horizon solved-fraction
decline) motivate a broader suite and stronger globalization, and
differentiability via the implicit function theorem on the KKT conditions
is flagged as the primary next step.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Two related benchmark-fairness/consistency fixes:

Problem-size study now median-of-5. Added a `problem_size_raw` stage to
raw_data.jl that fills per_rep.csv with 5-rep batched CPU/GPU timings for
the larger per-instance sizes (QP p64i32/p128i64; game T20/T30/T40/T50 at
B=1024, cold+warm). The base configs p32i16/T10 already had >=30 reps, so
both figure panels now draw median-of-N from one file. Updated the §VII-D
game numbers to medians (T=30: GPU 8.0s vs CPU 4.1s, 1.9x; T=50: 16.3s vs
12.7s) and the benchmark README to match.

PATH held to matched tolerance. PATH was running at its built-in default
convergence_tolerance=1e-6 while our solver used tol=1e-4 -- a 100x tighter
bar for PATH. Every PATH call now passes convergence_tolerance=tol
(batched_benchmark.jl, path.jl, raw_data.jl, table1_klu.jl). rerun_path_tol.jl
re-timed PATH at 1e-4 and patched only the PATH rows of every results CSV,
leaving all batched/IP/GPU rows byte-identical. Effect is negligible
(identical solved counts, times within run-to-run noise) since PATH's
quadratic convergence drives the residual below both thresholds in the same
iteration -- so the comparison is now provably fair with no material change.
Updated Table II PATH numbers and added a fairness note to the setup section.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- docs/paper/figures/: scripts (+ output PDFs) that generate the four
  figures main.tex includes (teaser schematic/throughput, thread
  scaling, GPU-vs-CPU), plus the car-icon assets and prep script used
  by the schematic.
- benchmark/results/scripts/verify_paper_numbers.jl: recomputes every
  cited number in the Experiments section from the raw benchmark data
  and diffs against the paper's claims.
- benchmark/results/scripts/ad_gradient_check.jl: finite-difference
  correctness check for the AD sensitivities described in the new
  Differentiable Solving section.
@dfridovi
dfridovi merged commit c3b85f6 into main Aug 4, 2026
2 checks passed
@dfridovi
dfridovi deleted the docs/technical-report branch August 4, 2026 03:25
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.

1 participant