✅ RESOLVED (2026-06-14)
CUDA-graph decode is now default-on for Gemma 4 (SHARPI_CUDA_GRAPH != "0"), measuring +9–10% at both low and ~1K context. The short-context regression this issue targeted is gone — from the #137 launch fusions + dp4a decode matvec, plus the finding (#142) that the original "27 t/s at depth 0" cliff was a --verbose-prompt full-vocab-sort bench artifact (real matvecs sit at ~90% HBM, no clock cliff). The proposed device-side-int position was not needed: per-token cost is one cuGraphLaunch + a handful of node-param updates scoped to the position-varying nodes (TrackPositionNode / GraphPosKind). Acceptance (no regression, default-on) met. Closing.
Follow-up to #136 / PR #139
CUDA Graph decode (PR #139, SHARPI_CUDA_GRAPH, default off) replays the captured Gemma 4
decode region but updates ~168 kernel nodes per token via cuGraphExecKernelNodeSetParams
(one per position-varying op: RoPE q/k, KvAppend, Attention, per layer) before each
cuGraphLaunch. That per-token host overhead is why the win is context-dependent:
| Context |
graph OFF |
graph ON |
Δ |
| short prompt (~20 tok) |
27.0 |
24.4 |
−9.7% |
| ~1K ctx |
42–43 |
45 |
+5–6% |
(gemma-4-E4B-it-Q8_0, RTX 4070 Ti, interleaved/warm.) When per-token GPU work is small the
SetParams overhead exceeds the launch-collapse savings → net slower. This regression is the
only reason the feature ships default-off.
Proposed fix: device-side position
Store position in a single device int that the five position-varying kernels read, instead
of passing it (and seqLen/window bounds) by value. Then each token costs 1 small
cuMemcpyHtoDAsync + 1 cuGraphLaunch — no per-node SetParams at all. Expected to:
- remove the short-context regression (host overhead becomes O(1)/token, not O(layers)),
- likely widen the long-context win,
- make graphs a net win across contexts → candidate to flip the default to on.
Cost / risk
- Edit 5 NVRTC kernels (
rope, rope_neox_with_factors, kv_append, attention,
attention_swa) to take const int* for position-derived args and compute the derived
quantities (seqLen = *pos + 1, SWA window bounds) in-kernel.
- These kernels are shared with the non-graph / prefill paths, so the by-value entry must be
preserved (or a uniform indirection adopted) — bit-parity risk against the direct-launch
path. The existing Gemma4CudaGraphParityTests bit-identical oracles will catch any drift.
- The win is still capped by the memory-bandwidth ceiling (~504 GB/s; decode reads 8 GB of
Q8_0 weights/token), so don't expect more than low-double-digit %.
Acceptance
- No short-context decode regression vs direct launches (within noise).
- ≥ the current +5–6% at ~1K ctx.
- All graph bit-parity oracles stay green.
- If both hold, evaluate flipping
SHARPI_CUDA_GRAPH to default-on.
Follow-up to #136 / PR #139
CUDA Graph decode (PR #139,
SHARPI_CUDA_GRAPH, default off) replays the captured Gemma 4decode region but updates ~168 kernel nodes per token via
cuGraphExecKernelNodeSetParams(one per position-varying op: RoPE q/k, KvAppend, Attention, per layer) before each
cuGraphLaunch. That per-token host overhead is why the win is context-dependent:(gemma-4-E4B-it-Q8_0, RTX 4070 Ti, interleaved/warm.) When per-token GPU work is small the
SetParams overhead exceeds the launch-collapse savings → net slower. This regression is the
only reason the feature ships default-off.
Proposed fix: device-side
positionStore
positionin a single deviceintthat the five position-varying kernels read, insteadof passing it (and
seqLen/window bounds) by value. Then each token costs 1 smallcuMemcpyHtoDAsync+ 1cuGraphLaunch— no per-nodeSetParamsat all. Expected to:Cost / risk
rope,rope_neox_with_factors,kv_append,attention,attention_swa) to takeconst int*for position-derived args and compute the derivedquantities (
seqLen = *pos + 1, SWA window bounds) in-kernel.preserved (or a uniform indirection adopted) — bit-parity risk against the direct-launch
path. The existing
Gemma4CudaGraphParityTestsbit-identical oracles will catch any drift.Q8_0 weights/token), so don't expect more than low-double-digit %.
Acceptance
SHARPI_CUDA_GRAPHto default-on.