Status: current, partly shipped · RTX 4090 (sm_89), CUDA 13.2 · checked 2026-10-08
Decision record for the campaign that makes native batched factorizations (potrf, getrf, geqrf, gesv, posv, orgqr) competitive with cuBLAS and cuSOLVER at n = 4..512. It records the baseline verdict, the design rules, what was decided, what shipped, and what is still open. Measured grids are in docs/perf/small-n-baseline.md; per-op evidence is in the potrf, lu and qr pages under docs/perf/.
Ratios are vendor_ms / native_ms; above 1 means native wins. "Large batch" means the saturating batch for the order (>= 2048 at n <= 64, >= 512 at n = 512). Measured 2026-09-10 on the pre-flat-selection tree (readiness/wp3-abi, aca64f6).
| op | float | cfloat | double | cdouble | vendor |
|---|---|---|---|---|---|
| potrf | 2.75x at n=8, 1.85x at 32, 1.26x at 48; 0.36-1.00x at 64..256 | 1.79x at 8, then 0.40-1.05x | 0.58-1.07x | 0.57-1.47x | cuSOLVER potrfBatched |
| getrf | 0.55x at 32, 1.61x at 64, 0.73-0.87x at 128, 1.25-2.35x at >= 256 | 0.60x at 32, 0.38-0.75x at 128 | 0.27-0.75x | 0.34-0.75x | cuBLAS getrfBatched |
| geqrf | 0.78x at 32, 2.0-12x at 64..512 | 0.71x at 32, 2.3-8.9x above | 0.21x at 32, 0.53x at 64, 1.6-6.8x at >= 128 | 0.33-0.84x to 256 | cuBLAS geqrfBatched, unblocked |
| getrs (nrhs <= 2) | 1.7-2.5x | 1.3-1.6x | 2.0-3.8x | 1.8-2.6x | getrsBatched; fused tier wins |
| orgqr | 3.8-123x | 2.3-65x | 6-114x | 1.7-12x | per-item loop; not a real comparison |
Diagnosis. The native factorizations keep the whole matrix in local memory, one work-group per matrix, with a runtime n and a rank-1 recurrence. Above about 48 KB of local memory that allows 1-2 resident work-groups per SM (sm__warps_active = 8.3% for potrf at n = 96..155). Below about 32 it wastes most of a warp on a matrix one sub-group could hold in registers. The blocked drivers lose at n <= 256 because their leaf is that kernel; their trailing GEMMs only dominate at n >= 512.
| kernel | design | loss mechanism |
|---|---|---|
| potrf CTA (potrf_cta.cc) | whole matrix in SLM, NB=8, L=32..256 | SLM footprint: 1-2 blocks/SM above n ~90 |
| getrf CTA (getrf_cta.cc) | whole matrix in SLM, rank-1 right-looking | 3 work-group barriers per column; wg >= 64 for a 32-column matrix |
| geqrf CTA (geqrf_cta.cc) | whole m x n tile in SLM, two reduce_over_group per reflector | 256-lane reduction for a 64-element column; 48 KB pad; FP64 rate |
| geqrf blocked (geqrf_blocked.cc) | right-looking WY, nb 32 (16 double), serial larft | larft 15-49% of time; V^H A22 on Tiled16 for complex |
| getrs fused (getrs_fused.cc) | one work-group per matrix, RHS resident | wins; the model for gesv/posv |
Reference designs. The literature and MAGMA's source agree on: thread-per-row with the matrix or panel in registers and a compile-time column count; local memory only as a mailbox; several matrices per work-group when n < 32; lazy row swaps; a left-looking fused Cholesky panel up to n ~ 160-224; a fused register panel as the LU and QR leaf up to m = 512; BLAS-3 blocking only above ~192-256.
Hardware anchors (RTX 4090): 128 SMs; 65,536 registers per SM; 101,376 B opt-in local memory per work-group; a dynamic request in (47,104, 49,664] B fails cold (the 48 KB hole); DRAM ~950 GB/s achievable; FP64 ~1.3-1.44 TFLOP/s. The DRAM roof for read-plus-write-once float n=32 at batch 8192 is 70 us; cuBLAS getrf there is 159 us and cuBLAS geqrf 772 us.
Targets and kill criteria as set in the plans (float, large batch, vs vendor):
| plan | idea | status |
|---|---|---|
| P0 | harness, baseline, routing flips | Partly shipped. benchmarks/factor_bench.cc, run_factor_grid.sh, gpu_guard.sh exist. Six of the 20 planned baseline CSVs are in benchmarks/results/ (getrs x4, orgqr double, potrf float). |
| P1 | register-resident sub-group kernels, n <= 32 | Shipped for potrf, getrf and geqrf (*_tiny.cc, tiny_device.hh, geqrf_tiny_device.hh). geqrf departed from the design: two barriers per column, C-derived tile, work-group 64. See docs/perf/qr.md#the-tiny-tier-wp6--p1-square-n--32-in-registers. |
| P2 | fused factor-and-solve, gesv and posv | Shipped gesv_tiny.cc, posv_tiny.cc, linalg::solve_spd, and the linalg::solve NoTrans routing (landed in a separate readiness pass). |
| P3 | left-looking Cholesky panel (lpanel) | Shipped, Lower only (potrf_lpanel.cc, panel 8 and float panel 16). The sm_89 tables do not yet pick it; see section 5. |
| P4 | register LU leaf | Partly shipped. getrf_panel_reg.cc (2026-09-11) is the leaf. The recursive nb=128 panel and the row-parallel right-hand laswp are not in getrf_blocked.cc. |
| P5 | fused QR panel and reflector apply | Partly shipped. GeqrfPanelRegKernel is the leaf, with cols = 16 for double only; the others are cols = 0 (absent) because each adds 39.6-60.5 s of ptxas to the link. Not built: the fused apply, geqrf_fused_driver, orgqr_fused, ormqr_fused, and the larft replacement. |
| P6 | transposed and complex wide-scalar GEMM | Superseded by the wide:m=..:n=..:k=.. candidates in src/ops/gemm/choice.hh. |
| P7 | capacity rule, G-packing, native_tier_preferred | Helper exists (src/util/resident_capacity.hh). native_tier_preferred no longer exists. Whether all three per-kernel capacity walks use the helper was not checked. |
Why P5's fused apply was not the next step. geqrf_nb_for_type<T>() is 32 for float, cfloat and cdouble, and 16 only for double. The panel loop returns after one panel for a 32-wide panel, so it never reaches pack_v, larft or the trailing GEMMs that the fused apply would remove. The "larft is 34% of time" figure came from a batch-8 profile and is not saturated; the larger-batch profiles put larft plus pack_v at 6.5-22%. Size any larft work against those.
| Alternative | Result | Verdict |
|---|---|---|
| potrf NB=16 in the SLM-resident kernel | 156-206 registers, 2x slower | dead |
| reduce_over_group over the work-group | 48 KB hole; 1.5-4.7x slower for FP64 | dead (R4) |
| Packed sub-group getrf with the matrix in SLM | no win | dead |
| Cooperative multi-lane trsm solve | no win | dead |
| Bespoke potrf panel-solve kernel | no win | dead |
| Launch-count fusion alone | <= 3% | dead |
| geqrf nb = 128 | no win | dead |
| Tiled16 double-buffering and B-packing for strided ld | no gain | dead |
| Pivot-search experiment with SLM matrix and runtime n (wp6_perf/proto/pivsg.cpp) | lost; did not test R1 register storage | inconclusive |
On 2026-09-11 the house rule became a gate. Measured numbers live in docs/perf/<op>.md behind an evidence: docs/perf/<page>.md#<anchor> pointer; code comments carry correctness rationale only.