So after the Taguchi experiment from my last post, I wanted to see how far I could push training. Applied that same optimized recipe (push batch to VRAM ceiling, hold a=2 s=128 r=32) to Mistral-Nemo-12B as a third validation point. Predictions landed within 1% on every metric:
Three-anchor hardware ceiling:
βββββββββββββββββββββββ¬βββββββββ¬βββββββββββ¬βββββββββββββββ
β Model β Params β Samp/sec β paramΒ·tok/s β
βββββββββββββββββββββββΌβββββββββΌβββββββββββΌβββββββββββββββ€
β Qwen3-0.6B β 0.75B β 171.02 β ~30 T β
β Qwen3-8B β 8.2B β 29.07 β 30.5 T β
β Mistral-Nemo-12B β 12.3B β 18.91 β 29.7 T β
βββββββββββββββββββββββΌβββββββββΌβββββββββββΌβββββββββββββββ€
β Mean β β β 29.9 T Β±1.5% β
βββββββββββββββββββββββ΄βββββββββ΄βββββββββββ΄βββββββββββββββ
Three different architectures, 15Γ parameter range, same ceiling. The dual R9700 saturates at ~30 T paramΒ·tok/s for LoRA training. Period.
There is a hard wall here.
I also tested whether the switch was leaving bandwidth on the table. lspci -vvv showed the PEX88096 running MRRS=128 (should support up to 4096) and ExtTag=OFF on the switch ports. Looked like free performance sitting there. Nope⦠setpci writes get silently rejected. The firmware locks DevCtl at 0x083f. Both AMD (Navi 10 bridges) and the card OEM (switch ports) lock their registers independently. 19.60 GB/s is the ceiling and no amount of software tuning moves it.
Hoping to find more throughput, I installed AOTriton flash attention. It works on gfx1201 (TORCH_ROCM_AOTRITON_ENABLE_EXPERIMENTAL=1) but the throughput gain is <1% across all three model sizes. What it DOES give you is VRAM savings that scale with batch Γ seqΒ²:
FA A/B on training (eager vs SDPA):
βββββββββββββββββββ¬βββββββββββββ¬ββββββββββββββββ
β Model β Throughput β VRAM β
βββββββββββββββββββΌβββββββββββββΌββββββββββββββββ€
β Qwen3-0.6B β +1.29% β β30 MB β
β Qwen3-8B β +0.24% β β590 MB β
β Mistral-Nemo-12Bβ +0.47% β β300 MB β
βββββββββββββββββββ΄βββββββββββββ΄ββββββββββββββββ
Free VRAM headroom, but the ceiling doesnβt move because training is compute-bound, not memory-bound. Worth enabling, just donβt expect speed gains.
Corrections to my last post: I hoped a Gen5 switch and proper FA2/3 could unlock more training performance. Both wrong. Training is compute-bound so Gen5 doubles bandwidth but thatβs not going to translate into extra training speed. The remaining training levers are more GPUs (cuz I havenβt burned enough money on this rig) or waiting for AMD to ship better ROCm kernels (lul).
So with training maxed out, I looked at how to boost inference. Same methodology, different problem. Built a Taguchi L8 (8 runs, 7 binary variables) on Qwen3-Coder-30B-A3B (MoE, 17 GB) using both the ROCm and Vulkan llama-bench binaries:
Factor table:
ββββββ¬ββββββββββββββββββββββ¬βββββββββββββββ¬βββββββββββββββ
β # β Factor β Level 1 β Level 2 β
ββββββΌββββββββββββββββββββββΌβββββββββββββββΌβββββββββββββββ€
β A β Backend β ROCm β Vulkan β
β B β Split mode (-sm) β layer β tensor β
β C β RADV_DEBUG β (unset) β nocompute β
β D β Flash attention β -fa 0 β -fa 1 β
β E β Batch size (-b) β 512 β 2048 β
β F β MMAP (-mmp) β 1 (default) β 0 (disabled) β
β G β Ubatch (-ub) β 256 β 1024 β
ββββββ΄ββββββββββββββββββββββ΄βββββββββββββββ΄βββββββββββββββ
Ran all 8 configs in ~15 minutes. This highlighted something that I havenβt quite processed yet, but reinforces my belief that LLMs are a tech stack more than a mythical black box where one config is perfect for every model and/or situation.
ββββββββββββββββββββββββ¬ββββββββββ¬ββββββββββββββ¬ββββββββββββββββ
β Config β Backend β tg128 (t/s) β pp4096 (t/s) β
ββββββββββββββββββββββββΌββββββββββΌββββββββββββββΌββββββββββββββββ€
β ROCm default β ROCm β 68 β 245 β
β Vulkan default β Vulkan β 131 β 3260 β
β Decode-optimal β Vulkan β 147.86 β 2743 β
β Prefill-optimal β ROCm β 76 β 5060 β
ββββββββββββββββββββββββ΄ββββββββββ΄ββββββββββββββ΄ββββββββββββββββ
Decode-optimal: Vulkan, -sm layer, RADV_DEBUG=nocompute, -fa 0, -b 2048, -mmp 0, -ub 256
Prefill-optimal: ROCm, -sm layer, -fa 1, -b 2048, -mmp 0, -ub 1024
The big surprise was flash attention. Everyone (myself included) treats it as βturn it on, itβs faster.β On RDNA 4 inference itβs actually a workload toggle:
-
FA ON: +55% prefill, -23% decode
-
FA OFF: worse prefill, much better decode
If youβre doing interactive chat, turn FA off. If youβre doing RAG or processing long contexts, turn it on. Not both. This may not be a hard truth, but more a reality of using workaround to get consumer hardware to work with industry standards.
The other shock: ROCm default prefill is leaving 95% of performance on the table. Out of the box: 245 t/s. Add three flags (-b 2048 -fa 1 -mmp 0): 5060 t/s. Thatβs a 20Γ improvement from flags that should probably be defaults.
A few other notes from the test:
-
-sm tensor is broken when combined with -mmp 0 on both backends. Crashes at context creation. Had to fall back to -sm row on two runs. Use -sm layer for now.
-
RADV_DEBUG=nocompute does help Vulkan decode on MoE, confirming what @digitalscream found.
-
-mmp 0 and -b 2048 are consistent wins on both metrics, both backends. No reason not to use them.
The practical takeaway: if youβre running llama-server or llama-swap, you want two configs:
- A Vulkan instance for chat
- A ROCm instance for heavy context work.
Same model, same hardware, different flags. The routing layer handles the rest.
Next up is a concurrency sweep on the Vulkan decode config and a dense model panel to see if the ROCm-vs-Vulkan picture changes on non-MoE architectures.
The Taguchi skill ZIP from my last post is what drove all of these experiments. @BillDStrong You legit inspired this testing so major shoutout!
Here is a troubleshooting skill that I put together from your pointers. I made it generic so you can point it at pretty much any issue and itβll help route you to a few testing methodologies to try and figure out the solution. Itβs made for Claude, but should be easy enough to tweak for another harness.
troubleshooting-suite.zip (34.7 KB)