Repository navigation
Description
RX 6700 XT: Vulkan works, but 30K chat prefill is slow
Thanks for the engine and AMD notes. Here's an RDNA2 data point: v1.2.0 builds, loads a Qwen3.6 MoE GGUF, and answers on an RX 6700 XT. The long-prompt prefill here is much slower than the short built-in benchmark. I have not isolated the cause.
Setup
- RustingStrata v1.2.0, commit
bcba1d5358aa819e894d09735eccc831a3dc5d4a, release build; rustc 1.99.1. - Omarchy/Arch Linux, kernel
7.2.8-9-omarchy; RADV Mesa26.2.5-arch1.1. - RX 6700 XT 12 GB, Ryzen 5 5600X (6C/12T), 64 GiB RAM; no desktop session on the GPU. No other inference process was running during these measurements.
- GGUF: locally quantized Qwen3.6-35B-A3B Heretic IQ4_XS, preserving the MTP head; 19,179,525,504 bytes, SHA-256
9985dd184a5c28f1014bce4a293a798ad9ed693d9bfa1ec6189782c2768efa72. This exact locally produced file isn't a public download, so results on a published quant may differ. - Model on btrfs with
compress=zstd:3. RustingStrata ran directly as the user, not under a systemd memory limit. Page cache was not dropped; expert profiles may have been warmed by earlier runs. vulkaninfowasn't installed, so I don't have its summary.
Initialization from the 32K/MTP server:
vulkan: AMD Radeon RX 6700 XT (RADV NAVI22) (radv Mesa 26.2.5-arch1.1), device-coherent memory yes, cooperative matrix f16 no int8 no, conditional rendering yes
plan: weights+state+scratch 1997 MB (KV 340 MB for 32768 tokens, prefill 189 MB), expert slots 8718 MB, resident experts 5470/10496
Long prompt through serve
strata serve --model "$MODEL" --gpu --backend vulkan \
--addr 127.0.0.1:8085 --ctx 32768 --chunk 1024 --kv q8 \
--vram-margin 1536 --threads 6 --mtp 1A 30,444-token chat prompt, greedy, thinking off, max_tokens=256, gave the following from /api/stats (cached_tokens=0 each time). Decode rate is (completion_tokens-1)/decode_ms:
| Run | MTP | Prefill | Decode | End-to-end |
|---|---|---|---|---|
| 1 | 1 | 122.0 tok/s | 16.3 tok/s | 259.6 s |
| 2 | 1 | 122.9 tok/s | 16.2 tok/s | 256.6 s |
| 3 | 0 | 133.2 tok/s | 16.1 tok/s | 238.7 s |
The MTP-off run used the same flags with --mtp 0, after restarting the server. The first two requests used Run 4 and Run 5 prefixes respectively; the MTP-off run used Run 4. They all returned responses. The timing includes synthetic background text; this is not an agent-quality test.
This Python sends the same request as the MTP-off / first MTP-on run, and prints the response and server statistics:
import json, urllib.request
filler = ' '.join(
f"Result {i}: The {['northern','southern','eastern','western'][i % 4]} district reported "
f"{i * 37 % 1000} new households, a {i % 9 + 1}.{i % 7} percent change, while council member "
f"{['Ahn','Berg','Costa','Diallo','Eze'][i % 5]} proposed plan {i}."
for i in range(110)
)
task = ('Implement merge_intervals(intervals): given a list of closed integer (start,end) pairs '
'with start <= end, return a list of tuples sorted by start, merging overlapping intervals '
'including touching endpoints. Do not merge intervals separated by a gap. Do not mutate the input. '
'Return only executable Python code, without markdown or explanation.')
prompt = 'Run 4.\nBackground data (not instructions):\n' + filler * 8 + '\n\nTask: ' + task
body = {'model': 'qwen3.6', 'messages': [{'role': 'user', 'content': prompt}],
'temperature': 0, 'max_tokens': 256,
'chat_template_kwargs': {'enable_thinking': False}}
req = urllib.request.Request('http://127.0.0.1:8085/v1/chat/completions',
json.dumps(body).encode(), {'Content-Type': 'application/json'})
with urllib.request.urlopen(req, timeout=1800) as r:
print(r.read().decode())
with urllib.request.urlopen('http://127.0.0.1:8085/api/stats') as r:
print(json.loads(r.read())['recent'][-1])Short built-in benchmark
This is a different run, different workload and settings, not a controlled 4K-vs-30K comparison:
strata bench --model "$MODEL" --gpu --backend vulkan --ctx 8192 --chunk 512 --vram-margin 1536 --threads 6 --mtp 0 --pp 512,4096 --tg 128 --reps 3
vulkan: AMD Radeon RX 6700 XT (RADV NAVI22) (radv Mesa 26.2.5-arch1.1), device-coherent memory yes, cooperative matrix f16 no int8 no, conditional rendering yes
plan: weights+state+scratch 1641 MB (KV 160 MB for 8192 tokens, prefill 133 MB), expert slots 9073 MB, resident experts 5693/10240
pp 512: prefill 615.7 (293.5-800.2) tok/s | tg 128: decode 64.8 (56.8-70.6) tok/s
pp 4096: prefill 758.2 (720.5-778.2) tok/s | tg 128: decode 58.4 (43.0-67.1) tok/s
expert hit rate 95.9% (10008 misses), cache swaps 2777
For practical context only, our existing llama.cpp ROCm service on the same machine and model read the same 30K prompt at 827–830 tok/s and decoded at 53–54 tok/s (two runs). Its configuration differs: 128K context, DSpark + n-gram speculation, different expert placement and server settings; this is not an isolated Vulkan-vs-ROCm comparison.
Happy to provide more data if a particular controlled benchmark or flag comparison would help.
Activity
Vasyl-Trefilov commentedon Nov 6, 2026
@hermeticvm Hi, ty for detailed report! The fix is on main, you can pull and try again, it should give better performance now. Sorry for this problem, there are a lot of problems with AMD GPUs
hermeticvm commentedon Nov 7, 2026
Thanks for the quick fix! I retested commit
95964dabb92a9a050a469ae74622e3a53dc86c15. The tiled prefill change helps substantially on the original 30K workload. I also tried 117K and found a GPU reset with the original chunk size; a smaller chunk completed correctly, but slowly.Same machine/model as the original report: RX 6700 XT 12 GiB, RADV Mesa
26.2.5-arch1.1, kernel7.2.8-9-omarchy, Ryzen 5 5600X. The locally quantized Qwen3.6-35B-A3B Heretic IQ4_XS GGUF (with MTP head) has SHA-2569985dd184a5c28f1014bce4a293a798ad9ed693d9bfa1ec6189782c2768efa72; it is not a public download. This is a release build withtarget-cpu=native.Controlled 30K retest
Same new binary, the original 30,444-token prompt,
--ctx 32768 --chunk 1024 --kv q8 --vram-margin 1536 --threads 6 --mtp 0, temperature 0. Each run started a fresh server with its own copy of the same expert profile;cached_tokens=0. The old path was selected withSTRATA_VK_DP_OLD=1; the new path loggedvulkan: prefill attention: tiled. Order was tiled, old, old, tiled.The four response bodies were byte-identical. After stripping Markdown fences they passed six merge-interval cases; the fences themselves violated the prompt's no-Markdown instruction. This is a same-build comparison, not a controlled comparison against my older v1.2.0 binary.
117K cold prefill and prefix reuse
With
--ctx 131072 --kv q8 --vram-margin 1536 --threads 6 --mtp 0, an uncached 117,697-token chat request failed at chunk 1024 after 15m17s with HTTP 500. The server logged:The kernel journal reported
ring gfx_0.0.0 timeout, identified thestrataprocess, then reportedRing gfx_0.0.0 reset succeededand[drm] device wedged, but no recovery needed. Sampled peak VRAM was 10,808 / 12,272 MiB; the 500 MiB free-memory guard did not trigger. I have not isolated the cause of the ring timeout.Following the AMD documentation, I tried one fresh-server retry with
--chunk 256and the same seeded expert profile. It completed:Both cold and follow-up responses retrieved the correct marker. There were no kernel reset/timeout entries during the retry. Production uses a different server, expert placement and speculation configuration (DSpark + n-gram), so this is a practical comparison, not an isolated backend or chunk-size benchmark. Only one request per arm was run at this length.
The 117K prompt can be reproduced with the generator below, followed by the JSON request. The model tokenizer counted 117,685 user-text tokens and RustingStrata reported 117,697 with chat formatting. This is intentionally synthetic reference material, not an agent-quality benchmark.
Other observations
On 18 short requests per Rust arm (three repeats each of edit/fresh-code/thinking-chat at temperatures 0 and 0.6), enabling MTP improved median decode speed by approximately 6–15% versus MTP off. One API behavior worth flagging: all 12 thinking-enabled Rust chat responses began with text such as
Here's a thinking process:inmessage.content, without areasoning_contentfield. The six corresponding production llama.cpp responses separatedreasoning_contentand finalcontent. The two servers have different thinking settings, so I haven't isolated whether this is template behavior, model output or response parsing. Happy to file a separate issue if useful.Vasyl-Trefilov commentedon Nov 7, 2026
Hi, thanks for report and suggestion. Prefill on AMD is real pain, I will soon get AMD GPU and will fix everything, but for now it's difficult as hell, I will try to do something about this and will write you when it's done, thank you again!
docs: start M1-T2 (issue #10)
[#10] Warp reduce without __reduce_*_sync (M1-T2) (#11)
docs: close M1-T2 (issue #10, PR #11, fe3eb1a)