Repository navigation
cuda: GDN conv-chain fusion and alpha/beta pair at speculative verify widths (1-16 tokens, rollback snapshots), bit-identical, +5-6% MTP3 / +4-5% DFlash7 on H200 (GGML_CUDA_GDN_FUSION=1) - #399
Merged
Conversation
… speculative verify widths (GGML_CUDA_GDN_FUSION=1) The fused GDN conv chain (#383) now matches batches of 1 to 16 tokens of one sequence, with one conv-state write (n_rs_seq = 0) or one per rollback snapshot slot (1 + n_rs_seq rows, up to 16, as build_conv_state writes them for MTP / DFlash verification). One launch replaces the conv-state GET_ROWS, the CONCAT, the snapshot CPYs (one batched launch since #379), SSM_CONV + SILU and the q/k L2_NORM. Same arithmetic per element as the unfused kernels: the conv sum as ssm_conv_f32, the L2 norm as l2_norm_f32 with WARP_SIZE threads (one warp per token). The ssm_alpha/ssm_beta pair now also runs at 2..8 columns through the same mmvf template instance (ncols_dst = the column count) the #380 thin-F32 path uses for each product alone; above 8 columns (mul_mat_f) it is left unpaired. The check on nodes evaluated between the CONCAT and the L2_NORM is narrowed to what moving the writes earlier requires (the SILU/L2 outputs written early, the cache, weights and ids), so a node that reuses the dead projection output (the DFlash hidden-state taps make the allocator do this in 6 of 24 layers on 9B) no longer blocks the fusion. Still opt-in under GGML_CUDA_GDN_FUSION=1.
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.
What
Extends the opt-in GDN fusions from #383 (
GGML_CUDA_GDN_FUSION=1) from one-token decode to speculative verify widths: 1 to 16 tokens of one sequence, with or without rollback snapshots (n_rs_seq > 0, the #358 replay / MTP / DFlash path).Conv chain, 5 kernels to 1 at any width. For an n_t-token batch, a GDN layer runs these kernels:
GET_ROWS;CONCAT(concat_dim0_transpose);CPYs (onecpy_f32_batchedlaunch since cuda: batch the rollback conv-state snapshot copies into one launch (+2.6% DFlash2 decode, exact) #379);SSM_CONV+SILU;L2_NORMof the q/k heads.ssm_conv_gdn_fused_f32now takes n_t tokens and a list of up to 16 conv-state rows to write. Each row gets the d_conv-1 conv inputs at its own window offset, exactly asbuild_conv_statebuilds the snapshotCPYs. It writes the SiLU output and the L2-normalised q/k heads of every token.Alpha/beta pair at 2-8 columns. The paired
mul_mat_vec_flaunch now runs atncols_dst = n_t. This is the same template instance and block size that the cuda: thin F32 mat-muls take mmvf up to 8 columns on Ampere+ (+6% MTP decode on H200) #380 thin-F32 path uses for each product alone. Above 8 columns (wheremul_mat_ftakes over) the pair is skipped.Narrower "nodes in between" check. With DFlash, the hidden-state taps change the buffer layout. In 6 of 24 GDN layers, the GDN state
GET_ROWSoutput then lands on the dead qkv projection output, and cuda: opt-in exact fusion of the GDN decode conv chain and alpha/beta mat-vecs (GGML_CUDA_GDN_FUSION=1), +5-10% decode on H200 (stacked on #379) #383's check refused the fusion there. The check now protects only what moving the writes earlier requires:The projection output is read at the
CONCATposition in both orders. TheCONCAT/SSM_CONVbuffers are never written by the fused launch.Still opt-in under the same env var. If the separate default-on PR for the one-token path lands, it covers these widths too.
Why
Under speculation, GDN is about 7.5% of GPU time, and #383 did not fire there (verify batches are multi-token with snapshots). In a verify forward on H200 (9B target, nsys
--cuda-graph-trace=node), the chain was 8 kernels per GDN layer.How (exactness)
ssm_conv_f32:sumf += x[t+j]*w[j]for j = 0..d_conv-1, then+ 0.0f(the absent bias).warp_reduce_sumasl2_norm_f32<WARP_SIZE>, with one warp per token.CPYrun only if:CONCAThas exactly n_cpy + 1 users.warp_reduce_sum,ggml_cuda_pdl_*,__syncthreads).Validation (H200, CUDA 12.8, sm_90 Release; base =
prismtip)Per-layer GDN chain kernels, one GDN layer of a verify forward (9B target; median kernel duration in µs over all verify forwards of a 3-prompt × 64-token run; env on, base = tip where #383 does not fire at these widths):
MTP K=3 verify, T = 4 tokens, 4 snapshot rows:
k_get_rows_float(conv state)concat_dim0_transpose_u32cpy_f32_batched(4 snapshot rows)ssm_conv_f32(+silu)l2_norm_f32ssm_conv_gdn_fused_f32mul_mat_vec_f<4>alpha / betagated_delta_net_cudaDFlash, draft 7 verify, T = 8 tokens, 8 snapshot rows:
k_get_rows_floatconcat_dim0_transpose_u32cpy_f32_batched(8 rows)ssm_conv_f32l2_norm_f32ssm_conv_gdn_fused_f32mul_mat_vec_f<8>alpha / betagated_delta_net_cudaPer verify forward:
The fused kernel runs in all 24 GDN layers in both cases.
Bit-exactness.
build_conv_statedoes (snapshot slots and window offsets included) plus an alpha/beta pair at n_t columns. It runs once with the env var at 0 and once at 1, and compares the L2 output, SiLU output, the whole conv cache and both products byte for byte.mul_mat_vec_fin those runs.test-backend-ops (CUDA0), env 1 and env 0:
nsys shows the fused kernel in every GDN_CONV_CHAIN case.
Output identity. llama-server, greedy, 5 prompts plus 1 copy-heavy prompt, 192 tokens each. Tokens, top-5 logprobs and draft/accepted counts were compared env 1 vs env 0 (same binary) and env 1 vs the
prismtip. All configurations below are identical:#391 interaction.
add_rms_norm_fwht_f32(both the FFN-input fusion and the layer-start look-ahead) andssm_conv_gdn_fused_f32all fire, with the same counts as the tip with env 1 (1320 + 825 fwht-fused launches, 792 conv-chain launches).add_rms_norm_fwht_f32fires as on the tip. The layer-start look-ahead does not fire at these widths on the tip either.Speed. llama-server, 9B, same binary with the env var off/on in ABBA order (e0 e1 e1 e0) × 3 rounds per job. Prompts: 4 non-copy (essay/chat/explainer/story) and 2 copy-heavy (repeat a ~90-line Python module with a rename), 192 tokens each. The statistic is the per-prompt median tok/s over 6 runs per arm, then the geometric mean of the on/off ratios. Acceptance is identical on and off.
Per-prompt ratios were 1.014-1.069 in job 1 and 1.046-1.069 in job 2. Job 1 had one stalled request (115 tok/s against a usual ~420 tok/s, in an env-on run). Single stalls like that also hit env-off and tip runs elsewhere, and the per-prompt median absorbs them. Its plain per-round means were noncopy MTP −4.0% (one round 0.77 from that stall), copy MTP +6.5%, and DFlash +3.2% / +4.2%.
Not covered
Default-on for the one-token path is proposed separately in #398 (Brian decides on default changes).