CUDA: route single-column f32 mul_mat through mmvf (transpose-free operand swap)#49
Open
roberteg16 wants to merge 1 commit into
Open
CUDA: route single-column f32 mul_mat through mmvf (transpose-free operand swap)#49roberteg16 wants to merge 1 commit into
roberteg16 wants to merge 1 commit into
Conversation
mgehre-amd
approved these changes
Jul 14, 2026
mgehre-amd
left a comment
There was a problem hiding this comment.
Nice! Could you please try to upstream this?
… operand swap A mul_mat of a single-column f32 weight (ne01 == 1) against a wide activation (ne11 > MMVF_MAX_BATCH_SIZE) misses the mmvf vector kernel because ne11 is the batch dimension, and falls back to an f32 cuBLAS/rocBLAS GEMM whose 32x32 macro tile computes just one useful output column. Since the output is a vector, the operands can be swapped transpose-free: the activation becomes the matrix and the single-column weight the lone F32 vector, with dst reinterpreted as [ne11, 1]. This runs the op as a proper GEMV. The graph is unchanged, so LoRA and any other consumer are unaffected. On Qwen3.6-35B-A3B (gfx1151, pp128) the shared-expert gate (weight f32[2048,1]) drops from ~78 us/instance on rocBLAS Sgemm to ~3.8 us on mmvf (~20x), ~3.0 ms saved across the 40 layers. Adds test-backend-ops regression cases (m=1, n sweeping across MMVF_MAX_BATCH_SIZE) that validate both the direct and swapped paths against the CPU reference. Co-Authored-By: Claude Opus 4 (1M context) <noreply@anthropic.com>
ff2810b to
e768c1a
Compare
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
A
mul_matof a single-column f32 weight (ne01 == 1) against a wide activation (ne11 > MMVF_MAX_BATCH_SIZE) currently misses the in-housemmvfvector kernel — becausene11(the token count) is the batch dimension and exceeds the 8-column cap — and falls back to an f32 cuBLAS/rocBLAS GEMM. That GEMM's 32×32 macro-tile computes just one useful output column out of 32, so it runs at a few percent of peak.Since the output is a vector, the operands can be swapped transpose-free: the activation becomes the matrix and the single-column weight becomes the lone F32 vector, with
dstreinterpreted as[ne11, 1](same contiguous floats, onlyne/nbrelabeled — no data movement). The op then runs as a proper GEMV throughggml_cuda_mul_mat_vec_f.The change lives entirely in the
ggml_cuda_mul_matdispatch (ggml/src/ggml-cuda/ggml-cuda.cu).Where this shows up
These single-column f32 weights are exactly the small, unquantized control-signal projections that stay F32 in a GGUF (routers/gates), e.g. the shared-expert gate (
ffn_gate_inp_shexp,weight f32[n_embd, 1]) in Qwen3.5/3.6 MoE.Measured improvement (Qwen3.6-35B-A3B, gfx1151, prefill pp128)
Per-op (kernel selection is deterministic, so this is the apples-to-apples comparison):
Cijk_…MT32x32x8Sgemmmul_mat_vec_f(mmvf)~20× on this op, ~3.0 ms saved across the prefill. The router (N=256) and delta-net α/β (N=32) correctly stay on their existing path — they aren't vector-shaped, so the swap doesn't (and shouldn't) apply.
Correctness / tests
Adds
test-backend-opsregression cases:m=1withn ∈ {1,7,8,9,16,128,512}atk=2048, sweeping acrossMMVF_MAX_BATCH_SIZEso both the direct mmvf path (n ≤ 8) and the new swapped path (n > 8) are validated against the CPU reference.test-backend-ops test -o MUL_MAT: 1141/1141 passed on the HIP backend; all 7 new cases OK.Notes / follow-ups
src0->type == F32because the weight lands in mmvf's F32-only vector slot (ggml_cuda_should_use_mmvfonly validates the matrix operand, so this must be checked explicitly).ne2 == 1 && ne3 == 1) and contiguous operands, which keeps thedstreinterpretation trivially correct.ne11 > ne11_mm_min) and Vulkan (mul_mat_vec_max_cols = 8). The optimization concept applies there too, but the transpose-freedstrelabel is a kernel-dispatch detail, so each backend would need its own analogous snippet + measurement — left as follow-up rather than a fragile frontend rewrite.