Repository navigation
Fused LDLᵀ: static pivot replacement, matching and iterative refinement; KKTSolver keeps the fused pipeline (refs #194) - #195
Merged
Conversation
) The no-pivot LDLᵀ of the fused kernel replaces a pivot with |d| < pivot_epsilon (or d == 0) by sign·pivot_epsilon, the rule of the stock static kernel: sign from pivot_sign (captured in factor-column order at plan build) or the sign of d. Regime-A subtrees, narrow panels (with the write-back of the replaced pivot into the sh diagonal) and in-kernel wide panels share one pure helper, so the factorization stays bitwise deterministic. The epsilon is resolved_pivot_epsilon (the stock and cuDSS default); pivot_epsilon = 0 restores the previous behaviour bit for bit. NaN and Inf pivots still fail the front. pivot_kind is written at all three sites; getparam(F, "npivots") counts the replaced pivots. "pivot_sign" is settable on "S"; pivot_epsilon_alg other than "default" is rejected for "S"; wides_potrf with epsilon > 0 is rejected. Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MsLzHkzHJjM2g5P6ZyZKhr
…refs #194) FusedSolver "S" accepts matching_alg (settable; "SPD" keeps rejecting it). Every analysis re-applies the pivot-pairs rule of KKTSolver's stock pipeline (_fused_pairs!: "none" without matching, "default" with it, so the pairs of the matching's cycles shape the ordering). The factorization already read the scaled values (_factor_values); the fused solve now passes the matching's permutations and scalings to the permutation kernels (solve_io), as DirectSolver's solve_alg = "algo1" does. getparam forwards "perm_matching", "scale_row" and "scale_col". Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MsLzHkzHJjM2g5P6ZyZKhr
…t replacement, docs, tests (refs #194) KKTSolver's `fused = nothing` now selects the fused pipeline for every `pivoting = :none` factorization on a backend with forward progress, also with matching, pivot_epsilon and pivot_sign, and `fused = true` accepts them; pivot_sign is set on the inner solver of either pipeline. Docstrings of KKTSolver and FusedSolver (static pivoting, the NaN/Inf deviation from the stock kernel, captured epsilon and sign, npivots, matching), the KKT, fused, MadNLP and expectations pages, and the CHANGELOG follow. Tests: test_fused covers the replaced zero pivot with its requested sign, the replacement at every kernel site against the inner stock factorization, matching with determinism, graph replay and the allocation budget, and the new restrictions; the zero-pivot failure test sets pivot_epsilon = 0. test_kkt flips the pipeline expectations and the former fused = true refusals, and adds the static LDLᵀ with matching, pivot_epsilon and pivot_sign on each pipeline. Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MsLzHkzHJjM2g5P6ZyZKhr
…Solver keyword ir_n_steps (refs #194) FusedSolver accepts "ir_n_steps" (read at every solve, both structures): the fused solve is followed by plain refinement through the stock driver (`_refine_phase!` / `refine!` with `fused`), the residual taken with the caller's unscaled values, the corrections through the matching's permutations/scalings and the fused sweeps (solve graph when captured). ir_n_steps = 0 keeps the previous path. The refinement workspace is allocated at the analysis or by setparam! after it; X === B copies B for the residual. ir_tol > 0 (host synchronization per step) and ir_mode = "fgmres" raise NotSupportedError at analysis and solve. getparam "ir_n_steps" reports the steps of the last solve, as DirectSolver. KKTSolver: keyword ir_n_steps, set on either pipeline. Docs, CHANGELOG, tests in test_fused.jl and test_kkt.jl. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MsLzHkzHJjM2g5P6ZyZKhr
…_epsilon default change under Changed (refs #194) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MsLzHkzHJjM2g5P6ZyZKhr
…ipeline; test_kkt pair test on the default pipeline (refs #194) Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MsLzHkzHJjM2g5P6ZyZKhr
michel2323
force-pushed
the
fused-ldl-matching
branch
from
November 11, 2026 16:28
1272bd0 to
c11f803
Compare
michel2323
marked this pull request as ready for review
November 11, 2026 16:39
Contributor
There was a problem hiding this comment.
VERDICT: APPROVE (head c11f803)
Not a TASKS.md task (follow-up of #193/#194 on top of the merged #192), so the task-section rules do not apply; reviewed against the issue, AGENTS.md and PLAN.md §1.2–§1.4/§3.1.
Checked
- Scope:
src/fused.jl,src/kkt.jl, tests, docs, CHANGELOG only.PLAN.md,TASKS.mdandManifest.tomluntouched. Parameter strings (pivot_epsilon,pivot_sign,matching_alg,ir_n_steps,perm_matching,scale_row,scale_col) and the phase strings are the cuDSS ones. - Static pivot:
_ldl_static_pivotis the stock_lt_select!rule withpivot_type = N(requested sign, else the sign ofd,+for 0), differing only on NaN/Inf, which the docstring,lib/kkt.mdand the CHANGELOG state.pivot_signis read in factor-column order throughS.perm, as the stock kernel does (psign[perm[g]]); the symmetric matching keepscperm = S.perm, so no second permutation is missing. The pure function keeps the factorization bitwise deterministic. - All three fused pivot sites write
d,pivot_kindand the replaced pivot where the later reads expect it: the subtree keepsDind(unit diagonal inbuf), the narrow panel writes the diagonal slot back in_front_ldl_scale!after the barrier (the loop only touchesi, k > j, as the subtree loop does), the in-kernel wide panel writes it underli == 1before the barrier.npivots/inertiaare device counts at the phase boundary, no@allowscalar. - Matching:
_factor_valuesrescales into the samest.valuesbuffer at every (re)factorization, so the captured factorization graph stays valid (F.gnz === F.nzval); the solve passes thesolve_iopermutations and scalings into the existing permutation kernels, no extra launch._fused_pairs!applies theKKTSolverrule (pairs with jobs 5/6 only) at every analysis. - Refinement: residual from
vec(solver.A.nzval)(unscaled, the user values), corrections throughfused = fs,ir_tol = 0andir_mode = "ir"enforced at analysis and at every refining solve (no host sync), workspace allocated at analysis or bysetparam!outside the solve phase, aliasingX === Bhandled by theW.Bccopy,ir_stepssemantics as onDirectSolver. KKTSolver: only:bkand backends without forward progress go to the stock pipeline;pivot_sign,pivot_epsilon,ir_n_stepsset on either inner solver; negativeir_n_stepsrejected by the option parser.- Conventions: generic
T/INT(epscast toreal(T)at launch,Int8signs),@Constonpsg, no atomics, no allocation inside the numeric/solve phases, errors fromsrc/errors.jl, no backend code outsideext/. - Tests: new testsets loop over
BACKENDSandeltypes_among((Float32, Float64))(the fused pipeline is real-only),Random.seed!(666),tol/growth_tol/weak_pivot_tolfrom the helpers; the removedNotSupportedErrorassertions are replaced by positive tests of the same options; the site-coverage test checks all four kernel sites; graph replay and allocation budgets covered. One nit inline (a hard-coded1e-6tolerance). - PR body vs diff: every behaviour claim matches the code; the body states honestly that only the new and changed testsets were run locally (CPU and CUDA, Float32/Float64) and leaves the full suite to CI.
- CI: docs built; the CPU, CUDA, AMDGPU and Aurora legs are still pending at review time, so nothing contradicts the body. Note for the owner: two tests assert the unrefined residual is above
tol(T)(kkt_slack_matrix(T, 200, 100, 0)underalgo5); the body measures a 10–30× margin for Float32 on CPU/CUDA, unmeasured on AMDGPU/oneAPI.
Blocking findings: none.
Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com>
#194) The ir_n_steps testset compared the graph-replayed solves of a fresh FusedSolver against the eager solve of F. The fresh solver analyzes A2 (a different scaling, hence different pivot pairs and ordering) and takes SDSTuning() defaults instead of the platform tuning (solve_workgroup 64 vs 128 on 2048-thread parts such as the H200 runner), so bitwise equality could not hold. Switch F's own tuning to graph_max_n = typemax(Int) between solves, as the #174 testset does: same analysis, factor and workgroups; three solves (capture, then replays) bitwise equal to F's eager solve. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MsLzHkzHJjM2g5P6ZyZKhr
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Refs #194
Task: not a
TASKS.mdtask; follow-up of #193 on top of #192. Stacked on PR #192 (basemaint/kkt-solver-0.4.0). The branch will be rebased once #192 is updated or merged: #192 gets one more commit with review fixes that is not pushed yet.What was built:
L D Lᵀ(FusedSolver, structure"S"):pivot_epsilonandpivot_signare applied at all three fused pivot sites (regime-A subtrees, narrow panels, in-kernel wide panels), with the same rule as the stock no-pivot kernel. Replaced pivots are counted in"npivots"and take the requested sign in"inertia". The defaultpivot_epsilonis now the stock one; it was0before, and settingpivot_epsilon = 0restores the old behaviour. A NaN or Inf pivot still fails the factorization."S"(matching_alg, including"algo5"and"algo6"): the factorization runs on scaled values, the 2×2 pivot pairs from the matching shape the ordering, and the solve undoes the scaling inside the permutation kernels, so no extra launch."perm_matching","scale_row"and"scale_col"are forwarded.KKTSolverstays on the fused pipeline when matching or pivot replacement is requested. 0.4.0: KKTSolver front end, MadNLP binding moves to MadNLP #192 sent these cases to the stock pipeline. Now onlypivoting = :bkand backends without forward progress use the stock pipeline.ir_n_stepson the fused solve: plain refinement against the caller's unscaled values, with no host synchronization, and captured by the solve graph. It is also a newKKTSolverkeyword that works on both pipelines. On fused,ir_tol > 0andir_mode = "fgmres"raiseNotSupportedError.lib/fused.md,lib/kkt.md,man/madnlp.md,man/expectations.md), docstrings and CHANGELOG are updated.Evidence (measured with CUDA on the owner's machine; timings are single wall-clock runs, not Slurm benchmarks)
60 recorded KKT matrices from a CO2-capture flowsheet solve. Settings: static pivoting +
algo5, ε = 1e-10,pivot_sign. Every row had 0 wrong inertias and 0 failures.Live MadNLP design solve of the flowsheet. Preset: ShiftedPreset, with the options patched in memory (MadNLP's binding does not pass
ir_n_stepsyet).What the live solve shows:
Open points:
sds_iroption, static + algo6 + 2 steps) is a follow-up on MadNLP PR #728, once this PR is green.Tests: new testsets in
test/test_fused.jlandtest/test_kkt.jlcover:growth_tol);algo5/algo6with refactorization, bitwise reproducibility, graph replay and allocation budget;ir_n_stepswith matching and pivot replacement, in place and out of place, against the stock pipeline, plus the graph replay of the corrections;KKTSolverwith matching,pivot_epsilon,pivot_sign, deferred analysis andir_n_stepson every pipeline the backend runs.Local status: only the new and changed testsets were smoke-run, on CPU and CUDA for Float32 and Float64. The full suite (all four element types, CPU, CUDA, AMDGPU, Aurora) is left to CI.
Two tests assert that the unrefined residual on
kkt_slack_matrix(T, 200, 100, 0)underalgo5is abovetol(T), which shows the test is not vacuous: without refinement the solve really is inaccurate. Margin measured over 5 seeds, on the CPU and CUDA backends and on both pipelines (always 50 replaced pivots):tol(T);tol(T);The inaccuracy comes from the ε perturbation of the 50 replaced pivots, not from rounding, so I do not expect AMDGPU or oneAPI to close the 10× Float32 margin. I have not run it there.
Deviations from PLAN.md / TASKS.md:
FusedSolver:"S"defaultpivot_epsilonchanged from0to the stock default (listed under "Changed" in CHANGELOG).wides_potrf = true(an internal, non-default plan option) refuses a nonzero LDLpivot_epsilonwithNotSupportedError.Follow-up issues opened: none (MadNLP binding: follow-up on MadNLP PR #728).
🤖 Generated with Claude Code
https://claude.ai/code/session_01MsLzHkzHJjM2g5P6ZyZKhr