Repository navigation
perf(cuda): default-on lm_head cuBLASLt + multi-shape capture guard - #1004
Merged
Merged
Conversation
Promote the #991 cuBLASLt lm_head decode MatMul to default-on and harden its CUDA-graph capture guard from a single cached shape to a shape-keyed plan cache, so the out-of-box glm base decode banks the cuBLASLt logits projection without an env flag. Capture guard (Gaff's #991 blocker): `launch_dense_capturable` now keeps a shape-keyed `Vec<DenseGemmPlan>` (LRU, MRU-promote on hit, bounded by DENSE_PLAN_CACHE_CAP=8) instead of a single `Option<DenseGemmPlan>`. Every distinct (dtype, M, K, N) the node runs keeps its own preselected cuBLASLt algorithm + persistent workspace, so alternating shapes (prefill M>1, decode M==1, a speculative M=K verify width) all replay with no per-call heuristic query inside the captured region. The reject-cold-miss-during-capture contract is unchanged (now enforced per shape); eviction only ever runs off the non-capturing creation path, and the hot decode plan is kept at the MRU front, so a plan baked into a live captured graph is never freed. Strictly safer than the single-slot path (evicts less; same recapture-on-change discipline). Default-on: `lmhead_cublaslt_enabled()` inverts to default-TRUE-unless-disabled; set ONNX_GENAI_LMHEAD_CUBLASLT=0/false/off for the hand-GEMV escape hatch. #999 GQA flash-decoding is already default-on (its env vars are rollback-only knobs), so no flip is needed there. GEMV fp16 (#996) stays untouched (not in this base) and is a one-liner default flip once it merges. Measured (glm-4-9b-int4, H200, graph-on, fallbacks=0): - default-on short decode 207.72 tok/s (vs escape-hatch-off hand-GEMV 202.24) - default-on KV2048 171.69 tok/s, fallbacks=0 across 8 bucket-growth recaptures - lm_head cuBLASLt(default) == hand-GEMV(off) top-40 logprobs BYTE-IDENTICAL - qwen no regression (152.16 vs 153.60, within noise), fallbacks=0 - f64 oracle 7/7, GQA fp16 2/2, fmt + clippy clean Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…guard Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
… flip) Correct the default-on decision drop: withdraw the "one-liner hook to default-on fp16 GEMV later / fp16 fixes fp32 nondeterminism" plan. Chew's #996 verdict is land-as-opt-in — glm has intermittent knife-edge argmax flips vs fp32, so fp16 is not byte-identity-safe for default. Out-of-box GEMV stays fp32 (byte- identical); the honest out-of-box default-on number is ~207.7 short / ~171.7 KV2048 (fp16 OFF). 232 requires user-set ONNX_GENAI_GEMV_FP16=1 and is not the default. 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 @@
## main #1004 +/- ##
=======================================
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:
|
justinchuby
force-pushed
the
squad/base-wins-default-on
branch
from
August 15, 2026 09:33
963a75a to
c3ecc24
Compare
…re guard Record the measured capture-guard evidence: over a glm KV2048 run (prompt->decode->8 KV-bucket-growth recaptures, 8623 replays) the shape-keyed plan cache does exactly 2 heuristic plan searches total (M=3 prefill once, M=1 decode once), both on the non-capturing path, and 0 searches during any replay or recapture, fallbacks=0. Confirms path (a): a shape-keyed multi-shape plan cache warming each shape once outside capture — the specific "no per-transition plan search" guarantee Gaff's #991 caveat asked for. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
🔴 Benchmark Regression DetectedComparison 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
|
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.
Default-on: lm_head cuBLASLt + multi-shape capture guard
Promotes #991's cuBLASLt
lm_headdecode MatMul to default-on (out-of-boxglm base), after clearing Gaff's #991 blocker: the single-shape cuBLASLt plan
cache is hardened into a shape-keyed multi-shape plan cache so plan creation
never happens inside a captured CUDA-graph region across prefill/decode/verify
shape switches. Rebased onto main
ad141370(post-#996); disjoint from #996'sopt-in fp16 (which stays OFF by default).
Capture guard — path (a): shape-keyed multi-shape plan cache
#991 already had a warm-once / reject-cold-miss-during-capture contract, but for
a SINGLE cached shape (a shape switch evicted + re-ran the heuristic search).
This PR generalizes it to a
Vec<DenseGemmPlan>keyed by(dtype,M,K,N),MRU-ordered, bounded by
DENSE_PLAN_CACHE_CAP = 8:Err(reject-before-warm, now per shape);stays MRU-front and is never the LRU victim. Strictly safer than the single
slot (evicts far less; same recapture-on-change discipline).
Shape-transition proof (temporary
DENSE_PLAN_SEARCHcounter, glm KV2048 run= prompt → decode → 8 KV-bucket-growth recaptures, 8623 replays):
M=3prefill (once) +M=1decode (once), each on the non-capturing path;
did). This is the "no per-transition plan search, not just steady-state"
guarantee. Counter reverted (not committed).
Default-on flip (byte-identical wins only)
lmhead_cublaslt_enabled()→ default-TRUE-unless-disabled; escape hatchONNX_GENAI_LMHEAD_CUBLASLT=0|false|off→ hand fp16 GEMV.rollback-only knobs); no flip needed.
verdict is land-as-opt-in: the fp16 K-accumulate is not byte-identity-safe vs
fp32 (glm knife-edge argmax flips), so it is not a default-on candidate. Out-of-
box GEMV stays fp32 (byte-identical); fp16 via
ONNX_GENAI_GEMV_FP16=1.Measured (glm-4-9b-int4, H200, graph-on, median, no env flags)
=0(hand GEMV)(short 207.72 GPU3 / 206.44 GPU4; KV2048 171.69 / 168.06 / 165.59 across
GPU3/6/4 — cross-GPU + shared-host variance; all fallbacks=0.)
logprobs byte-identical (both select token 315).
Out-of-box number is ~206–208 short / ~166–172 KV2048 and that is final for
this PR. The ~232 combined figure required fp16 GEMV opt-in ON; since fp16
ships permanently opt-in, 232 is only reachable via
ONNX_GENAI_GEMV_FP16=1.(capture-safe, fallbacks=0, win real).