我的 benchmark 量到 42 毫秒,算出來卻應該要 945 毫秒

在一張 RTX 3090 上比較 vLLM 和 TensorRT-LLM 學到的事 —— 大部分是關於「怎麼量」,而不是哪個引擎比較好。

2026 年 9 月 · 吳承融(Roger)

我的 benchmark 第一次跑出來:4,096 個 token 的 prompt,第一個 token 出來的時間(TTFT)只要 42 毫秒。

看起來很棒。然後我算了一下。

先算,再量

Prefill(在第一個 token 出來之前處理整段 prompt)受限於運算量。一般的 dense 模型大約要做 2 × 參數量 × token 數 次運算。以 Qwen3-8B、4,096 個 token 的 prompt 來算:

2 × 8.19e9 × 4096 / 71e12 FLOP/s  ≈  945 毫秒

71 TFLOPS 是 RTX 3090 的 FP16 tensor core 峰值。任何軟體都不可能讓硬體算得比這更多。所以如果 prefill 至少要 945 毫秒,量到 42 毫秒不代表引擎很快,而是代表量錯了。

原因很簡單:我的測試程式每次都送同一個 prompt,vLLM 的前綴快取就直接從快取拿 prefill 的結果。伺服器的 log 寫得很清楚:Prefix cache hit rate: 75.3%。那一輪所有的 TTFT,量到的都是「查快取」,不是真正的運算。

修法:每個請求開頭都加上一個唯一的 ID,讓 prompt 彼此不共用前綴,伺服器也加上 --no-enable-prefix-caching 關掉前綴快取。修完之後,prefill 量到 962.8 毫秒,是理論上限的 98%。

TensorRT-LLM 也有同樣的陷阱:它的 KV cache 區塊重用預設是開啟的。

兩個引擎都已經頂到硬體極限

Decode(一次生成一個 token)受限於記憶體頻寬,因為每一步都要把全部權重重新讀一次:

16.38 GB / 936 GB/s  ≈  每個 token 17.50 毫秒
階段受限於理論值vLLMTensorRT-LLM(pytorch)
Decode(batch 1)記憶體頻寬每 token 17.50 毫秒20.84 毫秒(84%)21.04 毫秒(83%)
Prefill(4,096 token)運算量945 毫秒962.8 毫秒(98%)990.8 毫秒(95%)

兩個引擎都達到硬體能力的 83–98%,已經沒什麼空間讓其中一個贏過另一個。這解釋了下一個結果。

「兩個一樣快」是錯的結論

同時請求數vLLMTensorRT-LLM(pytorch)差距
145.6 tok/s45.2 tok/s−0.9%
8308.3 tok/s310.2 tok/s+0.6%
32764.9 tok/s777.7 tok/s+1.7%

差距大約 2%。很容易就寫下「兩個引擎一樣快」然後結案。

但我每個引擎都跑了兩次,而且用 A-B-A-B 交錯執行,讓時間造成的飄移不會偏向任何一方。同一個引擎跑兩次,吞吐量的差異中位數是 5.4%。兩個引擎之間的差距,比同一個引擎跑兩次的差距還小。

所以誠實的結論不是「兩個一樣快」,而是「這個環境分辨不出它們的差別」。先量出雜訊有多大,這句話才有意義。(雜訊最可能的來源是溫度:GeForce 顯示卡沒辦法鎖定時脈。)

我其實沒量到我以為在量的東西

我的第二個錯誤,變成了最有意思的發現。

我第一版的文章把 TensorRT-LLM 描述成「預先編譯」的引擎。但我測的版本(1.2.0rc1)有兩種 backend:預設的 pytorch 並不會預先編譯,要預先編譯得用 --backend tensorrt。我在解釋一種機制,量的卻是另一種。

所以我兩種都量了,讓「預先編譯」成為唯一的差別:

啟動到可以接第一個請求
vLLM96 秒
TensorRT-LLM,pytorch backend115 秒
TensorRT-LLM,tensorrt backend1,225 秒(20 分鐘)

而且預先編譯的引擎並沒有比較快。同時請求數越多,它反而越慢:

同時請求數pytorchtensorrt差距
144.6 tok/s45.7 tok/s+2.4%
8302.1 tok/s279.7 tok/s−7.4%
32756.1 tok/s616.0 tok/s−18.5%

Batch 1 時預先編譯的版本快 2.4%,在雜訊範圍內;batch 32 時慢了 18.5%,遠遠超出雜訊。

我認為原因在排程,不在 kernel。pytorch backend 會把新進來的請求立刻加進正在跑的批次;為固定形狀預先編譯好的引擎,在執行時比較沒有彈性去重新安排工作。負載一高,排程比特化的 kernel 更重要。

後來,新版的 TensorRT-LLM 已經把 trtllm-serve 的 --backend 參數標成淘汰,tensorrt 也不再是它的選項。我的數字剛好說明了這個方向為什麼合理。

那該選哪個引擎?

在這張卡、這個模型上,引擎改變不了硬體已經定死的數字。選哪個是維運上的考量:啟動時間、部署難易度、改東西的速度。

我預期 TensorRT-LLM 只有在離開這個環境時才會拉開差距:

四個教訓

  1. 相信量測結果之前,先算。五分鐘的 roofline 計算,就抓到一個 20 倍的錯誤。
  2. 先量雜訊。比「同一個東西跑兩次的差異」還小的差距,不算差距。
  3. 確認你量的就是你在描述的東西。我在解釋預先編譯,量的卻是不做預先編譯的那個 backend。
  4. 一次只改一個變數。兩種 TensorRT-LLM backend 的比較之所以有意義,是因為其他條件全部相同。

程式碼、原始數據和圖表,一個指令就能全部重跑: github.com/wu840407/llm-inference-benchmark

測試環境:RTX 3090(24 GB、936 GB/s)、Qwen3-8B(BF16)、vLLM 0.27.1、TensorRT-LLM 1.2.0rc1、WSL2。每種設定先暖機三輪、再正式量五輪;每個引擎交錯跑兩次。