← go home回首頁
EN 繁中

vLLM 跑在 AMD RDNA4 上:什麼真正管用

每個人都跑 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_ATTN3,369 ms原生 HIP kernel。聽起來對。其實不是。
TRITON_ATTN2,527 msTriton 編譯的。快約 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,你會在這裡發現自己已經沒空間了。

重來一次我會做什麼不同

  1. host 的 RAM 加多。我有 39GB。模型要約 30GB 放 weights + KV。很緊。我想要 64GB,這樣能跑 70B 模型或 serving 更長的 context 而不用 swap。
  1. 獨立的 vision GPU。同一個 27B 模型同時處理文字和圖片編碼。視覺 request 一進來,就跟文字 decode 搶同一塊 VRAM。在另一張 GPU 上放一個較小的專用 vision model 可以消掉這個衝突。
  1. 一套真正的 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 硬體上,不是個玩具等級的數字。