Skip to content

qwen4_exp --device cuda is fluent but NOT token-exact against the CPU control, and the divergence is already there at prefill #2547

Description

@localai-org-maint-bot

Row: MODEL-MM-QWEN4-EXP

What changed

#2496 is fixed: the forward
read the host token_ids the asynchronous runner deliberately leaves stale for
decode rows, so every CUDA decode step was fed token id 0. With that repaired the
released unsloth/Qwen3.8-Flash-Next-GGUF UD-IQ1_S artifact goes from degenerate
to fluent on --device cuda. It does NOT go to token-exact.

Measured on thor:gpu0 (sm_110, driver 595.78, CUDA 13.0.88), shard-1 sha256
88a14208…, examples/vllm-server, greedy, max_tokens=8, prompt
The capital of France is, NO CUDA_LAUNCH_BLOCKING, one tree
(1ef7885ec28b4edde88c665a9c1ff00115b63e4a), one server binary
(sha256 082451c96aa20ec8ee34f1ed429972882cf0f8768294914ae2ca6d0691686663):

arm token ids text
--device cpu 11751 13 15767 411 2029 11 1092 369 Paris. Given this fact, what is
--device cuda 11751 13 15767 411 1928 11 628 567 Paris. Given this information, can we
--device cuda, before #2496's fix 11751 271 271 271 271 271 0 0 Paris\n\n\n\n\n\n\n\n\n\n!!

Five of eight agree, both continuations are grammatical English, and the first
disagreement is at token 4.

The divergence is NOT carried state, and that is measured

VT_Q4EXP_STATE_FP=1 prints a per-step summary of every persistent state and of
the layer loop's output. Post-fix, the PLE n-gram history rolls IDENTICALLY on
both arms — [9338,369] -> [369,11751] -> [11751,13] -> [13,15767] -> [15767,411]
— so the feedback #2496 broke is repaired.

What still differs is the prefill hidden state, at step 0, before any decode
state is read at all
:

step=0 hidden  cpu   n=12800 nonfinite=0 maxabs=192 sumabs=28054.1 v=0.353516,-0.171875,3.42188,0.486328
step=0 hidden  cuda  n=12800 nonfinite=0 maxabs=192 sumabs=27964.7 v=0.326172,-0.132812,3.42188,0.470703

About 0.3% on the aggregate, on the very first forward. It compounds across
steps (step=4 maxabs 57.75 against 44.75) until a near-tie flips at token 4.
Token 0 agreed all along only because Paris after The capital of France is
is not a near-tie — which is why the whole of #2496 was diagnosed under the
belief that "prefill is right", and why that belief needed the fingerprint rather
than an argmax to check.

Candidate, not conviction

The tree already records that vt::Qwen4ExpGatedResidual's CUDA arm is NOT
bit-identical to its CPU arm: it routes its three projections through the shared
vt::MatmulBT rather than a hand-written device GEMV, and
src/vt/cuda/cuda_qwen4_exp.cu states the consequence in its own words — "a
device GEMM re-associates the K reduction, so this arm is NOT bit-identical to
its CPU sibling and its gate is a tolerance the suite MEASURES". That site runs
three times per layer over 48 layers plus the terminal mixer, which is the
largest non-bit-identical surface on the path. It is a candidate because it is
the biggest one, not because anything has attributed the 0.3% to it.

Everything else on the path IS gated arm-against-arm and passes:
test_qwen4_exp_cuda 351/351 and test_qwen4_exp_cuda_reductions 160/160 on
this same binary, with the norm and the compressor byte-identical.

What convicting it needs

A per-op tap, not another token run: dump hidden after each decoder layer on
both arms over the same artifact and the same prompt, and report the first layer
whose output leaves an arm-vs-arm band derived from that layer's own operands.
VT_Q4EXP_STATE_FP already prints the loop's output once per step; the layer
axis is what it does not have.

Until then --device cpu is the token-exact arm for this architecture and
--device cuda is fluent-but-not-exact, which is what docs/USAGE.md should
say if this model is documented as CUDA-capable.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions