Skip to content

T35: deterministic fused pipeline — parent-pull extend-add, ordered wide tiles, pull forward solve (deterministic_mode = 1) - #182

Open
michel2323 wants to merge 2 commits into
mainfrom
task/T35-deterministic-fused
Open

michel2323 wants to merge 2 commits into
mainfrom
task/T35-deterministic-fused

Conversation

@michel2323

Copy link
Copy Markdown
Member

Closes #180

Task: T35 — Fused pipeline: deterministic extend-add and solve (deterministic_mode = 1 on FusedSolver); refs #177, #109, #82

What was built:

  • src/fused.jl: the extend-add of C-group parents is a parent pull (ROLE_EA per parent column block, children in child_list order over host-computed packed CB ranges, plain adds). It replaced the atomic push; the ordered-push candidate was measured and dropped, and both stay at cd94954 (ea_mode). The in-kernel wide-front tiles apply the panels in order through per-32×32-cell Int32 counters. The fused factorization is now bitwise reproducible under either mode.
  • Pull forward solve for deterministic_mode = 1 (fwd2_kernel!/fwd1_kernel! Val(DET), the existing ws.U buffers of PLAN §2.5, SDS._fwd_dense!(det = true) for fronts wider than 256). One CUDA graph is captured per mode.
  • FusedSolver: "deterministic_mode" is settable and read at every solve (_check_fused_options no longer rejects it). memory_estimates adds slot 13 (private CBs, fp.pcb) and slot 14 (the other plan buffers) to slots 1, 2 and 5 (Update-stack placement assumes level-synchronous execution: out-of-level-order kernels (experiment 5) need private contribution blocks or DAG lifetimes #109's reporting).
  • bench/ARCH-TUNING.md: the factor gate restated as (a) bitwise run-to-run under deterministic_mode = 1 and (b) fused vs stock ≤ the stock eps-perturbation response (strict 1e-8 on the well-conditioned harness), plus a T35 section with the A/B.
  • test/test_fused.jl: new testsets "deterministic_mode = 1/0" (laplacian3d 18³ "SPD" merged and "S", grid_kkt_matrix "S"; Float32/Float64; the plan has EA and WTILE roles): 10 refactorizations give bitwise equal factor and D (both modes) and bitwise equal solutions (mode 1); the factor is within growth_tol of stock; allocation budget; mode toggling; memory_estimates. The graph testset also solves under mode 1.

A/B (H200 sm_90, ms, three interleaved rounds, 20 gated refactorizations per configuration). Refactorization, atomic base → ordered push → parent pull (kept):

78k "SPD" 78k "S" lap3d_40 lap3d_40 "S" lap2d_300 bcsstk17 bcsstk38
atomic 18.18 26.66 27.04 65.43 5.38 4.53 3.93
push 18.88 27.00 27.98 66.45 5.60 4.65 3.90
pull 16.54 24.86 24.70 63.16 4.82 3.93 3.28

Ordered wide tiles cost nothing measurable (78k "S" 24.88 / 24.84 ms). The pull forward sweep costs 4–8% of the solve on the 78k (2.23 → 2.42 ms) and lap2d_300 (1.17 → 1.26 ms), so the default stays deterministic_mode = 0. Run-to-run: factor and D are exactly 0 in both modes on every matrix (base: 1.3e-8 on the 78k "SPD", 2.9–4.3e-8 on "S"); the solution is exactly 0 under mode 1. memory_estimates slot 1 before → after: 78k "SPD" 238.1 → 421.2 MiB, lap3d_40 "SPD" 182.8 → 1156.3 MiB (the private CBs: 121.9 / 960.7 MiB). The full tables are in the T35 Report in TASKS.md.

Tests: SDS_TEST_ONLY=test_fused,test_options,test_madnlp PTR_NUM_JOBS=3 julia --project=. -e 'using Pkg; Pkg.test()' (CUDA added to the test env locally, not committed). KA CPU + CUDA (H200), all four element types: 1434 pass, 0 fail, 0 broken. Full suite, AMDGPU, oneAPI: pending CI.

Deviations from PLAN.md / TASKS.md:

  • The ordered extend-add and the ordered wide tiles are the only factorization paths, not variants behind deterministic_mode = 1. The task allows this for a variant that "costs nothing"; the kept pull is faster than the atomic default everywhere. So deterministic_mode only selects the forward solve and is read at every solve; nothing in the plans depends on it, so a change after the analysis neither raises nor rebuilds.
  • Measured on sm_90 only. The pull extend-add also replaces the atomic one on AMD, where it is unmeasured (amdgpu CI checks correctness).
  • The left-looking CB-tile alternative was not tried, since the ordered tiles cost nothing.

Follow-up issues opened: #181 (gate (b): the 78k "S" fused LDLᵀ offset stays 1.1× above the stock eps response, unchanged from main). Refs #109: the private-slot reporting is done, but the placement question for a level-merged stock path stays open.

🤖 Generated with Claude Code

https://claude.ai/code/session_01MsLzHn17wzxqT2iMbuJyUh

michel2323 and others added 2 commits November 3, 2026 16:07
… plan flags, pull forward solve

The A/B state: build_fused_plan(...; ea_mode = :atomic | :push | :pull, ordered_tiles)
and build_fused(...; det). deterministic_mode = 1 on FusedSolver uses :push here; the
measurement in the T35 Report selects the variant kept in the next commit.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MsLzHn17wzxqT2iMbuJyUh
… deterministic_mode = 1 on FusedSolver

The A/B (H200 sm_90, TASKS.md T35 Report): the parent pull is faster than the atomic
extend-add on every harness matrix (78k "SPD" 18.18 -> 16.54 ms, lap3d_40 27.04 -> 24.70),
the ordered push slower (+1-4%); ordered wide tiles cost nothing measurable. Both replace
the atomic paths, so the fused factorization is bitwise reproducible in either mode.
deterministic_mode (settable, read at every solve) selects the pull forward sweep; it costs
4-8% of the solve on the 78k and lap2d_300, so the default stays 0. memory_estimates of a
FusedSolver adds the private CBs (slot 13) and the other plan buffers (slot 14), refs #109.
bench/ARCH-TUNING.md: the factor gate restated (bitwise run-to-run, eps-perturbation bound).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MsLzHn17wzxqT2iMbuJyUh
@michel2323 michel2323 added this to the M11 — Performance milestone Nov 3, 2026
@michel2323 michel2323 added the claude:pr PR opened by the Claude implementer; handled by the Claude pipeline label Nov 3, 2026

@claude claude Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

VERDICT: APPROVE (head 2f20c90)

Reviewed against the T35 text in TASKS.md (main), AGENTS.md, PLAN §2.5/§3.4, the diff of src/fused.jl, test/test_fused.jl, bench/ARCH-TUNING.md and the Report.

Scope. Only the four files of the task change. PLAN.md untouched, no Manifest.toml, TASKS.md edited only in the T35 section (status marker [!] and Report). No public name, parameter string or phase string changes; deterministic_mode stays the cuDSS string and FusedSolver now accepts it. Making the parent pull and the ordered wide tiles the only factorization paths is the "costs nothing → make it the only path" option the task offers, applied to the kept variant and documented as a deviation with the A/B numbers.

Correctness, checked by hand.

  • Parent-pull ROLE_EA: pl_q0 = _packed(a,a,mc) − 1 … pl_q1 − 1 = _packed(mc,b,mc) − 1 is exactly columns a..b of the column-major packed CB, _tri_decode(q, mc) is the inverse of _packed (the same pairing used elsewhere), pcb[cb + q] equals the former pcb[cb + _packed(ii,jj,mc) − 1]. Blocks of one parent own disjoint parent columns (relind increasing, searchsorted bounds), entries of one child are distinct (relind injective), children are separated by barriers: plain adds are race-free and the summation order is fixed. Write-through children are only chosen when they are the parent's sole CB child, so no write-through stores coexist with pull adds. Every wait (cb_done[c]) is on an earlier role, so the in-order-dispatch argument still holds. ea_left host/device accounting matches (one per emitted block).
  • Ordered wide tiles: for panels k < K the tile origin 64k is 32-aligned, so a tile is exactly one cell, and the tile grid of every panel covers every lower-triangle cell with J ≥ 2k'+1 up to cld(f,32) exactly once; the last panel's (unaligned, rb0 = w) tiles only wait and touch cells all earlier panels incremented. The == k−1 spin is safe because a cell is incremented once per panel and the panel-k tile is the only one that advances it to k. The wupd/wtrsml/wcbl protocol is unchanged; wcell is reset per refactorization.
  • Pull forward solve: buffer rows rowptr[v]+w … rowptr[v+1]−1 match the stock §2.5 layout and the stock _sv_scatter_kernel!(Val(true)) accumulates (U -= tmp) with the same sign convention, so the fused pull (zero, add children in child_list order, U -= L21 t) composes with _fwd_dense!(…, det = true) for fronts wider than 256 without double counting. In fwd2 only the front itself writes its columns of Y under DET, so the plain read replacing the atomic coherent read is sound; gpu-scope fences precede the arrival increments. Backward sweep untouched.
  • memory_estimates: slots 13/14 were documented as reserved zeros in the stock table; the additions to slots 1, 2, 5 match the test.

Conventions. Generic in T/INT; host lists built in Int with an explicit NotSupportedError before the Int32 cast of the packed range; no atomics left in assembly or extend-add (the previous violation of the KA rules is removed), atomics only in the default forward solve with the atomic-free variant now present; no @allowscalar; errors from src/errors.jl; docstring of FusedSolver updated.

Tests. The NotSupportedError checks became positive tests; the new testset runs on FUSED_BACKENDS (CPU excluded by the file's documented design), Float32/Float64, "SPD" (wides = :merged) and "S" on laplacian3d 18³ and grid_kkt_matrix 40×40, asserts ROLE_EA and ROLE_WTILE are present, 10 refactorizations bitwise (factor, D, inertia; solution under mode 1), residual ≤ tol(T), growth_tol(T, Nr) against the stock factorization, the allocation budget in both modes, the mode switch at solve time, and the memory-estimate slots. Helpers come from test/utils.jl/test/matrices.jl, Random.seed!(666) present. No @test_broken.

Report. Claims match the diff (parent pull only, ordered tiles only, deterministic_mode read at every solve, graph per mode, slots 13/14, docstring, bench ground rule restated). Deviations listed; follow-ups cite #181 and #109. Test counts plausible. CI (cuda, amdgpu, cpu-only, docs, Aurora) is still pending at review time; the Report states this openly rather than claiming a green full suite.

Blocking findings: none.

Non-blocking nits (no fix required): the Report writes _fwd_dense!(...; det = true) as a keyword, it is positional; fwd1! always passes Val(false) for SUB, which is equivalent because the SOLVE_SUBTREES branch of fwd_entry is empty, but differs in form from bwd_entry.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

claude:pr PR opened by the Claude implementer; handled by the Claude pipeline

Projects

None yet

Development

Successfully merging this pull request may close these issues.

T35 — Fused pipeline: deterministic extend-add and solve (deterministic_mode = 1 on FusedSolver; issues #177, #109, #82)

1 participant