Skip to content

qwen3-tts (HIP): talker diverges from CPU at frame 0, runs away to context limit #337

Description

@adam-schneider-dev

Bug: qwen3-tts on HIP samples a different token at frame 0 than CPU, then runs to the context limit

One line: Same input, same weights. GPU picks a different first token than CPU. GPU then never stops — it runs until the KV cache is physically full (3796 frames, 303.76s of audio) instead of stopping normally (under 10s). No error, no non-zero exit code — just a log line.


Environment

Component Value
CPU AMD Ryzen 7 7800X3D, 8 cores / 16 threads
RAM 32GB physical. free -h shows "30Gi" — that's GiB vs GB, not less RAM. See note below.
GPU (compute) AMD Radeon RX 7900 XTX, gfx1100, ~24GB VRAM
OS Fedora Linux 43 (Workstation)
Kernel 7.1.5-101.fc43.x86_64
ROCm stack 6.4.4 (see package table below)
HIP compiler clang 19.0.0git (rocm-clang)
CrispASR repo https://github.com/CrispStrobe/CrispASR
CrispASR commit 1a9ee81eb76bf52b3ba4a589306b986539f42010
CrispASR version 0.8.25
ggml submodule https://github.com/CrispStrobe/ggml.git @ 890278a8342c620197c90e702e1188bcab94f510
Backends compiled in cpu,hip only

RAM note: at capture time the machine was under memory pressure (16Gi/30Gi RAM used, 7.4Gi/8.0Gi swap used). Doesn't affect the sampled tokens below — may affect CPU-path timing numbers.

