最近用 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 不只是跑出數字而已——重點是理解系統在壓力下的行為。

這個過程比我一開始預期的有趣很多。

Tags:

Updated: