fp-cuda: device-resident F₂ row reduction (RREF) on Hopper#274
Draft
JoeyBF wants to merge 46 commits into
Draft
Conversation
Prototype CUDA kernel and the Phase 2 wgmma.b1 core: both operands pre-arranged on the host as K-major tiles, one m64n128k256 binary MMA. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01B1XztNMycTtSC9SQtpNYjR
…zation Steps (a)-(d): hoist the A TMA load out of the column-group loop, double-buffer A in the K pipeline, load B via TMA, and split into producer/consumer warpgroups. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01B1XztNMycTtSC9SQtpNYjR
Both b1 operands are K-major; move them to CU_TENSOR_MAP_SWIZZLE_128B so wgmma operand reads avoid bank conflicts. The TMA applies the swizzle on load, so the host now emits plain row-major K-major tiles (the hand-rolled cm() interleave is gone) and the wgmma matrix descriptors carry the matching layout bits (layout=1, LBO=16B, SBO=1024B), derived from CUTLASS make_gmma_desc<Major::K> / LayoutType::B128. The SMEM K-tile grows to 1024 bits (one full 128B K-major swizzle atom = 4 k256 sub-chunks) and moves to dynamic shared memory (~82KB, opt-in via CU_FUNC_ATTRIBUTE_MAX_DYNAMIC_SHARED_SIZE_BYTES). Per-stage wgmmas now run behind a single commit_group/wait_group and accumulate popcounts in-hardware (scale-D=1) into one resident accumulator per column group, replacing the previous one-wgmma-per-commit/wait serialization. Compile-verified only (nvcc + rustc); not yet validated on H100. Validate with a 64x256x64 identity product first, then matmul_b1_demo, then the bench. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Replace the four m64n64k256 wgmmas per k-step (one per output limb) with a single m64n256k256 covering all NG=4 limbs at once. Binary wgmma is k256-only, so N is the throughput lever; n256 is the max. Same accumulator register count (128 s32/thread) and same SMEM as the 4x n64 version, but 1/4 the wgmma instructions, B descriptors, and fence/commit churn. B is now arranged as one contiguous 256-column tile per CTA (host transpose_b packs 4 limbs side by side; the B TMA box is 256 rows tall) instead of four separate 64-column tiles, and is zero-padded to whole 256-column groups. A and the K tiling are unchanged. The output bit-pack splits the single acc[128] into the 4 output limbs: the m64n256 fragment is the m64n64 layout tiled along N, so register group gi in 0..32 maps to columns [gi*8, gi*8+8). Compile-verified (PTX emits m64n256k256); fragment->limb bit-pack still needs the 64x256x64 identity check on H100. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The producer warpgroup needs few registers; the consumer holds the 128-reg m64n256 accumulator. Issue setmaxnreg.dec(40) in the producer and setmaxnreg.inc(216) in the consumer so the consumer claims the producer's surplus. 128*(40+216)=32768 regs/CTA, leaving room for 2 CTAs/SM. Both are warpgroup-aligned and executed by all 128 threads of their warpgroup (the wg branch splits at warpgroup granularity). Compile-verified; PTX emits the dec/inc. Also fixes a stale m64n64k256 mention in the lib doc comment. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Bump the producer-consumer pipeline depth from 2 to 3 so the producer can run
two K-chunks ahead, hiding more TMA latency. SMEM grows to ~122 KB/CTA (one
CTA/SM vs two at STAGES=2); the host smem_bytes/cuFuncSetAttribute track it
automatically. STAGES is the latency-vs-occupancy knob to sweep on hardware.
Phase-array inits made depth-agnostic ({0}).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Replace the per-thread global stores with a single cp.async.bulk.tensor.2d.global.shared::cta per CTA. sC is now packed row-major ([row][limb]) so the 64×NG tile is contiguous for the store; thread 0 issues it after a __syncthreads + fence.proxy.async (making the atomicXor writes visible to the async proxy), then cp.async.bulk.commit_group/wait_group, then a final __syncthreads keeps sC alive until the store lands. The kernel now takes a C tensor map instead of a raw pointer (nlim/C* params dropped). C is padded on the host to whole NG-limb column groups (n_padded_lim = n_groups*NG) so every stored tile is complete; padded columns carry zeros from the zero-padded B and are trimmed on readback. README roadmap updated to reflect Phases 4-7. Compile-verified (PTX emits the bulk store + fence.proxy.async); store path and n256 bit-pack still need the H100 identity check. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Replace the cuda-oxide `cuda-core` dependency with `cudarc`. cuda-core pulled in nightly via an incidental `#![feature(f16)]` (a DeviceCopy impl for the unstable f16 primitive we never use); cudarc is stable Rust, mainstream/maintained, and dynamically loads the driver at runtime — so the crate builds with no CUDA present and the whole crate (lib + examples + benches) now compiles AND links locally without libcuda (previously examples failed at link with -lcuda). Host glue rewritten against cudarc: CudaContext/load_module(Ptx)/load_function, clone_htod/alloc_zeros/clone_dtoh, device_ptr for the TMA descriptor addresses, CudaFunction::set_attribute for the >48 KB dynamic-SMEM opt-in, and the typed launch builder. The three CUtensorMaps are passed by value as grid-constant args via a #[repr(transparent)] TmaArg: DeviceRepr wrapper. cuTensorMapEncodeTiled is called through cudarc::driver::sys. Kernel (.cu/PTX) and build.rs are unchanged. cudarc features: driver + nvrtc (for Ptx) + cuda-12080 + dynamic-loading; the version only selects pre-generated bindings, the real driver (12.8+/13.x) is resolved at runtime. Also delete examples/bench_breakdown.rs: it was stale (called the pre-TMA 7-pointer kernel signature with the old cm() transpose) and the only remaining cuda-core user; bench_kernel_only supersedes it. Compile + link verified locally (lib, examples, benches). Stable-toolchain build to be confirmed on the server (dev box only has Nix nightly). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The >16384 throughput cliff is L2-residency bound on B: with the old (grid_x=n_groups, grid_y=m_tiles) launch (N fastest), every B column-panel is re-touched only once per full sweep of B, so its reuse distance is the whole matrix and it spills from L2 once K*N/8 > ~50 MB, getting re-streamed from HBM per M-tile. Convert to a persistent 1-D grid of ~SM-count CTAs that sweep all output tiles in a grouped-along-M order (bi fastest within a GROUP_M-row band, bj slowest). This shortens each B-panel's reuse distance to <= GROUP_M, cutting B's HBM re-reads by a factor of GROUP_M while A still streams once. Kernel: take n_groups as an arg (no longer gridDim.x), add GROUP_M=8, wrap the per-tile body in a persistent `for (tile = blockIdx.x; ...)` loop, and re-init the mbarriers per tile (fresh phase-0 bookkeeping) so barrier parity isn't carried across iterations. setmaxnreg stays a one-time per-warpgroup action before the loop. Per-tile compute is byte-identical to Phase 7. Host: query CU_DEVICE_ATTRIBUTE_MULTIPROCESSOR_COUNT, launch (num_ctas = min(SMs, total_tiles), 1, 1), and pass n_groups. TMA descriptors, padding, and readback are unchanged. GROUP_M is a tuning knob (8/16/32) to sweep on the H100. Code-only on the dev box; validate on the server. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Phase 8 shortened each B-panel's reuse distance via rasterization; this shares the remaining HBM read of a B-panel across CTAs. CLUSTER (=2) CTAs along M form a thread-block cluster: each computes a different M-tile (its own n256 accumulator) but receives the same B-panel via one multicast HBM read (cp.async.bulk.tensor.2d...multicast::cluster), cutting B's HBM traffic by a further factor of CLUSTER on top of Phase 8's GROUP_M. (A second per-CTA accumulator can't fit the 256-reg budget, so cross-M B-reuse must be cross-CTA, i.e. clusters — not single-CTA SMEM reuse.) Kernel: __cluster_dims__(CLUSTER,1,1); the schedule walks M-super-rows of CLUSTER tiles (bi = sbi*CLUSTER + rank, shared bj). A is loaded per-CTA; B is multicast by rank 0 with an all-ranks mask into every member's sB and counted against every member's full barrier. The empty barrier is cluster-wide (init count CLUSTER; each consumer arrives on every member via mapa). Pipeline barriers are initialized once and flow continuously across tiles (single qidx/p) rather than re-init per tile, which would race the cross-CTA arrivals. One barrier.cluster after init. Mirrors pranjalssh/fast.cu matmul_9. Host: pad m_tiles to a multiple of CLUSTER (every rank gets a valid M-tile), round the persistent grid down to a whole number of clusters. CLUSTER must match the kernel constant (like STAGES). GROUP_M and CLUSTER are tuning knobs to sweep on the H100. Multicast/cluster ordering is the highest-risk, hardware-unverifiable part — see HANDOFF.md. Code-only on the dev box; validate on the server. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Diagnosis on H200: the kernel was L2-refill-bandwidth bound, not compute bound. Microbenchmarks put the single-warpgroup wgmma.b1 ceiling at ~12,468 TOPS while sustained L2->SMEM refill tops out at ~8 TB/s (full tensor rate needs ~14.6). Each CTA computed a 64x256 tile, so it reloaded 8KB A + 32KB B per k-chunk -- B was 80% of the traffic because the tile was 4x wider than tall, and the tensor core starved waiting for it. Fix: register-block the output. Each CTA now computes a TM x NB block as MSTRIPS m64n128 wgmma strips (wgmma_n128, acc[MSTRIPS][64]) that all reuse one loaded B sub-tile, so a single L2->SMEM read of B feeds every strip. Winning config MSTRIPS=3 (192x128 block), NB=128, STAGES=4, GROUP_M=16, CLUSTER=2 cuts refill bytes/MAC by 33%. Also validates the previously-unvalidated Phase 8-9 batch on hardware (bit-exact) and lands the STAGES 3->4 / GROUP_M 8->16 tuning. Kernel-only, bit-exact (32768 cube): 8,585 -> 9,344 TOPS (+8.9%); 16384 7,636 -> 8,301; now ~75% of the tensor-core ceiling. Constants MSTRIPS in the kernel and TILE_M(=64*MSTRIPS)/NG in src/lib.rs must match. NB must be a multiple of 128 (NG even) or the C-store TMA box breaks 16B alignment; MSTRIPS=4 would exceed the 255-reg/thread cap. Adds examples/tune.rs, a fast no-CPU-check sweep harness. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The per-tile epilogue drained its C store inline (cp.async.bulk.wait_group 0 between two __syncthreads), stalling the whole CTA for the store latency every output tile -- a bubble that grows as a fraction of tile time at smaller K. Double-buffer sC and defer the wait: each tile packs into sC[titer&1], issues its store, then does cp.async.bulk.wait_group.read 1, which blocks only until every store but the newest has finished *reading* its SMEM source. So tile T's store drains during tile T+1's compute, and the buffer freed (two tiles back) is safe to reuse. A final wait_group 0 after the persistent loop drains the last store before the CTA exits. Kernel-only, bit-exact: 32768 9,344 -> 9,426; 16384 8,301 -> 8,420; 8192 6,425 -> 6,591; 4096 3,835 -> 3,948 (smaller K gains more, as the epilogue is a larger share of tile time there). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
wgmma.fence.sync.aligned is a warpgroup-wide (128-thread) sync; the consumer was issuing two per k-chunk (one before the wgmma group, one after the wait), i.e. 64 warpgroup syncs per output tile. But the fence is only needed to order non-wgmma writes to the accumulators before a wgmma reads them -- in steady state the accumulators are touched only by wgmma, so one fence before the whole K-loop (ordering the accumulator zeroing) suffices, matching CUTLASS's warpgroup_arrive-once pattern. The per-chunk wgmma.wait_group 0 still makes results readable for the epilogue. Kernel-only, bit-exact: 32768 9,426 -> 9,605; 16384 8,420 -> 8,618; 8192 6,591 -> 6,744. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Launch (occupancy × SM-count) CTAs instead of hard-coding SM-count, so the persistent grid exactly fills the machine for whatever the resource footprint allows. No change at the shipping config (occ=1/SM), but it's the correct, self-adapting launch and it drove the 2-CTA/SM experiment. That experiment is a documented dead end (H200, 2026-07-07): 2 CTAs/SM needs the compiled register count <=128/thread (2*256*128 = the 64K reg file), but the resident accumulator that gives the kernel its arithmetic intensity is 192 regs/thread at MSTRIPS=3. The only way to reach occ=2 is MSTRIPS=1 (64-reg acc), which collapses AI and drops 16384 from ~8,600 to ~5,500 TOPS. High AI (big accumulator) and high occupancy compete for the same register file and AI wins -- so 1 CTA/SM with the largest accumulator under the 255-reg cap is optimal. This confirms on-device that the ~10-13k TOPS bandwidth wall cannot be moved by raising occupancy. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Important Review skippedDraft detected. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Organization UI Review profile: ASSERTIVE Plan: Pro Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
JoeyBF
force-pushed
the
claude/blas3-row-reduction-lgjz3y
branch
from
July 17, 2026 21:44
52f4503 to
7ee600c
Compare
Wire the Hopper GPU backend into `fp` behind an optional `gpu` feature and keep the workspace CI green: `--workspace` builds `fp-cuda`, whose build.rs emits a stub PTX when nvcc is absent (every ubuntu-latest runner) instead of panicking. fp-cuda's library API is fp-agnostic (raw limbs), breaking the dependency cycle so `fp` can dispatch large p=2 products to the device. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01B1XztNMycTtSC9SQtpNYjR
JoeyBF
force-pushed
the
claude/blas3-row-reduction-lgjz3y
branch
from
July 17, 2026 22:01
7ee600c to
a58f096
Compare
- build.rs: fall back to the stub PTX only when nvcc is genuinely absent (io::ErrorKind::NotFound); fail fast on other spawn errors (permissions, broken exec) instead of silently building the stub. - flake.nix: move the CUDA toolkit out of the default dev shell into a dedicated `devShells.gpu` (`nix develop .#gpu`), matching the sibling GPU PR, so contributors/CI don't fetch the multi-GB unfree CUDA closure for the opt-in backend. Drop the obsolete cuda-oxide/libclang/bindgen env (the crate uses cudarc, which dlopens libcuda). - README/Cargo.toml/cuda.rs: call the Cargo feature `gpu` consistently; note the kernel is sm_90a-only (architecture-specific, not forward-compatible — no Blackwell/sm_100). - cuda_dispatch test: add non-tile-aligned shapes (2049x2051x2053, 3000x2112x4097) to exercise edge masks, partial limbs, and raster tails. - bench examples: drop obsolete pre-optimization diagnosis prose; label the bench_shapes L2 size as an assumption rather than a device fact. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01B1XztNMycTtSC9SQtpNYjR
Design for a BLAS3 (GEMM-based) F₂ row reduction and the CPU blocked prototype (Phase 1), with proptest regression seeds. Establishes the algorithm and the CPU oracle the device port stays bit-exact against. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01B1XztNMycTtSC9SQtpNYjR
CPU-side benchmarking that shows the blocked prototype is not GEMM-bound and the M4RI/blas3 ratio collapses with n — motivating the GPU port. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01B1XztNMycTtSC9SQtpNYjR
Replace the O(R^2 n) row-op back-substitution with a blocked version in the same X*U GEMM shape as the forward trailing update, walking pivot blocks right-to-left: reduce each block of pivots to RREF among itself with a few full-width row ops, then clear its pivot columns from all rows above via one GEMM. Same asymptotic work, but now as data-parallel GEMMs (and cache-friendly), so the back-substitution is BLAS3 and scales with cores instead of being a serial drag that grew with n. Proptested against row_reduce (RREF + pivots) across the existing shape grid, rank-deficient generators, and the deterministic sweep. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ESAMSPB8MQL4SSFxRJSEyR
Rewrite the panel pivot search and forward elimination -- and the back-substitution's X column-gather -- to read/write raw limbs directly instead of going through entry()/add_basis_element()/per-row split_at_mut. The pivot bit test becomes (data[row*stride + q/64] >> (q%64)) & 1, and the elimination snapshots the pivot row's panel limbs once and XORs them into below rows in a tight limb loop. Same algorithm, same results (proptest green), but the O(m*n) panel scan sheds the FpSlice/FieldElement machinery that dominated it. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ESAMSPB8MQL4SSFxRJSEyR
DeviceMatrix (upload/download, in-place device residency) and the trailing update gemm_xor_into, built on the Hopper wgmma.b1 kernel. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01B1XztNMycTtSC9SQtpNYjR
The one genuinely-new kernel (BLAS3-GPU-HANDOFF §4, design §5): forward factorization of one 64-bit column panel, the only column-indexed region of the reduction. A single CTA sweeps the 64 bit positions with a __syncthreads between them; per bit a find-first reduction picks the pivot (the lone column op) and a row-parallel masked XOR clears it from the rows below, recording the multiplier bit into L. Forward-only, matching CPU Step A. Rows are addressed through a virtual perm (design §4.3): a pivot is promoted by a perm swap, so the matrix bytes never move; L is indexed by original row id, needing no swap. Only (pr, pivcols) come back to host. - lib.rs: GpuContext loads the kernel; identity_perm, panel_factor (returns (pr, pivcols)), download_u32. - examples/panel_factor_demo: validates against a CPU transliteration of the kernel bit-for-bit — reduced panel limb, perm order, L, pr, pivcols — across full-rank and rank-deficient panels (free-column branch), partial panels (n<64), higher limbs, mid-sweep r>0, and m=100000. All match on the H200. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
forward_reduce runs the whole blocked forward elimination over one persistent device buffer (design §4): sweep 64-bit panels, and per panel factor it (panel_factor), promote the pivot rows' deferred trailing, drop the pivots from L, and apply the trailing update M[:, c+b:] ^= L·U as one wgmma GEMM. No host round-trip inside the sweep — only (pr, pivcols) come back per panel. Three small driver kernels added to matmul_b1.cu: promote_pivots (triangular trailing solve among a panel's pivot rows, one CTA), zero_pivot_l (exclude pivots from the GEMM), gather_rows (build the contiguous U operand from the scattered pivot rows via perm). examples/forward_reduce_demo validates against the ground-truth reducer: the forward pass is elementary row ops so it preserves row space, hence row_reduce(device_M) == row_reduce(original); plus rank, pivot columns, and that the non-pivot rows (perm[r..]) are zeroed. All cases pass on the H200 — full-rank, rank-deficient, multi-panel, and wide. Back-substitution to full RREF is Phase 5b. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Completes the device-resident reduction (BLAS3-GPU-HANDOFF §5 decisive gate). back_substitute turns the echelon form into full RREF, blocked right-to-left over ≤64-pivot blocks (design §4.6): reduce each block among itself, then clear its pivot columns from every row above via one X·U wgmma GEMM, scattered back through perm. row_reduce_dev = forward_reduce + back_substitute — the whole reduction over one persistent buffer, no host round-trip beyond the per-panel pivot read-backs. Three back-sub kernels added: block_reduce_rref (block-internal reduction, one CTA), gather_cols (build X = above-rows at the block's pivot columns), xor_into_perm (scatter the GEMM result through perm). examples/row_reduce_demo validates row_reduce_dev == fp::Matrix::row_reduce bit-for-bit — the RREF matrix, the pivot list, and rank — and agreement with row_reduce_blas3, across full-rank/rank-deficient/wide/tall shapes on the H200. Correctness Definition-of-Done (#1) met. Remaining: dispatch threshold in fp (Phase 7), active-row compaction / perf (Phase 6). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
row_reduce now tries the device-resident reduction for p=2 matrices above the FP_CUDA_THRESHOLD (min(rows,cols) ≥ 2048, same knob as the mul dispatch), and falls back to the CPU M4RI path when the GPU is unavailable or below threshold. blas::cuda::try_row_reduce uploads once, runs row_reduce_dev, and materializes the canonical RREF (pivot rows at top in column order, zeros below, pivots set) — bit-identical to the CPU path. tests/cuda_dispatch: gpu_row_reduce_matches_cpu checks the dispatched row_reduce == row_reduce_blas3 (RREF, rank, pivots) on full-rank and rank-deficient matrices above threshold; FP_CUDA_DEBUG confirms the GPU path fires (218 kernel launches). Default (non-cuda) build unaffected. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Device-vs-CPU scaling harness over 2^n half-rank matrices out to 2^18, reporting effective binary-op throughput (Tbop/s = 2*m*n*R/t). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01B1XztNMycTtSC9SQtpNYjR
Profiling (Pass 0) showed the single-CTA panel_factor kernel — one SM of 132 sweeping 64 sequential bit-steps — dominated the forward pass at ~76% of GPU time, NOT the trailing GEMM as the plan assumed (GEMM was ~11%). This pivots Phase 6 to attack the single-CTA kernels first. panel_factor_coop does the identical math grid-parallel: each bit-step's find-first and masked-XOR spread across the whole grid, with a self-contained atomic sense-counting grid barrier between the 64 steps (no cooperative_groups / cudadevrt dependency, so it compiles under nvcc -ptx). Launched cooperatively so all CTAs are co-resident (required by the spin barrier). The pivot row/word is broadcast via global scratch and the perm swap is barrier-separated from both the pre-swap read and the XOR, so there are no races (3 barriers per pivot-bit). Default on; FP_CUDA_NO_COOP=1 falls back to the single-CTA kernel. Also adds the Pass 0 profiling harness: examples/probe_pr.rs (flat-K microbench, which confirmed Loss 1 — kernel time flat pr=64..1024 while effective Tbop/s climbs 16×) and FP_CUDA_PROF per-phase GPU timers in forward_reduce / back_substitute (printed by row_reduce_dev). Results (half-rank square, bit-exact vs CPU row_reduce): n=32768: device 2.688s -> 0.787s (3.4x), vs-M4RI 3.9x -> 13.3x n=65536: device 11.89s -> 4.04s (2.9x) panel_factor share 76% -> 21%; the trailing GEMM (K-padding, Loss 1) is now the top cost at ~33%, promote_pivots + block_reduce_rref (still single-CTA) ~33%. Gates: forward_reduce_demo, row_reduce_demo, cuda_dispatch all bit-exact; clippy clean. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01B1XztNMycTtSC9SQtpNYjR
Two single-CTA back-of-the-pack kernels the Pass-5 profile exposed (promote ~18%, block_reduce ~15% of GPU time). promote_pivots: the deferred-trailing promotion is embarrassingly parallel over columns — each trailing limb runs its own sequential k-loop and never touches another column — so it needs no __syncthreads at all. Rewrote it grid-strided over limbs and launch a full grid (was one CTA). 132ms -> 56ms at n=2^15. block_reduce_rref: kept single-CTA (it is entangled with the sequential right-to-left back-substitution and cannot be batched across blocks), but cut its barrier count from ~bp² (a __syncthreads per (k,j) pair) to ~2·bp by gathering all of pivot k's clear-conditions into shared memory once, then doing a limb-parallel XOR that loads rowk[c] a single time and reuses it across the ≤64 rows. (A first flattened-(j,limb) attempt regressed to 233ms via emulated 64-bit division; the limb-outer form avoids it.) 110ms -> 95ms. End-to-end (half-rank, bit-exact vs CPU): n=32768 0.787s -> 0.698s (3.85x vs the 2.688s pre-Pass-5 baseline); n=65536 4.04s -> 3.64s. The trailing GEMM (Loss 1, K-padding) is now the clear top cost at ~38% + ~9% back-sub. Gates: row_reduce_demo, forward_reduce_demo, cuda_dispatch bit-exact; clippy clean. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01B1XztNMycTtSC9SQtpNYjR
…idth Attacks Loss 1 (K-padding): the trailing GEMM pads the contraction dimension to TILE_K=1024, so a 64-pivot panel wastes 15/16 of the tensor-core issue. Widening the panel to b=64·bl columns raises pr toward b and reclaims that waste. panel_factor_coop now factors a bl-limb-wide panel natively: the masked XOR clears each pivot from the below rows across ALL bl panel limbs inline (the intra-panel Schur update — CPU blas3.rs Step A, lines 159-161), so a later sub-column's find-first sees fully reduced bits and ONE wide trailing GEMM (K=pr≤b) fixes up the columns beyond the panel. Multipliers go into a bl-limb L indexed by the global pivot index; the pivot's bl panel limbs are broadcast via g_pivword. bl=1 reproduces the single-limb kernel exactly. matmul_b1_dev gains a _strided variant (pack_a takes a source row stride distinct from the K-limb count) so the wide L — stored with stride bl but only ceil(pr/64) limbs occupied — feeds the GEMM without a trim copy. The width is a genuine tradeoff: wider panels halve the GEMM but the O(pr²)/panel promotion replay is O(bl) total and the inline XOR is O(bl), so past an optimum they lose. Measured optimum grows with the trailing width (bl ≈ stride/256): bl=2 @ n=2¹⁵, 4 @ 2¹⁶, 8 @ 2¹⁷ (bl=16 regressed everywhere). Default is adaptive adaptive_bl(stride)=clamp(stride/256,1,16); override with FP_CUDA_BL. Results (half-rank, bit-exact vs CPU), vs the original pre-Phase-6 baseline: n=32768: 2.688s -> 0.64-0.84s (~3-4x), vs-M4RI 12.5x n=65536: 11.89s -> 2.70s (4.4x), vs-BLAS3 24.3x n=131072: 74.90s -> 12.81s (5.8x), 176 Tbop/s (was ~50) Speedup grows with size. Balanced profile now (panel 27 / gemm 21 / promote 20 / block_reduce 17 / bs.gemm 11 %). Gates: forward_reduce_demo, row_reduce_demo bit-exact at bl=1,2,4; cuda_dispatch pass; clippy clean. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01B1XztNMycTtSC9SQtpNYjR
…buffers The device GEMM's a_int / bt / c_dev and the gather outputs (u_buf, x_buf) are fully written before they are read — pack_a/pack_b write every element (padding as explicit zeros), the GEMM stores the whole C tile grid (overwrite, not accumulate), and the gather kernels fill every output limb. Their per-call alloc_zeros therefore paid for a memset that the following kernel immediately clobbers — and c_dev is m_padded × n_padded_lim, multi-GB at large n, so that memset is real HBM bandwidth wasted every trailing update. Switched those five to unsafe `alloc` (uninitialized; uses malloc_async where available). The multiplier L and the cooperative barrier scratch stay zeroed — they are OR-accumulated / must start at 0. Bit-exact (matmul_b1_dev_demo, gemm_xor_into_demo, forward_reduce_demo, row_reduce_demo). Modest end-to-end (n=32768 half-rank ~55.9 Tbop/s); the win grows with n as the C memset dominates. Full persistent-buffer reuse (avoiding the cudaMalloc itself) is left as a follow-up. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01B1XztNMycTtSC9SQtpNYjR
The GPU row-reduce path shared the matmul dispatch threshold (min-dim 2048), but a full reduction is many dependent panel steps, not one GEMM, so its CPU crossover is later. Measured on an H200 (random square, device incl. upload/download vs multi-threaded M4RI row_reduce): the GPU first wins at n≈8192 (n=4096 is still ~1.7× slower), so dispatching at 2048 spent 2048–4096 losing to the CPU. Added DEFAULT_RR_THRESHOLD = 8192 (env FP_CUDA_RR_THRESHOLD), used only by try_row_reduce; the matmul path keeps its own 2048 (FP_CUDA_THRESHOLD). The cuda_dispatch row-reduce test forces the threshold down to 2048 so its small fast shapes still exercise the GPU path. cuda_dispatch passes; non-cuda `cargo build -p fp` and `cargo clippy -p fp --features cuda` clean. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01B1XztNMycTtSC9SQtpNYjR
A below row that is entirely zero across the remaining columns can never pivot and carries no multiplier, so it is permanently dead. mark_live flags such rows in [r, m_active); compact_perm stable-partitions perm so live rows precede dead ones and returns the shrunk m_active, which panel_factor_coop now takes as its row bound (find-first / masked-XOR skip the dead tail). Pivot rows [0,r) are never touched, so back-substitution and RREF materialization are unaffected. Re-scanned every COMPACT_PERIOD=4 panels to amortize the mark+host-partition cost (a few hundred KB of perm/flags per call). Default on for the coop path; disable with FP_CUDA_NO_COMPACT. The bigger win rides on the early-exit it enables: once compaction finds no live below rows (m_active <= r) no later column can hold a pivot, so the forward sweep stops immediately instead of grinding through empty panels (each of which still paid 64 grid-barrier bit-steps). Effect: dense half-rank ~1% (dead rows appear late there); low-rank / rectangular inputs, which the general row_reduce must handle, benefit sharply — a rank-1024 65536×16384 reduce drops 0.198s -> 0.141s (29%). Bit-exact throughout (row_reduce_demo, forward_reduce_demo, cuda_dispatch); clippy clean. Adds examples/probe_lowrank.rs. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01B1XztNMycTtSC9SQtpNYjR
Re-profiling at the larger adaptive bl (n=2^16, bl=4) showed the single-CTA block_reduce_rref had become ~22% of GPU time — it reduces each ≤64-pivot block to RREF on one SM of 132, and its full-width row XORs grow as r·stride. block_reduce_coop does the identical math grid-parallel: gather pivot k's clear-conditions into a global buffer, atomic grid barrier, XOR rowk into the flagged block rows flattened over (row × limb) across the whole grid, barrier, next k. Reuses the validated grid_sync (cooperative launch, grid-uniform k-loop). The per-block cooperative launch + grid barriers only pay once block work (≈ bp·stride) is large, so it is gated on stride >= 1024 (n >= 65536); below that the single-CTA kernel wins. Measured (H200, half-rank): neutral at n=2^15, +6% at 2^16 (2.67s -> 2.51s), +18% at 2^17 (13.1s -> 10.7s, 210 Tbop/s). FP_CUDA_NO_COOP forces single-CTA. Bit-exact (row_reduce_demo, forward_reduce_demo, cuda_dispatch); clippy clean. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01B1XztNMycTtSC9SQtpNYjR
Splitting the GEMM phase showed the kernel — not packing — dominates it, and the back-sub X·U GEMM ran at K=bp_eff=64, padded to TILE_K=1024: 16× of its tensor-core issue was multiply-by-zero (bs.gemm ~16% of GPU time). block_reduce_coop's cost is ~bp-independent (its compute and its 2·r grid barriers both scale with the pivot count r, not the block width), so on the cooperative path the back-sub block can be widened for free. Raised bp from 64 to 512 (K padded 1024 → only ~2× waste, and ~8× fewer GEMM launches). The single-CTA fallback keeps bp=64 (its shared cond[] is sized 64); override with FP_CUDA_BP. Measured (half-rank, H200): n=131072 10.78s -> 7.52s (30%, 300 Tbop/s); bp=1024 is marginally better (7.40s) with diminishing returns. Bit-exact vs CPU BLAS3 at n=65536 (32.7× faster, 2.07s vs 67.6s) and all demos; clippy + cuda_dispatch clean. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01B1XztNMycTtSC9SQtpNYjR
After the back-sub GEMM was de-padded, the single-CTA promote_pivots became the dominant forward cost (~29% at n=2^16): it parallelizes only over trailing limbs (a few CTAs) while doing an O(pr²) serial replay per column. promote_coop reformulates it right-looking: process pivots i = 0..pr in order, and once pivot i is final add it to every later pivot k>i carrying its multiplier (M[k] ^= M[i] across the trailing) — so pivot i is fully promoted by the time we reach it. Sequential in i with a grid barrier between steps (reusing the validated grid_sync), but each step's XOR is flattened over (later-pivot × limb) across the whole grid instead of a handful of CTAs. Gated on stride >= 1024 (the same amortization threshold as block_reduce_coop); FP_CUDA_NO_COOP forces the single-CTA kernel. Measured (half-rank, H200): n=65536 2.07s -> 1.95s; n=131072 7.40s -> 6.19s (364 Tbop/s). Bit-exact vs CPU BLAS3 at n=65536 (32×) and all demos; clippy + cuda_dispatch clean. Cumulative vs the original pre-Phase-6 baseline: n=131072 74.9s -> 6.19s (12×). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01B1XztNMycTtSC9SQtpNYjR
promote_coop is ~bl-independent (its compute and 2·pr·(stride/bl) grid barriers both scale with the pivot count, not bl), so once it is active (stride ≥ 1024) the forward panel can be widened until the trailing GEMM stops K-padding (bl=16 ⇒ K=1024 exactly) at no promote cost. Below that threshold the single-CTA promote is O(bl) and narrow panels still win. adaptive_bl now uses stride/128 on the coop path and stride/256 below it — matching the measured optima (half-rank): bl=2 @ n=2^15, 8 @ 2^16, 16 @ 2^17. End-to-end (half-rank): n=32768 0.63s -> 0.59s, n=65536 1.95s -> 1.78s, n=131072 6.19s -> 5.92s (380 Tbop/s). Bit-exact (row_reduce_demo); clippy clean. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01B1XztNMycTtSC9SQtpNYjR
The cooperative kernels are barrier-bound. promote_coop's clear-condition reads L, which its XOR never mutates, so the condition can be tested inline in the XOR instead of gathered under a separate grid barrier — dropping barrier [A] and its cond buffer, one barrier per pivot instead of two. Measured (half-rank, H200): n=65536 1.79s -> 1.64s (41× vs CPU BLAS3, bit-exact); n=131072 5.93s -> 5.57s (404 Tbop/s). Confirms the barrier count, not compute, gates these kernels. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01B1XztNMycTtSC9SQtpNYjR
The cooperative kernels are barrier-bound and the grid barrier's spin was the cost: atomicAdd(barrier, 0) is a read-modify-write that acquires the counter's cache line exclusively every iteration, serializing the ~1000 spinning CTAs on a single line (~µs per barrier). Spinning on a plain volatile load instead leaves the line shared; arrival stays a single atomicAdd per CTA. Measured (half-rank, H200, bit-exact): n=65536 1.64s -> 1.52s; n=131072 5.57s -> 5.31s (424 Tbop/s, 4.9% of the wgmma.b1 roofline). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01B1XztNMycTtSC9SQtpNYjR
block_reduce_coop is bp-independent in wall time (compute and its 2·r barriers both scale with the pivot count, not the block width), so the back-sub block can be K=1024 — an exact TILE_K multiple, eliminating the last of the back-sub GEMM's K-padding. Measured (half-rank): n=65536 44.5× vs CPU BLAS3 (1.52s, bit-exact); n=131072 5.32s -> 5.20s (433 Tbop/s, 5.0% of the wgmma.b1 roofline). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01B1XztNMycTtSC9SQtpNYjR
…factor Validates the "row-reduce a 1024-wide strip" idea: factor each bl-wide macro panel as bl_micro-wide micro sub-panels, apply each micro's pivots to the rest of the macro via an intra-macro GEMM (so the next micro's find-first sees reduced bits), then one wide far GEMM with K = all macro pivots. This shrinks the elementwise panel_factor (the intra-panel XOR, the top cost at wide bl) while keeping the far GEMM at K=1024. Bit-exact vs CPU (row_reduce_demo; vs BLAS3 at n=2^16). Off by default (FP_CUDA_MICRO); ~6% at n=2^17 (micro=4, 461 vs 434 Tbop/s). Mechanics: a fresh per-micro L (offset 0) feeds each intra-macro GEMM, and a new l_shift_or kernel accumulates the micros into a contiguous macro L (offset 0) for the far GEMM — so promotes/GEMMs all use offset-0 operands; only l_shift_or handles the bit offset. Extracted the promote+zero+gather+GEMM+xor sequence into a shared trailing_update() helper used by both the single-wide-panel (default) and recursive paths. Two known limits, documented at the knob: (1) the intra-macro GEMMs still pad K to TILE_K=1024, capping the win — the full payoff needs a small-TILE_K GEMM variant; (2) per-sub-panel scratch is not yet reused, so at small micro + large n the async-malloc high-water mark can intermittently exhaust memory (a per-macro sync mitigates but doesn't eliminate it). Hence experimental / opt-in; the default single-wide-panel path is unchanged and bit-exact. clippy clean; cuda_dispatch + all demos pass. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01B1XztNMycTtSC9SQtpNYjR
…is a net loss Add factor_panel_rec: a wide-panel (bl=64) halving recursion so the forward trailing GEMMs run at K≈4096 instead of K=1024. Gated behind FP_CUDA_DEEP (FP_CUDA_BASE sets the elementwise base width, default 16 limbs); the non-deep default path is byte-identical. This is the definitive test of the "raise K → compute-bound" thesis. Result (2^16, FP_CUDA_PROF): the far update drops 225ms→47ms (4.8× — the K-scaling is real), but it is a NET LOSS. The trailing GEMM is only ~20% of the row reduction; the other ~80% is elementwise (panel_factor 31%, back-sub block_reduce 29%, promote 15%). Each intra-panel GEMM needs a promote wrapper, so promote explodes 175ms→426ms and swamps the GEMM saving. The row reduction is elementwise/latency- bound, not compute-bound; panel-width sweeps confirm a flat optimum at bl≈12-16 (= adaptive_bl), so the current default is already near-best. Bit-exact: forward_reduce_demo, row_reduce_demo, cuda_dispatch all pass with FP_CUDA_DEEP=1. Kept behind the flag as the record: if promote is ever made cheap, deep recursion flips to a win. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01B1XztNMycTtSC9SQtpNYjR
Refactor back_substitute's clear-from-above (gather X/U, GEMM, scatter-XOR) into a shared bs_clear_above helper, reused by both the outer loop (above=[0,s)) and a new recursive within-block TRSM (FP_CUDA_BS_TRSM). The recursion halves each pivot block, clears the right half's pivots from the left via a large X·U GEMM, and recurses — the BLAS3 triangular-solve shape, with NO promote (source/target rows disjoint and already reduced). gather_cols/xor_into_perm read perm through a .slice(above_start..) view, so no kernel changes. MEASURED (2^16, FP_CUDA_PROF): NEUTRAL. block_reduce is grid-barrier-bound, not work-bound — block_reduce_coop does 2 grid syncs/pivot (~2r total) and the recursion with a coop base keeps them all (block_reduce 334→296ms while bs.gemm 29→65ms — a wash). A barrier-free single-CTA base removes the syncs but is worse (485ms, one SM can't cover full width). The real fix is a shared-memory 64x64 triangular base kernel (reduce base block in-CTA, no grid sync; apply via GEMM), bounded ~1.2x since block_reduce is 29%. Kept behind the flag as correct BLAS3 scaffolding for that kernel; default path unchanged and bit-exact. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01B1XztNMycTtSC9SQtpNYjR
…reduce)
The back-sub within-block reduce (bs.block_reduce, ~29% at 2^16) is grid-barrier-
bound: block_reduce_coop does 2 grid syncs/pivot and a grid_sync's cost scales
with CTA count. Fix, in two parts that only work together:
(1) recurse each block to narrow (<=64) base blocks + large X.U GEMMs (the BLAS3
TRSM shape, no promote — source/target rows disjoint & already reduced);
(2) run those narrow base reduces on a SMALL grid (br_ctas cap 128) so the
per-pivot barrier is cheap while the tiny XOR width is still covered.
Measured: bs.block_reduce 334->115 ms (2.9x), +12% at 2^16, +6% at 2^17, bit-exact.
Neither part alone helps — recursion+full-grid keeps every barrier (neutral);
small-grid on the full 1024-block starves its XOR (worse, 485 ms).
Now the default on the coop path; FP_CUDA_NO_BS_TRSM restores the elementwise
full-block reduce, FP_CUDA_BS_BASE / FP_CUDA_BR_CTAS tune it. bs_clear_above is
shared with the outer loop and reads perm via .slice(off..). Gates: row_reduce_demo,
forward_reduce_demo, cuda_dispatch all bit-exact on both default and fallback.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01B1XztNMycTtSC9SQtpNYjR
panel_factor (grid_sync/pivot-bit) and promote (the forward-substitution TRSM, grid_sync/pivot) are both partly grid-barrier-bound, and a grid_sync's cost scales with CTA count. Cap their cooperative grids (panel_factor 128, promote 384 on H200) so barriers are cheap while the per-step work is still covered — same lever as the back-sub small-grid reduce. Measured: panel_factor +3%, promote +6% at 2^16; together +8% at 2^16, +5% at 2^17, on top of the TRSM win. Env overrides FP_CUDA_PF_CTAS / FP_CUDA_PROM_CTAS; both clamp to the natural grid so small matrices are unaffected. Bit-exact (row_reduce_demo, forward_reduce_demo, cuda_dispatch). Cumulative this session: 2^16 186->227 Tbop/s, 2^17 432->487. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01B1XztNMycTtSC9SQtpNYjR
…xperiment promote is a forward-substitution triangular solve; built it as a blocked TRSM (promote_rec/promote_clear): recurse the pr pivots into 64-aligned halves, apply the left to the right pivot rows via one X.U GEMM where X is gathered straight from L (no M-column gather; needs the new l_limb_off arg on promote_coop so a sub-block reads the right L bits), U is the promoted left rows. Correct (a pivot row only receives contributions from earlier pivots — no double counting), bit-exact on both paths incl. cuda_dispatch. MEASURED (2^16): a WASH. promote drops 176->107 ms in isolation but end-to-end is flat-to-worse (best +1.4% at base=512). Unlike back-sub's clear-above GEMM (spans all above rows, large m), promote only touches the pr<=1024 PIVOT rows, so its GEMMs are small-m AND small-K — too small for the tensor cores, and the many small kernels/panel pipeline worse than one elementwise sweep. The promote grid-cap (+6%, already default) was the real win; promote is intrinsically a small op. Kept behind FP_CUDA_PROM_TRSM as the definitive experiment; default unchanged. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01B1XztNMycTtSC9SQtpNYjR
Post-optimization cleanup now that the row-reduction tuning is settled. Removed (flag-gated experiments, all net-loss/wash, kept in earlier commits): FP_CUDA_PROF harness, FP_CUDA_DEEP/BASE deep recursion (factor_panel_rec), FP_CUDA_MICRO, FP_CUDA_PROM_TRSM/PROM_BASE (promote_rec, promote_clear), l_shift_or + kernel, FP_CUDA_DEBUG. Dropped A/B kill-switches (committed to defaults): NO_COOP, NO_COMPACT, NO_BS_TRSM. Kept tuning knobs: BL, PF_CTAS, PROM_CTAS, BR_CTAS, BP, BS_BASE, GEMM_CTAS. RR dispatch threshold re-validated at 8192. Net -520 lines in lib.rs. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01B1XztNMycTtSC9SQtpNYjR
JoeyBF
force-pushed
the
claude/blas3-row-reduction-lgjz3y
branch
from
July 18, 2026 03:30
a58f096 to
8b721f1
Compare
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.
Stacked on #273 (the
fp-cudaHopper GEMM backend) — review/merge #273 first;until it lands this PR's diff includes #273's commits. Rebases to blas3-only once
#273 merges.
Adds a fully device-resident F₂ (GF(2)) row reduction (reduced row echelon form)
on Hopper, dispatched from
fp::Matrix::row_reducefor largep = 2matrices,and bit-exact vs the CPU M4RI oracle.
What's here
benchmarks establishing the algorithm and the bit-exact oracle.
(Phase 3),
panel_factorbase kernel (Phase 4), device-resident forwardrow-echelon pass (Phase 5a), back-substitution to canonical RREF (Phase 5b),
and dispatch of
fp::Matrix::row_reduceto the device above a size threshold(Phase 7). The whole reduction stays on the GPU — uploaded once, downloaded
once.
panel_factor/ promote /block_reduce, wide adaptive forward panels,active-row compaction, blocked-TRSM back-substitution (now default) with a
small-grid reduce, grid-cap tuning, and a low-barrier
grid_sync. Flag-gatednet-loss/wash experiments (deep recursion, blocked-TRSM promote, recursive
panel) are kept as distinct commits documenting the exploration.
FP_CUDA_PROFinstrumentation, dropsthe A/B kill-switches (commits to the proven defaults), keeps the tuning knobs,
and re-validates the dispatch threshold at 8192.
Testing (H200)
cargo test -p fp --features gpu --test cuda_dispatch—gpu_row_reduce_matches_cpuand
gpu_dispatch_matches_cpupass (bit-exact vs CPU).fp-cudademos (row_reduce_demo,forward_reduce_demo,panel_factor_demo,gemm_xor_into_demo,matmul_b1_dev_demo) print all-OK.🤖 Generated with Claude Code