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).
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.
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
free -hshows "30Gi" — that's GiB vs GB, not less RAM. See note below.1a9ee81eb76bf52b3ba4a589306b986539f42010890278a8342c620197c90e702e1188bcab94f510cpu,hiponlyRAM 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_DEVICESwas 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 --showdriverversionprints "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.fc43Build config
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).
qwen3-tts-12hz-1.7b-base-q8_0.ggufbbb93ab1...520dqwen3-tts-12hz-1.7b-base-f16.ggufc7039d04...e00eqwen3-tts-12hz-0.6b-base.gguf9b719418...af6dcqwen3-tts-12hz-0.6b-base-q8_0.gguf61626370...c1961qwen3-tts-tokenizer-12hz.gguf70dc95db...19bFull 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:"Good evening. I am the voice assistant running on this workstation."(68 chars)"Hello, this is a quick test of the new voice model."(51 chars)1. Run on GPU with the failing text → it runs away
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 fineResult: 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
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→ fineResult: clean stop, 54 frames, 4.32s.
6. Dump per-frame tokens on both backends to find where they diverge
Repeat with
--gpu-backend cpu, output tot6_cpu.log. Then: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.ggufinstead.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 awaySame as Test 1 plus
--no-flash-attn.Result: identical failure. Doesn't matter anyway — this build has
GGML_HIP_ROCWMMA_FATTN=OFFcompiled 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→ fineResult: 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: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
--no-flash-attnflagGGML_HIP_GRAPHS=OFFin every test, including failing ones--temperature/--seedtop_k=50, temp=0.9in source (qwen3_tts.cpp:6945-6946) — CLI flags never reach itImpact
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.