Repository navigation
perf(cuda): PORT-1 interleave — measured no-go on multicol asymmetric GEMV (ncu: 0 reg / 0 occupancy delta) - #1002
justinchuby wants to merge 6 commits into
Conversation
…opt-in) Adds an opt-in fp16 sibling of the column register-blocked wide GEMV (matmul_nbits_gemv_f16_general_bs_wide_multicol_fp16), gated by ONNX_GENAI_GEMV_FP16=1. The per-chunk MAC runs in fp16 __hfma2 (two fused MACs/instruction) instead of fp32 FFMA, matching ORT's fp16 arithmetic — the equal-conditions fp16-vs-fp16 path. PRECISION CONTRACT: only the 32 products of ONE quant chunk (single per-block scale) are summed in fp16 into a __half2 accumulator, then reduced to fp32, scaled, and accumulated across chunks in fp32. The K=4096 reduction stays fp32 (NOT a full-fp16 K accumulate). Not byte-identical to fp32 by construction -> opt-in, gated on accuracy (error <= ORT vs f64 oracle), not bit-identity. ncu (glm gate_up): 34.3->27.0us, DRAM 1.76->2.24 TB/s (ORT 2.42), L1/TEX 28->36%, SM 60->54% (now occupancy-bound at 41%). glm base decode 198->210 tok/s (+6.2%); greedy tokens identical to fp32 over 160 tokens on generic + repetitive prompts. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Adds fp16_mixed_gemv_matches_f64_oracle_glm_decode: runs the fp16 kernel (ONNX_GENAI_GEMV_FP16=1) on glm's real block-128 M=1 projection shapes (qkv/o/gate_up/down x sym+asym zp x fp16+fp32 scales) against the same f64 dequant->GEMM oracle, and holds it to the SAME justified Envelope the reviewed fp32 int4 path satisfies (no weakening). Also runs the fp32 path on each problem and reports the fp16-vs-fp32 oracle-error ratio, and asserts the fp16 output diverges from fp32 (proving the fp16 entry -- not the fp32 fallback -- was selected). Pins ONNX_GENAI_GENERAL_SPLITK=0 for deterministic entry selection. Measured: fp16 max_rel=6.3e-3 (8x under the 5e-2 bound); symmetric ~1.05-1.20x the fp32 path, asymmetric-zp 9-13x on rel but abs error stays <<bound (small output magnitude). fp16 keeps the K reduction in fp32 so it is at least as precise as any full-fp16 kernel. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Replace the per-chunk fp32 flush in the multicol fp16 int4 GEMV with a full-fp16 half2 accumulate across the lane's K chunks, folding the per-block scale in with __hfma2 and deferring fp32 to only the final cross-lane warp_sum. This mirrors ORT's MatMulFloat4BitsKernelM1 arithmetic exactly. Safe because the per-lane fp16 reduction is a wide, shallow tree: with 32 lanes striding K by 32, each lane folds only ~K/1024 chunks (~4 for K=4096), so fp16 accumulation depth is tens, not thousands — negligible mantissa loss (unlike a naive deep single-accumulator fp16 sum, which flips tokens). f64 oracle: fp16 max_rel=1.16e-2, 4.3x under the 5e-2 envelope (same error class as the reviewed fp32 int4 path). Steady glm base decode 212.8 vs fp32 199.4 tok/s. Speed is a tie with the per-chunk-fp32 version (kernel 26.4 vs 27.0us): the limiter is multicol register state + wave/tail quantization (achieved occupancy 41%), not the accumulator, so ORT's "full-fp16 is faster" holds only marginally at the kernel level. Kept behind ONNX_GENAI_GEMV_FP16=1 for the validation phase. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Reflect the final shipped kernel (full-fp16 half2 accumulate matching ORT's MatMulFloat4BitsKernelM1) and the honest findings from the ORT source read: LOP3+prmt idioms were already present; full-fp16 vs per-chunk-fp32 is a speed tie (occupancy/wave-bound, not accumulator- bound); WIDE_NC_FP16=2 rejected; f64 max_rel 1.16e-2; glm 212.8 tok/s. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…ric GEMV Add a template<bool NATURAL> to the fp16 multicol GEMV device fn to isolate the only runtime op PORT-1's offline bias+interleave removes on our kernel: the 4x prmt.b32 activation reorder (decode_activation8_h2). NATURAL=true swaps in a prmt-free decode assuming offline-interleaved weights; the <false> path is byte-identical codegen to the shipping #996 fp16 kernel (f64 oracle 8/8, regs and kernel time unchanged). ncu on the glm gate_up matrix (GPU6, --graph-profiling node) shows PORT-1's premise (cheaper dequant -> fewer regs -> higher occupancy) does not hold here: registers 64->64 (0 freed), occupancy 41.17%->40.92% (noise), kernel time 26.69->26.53us (noise). The 64-reg footprint is dominated by WIDE_NC=4 column state, and the asymmetric zero-point subtract (glm/qwen both use zp) is irreducible, so FastInterleaved's symmetric-only op saving does not apply. The activation prmt is amortized across 4 columns and register-transient, so removing it frees nothing. Probe entry gated behind ONNX_GENAI_GEMV_FP16_NATURAL=1 (default off); its output is intentionally wrong vs current non-interleaved weights and it is never a correctness path. Kept as reproducible evidence. Decision drop documents the measurement and recommends not building the offline-repack surface for this kernel. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## squad/int4-gemv-fp16-mixed #1002 +/- ##
===========================================================
Coverage 83.56% 83.56%
===========================================================
Files 12 12
Lines 5270 5270
Branches 5270 5270
===========================================================
Hits 4404 4404
Misses 682 682
Partials 184 184
Flags with carried forward coverage won't be shown. Click here to find out more. 🚀 New features to boost your workflow:
|
✅ Benchmarks — No RegressionComparison of criterion micro-benchmarks: PR head vs merge-base, measured on the same runner in the same job (base first → PR second).
Visual flags: Host infoWhat this cannot catch
|
|
Triage, not a rejection. The measured negative is valuable and I want it in the tree; the code half I cannot validate. Why I am not merging as-is: this PR carries a CUDA kernel variant alongside its decision drop, and this machine no longer has a CUDA runtime -- a recursive search finds no What I would like: split the decision drop out and I will land it immediately, the way #1011 just landed. A measured no-go deserves permanent shelf space -- the whole point is that the next person to propose this lever finds the numbers instead of re-running the experiment. The kernel variant can then wait for a reviewer with a working CUDA host, where "default off" and "byte-identical" can actually be demonstrated rather than asserted. If you would rather not split it, say so and I will leave this open until I have GPU hardware again; that is a worse outcome only because the negative record stays unpublished in the meantime. |
PORT-1 (offline bias+interleave) — measured NO-GO on the multicol asymmetric-zp GEMV
Stacked on #996 (fp16 multicol GEMV). Base branch:
squad/int4-gemv-fp16-mixed.PORT-1 from the TRT-LLM port plan was ranked as an occupancy lever: bake the signed→unsigned +8 bias and odd/even nibble interleave into the stored weight layout so the runtime dequant drops ORT's
prmt.b32→ fewer registers → higher occupancy → faster. On our kernel that mechanism does not exist, and I proved it with ncu before building the large offline-repack/versioned-layout/loader surface (which would have delivered ~0 gain).Method
Added
template<bool NATURAL>to the fp16 multicol device fn.NATURAL=trueswaps the 4×prmt.b32activation reorder (decode_activation8_h2) for a prmt-free decode that assumes offline-interleaved weights — isolating exactly the one op PORT-1 removes for the asymmetric case.<false>is byte-identical codegen to the shipping #996 kernel (f64 oracle 8/8, registers + kernel time unchanged). Probe entry gated behindONNX_GENAI_GEMV_FP16_NATURAL=1(default OFF); its output is intentionally wrong vs current non-interleaved weights and is never a correctness path — kept as reproducible evidence only.Result (ncu, glm gate_up, GPU6, fresh idle,
--graph-profiling node)Why the premise fails here
prmtis amortized across WIDE_NC=4 columns and is register-transient, so removing it frees 0 registers and is already latency-hidden.w[4]uint4=16,col_kb[4]long=8, total/acc/scale2/sub2). Occupancy won't rise without dropping WIDE_NC — which loses the multicol L1 win.Bottom line
Third convergent "occupancy is the wall and it doesn't move" finding (after depth-4 prefetch and cp.async #1000). The fp16 GEMV is already ~95% of ORT's DRAM and near per-kernel parity; the remaining base-vs-ORT gap is dominated by other ops (lm_head — deckard-4). Recommendation: don't build PORT-1's offline-repack surface for this kernel; land #996, let lm_head land, re-measure the gap.
Draft / documented-negative PR — not intended to merge as a feature. Kept for reproducibility + decision record (
.squad/decisions/inbox/deckard-port1-nogo.md).