xiaoye@meowlab:~$ cat 2026-09-13-4x3090-180b-moe.md

四張 3090 塞一顆 180B:家庭伺服器的 MoE 塞入記

·5 min readvllmmoequantizationgpu

故事的開始很隨意:看到 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。

啟動參數裡幾個要命的:

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_MODEstagedPLE 表在 NVMe,staged gather(staged/mmap/none 三選一)
MTP3MTP3 speculative decoding,速度從 55.8 拉到 193.3 tok/s 的主因
GMU0.950.97 會 OOM(剩 310MiB 的時候它要 512MiB)
KV_DTYPEfp8_e5m2Ampere 編譯不過 e4m3,只能用 e5m2
CAPTURE_SIZES4,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 agent317 tok/s 持續、570 峰值,TTFT 0.47s
Prefill~311 tok/s
Context262,144 native,6 座位,KV pool 279,462 tokens
VRAM20.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 的工具」,但這個定位本身就錯。它的儲存在 host RAM(L1)和 disk(L2),GPU 的 KV pool 大小仍由 --gpu-memory-utilization 決定,它不會把 pool 變小,也不會讓 active context 塞進更少的 VRAM。它做的是 跨 request 的 KV reuse / prefix caching:重複的 prefix 不用重算,換來的是重複 prefix 場景下的 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,checkpoint 在 HuggingFace 上 129GB。

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

views

$ comments

no comments yet — be the first