故事的開始很隨意：看到 tonyd2wild 把 Qwen3.8-Flash-Next 塞進四張 3090 的 recipe，剛好想試試而已。要求也不高——256K context 能跑就 ok，不奢求更大的 pool。

家庭伺服器的配置是四張（對，現在是四張）RTX 3090。3090 是 2020 年的 consumer 卡，每張 24GB、936 GB/s 頻寬，放到 2026 年算是中年大叔——但四張排起來就是 96GB VRAM。目標模型是 Qwen3.8-Flash-Next：125B 主 MoE（每 token 只激活 6B）加 51B 的 PLE 查表，再加 4B MTP head，總共約 180B，原生 262K context。

第一個問題就擺在桌上：**塞不下。**

## VRAM 算術：為什麼三張卡是死路

先把帳算清楚。非表權重（W4A16 experts + BF16 attention/GDN/router/embedding）一共 68GB。

- 四卡分：每卡 17GB
- 三卡分：每卡 22.7GB，24GB 的卡只剩約 1.3GB 給 KV cache、MTP draft、activations 和 CUDA context

1.3GB 要塞 256K context 的 KV？這不叫推理，叫行為藝術。所以結論很乾脆：**三張卡沒有可行解，第四張卡必須到貨。** llama.cpp 路線也要約 60GB 權重進 VRAM，兩卡 recipe 要 128GB RAM，全都不通。

然後還有更大的問題：那 51B 的 PLE 查表，FP8 量化後約 47.7GB——**VRAM 放不下，RAM 也放不下**（機器 94GB RAM，但權重和 KV 已經把空間吃掉了）。

## 核心 trick：把 47.7GB 的表留在 NVMe

這是整個 recipe 最妙的地方。PLE 表根本不動，留在 NVMe 上，**每個 token、每個 step 只 staged gather 16 行進一個固定的 GPU buffer，而且跑在 CUDA graphs 裡面**。

聽起來很恐怖：每個 token 都要讀一次磁碟。但 NVMe 的隨機讀頻寬撐得住這種小批量、高頻率的 pattern，CUDA graphs 又把 launch overhead 吃掉了。結果就是：一張「邏輯上存在、物理上住在磁碟裡」的 47.7GB 表，VRAM 成本幾乎是零。

這是 consumer 硬體跑大 MoE 的關鍵思路：**不是把模型塞進 VRAM，是讓模型大部分時間住在該住的地方。**

## 引擎與關鍵 flags

引擎用 vLLM nightly image（現役的 vLLM 0.28.0 不認 `qwen4exp` 架構，PyPI 裝起來也沒用，官方 recipe 明說要專用 image），加上 tonyd2wild 的 overlay patches。

啟動參數裡幾個要命的：

```bash
LM_ONLY=0 NCCL_MODE=nvl PLE_MODE=staged GRAPHS=nocompile MTP=3 TP=4 GMU=0.95 SEQS=6 \
CHUNK=2048 MAXLEN=262144 KV_DTYPE=fp8_e5m2 CAPTURE_SIZES=4,8,12,16,20,24 \
EXTRA="--quantization gptq_marlin --enable-expert-parallel \
  --mm-processor-kwargs {\"max_pixels\":1048576} --mamba-ssm-cache-dtype bfloat16" \
bash launch/qwen38fn-w4a16-3090-tp4.sh
```

| 參數 | 值 | 為什麼 |
|---|---|---|
| `PLE_MODE` | staged | PLE 表在 NVMe，staged gather（staged/mmap/none 三選一） |
| `MTP` | 3 | MTP3 speculative decoding，速度從 55.8 拉到 193.3 tok/s 的主因 |
| `GMU` | 0.95 | 0.97 會 OOM（剩 310MiB 的時候它要 512MiB） |
| `KV_DTYPE` | fp8_e5m2 | Ampere 編譯不過 e4m3，只能用 e5m2 |
| `CAPTURE_SIZES` | 4,8,12,16,20,24 | 必須是 (MTP+1) 的倍數，MTP=3 就是 4 的倍數 |

EXTRA 裡最陰的是 `--enable-expert-parallel`：**不開會靜默掉到沒調過的 Triton WNA16 kernel**，速度直接砍半（103.5 → 193.5 tok/s 的 1.87 倍差距就是它）。靜默失敗，log 裡什麼都沒寫，你只會覺得「怎麼有點慢」。這種 bug 最討厭，因為它不報錯，它只是慢。

## 實測數字

tonyd2wild 的 4×3090 實測（96GB VRAM + 31GB RAM）：

| 場景 | 速度 |
|---|---|
| Count to 100（單流，temp 0） | **193.3 tok/s**（無 draft 只有 55.8） |
| 真實 prompt（prose / chat / code） | 109.5 / 108.4 / 145.8 tok/s |
| 6 個並行 coding agent | 317 tok/s 持續、570 峰值，TTFT 0.47s |
| Prefill | ~311 tok/s |
| Context | 262,144 native，6 座位，KV pool 279,462 tokens |
| VRAM | 20.5–22.6 GB/卡 |
| 載入時間 | ~2 分鐘 |

注意 count-to-100 的 193 tok/s 是 draft 最理想的 case（數數字，token 高度可預測），真實生成看 109–146 那行。但 193 那個數字還是會讓人中二一下：consumer 卡，四張，180B，每卡只吃 20 多 GB。

## 九個陷阱（每個都燒過 boot）

1. **FP8 e4m3 KV 在 Ampere 編譯不過**：`type fp8e4nv not supported in this architecture`（要 SM89）。用 e5m2。
2. **`--enable-expert-parallel` 必須開**：不開就靜默掉到慢 kernel，速度砍半。
3. **MTP draft 要單獨載入**：draft 在 `runtime/mtp-int4-g32`，stock vLLM 當 separate draft directory 讀。沒 draft 只有 55.8 tok/s。
4. **`reasoning_effort` 預設 xhigh**：寫 code 要傳 `medium`，否則一個 page 花 10–50k reasoning tokens 然後 truncate。`high` 不是合法值，對，你沒看錯。
5. **NVLink 只有兩對**：0-1（28GB/s）、2-3（56GB/s），中間靠 PCIe host bridge，4-way all-reduce 被 PCIe 卡住。
6. **gmu 0.97 會 OOM**：fleet load 下剩 310MiB 要 512MiB，剛好差一點，剛好 OOM。
7. **CUDA graph capture sizes 要 (MTP+1) 的倍數**：MTP=3 → 4,8,12,16,20,24。
8. **NVFP4/FP8 專家 checkpoint 在 Ampere 沒 kernel**：只能用 W4A16（Marlin）。
9. **直接套 albucino 的 overlay 會掛**：27 個整檔替換是針對另一個 vLLM tree 的，只借 checkpoint 和 flag 形狀，overlay 要換 tonyd2wild 的。

## 為什麼不用 LMCache

LMCache 跟省 VRAM 沒關係，它的爽點就在快取儲存：重複的 prefix 存進 cache，命中就不用重算，TTFT 直接拉快，能達到商業級同等的體驗。

更實際的 blocker：這個模型是 GDN + QSA（Qwen Sparse Attention），LMCache 的 verified list 只有 Qwen3.5/3.6，QSA 沒驗證過；QSA 的 indexer cache 是第三個 KV group，layout 非標準，LMCache 的 detector 根本不認；MTP3 是 EAGLE 家族，draft 會寫進 target 的 KV blocks，MTP + prefix hit 在 hybrid mamba 上是已知的 poisoning 路徑。

最妙的是 recipe 作者自己測過 prefix caching，結論是「did not hold」，所以 launcher 第 70 行硬編碼 `--no-enable-prefix-caching`——連 EXTRA 都蓋不掉，要改腳本。前人的坑就是後人的路標。

## 結語

3090 是中年大叔，但四個中年大叔抬得動 180B。

關鍵從來不是買新硬體，而是算術：W4A16 量化把 68GB 壓進 4×17GB，NVMe staged gather 讓 47.7GB 的表住在磁碟裡，MTP3 把速度拉 3.5 倍，fp8 e5m2 KV 讓 262K context 在 96GB 裡有餘裕。每一步都是「放不進去」逼出來的設計。

所有數字都來自實測。想複現的話，recipe 在 [tonyd2wild/Qwen38-Flash-Next-4x3090](https://github.com/tonyd2wild/Qwen38-Flash-Next-4x3090)，checkpoint 在 HuggingFace 上 129GB。

（第四張 3090 已經在路上了。到貨之後會有續集：實際 boot log、載入過程、以及第一句它說出來的话。）