每個人都跑 vLLM 在 NVIDIA 上。我跑在兩張 AMD R9700(RDNA4,各 32GB)上、用 ROCm,體驗嘛……不一樣。以下是我三個多月跑 27B 模型上 production 學到的東西。
我的設定
- GPU:2x AMD R9700(RDNA4,各 32GB VRAM),PCIe 4.0 x16
- ROCm:6.4.x(真正支援 RDNA4 的版本)
- vLLM:0.28.0(RC build:
capicua25x/vllm-rocm-rdna4:0.28.0-rdna4-rc13) - Model:Qwen3.8-27B Uncensored(FP8,~29GB)
- Tensor parallel:TP=2(每張 GPU 一個 weight shard)
- Context:最多 262K tokens
- MTP:speculative decoding,n=5 tokens
這不是個「裝上就能跑」的設定。有好幾件獨立的事情得搞對,它才能跑得漂亮。
Attention backend:選 TRITON_ATTN
這個選錯會花掉你最多時間。vLLM on ROCm 支援幾個 attention backend:
| Backend | 中位延遲 (256 tok) | 備註 |
|---|---|---|
ROCM_ATTN | 3,369 ms | 原生 HIP kernel。聽起來對。其實不是。 |
TRITON_ATTN | 2,527 ms | Triton 編譯的。快約 25%。 |
FLASH_ATTENTION_TRITON | ~跟 TRITON_ATTN 一樣 | 只影響 ViT,不影響 LM decode |
TORCH_SDPA | 最慢 | PyTorch 的 fallback。別用。 |
反直覺的地方:ROCM_ATTN 是「原生」backend,是 AMD 自己寫的那個。但在 RDNA4 上,Triton 編譯的版本贏。我用完全相同的 config 對兩者做 A/B 測試(10 次、1024 tokens、concurrency=1、temp=0),差距在每一次執行都一致。
為什麼:RDNA4 的原生 paged-attention fast path 需要 head_size=128 和自動 KV dtype。我的 config 用 head_dim=256 搭配 FP8 KV cache,所以「原生」路徑最終還是 fallback 到一般 kernel。反過來說,Triton 編譯器是針對我實際跑的 shape 產生代碼的。
這代表「正確答案」取決於你模型的 head dimension。如果你在跑 head_dim=128 加上標準 FP16/BF16 KV 的模型,ROCM_ATTN 反而可能贏。我沒測那個。我測的是我自己跑的。
MTP 的陷阱
Multi-Token Prediction(speculative decoding)才是真正加速的地方。我的數字:
- 沒有 MTP:~35 tok/s decode
- 有 MTP(n=5,68% acceptance):~63 tok/s decode
這是 1.8 倍的加速。但這裡有個差點讓我整個關掉它的陷阱:
當我用隨機 input tokens benchmark MTP 時,得到 49% 的 acceptance rate。用真實的對話 prompts(真正的 agent 歷史 + system prompt),跳到 68%。僅僅是 input 分佈的差距,就差了 19 個點。
如果你用隨機 token benchmark 看到 49%,你會覺得 MTP 幾乎不值得開。其實不是它不值得——是你的 benchmark 錯了。
規則:永遠用跟實際 workload 相符的 input 來 benchmark。對話式 agent 就是真實的對話歷史;code completion 工具就是真實的 code context。隨機 token 量出來的是一個跟你在 serving 的不同模型。
冷啟動:你不該付的 9 秒
閒置後的第一個 request:9.1 秒(穩態 2.5s)。這是 GPU 熱身 + vLLM 為第一個 sequence 編譯 attention kernels。
我用一個 backend gate 解決:一個在真實流量打到模型之前就發射的 canary request。canary 吸收那 9 秒的冷啟動。使用者看到 2.5s。canary 是一個短 prompt(50 tokens),白天每 30 秒跑一次,晚上每 2 分鐘一次。
canary 同時也是健康檢查:如果它花超過 10 秒,就表示有問題(GPU 熱降頻、VRAM 洩漏、殭屍 process 吃記憶體)。
VRAM:真正要盯的數字
TP=2 搭配 29GB 模型,理論上每張 GPU 約 14.5GB。實際上是:
- Weights:每張 GPU ~14.5 GB(FP8,TP=2)
- KV cache:每張 GPU ~8-10 GB(262K context,FP8)
- Activations + 開銷:每張 GPU ~2-3 GB
- 總計:每張 GPU ~25-27 GB / 32GB
這留了 5-7 GB 的餘裕。夠用到同時約 4-5 個 sequences,再多就會開始 OOM。如果你在 serving 單一 agent 對話(就是我的使用情境),你根本碰不到上限。
要盯的數字:vLLM metrics endpoint 的 gpu_memory_utilization。我設在 90% 告警。如果你在跑多個模型或更長的 context,你會在這裡發現自己已經沒空間了。
重來一次我會做什麼不同
- host 的 RAM 加多。我有 39GB。模型要約 30GB 放 weights + KV。很緊。我想要 64GB,這樣能跑 70B 模型或 serving 更長的 context 而不用 swap。
- 獨立的 vision GPU。同一個 27B 模型同時處理文字和圖片編碼。視覺 request 一進來,就跟文字 decode 搶同一塊 VRAM。在另一張 GPU 上放一個較小的專用 vision model 可以消掉這個衝突。
- 一套真正的 monitoring stack。我用 bash script + curl 每 60 秒 scrape 一次 GPU 指標。我想要 Prometheus + Grafana。那是另一天的工程。
不光鮮的 80%
「跑 vLLM 在 AMD 上」的絕大多數是:
- 保持 container 活著(systemd 崩了就重啟)
- 確認 MTP 設定在重啟後還活著(預設不會——你每次都得把它當 CLI flag 傳進去)
- 盯 VRAM 使用,殺掉失控的 context
- 有新的 ROCm build 出來就更新,然後祈禱什麼都沒壞
- 搞清楚哪些
HIP_環境變數還是需要的、哪些在上個版本就被移除了
這不光鮮。但它就是一個「能用」的模型和一個「玩具」模型之間的差別。而且 63 tok/s 在消費級 AMD 硬體上,不是個玩具等級的數字。