Repository navigation
Conversation
added 2 commits
October 22, 2026 06:24
Problem: in linear-chain speculative verify, causal_conv1d_update saves the
intermediate conv window after every draft token with three lane-strided
element stores. MambaPool's deduplicated window view (step stride == window
stride) maps those stores onto one shared row per channel, so the same
addresses are written up to three times by scattered narrow stores. On GB300
(dim 10240, bf16, T=8, bs 128) the verify conv takes 106 us with the
deduplicated window versus 39 us with a dense one.
Fix: when the window view's step stride equals its window stride, step t's
window is columns t..t+2 of the row [state[1], state[2], x_0, ..., x_{T-1}].
For a chain (no tree mask) with kernel width 4, write that row once as a
[BLOCK_N, next_pow2(T+2)] tile before the token loop and skip the per-token
stores. Tree verify, other widths and dense windows keep the per-token
stores. The bytes written are the same; the new test checks them bitwise
against the per-token path.
Effect: kernel time on GB300, dim 10240, bf16, T=8: bs 1 5.4 -> 2.5 us,
bs 64 57.4 -> 10.0 us, bs 128 106.5 -> 17.6 us (T=16, bs 128: 322.6 ->
39.1 us). Every CUDA chain verify on a model with a width-4 conv (GDN,
Mamba2) takes this path. The tile store is SubSir's.
Problem: the deduplicated conv window tile writes [state[1], state[2], x...],
which is the right row only for kernel width 4, but only the Python wrapper
enforced that; a caller that set the flag for another width would get wrong
window bytes. The new test compiled 32 kernel variants (dim is a constexpr),
and it compared conv outputs of two separately compiled kernels bitwise, which
is not guaranteed on every backend the file runs on. The MambaPool comment
still described the kernel's window write as overlapping per-token stores.
Fix: static_assert KERNEL_WIDTH == 4 inside the tile branch. Test one dim
(1000, a partial channel block) instead of two; T in {2, 4, 8, 16} still covers
every tile width. Keep the bitwise checks on the window and conv state, which
are copies, and compare the conv output with the file's update tolerances.
Update the MambaPool comment.
Effect: no runtime change. The test compiles half as many kernel variants.
This was referenced Oct 22, 2026
… layouts Problem: only the Python wrapper kept tree verify off the deduplicated conv window tile; a caller that set both flags would get the tile plus the tree branch's per-token stores into the same window. The wrapper comment tied the tile to MambaPool's overlapping view, while the real argument is about the strides alone. Kernels that do not take the tile still got a seqlen-dependent NP2_WINDOW_COLS. The test did not cover a num_accept_tokens state offset, MambaPool's axis-0 window layout, or an odd T. Fix: static_assert that the tile branch has no tree mask, next to the width assert. Rewrite the comment: with step stride == window stride == s, the per-token stores write row column t + w at (t + w) * s and the tile writes column c at c * s, the same addresses and values for any layout with those strides. Pass NP2_WINDOW_COLS=1 when the tile is off. Test T = 3, a num_accept_tokens offset, and the axis-0 window (a [T + K - 2, dim] row seen transposed); T = 3 replaces T = 4 in the plain cases, so T + 2 still pads to tiles of 4, 8, 16 and 32. Effect: no runtime change. The test compiles 24 kernel variants instead of 16.
This was referenced Oct 24, 2026
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.
Motivation
On hybrid models, linear-chain speculative verify (EAGLE chain, MTP, DFLASH, DSPARK, standalone) calls
causal_conv1d_updatewith an intermediate conv window. The window lets the conv state of the accepted prefix be restored after verify.On CUDA and ROCm,
MambaPoolstores this window deduplicated. Each slot holds one[dim, T + K - 2]row, exposed as an overlapping[T, dim, K - 1]view whose step stride equals its window stride.The kernel still writes the window per token, with three lane-strided element stores per draft token. Each column is therefore written up to three times by narrow scattered stores. On GB300 (dim 10240, bf16, T = 8, bs 128), the verify conv takes 106 us with the deduplicated window, against 39 us with a dense window.
Modifications
_causal_conv1d_update_kernel: adds aDEDUP_CONV_WINDOWpath.[state[1], state[2], x_0, ..., x_{T-1}].[BLOCK_N, next_pow2(T + 2)]tile before the token loop, and skips the per-token window stores.tl.static_asserts keep the branch from being compiled for a conv width other than 4 or together with a tree mask, so the kernel is safe without relying on its caller.causal_conv1d_update: takes the tile path only when all three hold:With step stride == window stride == s, the per-token stores write column t + w of the row at (t + w) * s, and the tile writes column c at c * s: the same addresses and the same values, for any layout with those strides. MambaPool's deduplicated view is one such layout. There are no new arguments, and callers are unchanged. Kernels that do not take the tile get
NP2_WINDOW_COLS=1, so no extra variants are compiled.Test:
test_causal_conv1d_update_dedup_window_matches_per_token.num_accept_tokensstate offset, MambaPool's axis-0 window layout seen transposed, fp32 and bf16, a dim that leaves a partial channel block, with and without padded rows.A comment in
MambaPoolis updated to describe the new write.Scope
This path is used by every chain verify (topk <= 1) whose conv window uses
MambaPool's deduplicated layout. Examples are GDN models (Qwen3.5/3.8) and Mamba2, on CUDA and ROCm.These are unchanged and keep the per-token stores:
UnifiedMambaPool, KDA / Kimi-Linear, tree verify;Accuracy Tests
num_accept_tokensfails exactly the accept-offset cases, and forcing the tile together with a tree mask fails at compile time on the new assert.NP2_WINDOW_COLS=1for launches without the tile. The extra 72-case check below, which covers accepted-token offsets, transposed views and odd T, is also memcheck-clean.Benchmarking and Profiling
Kernel time per call in microseconds (GB300, dim 10240, K 4, bf16, CUDA graph):
End-to-end setup:
--max-running-requests 128;Decode tok/s = per-request decode rate (output tokens after the first / time after the first token), times concurrency; c1 is the single-request decode rate.
Acceptance length is 3.59-3.63 in all six runs.
At c128 the running cap equals the concurrency, so about one request waits in the queue. #8 shows that this setup reads 7-15% higher at that level than a larger cap, on both sides, so compare c128 only within this table.
Single runs at lower concurrency: c8 2031 -> 2104 (+3.6%), c32 4723 -> 4973 (+5.3%). At c1 the change is within noise: 332 -> 321, with acceptance length 3.70 vs 3.60 from sampling; per accepted token the rate differs by under 0.5%. Greedy (single runs): c1 357.0 -> 357.1 with identical acceptance length and output tokens, c64 6746 -> 7145 (+5.9%).
The end-to-end runs used the first commit; the two later commits add compile-time asserts, comments and tests, and pass
NP2_WINDOW_COLS=1to kernels that skip the tile, with no runtime change on the tile path.Related upstream work
causal_conv1d_update_varlen, to the same file. It writes the intermediate window per token with three stores per step, the pattern this PR replaces. There is no textual conflict (it is a new function), but if both land, packed verify through that kernel does not get the tile store. [Feat] gdn: run target verify on packed ragged tokens (ReplaySSM) #6 takes the other route: it adds packed rows to this kernel, so the tile store covers them too.Notes
MambaPoolcomment), 8eb01f2 (tree-mask assert, stride-based wrapper comment,NP2_WINDOW_COLS=1when the tile is off, more test layouts). Squash them when merging upstream.