mudler / mudler/vllm.cpp

qwen4_exp QSA widens q and the output gate to f32 where vLLM keeps the bf16 model dtype

Open
#2,488 2 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
C++
Stars
423
Forks
53
Avg merge
20h 26m
Merged PRs (30d)
310

Description

Row: MODEL-MM-QWEN4-EXP

Spec: qwen4-exp-cuda-rmsnorm-weight-dtype.md, under ## Owed.
Campaign: #1978. Found while diagnosing #2477; not fixed in that flow, for the reasons below.

What

src/vllm/model_executor/models/qwen4_exp_qsa_block.cpp:694-696 allocates

DBuf q_f32(d, DType::kF32, {T, Hq, Dh});
DBuf gate(d, DType::kF32, {T, Hq, Dh});
vt::AttnGateSplit(d.q, q_f32.t(), gate.t(), qgate.t());

vLLM reaches the same point through torch.chunk, which does not change dtype:
vllm/model_executor/models/qwen3_next.py:426-435 (vLLM origin/main 25efcfa78,
an unpinned forward reference — vLLM landed qwen4_exp on 2026-08-31, after this
row was pinned). q and gate stay at model_config.dtype, which
vllm/models/qwen4_exp/nvidia/qsa.py:188-189 forces to bf16 for this
architecture.

Why it matters, and why it is invisible

The value is already bf16-rounded by the qgate store at :691, so the widening
costs bandwidth and not precision: T * Hq * Dh * 4 bytes where upstream moves
half that, at Hq = 24, Dh = 256. AGENTS.md names this case exactly --- a
token gate cannot see a dtype that is too wide, because the tokens still match
while the path moves twice the bytes.

Why it was not fixed in the same flow

It is not a wall. That site pairs an f32 activation with the f32 gamma the GGUF
stores and passes today, so nothing refuses and no forward stops on it.

More importantly the f32 is welded into the shared op, not into this model:
src/vt/cuda/cuda_glue.cu:181 declares
AttnGateSplitKernel(float* q_out, float* gate_out, const Tin* qgate, ...), and
the op is shared with qwen3_5.cpp:5328 and :5501. Narrowing it moves a second
model's path and needs that model's gate, which is a different row's evidence.

What would close this

Give vt::AttnGateSplit an output dtype rather than a hardcoded float, narrow
both qwen4_exp buffers to hidden.dtype, and gate the change on Qwen3.5 as well
as qwen4_exp.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with src/vt/cuda/cuda_glue.cu:181 and trace the shared vt::AttnGateSplit callers in src/vllm/model_executor/models/qwen4_exp_qsa_block.cpp:694-696 and qwen3_5.cpp:5328, :5501. Compare the qwen4_exp path with vllm/model_executor/models/qwen3_next.py:426-435 and vllm/models/qwen4_exp/nvidia/qsa.py:188-189. Done means the operation accepts an output dtype, qwen4_exp uses hidden.dtype for both buffers, and the Qwen3.5 path is gated appropriately.

Written by the indexing model from the issue text.

Assessment

Tech stack
cpp
Domain
backend, machine-learning, performance
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Active
Clarity
Clearly specified
Newbie friendliness
68/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.