[Kernel] DSv4 flashmla norm-rope: K-tokens-per-block ILP to hide load latency - #33358
Rainchar9119 wants to merge 3 commits into
Conversation
… latency
The fused_norm_rope_flashmla kernel (head_dim=512 RMSNorm + tail-64 RoPE +
paged KV-cache store, FP8-quant and bf16 store paths) is memory-latency-bound:
NCU shows long_scoreboard as the dominant stall (~15/issue) with DRAM at only
~5-7% of peak, i.e. it stalls waiting on global loads rather than on bandwidth.
The 1-token-per-block layout issues one input load and immediately consumes it,
so nothing hides the ~hundreds-of-cycles load latency. This makes a block
process K tokens back-to-back (K=4): resolve all K plans first, then issue all
K input loads before consuming any, keeping multiple independent global loads
in flight. Input is streamed via __ldcs (evict-first) so it doesn't evict the
reused weight/freqs from L1. The per-token reduction tree and store bytes are
unchanged, so output is bit-identical to the original on both store paths.
Small num_tokens is grid-starved at K=4 (fewer blocks than SMs), so the
launcher drops to K=1 below a cutoff (2048). The RoPE complex multiply is
pinned to an explicit fma so the unrolled K-loop keeps the baseline's rounding.
Correctness: whole-kvcache byte parity vs the unmodified kernel across
extend/decode x N in {256..16384} x ordered/permuted out_loc, both FP8 and
bf16 paths -> 0 byte diffs, no NaN/Inf, skipped slots untouched.
Perf (median, L2-flushed, ratio=new/old, <1 is faster): bf16 store large-N
~0.75-0.88 (up to ~1.33x); FP8 store large-N ~0.85-0.92; small-N neutral.
Co-Authored-By: Claude <noreply@anthropic.com>
Covers both store paths of compress_norm_rope_store (head_dim=512): the
default FP8-UE8M0-quant path and the bf16-store path, each against an
independent torch reference (RMSNorm-512 + per-dim bf16 weight + trailing-64
RoPE). Batch sizes {1,8,64,256,2048} exercise both the K=1 small-N and K=4
large-N launcher branches. FP8 nope dims are checked by dequantizing the
per-64-group UE8M0 scales (fp8-e4m3 round-to-nearest bound rtol=1/16); the
rope bf16 tail and the bf16 path use rtol=atol=2e-2, plus NaN/Inf checks.
Co-Authored-By: Claude <noreply@anthropic.com>
|
Caution The consumer version of Gemini Code Assist on GitHub has been sunset. All code review activity has officially ceased. |
|
/rerun-failed-ci Hi maintainers — this is my first contribution, so the pr-gate step failed before Quick summary for reviewers:
cc @BBuf @DarkSharpness @HydraQYH @celve @yuan-luo — thanks for taking a look! |
|
Hi maintainers, could someone please help review this PR when you have a chance? |
…project#32035 sgl-project#33656 sgl-project#32183 sgl-project#33145) Applied PRs (latest from GitHub): sgl-project#33288 Indexer logits OOM fix sgl-project#30393 HiCache packed/sidecar draft caches sgl-project#31170 DPA prefix_affinity load balancing sgl-project#33795 DSpark compact ragged-verify CUDA graph JIT race sgl-project#32467 C128 plan-kernel warp barrier sgl-project#33865 DSpark x prefill CP unblock sgl-project#30371 SWA state pool sizing (storage page) sgl-project#33358 FlashMLA norm-rope K-tokens-per-block ILP sgl-project#33872 num_draft_tokens clamp + extend_len==0 skip (supersede sgl-project#32183) sgl-project#34002 Sidecar backup vacuously-successful fix (replaces sgl-project#33656, with tests) sgl-project#33862 Reclaim redundant host mirrors after storage backup sgl-project#31315 Avoid repeated Mooncake gets after stale hits sgl-project#32327 Q8KV8 sparse MLA prefill backend (flashmla_sparse_q8) sgl-project#31668 Fix sidecar pool life-time (use-after-free on prefetch abort) sgl-project#31195 TP0 verify-token-budget broadcast (adapted to get_schedule() API) Dropped (per user request or superseded): sgl-project#32771 IndexCache C4 top-k reuse — has bug sgl-project#32035 DSpark C128 online compressor — has bug sgl-project#33656 Superseded by sgl-project#34002 (same fix + unit tests) sgl-project#32183 Superseded by sgl-project#33872 (included in supersede PR) sgl-project#33145 Base f01f706 already has superior reasoning-effort profile system Conflicts resolved: sgl-project#31195: adapted to base get_schedule().disable_overlap_schedule API sgl-project#32327: path remapped jit_kernel/ -> kernels/jit/ and kernels/ops/attention/ sgl-project#31668: applied cleanly on top of sgl-project#30393+sgl-project#34002+sgl-project#33862 modifications
b8zhong
left a comment
There was a problem hiding this comment.
Do we really need this? Although the unit speedup is good, norm+rope kernel should be really small at these large num tokens
Motivation
fused_norm_rope_flashmla(the DSv4 FlashMLA norm-rope-store kernel: RMSNorm overhead_dim=512 + RoPE on the trailing 64 dims + write to the paged KV cache; shared
by the default FP8-UE8M0-quant store path and the
kBf16Storebf16 path) ismemory-latency-bound, not bandwidth-bound. NCU on the baseline shows
long_scoreboard(warps stalled waiting on global loads) as the dominant stall(~15 cyc/issue) while DRAM sits at only ~5–7% of peak and the SM throughput is
~25–41%. The baseline processes one token per block and consumes each input
load immediately, so nothing is in flight to cover the ~hundreds-of-cycles load
latency — the lever is instruction-level parallelism on the loads, not the math.
Modifications
Pure launch/ILP restructuring of the flashmla kernel; the RoPE, RMSNorm reduction
tree, UE8M0 quant, and store byte layout are untouched. The indexer and fp4
paths are not modified.
back-to-back. Stage A resolves all K plans first (K independent 16 B plan
loads in flight), stashing position / out_loc / valid. Stage B then issues
all K input (+ rope-warp freqs) loads back-to-back — addresses are already
resolved, so the K global loads have no mutual dependency and stay in flight
together, covering the load latency the 1-token layout stalled on. The weight
vector is loaded once (shared across the K tokens).
__ldcs(evict-first / read-only path). Input isread exactly once, whereas weight/freqs are reused; streaming the input keeps
it from evicting the reused data from L1. This drives
long_scoreboarddownfurther than the K-ILP alone (16384 decode: 15.1 → ~6.3 cyc/issue).
than SMs → occupancy collapses), so the launcher drops to K=1 below a
cutoff (
kFlashmlaSmallNCutoff = 2048). Per-token math/store are identicalacross K, so this is purely a scheduling choice.
__fmaf_rn. With the K-loop unrolled, nvccwould otherwise pick different fp-contraction forms for
a*b - c*dacrossiterations and produce 1-ULP drift. Pinning the fma keeps the exact rounding of
the 1-token baseline.
Each (token) is a fully self-contained work-item (its 512-dim reduction, RoPE, and
store depend only on its own input/plan), so which SM / block / K-grouping runs it
does not change its output bits. Output is therefore bitwise-identical to the
previous kernel on both store paths — default FP8 quant and
kBf16Store.kFlashmlaTokensPerBlock=4andkFlashmlaSmallNCutoff=2048are sm_100 (B200)autotune values, kept as kernel
constexpr(easy to retune for other SM counts).Accuracy Tests
New
test/registered/kernels/ops/attention/test_dsv4_flashmla_norm_rope.pychecksboth store paths against an independent torch reference (RMSNorm-512 + trailing-64
RoPE + bf16/FP8 store), across batch sizes spanning the K=1 small-N branch and the
K=4 large-N branch, decode mode.
The FP8 nope dims are checked by dequantizing the 448 fp8-e4m3 bytes with the 7
per-64-element-group UE8M0 exponents and comparing to the reference at
rtol=1/16(2⁻⁴, the round-to-nearest bound for fp8-e4m3's 3 mantissa bits);the rope bf16 tail and the whole bf16 path use
rtol=atol=2e-2, plus NaN/Infchecks. (
num_tokens ∈ {1,8,64,256}hit the K=1 launcher branch, 2048 hits K=4;5 shapes × 2 store paths = 10 tests.)
Additionally verified byte-exact against the pre-change kernel across
N ∈ {256, 1024, 2048, 4096, 8192, 16384} × {extend, decode} × ordered/permuted
out_loc, for both FP8 and bf16 paths: q/kv 0 bytes differ, no NaN/Inf, skipped
slots untouched (
to_kaiyuan/correctness_full.txt).Speed Tests and Profiling
CUDA-event median, L2-flushed, on B200 (sm_100), baseline = pristine upstream
kernel, ratio = this-PR / baseline (<1 is faster):
bf16-store path
FP8 quant path
Small N stays at parity (the grid can't fill the SMs; the K=1 dispatch avoids the
K=4 slowdown there). The benefit grows once work fills the GPU. The bf16 path
gains the most (up to ~1.33× at N=16384); the FP8 path gains less — its per-warp
abs_max reduce + quant ALU make it less latency-bound, diluting the ILP win.
Reproduce:
cd to_kaiyuan && python verify_pr.py [--bf16-store].Checklist
Review and Merge Process
/tag-and-rerun-ci,/tag-run-ci-label,/rerun-failed-ciCI States
Latest PR Test (Base): ❌ Run #30873411815
Latest PR Test (Extra): ❌ Run #30873411698