GPU enumeration note: ggml sees 2 ROCm devices at startup — Device 0 (7900 XTX, gfx1100) and Device 1 (the CPU's integrated GPU, gfx1036, ~512MB). Build only targets gfx1100. HIP_VISIBLE_DEVICES was confirmed unset for every test. Whether the iGPU took part in any run was not measured — disclosed as an open variable, not ruled out.

Driver-version gotcha: rocm-smi --showdriverversion prints "7.1.5-101.fc43.x86_64" — that's the Fedora kernel/amdgpu driver, not a ROCm version. Don't read it as "ROCm 7.1.5."

ROCm packages installed

rocm-6.4.4-1.fc43.noarch, rocm-core-6.4.4-1.fc43, rocm-runtime-6.4.2-3.fc43, rocm-hip-6.4.2-2.fc43, rocminfo-6.4.0-2.fc43, rocm-smi-6.4.3-1.fc43, rocm-opencl-6.4.2-2.fc43

Build config

cmake -B build -DCMAKE_BUILD_TYPE=Release -DGGML_HIP=ON -DGPU_TARGETS=gfx1100 -DAMDGPU_TARGETS=gfx1100
Flag Value
GGML_HIP ON
GGML_HIP_GRAPHS OFF
GGML_HIP_MMQ_MFMA ON
GGML_HIP_NO_VMM ON
GGML_HIP_RCCL OFF
GGML_HIP_ROCWMMA_FATTN OFF

The two bolded flags matter later — they rule out two suspects (HIP graphs, flash-attention's rocWMMA kernel) before you even ask.


Models used

All from https://huggingface.co/cstr (talker + code_pred + speaker unified GGUF — not the split talker/predictor/ONNX format some other converters ship).

Label Filename URL sha256
1.7B Base Q8_0 qwen3-tts-12hz-1.7b-base-q8_0.gguf cstr/qwen3-tts-1.7b-base-GGUF bbb93ab1...520d
1.7B Base F16 qwen3-tts-12hz-1.7b-base-f16.gguf cstr/qwen3-tts-1.7b-base-GGUF c7039d04...e00e
0.6B Base F16 qwen3-tts-12hz-0.6b-base.gguf cstr/qwen3-tts-0.6b-base-GGUF 9b719418...af6dc
0.6B Base Q8_0 qwen3-tts-12hz-0.6b-base-q8_0.gguf cstr/qwen3-tts-0.6b-base-GGUF 61626370...c1961
Tokenizer-12Hz codec qwen3-tts-tokenizer-12hz.gguf cstr/qwen3-tts-tokenizer-12hz-GGUF 70dc95db...19b

Full sha256 values and exact byte sizes: see the source report (crispasr-qwen3-tts-hip-bug-report.md), same folder.

Voice reference (Tests 1-8): redacted — real recording of a specific person, not shareable. Original available privately to maintainers on request.

Voice reference (Tests 9-10, public, use this one to reproduce): BBC recording of the late Daphne Park (MI6), from Wikimedia Commons, free license: https://commons.wikimedia.org/wiki/File:Daphne_Park_in_MI6_A_Century_in_the_Shadows_b00lv0bm.flac. Converted to 24000 Hz mono WAV (ffmpeg -i <flac> -ar 24000 -ac 1 daphne_ref.wav). Transcript, used verbatim as --ref-text: "I wanted to learn Russian. I wanted to serve in Russia. The enemy, the most interesting enemy. And also I was deeply interested by the Russians. I'd seen them on the streets of Vienna and how they behaved, and I felt anger about that. But also I felt an enormous interest in the country and the people as distinct from the philosophy, which I hated." No privacy concern — link it directly.


The bug, step by step

Every command below needs --i-have-rights (voice-cloning consent flag). Two test strings:

  • Failing text: "Good evening. I am the voice assistant running on this workstation." (68 chars)
  • Passing text: "Hello, this is a quick test of the new voice model." (51 chars)

1. Run on GPU with the failing text → it runs away

crispasr --backend qwen3-tts-1.7b-base \
  -m qwen3-tts-12hz-1.7b-base-q8_0.gguf \
  --codec-model qwen3-tts-tokenizer-12hz.gguf \
  --voice reference_voice.wav \
  --ref-text "<your ~81-token transcript>" \
  --i-have-rights \
  --tts "Good evening. I am the voice assistant running on this workstation." \
  --tts-output t1.wav

Result: talker kv cache full at frame 3796 (n_past=4095). 303.76s of audio. Exit code 0 — looks like success.

2. Same command, add --gpu-backend cpu → it's fine

Result: clean stop, ~40 frames, 3-8s of audio (varies a bit run to run — the point is it stops normally, not the exact length).

3. Same GPU run, swap to the passing text → also fine

Result: clean stop, 145 frames, 11.6s of audio.
Caveat: passing text is also shorter (51 vs 68 chars) — this test alone doesn't prove content matters more than length. See "Open question" below.

4. Swap model to 0.6B, keep failing text, GPU → still runs away

crispasr --backend qwen3-tts \
  -m qwen3-tts-12hz-0.6b-base.gguf \
  --codec-model qwen3-tts-tokenizer-12hz.gguf \
  --voice reference_voice.wav \
  --ref-text "<your ~81-token transcript>" \
  --i-have-rights \
  --tts "Good evening. I am the voice assistant running on this workstation." \
  --tts-output t4.wav

Note: 0.6B uses --backend qwen3-tts, not --backend qwen3-tts-1.7b-base.

Result: identical failure — frame 3796, 303.76s. Same as Test 1. Model size isn't the cause.

5. Same as Test 4, add --gpu-backend cpu → fine

Result: clean stop, 54 frames, 4.32s.

6. Dump per-frame tokens on both backends to find where they diverge

CRISPASR_QWEN3_TTS_EMBD_CHECK=1 crispasr --backend qwen3-tts-1.7b-base \
  -m qwen3-tts-12hz-1.7b-base-q8_0.gguf --codec-model qwen3-tts-tokenizer-12hz.gguf \
  --voice reference_voice.wav --ref-text "<your transcript>" --i-have-rights \
  --tts "Good evening. I am the voice assistant running on this workstation." \
  --tts-output t6_gpu.wav > t6_gpu.log 2>&1

Repeat with --gpu-backend cpu, output to t6_cpu.log. Then:

grep -oE "frame=[0-9]+ cb0=[0-9]+" t6_gpu.log | head -5
grep -oE "frame=[0-9]+ cb0=[0-9]+" t6_cpu.log | head -5

Result: see the divergence table below. GPU log reproduces the runaway independently; CPU log stops cleanly at frame 43.

7. Swap to F16 (not quantized), keep failing text, GPU → still runs away

Same as Test 1, -m qwen3-tts-12hz-1.7b-base-f16.gguf instead.

Result: identical failure. Quantization isn't the cause (Test 1 = Q8_0, this test = F16, same model size, same outcome).

8. Add --no-flash-attn, keep failing text, GPU → still runs away

Same as Test 1 plus --no-flash-attn.

Result: identical failure. Doesn't matter anyway — this build has GGML_HIP_ROCWMMA_FATTN=OFF compiled in, so the flag has nothing to disable. Confirms the flag has no effect, not that flash-attention was ever active.

9. Swap to a totally different voice reference, keep failing text, GPU → still runs away

Same as Test 1 but --voice daphne_ref.wav --ref-text "<Daphne Park transcript, see above>" — a completely unrelated speaker and recording.

Result: frame 3795 (vs Test 1's frame 3796 — same failure, essentially identical), 303.68s. Voice reference isn't the cause either.

10. Same as Test 9, add --gpu-backend cpu → fine

Result: clean stop, 43 frames, 3.44s. Matches the CPU pattern from every other test.


Where the two paths first disagree

First 5 sampled tokens (cb0 = codebook-0), same input, GPU vs CPU:

frame GPU token CPU token
0 1290 1543
1 2005 1365
2 2005 146
3 2005 532
4 882 129

They disagree from frame 0. Not a slow drift — wrong from the very first decode step. This pins down when it goes wrong, not why — the actual ggml/HIP op responsible hasn't been found.


Ruled out

Suspect Verdict Evidence
Quantization ❌ not it Q8_0 (Test 1) and F16 (Test 7), same model size → same failure
Model size ❌ not it 1.7B (Test 7) and 0.6B (Test 4), same quantization → same failure
--no-flash-attn flag ❌ not it Test 8, no change — also moot, rocWMMA fattn compiled off
HIP graph capture ❌ not it GGML_HIP_GRAPHS=OFF in every test, including failing ones
Voice reference identity ❌ not it Test 9, totally unrelated speaker/recording → same failure (frame 3795 vs 3796)
CLI --temperature / --seed ❌ not wired up Talker's sampler hardcodes top_k=50, temp=0.9 in source (qwen3_tts.cpp:6945-6946) — CLI flags never reach it

Impact

GPU path: no error, exit code 0, valid WAV file written — but it silently ran ~30-90x longer than it should (303.76s of audio vs under 10s). The only signal is one informational stderr line: qwen3_tts: talker kv cache full at frame 3796 (n_past=4095). Any caller that isn't scraping logs sees a "successful" run.

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