Skip to content

Misc. bug: draft-MTP acceptance 0.0 on qwen4exp at prompt lengths >= ~4k tokens (0.75 shallow, output unaffected) #76

Description

@ChrisC381

Name and Version

Built from the pwilkin strix-halo stack (merged into this repo by #63) at two
pins, both affected:

  • pwilkin/llama.cpp branch strix-halo @ be905cf7 (Sep 13 2026 build)
  • pwilkin/llama.cpp branch strix-halo @ d67d5883 (Sep 14 2026 build;
    includes the sparse QSA decode rewrite and the incremental indexer state
    with cache-lifecycle invalidation hooks)

Rest of the stack: installer pwilkin/strix-halo @ fa169251,
pwilkin/rocm-systems @ 7dda3ac6c (custom ROCr/HIP runtime), SDK ROCm
7.14.1. Hardware: Radeon 8060S (gfx1151) on Ryzen AI Max+ 395, 128 GB unified
memory, Linux 7.0.0-31-generic.

Operating systems

Linux

Which llama.cpp modules do you know to be affected?

  • libllama (core library)
  • llama-server

Command line

llama-server \
  -m <qwen4exp target gguf> \
  -md <shared-MTP draft gguf, Q8_0> \
  --spec-type draft-mtp --spec-draft-device ROCm0 --spec-draft-ngl 99 \
  --spec-draft-n-max 2 --spec-draft-p-split 0.10 \
  -dev ROCm0 -ngl 999 -fa on -fit off \
  --load-mode none --lazy-mode on-direct \
  -ctk f16 -ctv f16 \
  -c 262144 -b 16384 -ub 16384 --parallel 1 --metrics

-ctk/-ctv f16 is forced by the QSA kernels (the assert at
qwen4exp.cpp:1309 rejects non-F16 KV). Env vars come from the launcher
profile: HSA_OVERRIDE_GFX_VERSION=11.5.1,
GGML_HIP_ENABLE_UNIFIED_MEMORY=1, ENABLE_RETAINED_PM4=1.

Client side: POST /completion with temperature 0, n_predict 256, a prompt of
N tokens, then read the delta of llamacpp:spec_decode_num_draft_tokens_total
/ llamacpp:spec_decode_num_accepted_tokens_total on /metrics across exactly
that request (the counters are process-lifetime cumulative, so only deltas
are meaningful).

Problem description & steps to reproduce

With a qwen4exp target and the shared-MTP draft, acceptance is 0.0 whenever
the prompt is >= ~4k tokens (threshold not bisected between 200 and 4096);
shallow prompts accept normally. Greedy output is unaffected (canary hashes
byte-identical across spec / no-spec legs at both pins), so this is an
efficiency bug, not corruption: at depth, spec decode runs below the no-spec
control.

Acceptance vs prompt length, same server and session (pin d67d588,
n-max 2; acceptance identical at be905cf):

prompt tokens acceptance tok/s (spec) tok/s (no-spec control, be905cf)
~200 0.754 (765/1015) 37.5 26.0
4096 0.0 (0/2535) 15.2 24.4
32768 0.0 (0/2535) 15.0 20.8
131072 0.0 (0/1521) 14.5 14.1

What we ruled out:

  1. Not prefix-cache restore. cache_prompt=false at 4k on d67d588:
    accept 0/251 over 128 generated tokens.
  2. Not n-max. With n-max 3 acceptance is 0.0 at every depth including
    shallow; dropping to n-max 2 restores shallow only.
  3. Not the draft weights. The draft file is the flash-next profile pin
    mtp-Qwen3.8-Flash-Next-shared-Q8_0.gguf, sha256
    5ff54097406a905cf3a724c709124ceb0e3e10235ee862298969e91c96fa96e6,
    byte-identical to the installer pin. The same draft file with the same
    target on a different llama.cpp build (Vulkan backend) accepts 816/1020
    shallow, and 955/955, 955/955 and 573/573 at 4k/32k/128k.
  4. Not the engine's spec plumbing at depth in general. The published
    27B IQ4_XS + DFlash2 pair through the same engine at a 31.5k prompt:
    accept 218/220. That pair uses the DFlash2 draft path, not shared-MTP.
  5. Not output corruption. Greedy canary sha256 identical across spec /
    no-spec / n-max 2 legs on this engine (a73e540c...).
  6. Not a stale build. Identical at be905cf and d67d588, i.e. across
    the sparse QSA decode rewrite and the cache-lifecycle invalidation hooks.

One cell we did not run: this engine + shared-MTP + your own PROJFIX sharded
quant (93 GiB download). Our target was an own quant of the same public
weights, so a target-quant interaction is not fully excluded. But the draft
file is exactly yours and the same pair accepts normally at shallow length,
so the likelier cause is the qwen4exp shared-MTP path at length, not the
draft weights.

First Bad Commit

Not bisected. Present at both be905cf and d67d588.

Relevant log output

Server's own timing line for the cache_prompt=false 4k probe (d67d588),
confirming the same reading from the server side:

Sep 14 09:29:30 ... slot print_timing: id 0 | task 4148 | draft acceptance = 0.00000 (  0 accepted /  251 generated), mean len =  1.00

AI disclosure (per AGENTS.md): assembled by an AI agent (Hermes, GLM)
from saved run artifacts on the machine above; every number is quoted from
those artifacts; the one untested cell is named; the operator reviewed this
report before filing.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions