feat(sm89): GeGLU silu-fold megakernel for Qwen3-VL prefill (opt-in)#151
Open
heiheiha798 wants to merge 2 commits into
Open
feat(sm89): GeGLU silu-fold megakernel for Qwen3-VL prefill (opt-in)#151heiheiha798 wants to merge 2 commits into
heiheiha798 wants to merge 2 commits into
Conversation
Add RESID template param to fp8_bs_gemm_device.cuh: D = bf16(acc) + resid, folding the residual add into the GEMM epilogue (no separate residual_add launch, no D HBM round-trip). if constexpr (RESID) dead-strips the residual branch, so RESID=false stays byte-identical to the baseline kernel. Adds 4 residual-fold tile variants (32x64 / 64x64 / 64x64_s1 / 128x128_s1) for the prefill down-proj shapes, bound bench-only under ENABLE_QWEN3_VL_DEV_KERNELS (additive; production dispatcher untouched). Phase 1 finding: prefill's residual add is ALREADY fused by residual_add_rms_norm_to_fp8 on every non-last layer, so residual-fold only saves ~1 [S,hidden] bf16 transfer (0 launches). Validated as the epilogue-fold idiom proof (cos 1.0 vs fp32 ref, 1-ULP reassociation diff) but NOT wired — real prize is silu-fold (Phase 2). Bench script included.
Fuse gate_up GEMM + silu_mul + per-token block-128 FP8 quant into one launch, eliminating the [S, 2*inter] BF16 transient the baseline two-launch path (gate_up GEMM -> silu_mul_merged_to_fp8) materializes. Mirrors flashrt-project#134's SwiGLU epilogue fold (sm120) and sm100 flashrt_megakernel_geglu's gate-in-smem pattern, ported to sm89's hand-written cp.async + ldmatrix.x4 + mma.m16n8k32 idiom (no TMA/tcgen05). Two kernel variants in fp8_bs_gemm_device.cuh (additive; fp8_bs_gemm_kernel untouched): - fp8_bs_geglu_silu_fold_kernel: two-pass (gate -> smem -> up), the shipped tile (32x128_w4_s1). Single B smem region reused across passes; only one accumulator live at a time (~70 regs). - fp8_bs_geglu_silu_fold_apersist_kernel: A-persistent interleaved experiment (both gate+up acc in regs, B reloaded per k-iter). Kept as a bench-only tile; the two-pass s1 won the e2e sweep. ncu diagnosis (issue #1 Phase 2) isolated why naive two-pass lost: s2 occupancy collapse (gate_smem + 2 cp.async stages -> 49.7KB -> 1 CTA/SM, 8.33% occupancy) + 2x pipeline drain. s1 (29.7KB, 3 CTA/SM) recovers and wins in the launch-bound regime. The gate_up GEMM is compute-bound above the S crossover, so the fused path is shape-gated per-inter (2B S in [96,192], 8B S=128 only); outside the band the baseline two-launch path runs unchanged (default byte-identical, env FLASH_RT_SM89_SILU_FOLD_PREFILL=1 opt-in). Correctness: e2e prefill cosine 0.9992-1.0 vs baseline, argmax preserved across S (saturating-FP8 real activations). One fewer bf16(acc) rounding than baseline (documented). Fixed an out_scale write bug (single-thread guard left rows 1..BM-1 unwritten -> garbage scale, correct fp8). E2E prefill (4090, 30-iter median, fold on vs off): 2B S=128: 6.10 -> 5.64 ms +7.5% 2B S=192: 6.10 -> 5.54 ms +9.2% 8B S=128: 13.97 -> 13.45 ms +3.7% (stable across 3 runs) Above the band: no regression (falls back to baseline). Bench script scripts/test_geglu_silu_fold_correctness.py included.
heiheiha798
force-pushed
the
feat/sm89-prefill-ffn-megakernel
branch
from
July 17, 2026 14:28
9a27090 to
fab8cbb
Compare
heiheiha798
marked this pull request as ready for review
July 17, 2026 14:38
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.
Summary
First sm_89 (RTX 4090) megakernel for the Qwen3-VL FP8 prefill path. Fuses the gate_up GEMM + SwiGLU
silu_mul+ per-token block-128 FP8 quantization into a single launch, eliminating the[S, 2*inter]BF16 transient the baseline two-launch path (gate_upGEMM →silu_mul_merged_to_fp8) materializes. Mirrors #134's SwiGLU epilogue fold (sm_120) and the sm_100flashrt_megakernel_geglu"gate stays in smem" pattern, ported to sm_89's hand-writtencp.async + ldmatrix.x4 + mma.m16n8k32idiom (no TMA / no tcgen05).sm_89 previously had zero megakernels (sm_100/110 have encoder GeGLU; sm_120 has
und_ffn_v5t/action_ffn_v6t/tinyfp8+ #134). #139 already optimized the decode path; this addresses the untouched prefill path.Opt-in, additive-only, default path byte-identical: env
FLASH_RT_SM89_SILU_FOLD_PREFILL=1(oruse_silu_fold_prefill=True), shape-gated per-interso it only fires in the launch-bound regime where it wins.What's in here
fp8_bs_geglu_silu_fold_kernel(csrc/gemm/fp8_bs_gemm_device.cuh) — two-pass per CTA: pass 1 accumulates gate over K and storessilu(gate)as BF16 into a smem gate buffer; pass 2 reuses the same A/B smem staging, accumulates up, then the epilogue reads gate from smem, formsv = bf16(bf16(silu(gate))*up)(matchingsilu_mul_merged's two bf16 roundings), per-row amax-reduces over the 128-col quant block, and quantizes to FP8. No grid_barrier (BLOCK_N==128= one quant block, amax is intra-CTA). Shipped tile32x128_w4_s1.fp8_bs_geglu_silu_fold_apersist_kernel— A-persistent interleaved experiment (both gate+up acc in registers, B reloaded per k-iter). Kept as a bench-only tile; the two-pass s1 won the e2e sweep. Documented as an alternative explored.fp8_bs_gemm_kernelRESID variant (Phase 1, commita17f971) —D = bf16(acc) + residepilogue-fold,if constexpr (RESID)dead-strips so baseline stays byte-identical. Validated the epilogue-fold idiom on sm_89 but not wired (prefill's residual is already norm-fused → ~4µs, not worth it). Kept as bench-only + documented.qwen3_vl_fp8_sm89.py):use_silu_fold_prefillopt-in + per-interS-band gate (2B[96,192], 8BS=128); outside the band the baseline two-launch path runs unchanged.Why the shape gate (the ncu diagnosis)
ncu + a multi-agent diagnosis isolated why a naive two-pass loses on sm_89:
gate_smem(8KB) + 2 cp.async stages (40KB) → 49.7KB > 48KB opt-in tier → 1 CTA/SM, 8.33% occupancy. s1 (29.7KB, 3 CTA/SM) recovers.So the gate only fires where it wins; above the crossover it falls back. This is the honest ceiling on a compute-bound 4090. (A follow-up survey confirmed silu-fold is the last high-value sm89 megakernel — decode is graph-captured so launch-fusion can't win there, and the remaining prefill candidates repeat this small-S-only pattern for smaller transients.)
Measurements (RTX 4090, 30-iter median, CUDA-event)
Methodology note (per CONTRIBUTING §Performance Measurement): these are eager (non-graph-captured) prefill latencies — the
_layer_forward_prefill_fp8_blockscaledpath timed with CUDA events around the full layer-forward loop (host issues every launch; not acudaGraphLaunchreplay). The prefill path is not graph-captured in this frontend, so eager is the honest metric. The decode path is graph-captured and is untouched by this PR.E2E prefill (text path, single layer-forward, eager):
Outside the gated S-band: no regression (falls back to baseline).
Correctness
bf16(acc)rounding than baseline (fused keeps the accumulator in fp32); documented, more precise.silu_mul_merged_to_fp8's exact two-bf16-rounding sequence on the silu product.out_scalewrite bug found via real-data testing (a single-thread guard left rows 1..BM-1 unwritten → garbage scale; cosine was scale-invariant so synthetic data hid it, real saturated-FP8 data exposed it).Compliance (CONTRIBUTING.md / exec_contract §9)
fp8_bs_gemm_kerneland the production dispatcher are untouched; new behavior is new kernels + an opt-in env-gated frontend branch. Default path byte-identical.FLASHRT_ENABLE_QWEN3_VL_DEV_KERNELS; the production bindingfp8_bs_geglu_silu_fold_sm89_fp8outis underENABLE_SM89_BLOCK_FP8_GEMM(same gate as its source object). No unconditionalm.defreferences gated symbols. Binding comment documents argument shapes.use_silu_fold_prefillresolves the tile viagetattr(fvk, ..., None)— an optional fast path with a correct baseline fallback, not hardware routing (sm_89 is enforced by the frontend class's capability check).grid_barrier, no>48KBsmem on the shipped tile, no GEMM-internal double-buffer.Test
All passing locally (RTX 4090, sm_89, CUDA 12.4, torch 2.6.0+cu124,
flashrt-liberoenv):test_qwen3_vl_fp8_sm895/5,test_build_inventory5/5,test_generic_kernel_bindings7/7, CONTRIBUTING basic smoke set 55/55.Unsupported hardware: the GeGLU silu-fold kernel is sm_89-only (Ada FP8
mma.m16n8k32, no.kind::f8f6f4). Not built/validated on sm_80/86/87/120 — theENABLE_SM89_BLOCK_FP8_GEMMgate keeps it out of those builds.Tracking issue: heiheiha798#1.