用 GPU 做 vLLM Benchmark 的一些心得
最近用 GPU 跑了 vLLM 的 benchmark,想了解在 LLM serving 的環境下,實際上能期待多少 throughput。
有一件事一開始就讓我蠻意外的:
GPU 使用率高不代表 throughput 就高。
初始設定與困惑
Model 和 vLLM server 跑起來之後,OpenAI 相容的 endpoint 就可以開始測了。我用了一個 benchmark script 來調整:
- 連線數
- Concurrency 等級
- Request 的模式
主要量測的指標:
- Requests per second
- Token throughput
第一批結果讓我很困惑。
GPU 使用率一直維持在 99% 左右,但整體 throughput 卻比預期低。一開始搞不清楚這到底是正常現象還是我的 benchmark 方法有問題。
感覺系統明明很忙,但產出卻沒有想像中多。
Benchmark 方法比我想像中重要
經過好幾輪測試跟同事討論之後,我調整了測試方法讓它更一致:
- 固定總 request 數量
- 控制連線數
- 用 burst 方式送 request,而不是分批慢慢送
- 逐步增加連線數
這改變了一切。
在之前的測試裡,我是用分批加上時間間隔的方式送 request。如果同時活躍的連線數不足以把 server 打滿,量測出來的 throughput 就會偏低。
換成 burst 方式,確保 request queue 維持滿載之後,throughput 明顯提升了。
Requests per second 和 token throughput 都隨著連線數增加而上升——到某個程度為止。
超過某個門檻之後,再加連線反而會降低效能。這時候才看到真正的容量上限在哪裡。
這是我的第一個重要發現:
如果 server 沒有被打滿,benchmark 的結果就不能反映真正的容量。
對 "Batching" 的誤解
另一個我搞錯的是 "Batch API" 實際上代表什麼。
我一開始的 prompt 結構是:
- 一個固定的 system message
- 一個變動的 user message
因為 system message 固定不變,prefix caching 效果不錯,cache hit rate 大約在 50–60%。
後來我試了另一種做法:
把一個 system message 加上多個 user message 合併成一個 request。
我以為這樣可以更有效率地處理更多輸入。但結果跟我預期的不一樣。
發生了這些事:
- 更容易碰到 context window 的上限
- Prefix cache hit rate 下降了
- KV cache memory 更快被用完
vllm:num_preemptions_total增加了
Preemption 代表某些 sequence 被從 GPU memory 中踢出去,之後還得重新計算。
雖然一個 request 處理了更多文字,但能有效處理的獨立 user message 數量並沒有等比例增加。
共用的 prefix 在整個 prompt 中佔的比例變小了,prefix caching 的效益因此下降。同時 memory 壓力也明顯增大。
vLLM 實際上在做什麼
仔細看 vLLM 的 metrics 之後,比較能理解到底發生了什麼事。像是這些指標:
- Prefix cache hit rate
- KV cache 使用率
- 正在跑的 request 數量
- Prefill 時間
- Decode 時間
- Preemption 次數
可以看到 scheduler 內部的行為。
我最後理解到的是,vLLM 用的是 continuous batching,而不是 static batching。
Continuous batching 會在每個 decode step 動態地把 sequence 組合在一起。Scheduler 根據以下限制來決定同時能跑多少 token 和 sequence:
- 最大 batched token 數
- 最大 sequence 數
vLLM 的 batching 是以 token 為基礎、動態進行的,不只是把多個 prompt 合併成更大的 request 這麼簡單。
這也解釋了為什麼我用「大 prompt」的方式並沒有如預期般提升 throughput。
重點整理
- 99% GPU 使用率不代表 throughput 最佳化
- Server 沒被打滿的話,benchmark 結果會有誤導性
- 連線數對量測出來的容量影響很大
- 更大的 prompt 可能降低 prefix cache 的效率
- Memory 壓力可能觸發 preemption 和重新計算
- Continuous batching 改變了我們對 batching 的理解方式
最後
這不是什麼深入的研究,我在 LLM serving 這塊還算新手。
但經過這個過程,讓我對 load、memory、caching 和 scheduling 在實際環境下怎麼互動有了更清楚的概念。
Benchmark 不只是跑出數字而已——重點是理解系統在壓力下的行為。
這個過程比我一開始預期的有趣很多。