the x4 link, measured: 6.72 GB/s — which decides the streaming question

pcie-link-test.cu (CUDA, pinned buffers, cudaEvent timing, both services stopped):
pinned H2D 6.72 GB/s and D2H 6.60 GB/s, i.e. 85 % of the Gen4 x4 ceiling. Async
is no better than sync and pageable sits within 1 % of pinned, so these copies are
link-limited rather than staging-limited — the usual "pageable costs 2x" does not
apply on this host. A 3 MiB slab costs 474 us, essentially 3 MiB / 6.72 GB/s plus
~27 us of launch.

That settles the expert-streaming question quantitatively: 1.70 GiB of active
experts per token (A3B @ Q4_K_M, 75 GiB expert mass) is 272 ms of link time ⇒
3.7 tok/s, 4.6 with a fifth of the experts resident. The same bytes computed in
host RAM at 256 GB/s cost 7.1 ms — the link is 31x slower than the memory behind
it, which is why llama.cpp's -ot exps=CPU path measured 20.46 tok/s while an
engine that streams loses by 3-5x. The port under the card is x8-capable and the
card is x16-capable, so the x4 is the adapter; doubling it still leaves streaming
well behind.

Also records FreeToken's own constraint: it keeps experts in host RAM with an LRU
GPU cache (exactly this streaming shape) and loads only modelopt block-FP8
safetensors for qwen3_5_moe, so Q4_K_M GGUF is unloadable there regardless.
This commit is contained in:
Frank
2026-09-16 23:25:06 -07:00
parent 262ac53c14
commit 2f012fba39
2 changed files with 72 additions and 1 deletions
+18
View File
@@ -415,6 +415,24 @@ linked runbooks.
target that works, because the CPU backend is last in the list. Pure Vulkan target that works, because the CPU backend is last in the list. Pure Vulkan
changes nothing here — the gate is the override, not the driver mix. See changes nothing here — the gate is the override, not the driver mix. See
[[x4-pair-experiment]]. [[x4-pair-experiment]].
- **A host↔3090 link at x4 measures 6.72 GB/s, and that is a design
constraint, not a detail.** `pcie-link-test.cu` on Dankclaw (2026-09-16,
services stopped): pinned H2D 6.72 GB/s, D2H 6.60 GB/s — 85 % of the Gen4 x4
ceiling — with async no better than sync and pageable within 1 % of pinned,
i.e. the copies are link-limited rather than staging-limited (the usual
"pageable costs 2x" rule does not apply here). The card is x16-capable and the
Strix Halo port beneath it is x8-capable (`LnkCap: 16GT/s, Width x8` on
`00:03.1`), so the x4 lives in the adapter or cable; the link idles at
2.5 GT/s x4 and trains up under load. Consequence for any engine that
*streams* weights across it: a 3 MiB slab costs 474 us, and a 111 GiB A3B
model needs ~1.70 GiB of active experts per token ⇒ **3.7 tok/s** (4.6 with a
20 % resident cache), against **20.46 tok/s** for the same model with the
experts *computed* in host RAM by llama.cpp (`-ot exps=CPU`, whence the link
carries activations only) and 25–26.5 on the iGPU. Streaming over PCIe loses;
computing in RAM does not care. FreeToken — expert LRU in host RAM, exactly
this streaming shape — also loads only modelopt block-FP8 safetensors for
`qwen3_5_moe`, so a Q4_K_M GGUF is unloadable there before bandwidth is even
the question. See [[x4-pair-experiment]].
**ROCm / GPU** **ROCm / GPU**
@@ -348,4 +348,57 @@ Nothing structural moves — `-ot` still gates per-tensor placement and a layer
split still cannot consolidate KV on the 3090. If the aim were to *deploy* split still cannot consolidate KV on the 3090. If the aim were to *deploy*
mainline on this iGPU, `i1-IQ4_XS` (90.78 GiB, single file, sha256 mainline on this iGPU, `i1-IQ4_XS` (90.78 GiB, single file, sha256
`b4787265…d0574f9d`) is the rung that changes the memory arithmetic and q8_0 KV `b4787265…d0574f9d`) is the rung that changes the memory arithmetic and q8_0 KV
is the lever that changes depth bandwidth; Q4_K_S is the conservative middle. is the lever that changes depth bandwidth; Q4_K_S is the conservative middle.
## The link, measured (2026-09-16)
`pcie-link-test.cu` — CUDA, pinned host buffers, `cudaEvent` timing, both
services stopped. Gen4 x4 spec ceiling is 7.877 GB/s usable; GB/s throughout.
| size | H2D sync | H2D async | D2H sync | 1× sync xfer | pageable H2D |
|---|---|---|---|---|---|
| 3 MiB | 6.65 | 6.71 | 6.54 | **474 µs** | 6.33 |
| 16 MiB | 6.72 | 6.72 | 6.59 | 2.50 ms | 6.61 |
| 64 MiB | 6.72 | 6.71 | 6.60 | 9.97 ms | 6.64 |
| 256 MiB | 6.73 | 6.73 | 6.60 | 39.9 ms | 6.67 |
| 1 GiB | 6.72 | 6.72 | 6.60 | 160 ms | 6.67 |
| 2 GiB | 6.72 | 6.72 | 6.60 | 320 ms | 6.67 |
Three readings:
- **6.72 GB/s H2D = 85 % of the Gen4 x4 ceiling** (D2H 6.60 = 84 %). The card is
x16-capable and the Strix Halo port beneath it is x8-capable
(`LnkCap: 16GT/s, Width x8` on `00:03.1`), so the x4 lives in the
adapter/cable, not the slot. The link idles at 2.5 GT/s x4 and trains up under
load — which these numbers prove on their own, since Gen1 x4 tops out near
1 GB/s.
- **Async buys nothing and pinning buys ~1 %**: one stream of copies already
saturates the link (6.71 async vs 6.65 sync at 3 MiB; identical at 1 GiB), and
pageable transfers run within noise of pinned ones. On this host the copies
are link-limited rather than staging-limited, so the usual "pageable costs 2×"
rule does not apply.
- **A 3 MiB slab costs 474 µs**, essentially `3 MiB / 6.72 GB/s` (447 µs) plus
~27 µs of launch. The streaming tax here is bandwidth, not per-copy overhead.
### What that means for an expert-streaming engine
Active expert bytes for this model are ~1.70 GiB/token (A3B @ Q4_K_M, 75 GiB
expert mass) — 580 × 3 MiB slabs:
| assumption | per-token link time | ceiling |
|---|---|---|
| every slab misses (bandwidth-bound) | 272 ms | **3.7 tok/s** |
| 20 % resident in a 24 GiB card's expert cache | 217 ms | 4.6 tok/s |
| 50 % hit rate (generous locality) | 136 ms | 7.4 tok/s |
| *the same bytes computed in host RAM at 256 GB/s* | 7.1 ms | the link is **31× slower** than the memory behind it |
Against this host's measured alternatives — **20.46 tok/s** for llama.cpp with
the experts *computed* on the CPU (link carries activations only), 25 for
halogen, 26.5 for mainline on the iGPU — an engine that *streams* experts across
this link loses by 3–5×, and the port's x8 ceiling would only double it (≈7–9
tok/s).
For the specific engine that raised the question: FreeToken v0.1.2 keeps expert
weights in host RAM with an LRU GPU cache — exactly this streaming shape — and
it loads only modelopt block-FP8 HF safetensors for `qwen3_5_moe`, so a Q4_K_M
**GGUF** is not loadable at all before bandwidth enters the discussion.