Repository navigation
T35: deterministic fused pipeline — parent-pull extend-add, ordered wide tiles, pull forward solve (deterministic_mode = 1) - #182
michel2323 wants to merge 2 commits into
Conversation
… 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
There was a problem hiding this comment.
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) − 1is exactly columnsa..bof the column-major packed CB,_tri_decode(q, mc)is the inverse of_packed(the same pairing used elsewhere),pcb[cb + q]equals the formerpcb[cb + _packed(ii,jj,mc) − 1]. Blocks of one parent own disjoint parent columns (relindincreasing,searchsortedbounds), entries of one child are distinct (relindinjective), 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_lefthost/device accounting matches (one per emitted block). - Ordered wide tiles: for panels
k < Kthe tile origin64kis 32-aligned, so a tile is exactly one cell, and the tile grid of every panel covers every lower-triangle cell withJ ≥ 2k'+1up tocld(f,32)exactly once; the last panel's (unaligned,rb0 = w) tiles only wait and touch cells all earlier panels incremented. The== k−1spin is safe because a cell is incremented once per panel and the panel-ktile is the only one that advances it tok. Thewupd/wtrsml/wcblprotocol is unchanged;wcellis reset per refactorization. - Pull forward solve: buffer rows
rowptr[v]+w … rowptr[v+1]−1match 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 inchild_listorder,U -= L21 t) composes with_fwd_dense!(…, det = true)for fronts wider than 256 without double counting. Infwd2only the front itself writes its columns ofYunder 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.
Closes #180
Task: T35 — Fused pipeline: deterministic extend-add and solve (
deterministic_mode = 1onFusedSolver); refs #177, #109, #82What was built:
src/fused.jl: the extend-add of C-group parents is a parent pull (ROLE_EAper parent column block, children inchild_listorder 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.deterministic_mode = 1(fwd2_kernel!/fwd1_kernel!Val(DET), the existingws.Ubuffers 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_optionsno longer rejects it).memory_estimatesadds 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 underdeterministic_mode = 1and (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 withingrowth_tolof 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):
"SPD""S""S"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 staysdeterministic_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_estimatesslot 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 inTASKS.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:
deterministic_mode = 1. The task allows this for a variant that "costs nothing"; the kept pull is faster than the atomic default everywhere. Sodeterministic_modeonly 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.amdgpuCI checks correctness).Follow-up issues opened: #181 (gate (b): the 78k
"S"fused LDLᵀ offset stays 1.1× above the stock eps response, unchanged frommain). 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