Conversation
Stage K group g+1 codes, scales and activation tiles with cp.async while group g runs on the tensor cores. The stage count and launch bounds derive from the static shared-memory footprint: two stages for 8/16-column tiles, one for wider tiles. Numerics are unchanged. RTX 5090, CUDA 13.3, cold-cache public Op benches (median): Q4 LinearSwiGLU 34816x5120 T=4/8/16 77.8/79.9/106.5 -> 75.2/79.7/102.4 us Q4 Linear 4096x5120 T=2/6/10 20.5/20.5/22.5 -> 15.8/16.4/18.4 us Q4 Linear 7168x5120 T=4/8 24.6/24.6 -> 20.5/22.5 us In-model MTP3 T=4 SwiGLU kernel median 75.4 -> 73.1 us (nsys, qwen3_8_27b). Q4 linear/linear_swiglu/linear_add/linear_topk oracle suites pass.
Replace the superseded small-T Q5 LinearAdd kernels and retune affected Q5 dispatch boundaries around the shared k-split MMA implementation. 🤖 Generated with Codebuff Co-Authored-By: Codebuff <noreply@codebuff.com>
The previous commit collapsed the small-T Q5 LinearAdd bands into a single
{1,60}/{1,64} K-split MMA band whose crossover was fit against the batched end
of the range. At T=1 that route leaves most of a 32-column tile idle, so
ordinary (non-speculative) decode pays for tiles it cannot fill. Plain Q5
linear already carves T=1 out to a dedicated kernel; do the same here.
Restores q5_linear_add_gemm_simt.cu and routes T=1 back to
Split2ExactResidual, leaving T>=2 on the K-split MMA route. Speculative decode
verifies at T>=2 and is untouched.
Measured on qwen3.8-27b, RTX 5090, pp2048+tg512, r=5, both builds compiled back
to back in one session:
ordinary decode, bf16 KV: 76.12 -> 77.22 tok/s (+1.4%)
ordinary decode, fp8 KV: 77.56 -> 78.16 tok/s (+0.8%)
MTP3 decode, fp8 KV: 222.87 tok/s, acceptance 93.32% (unchanged)
Note: an earlier report put the T=1 regression at ~4%. That magnitude did not
reproduce here under either KV dtype; the recovered margin is ~1%, at roughly
3 sigma in the bf16 pair. The boundary itself is still unswept - T=2 and T=4
are untested candidates.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The Q5 shape dispatchers routed all of T<=96 to a single Capacity=32 K-split MMA instance. Capacity is compile time: it sizes the accumulator fragments and kItemsPerSplit, so one wide instance makes small T stage and accumulate columns it never fills. The cost was flat and visible - at N=5120/K=6144, T=2 took the same 28.7us as T=17. Q4 has laddered its K-split rungs since it landed (4/8/16/24 per shape), and the Q5 linear_add route already instantiates the same template at 8/16/24/32. Only the Q5 plain-linear dispatchers were left on a single rung. This wires them to rungs 4/8/16, then the existing Capacity=32 column-tiled instance for T<=96. No new kernels. T=1 moves to the 4-rung only on the two N=5120 shapes, where it beats the dedicated split4_c1 GEMV; N=6144 and N=7168 keep that GEMV, which is still faster for them. A 24-rung was measured and rejected: it lost to the column-tiled Capacity=32 instance across T=17..24. Measured with ninfer_linear_bench, --repeat 100, cold cache, RTX 5090, min_us, against a build of the parent commit. T=17..32 is an unchanged control band and brackets per-run drift: shape T=1 T=2..16 T=17..32 (control) 5120x6144 -11.3% -27.5% +0.0% 5120x17408 -7.8% -26.5% -0.0% 6144x5120 -0.4% -29.4% -0.1% 7168x5120 -0.2% -33.4% +6.8% End-to-end on qwen3.8-27b, pp2048+tg512, fp8 KV, this is flat: ordinary decode 77.74 vs 78.16 tok/s and MTP3 221.08 vs 222.87 tok/s, both within a sigma of the parent commit. The op-level win is real and repeatable; this model's decode path just does not spend enough time in these four Q5 plain-linear shapes for it to surface. Kept because it is strictly faster per call at no cost, but it is not an end-to-end throughput change and should not be reported as one. launch_q5_split4_c1_k6144 and launch_q5_split4_c1_k17408 now have no caller. Left in place rather than deleted. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Wallawalla47
pushed a commit
to Wallawalla47/ninfer-custom
that referenced
this pull request
Sep 19, 2026
…to the token count
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.
Problem and scope
Related Issue: #293
The Q5 small-batch linear paths leave throughput on the floor in two distinct
ways.
First, Q5 had no shared K-split MMA implementation, so the small-T
linear_addroutes fell back to
MmaResidualR64C16/R64C24kernels that Q4 had alreadymoved past.
Second, the Q5 plain-linear shape dispatchers served all of
T<=96from asingle
Capacity=32K-split rung.Capacityis compile time and sizes theaccumulator fragments and
kItemsPerSplit, so one wide rung makes small T stageand accumulate columns it never fills. The cost was flat and directly visible:
at N=5120/K=6144, T=2 took the same 28.7us as T=17. Q4 has laddered its rungs
since it landed; only Q5 was left on one.
What changes as a result: MTP3 speculative decode gains 11.4% end to end.
Ordinary (non-speculative) generation moves about a percent, which is close to
this machine's run-to-run noise. This is not a general "Q5 generation is faster"
change and is not presented as one.
Implementation
Q5 K-split MMA (
src/ops/linear/q5/q5_ksplit_mma.cuh, new) — sharedexact-shape kernel.
Capacityis the compile-time column capacity of one tile;MaxColumns > Capacitytiles alongblockIdx.y, streaming the weights once pertile.
Q5 linear_add — small-T routes now go through the K-split MMA
(
KSplitMmaResidual) instead of the supersededMmaResidualR64C16/R64C24kernels, with the crossover at T=60 (K=6144) and T=64 (K=17408). T=1 stays on
the split2 SIMT kernel: the K-split route leaves most of a 32-column tile idle
for a single row, and collapsing T=1 into the batched band cost ordinary decode
measurably. This mirrors what Q4 linear_add already does.
Q5 plain linear shape dispatchers — laddered to Capacity 4/8/16 before the
existing column-tiled Capacity=32 instance. No new kernels: every rung was
already being instantiated from the same template on the linear_add side, so
this is wiring.
Q4 K-split MMA (
src/ops/linear/q4/q4_ksplit_mma.cuh) — double-bufferedstaging pipeline.
Two boundaries were chosen by measurement rather than by copying Q4:
Capacity=32 instance across T=17..24.
dedicated
split4_c1GEMV by 8-11%. N=6144 and N=7168 keep that GEMV, whichis still 7-12% faster for them.
Limitations and tradeoffs:
model's decode path does not spend enough time in those four Q5 plain-linear
shapes for a ~27% per-call improvement to surface. It is included because it
is strictly faster per call at no cost, not because it moves throughput.
against that one GPU and may want re-checking elsewhere.
launch_q5_split4_c1_k6144andlaunch_q5_split4_c1_k17408no longer havecallers. Left in place rather than deleted.
Verification
End to end,
ninfer_bench, qwen3.8-27b (q5_g64_fp16), RTX 5090, fp8 KV, r=5.Both builds compiled and run back to back in one session:
Speculative acceptance is identical on both sides (93.32%, 675 rounds, 0
fallbacks), so the MTP gain is time per round rather than a change in what the
draft head accepts. Prefill sits at the noise floor.
Op level, cold cache, comparing
min_usagainst a build of the parent commit:T=17..32 is an unchanged route in both builds and is reported as a control band
for per-run drift:
The 7168 row's control band drifted +6.8%, so treat its small-T figure as the
softest of the four.
Correctness:
ctest: 119/120 pass. The one failure isninfer_chat_templates_test, whichfails on a missing
jinja2Python module in my environment and is unrelatedto these changes. The
qwen3_5_*_realtests skip for lack of local artifacts.tests/ops/linear/test_q5_a16.cppandtests/ops/linear_add/test_q5_a16.cppextended; existing route coverage already exercised the new rung boundaries
(4/5, 8/9, 16/17) on all four shapes.
output is coherent on both.