Expand description
The native-FP8 M=1 decode DOWN projection (#928) — the arm rule. A CHILD module, not a sibling: this file is already at the CI size cap, and the nsys attribution that motivates the arm needs room this file does not have. The native-FP8 M=1 decode DOWN projection — which arm runs it (#928).
WHY. nsys, 1xH100, Qwen/Qwen3.8-27B-FP8, 2026-09-11 round 7, C=1 steady-state decode step 21.891 ms, GPU busy 96%:
| kernel | shape | grid | launches | us each | ms/step | GB/s |
|---|---|---|---|---|---|---|
w8a16_gemv_silu_input (down) | N=5120 K=17408 | 1280 | 64 | 103.9 | 6.65 (30.4%) | 858 |
w8a16_gemv_dual (gate+up) | N=17408x2 K=5120 | 4352 | 64 | 90.1 | 5.77 | 1,979 |
w8a16_gemv | N=16384 K=5120 | 4096 | - | - | - | 1,852 |
Same 89.1 MB of FP8 weights per layer on the first two rows. The down projection reads them at 43% of the rate the gate/up pair reads its own. Two independent causes; this arm addresses the first.
1. The fused SwiGLU is recomputed per OUTPUT, not per CTA. In
w8a16_gemv_silu_input, each of the ceil(N/4) CTAs gives each of its 4
outputs a 64-lane team, and every team walks the whole K computing
silu(gate[k])*up[k] for itself. That is N*K = 5,120 x 17,408 =
89.1 M SwiGLU evaluations per launch where the token needs K = 17,408
— a 5,120x redundancy — and each one is an __expf plus a TRUE FP32
division: kernels/gb10/common/KERNEL.toml builds with --fmad=false and
no -use_fast_math, so g / (1.0f + __expf(-g)) lowers to the IEEE
division sequence, not a reciprocal. Counting ~20 ops per K element
against the dual GEMV’s ~6, the fused kernel issues ~56 M warp
instructions per launch — ~60 us of pure issue on 132 SMs at 4
instructions/SM/cycle — on top of a 26.6 us weight stream. It is
issue-bound, not bandwidth-bound. The NVFP4 arm reached the same verdict
with ncu (SM 57% vs memory 23%) and has staged the activation once ever
since; the FP8 arm simply never got the same treatment. Staging it here is
what this module’s default arm does.
2. This kernel family’s grid is a pure function of N — LEFT OPEN.
w8a16_gemv.cu puts N_PER_BLOCK=4 outputs in a 256-thread CTA, so K
never enters the CTA count. ~8 such CTAs co-reside per SM, and H100 has 132
of them: grid 4352 is ~4.1 full waves (a ~2% tail), grid 1280 is ~1.2 waves
— one full wave plus a 224-CTA tail. A split-K GEMV was written for exactly
this and MEASURED AS A NULL on the H100 (down 61.8 us split-K vs 58.9 us
for the staged scalar kernel; the k/v shape came back at 0.67x), so it was
dropped rather than shipped behind a lever. Whatever the residual cost is,
wave quantisation alone does not explain it, and the next attempt should
start from a fresh profile rather than from that plan.
PTXAS RECEIPT (nvcc -cubin -Xptxas -v -arch=sm_90a --fmad=false, CUDA
13.0, taken 2026-09-11 on the gate box): w8a16_gemv uses 32 registers /
1,056 B smem / 0 spills, so 32 x 256 x 8 = 65,536 = exactly the SM’s
register file — 8 CTAs/SM is not an estimate, it is the ptxas-pinned
ceiling. w8a16_gemv_silu_input uses 53 registers, which is 13,568 per
CTA and therefore only 4 CTAs/SM. The fused kernel halves resident
warps on top of the redundant transcendentals — a third, independent cost,
and one staging the activation removes for free.
A CHILD module of dense_ffn, not a sibling: dense_ffn.rs is already at
the CI size cap, and the nsys attribution above needs room it does not
have.