跳至主要內容
推論系統

降低每個 Token 的生成成本

以搬移的位元組數估出 decode 時間下限,再檢驗量化、推測式解碼與注意力 kernel 實際省下什麼。

作者
Huang Tzu LinFounder
發布日期
閱讀時間
約 41 分鐘
目錄
  1. 01本篇在學習路線中的位置
  2. 02開始之前:先備知識與學習成果
  3. 03你將建置的成品
  4. 04凍結模型與硬體
  5. 05為何 Decode 通常受記憶體頻寬限制
  6. 06權重量化
  7. 格式
  8. 為何離群值讓單純的 INT8 難以奏效
  9. GPTQ、AWQ 與 SmoothQuant
  10. 量化省下什麼,又省不下什麼
  11. 07完整範例:各格式的權重位元組與下限
  12. 08推測式解碼
  13. 先起草,再驗證
  14. 為何接受規則能保留目標分布
  15. 每次目標步驟的期望 Token 數
  16. 草稿成本與加速比
  17. 為何批次變大時效益縮小
  18. 變體:EAGLE 與 Medusa
  19. 推測式解碼對 ITL 的影響
  20. 09完整範例:一次推測式解碼試算
  21. 10注意力 Kernel
  22. FlashAttention:精確注意力,更少 HBM 往返
  23. FlashAttention-2 與 FlashAttention-3
  24. 為何 Kernel 對長 Prefill 最關鍵
  25. 11建立單一 Token 成本計算器
  26. 12刻意破壞的案例
  27. 「INT4 一定快 4 倍」
  28. 「推測式解碼會改變輸出」
  29. 「推測式解碼一定有幫助」
  30. 「FlashAttention 是近似注意力」
  31. 「量化對 Prefill 與 Decode 的加速一樣多」
  32. 13動手實作
  33. 14可量測的完成標準
  34. 15提取練習
  35. 16解答
  36. 17本文不涵蓋的內容
  37. 18來源與延伸閱讀

在小批次下,decode 步驟大部分時間都花在搬移位元組,而不是算術運算:為了替每條序列產生一個 token,它要從 GPU 記憶體讀出模型權重,以及每條活躍序列的 KV 快取。因此,降低單一 token 成本有三個著力點。量化讓每個權重少搬一些位元組,推測式解碼讓每讀過一輪權重就產出好幾個 token,FlashAttention 這類 IO 感知(IO-aware)的注意力 kernel 則減少注意力內部的記憶體流量。三者都有代價,也都會改變你該量測的項目。

本篇先讓單張 GPU 發揮效率,下一篇再把模型分散到多張 GPU。我們會為 700 億參數的範例模型推導 BF16、FP8 與 INT4 下 decode 時間的頻寬下限,用明確的接受率、草稿長度與草稿成本算出一個推測式解碼加速比,並指出每一種節省在什麼情況下不再適用。

互動實驗

估算單次 decode 步驟的成本下限

權重位元組變少能加快 decode,直到 KV 讀取或驗證運算成為瓶頸。

每個目標模型步驟的下限,ms

讀取權重46.667 ms
讀取權重與 KV53.825 ms
一般步驟的矩陣運算4.48 ms
驗證的矩陣運算 · 160 個位置22.4 ms
130.39 + 20 GiB權重加 KV · 超過單張 80 GiB GPU,只能當頻寬下限看
53.825 ms一般步驟下限
3.3616每個目標步驟產出的 token
2.40×相對一般 decode 的加速
這組設定下推測式解碼划算

驗證只讀取同一份權重與 KV 一次;其矩陣運算(22.4 ms)仍低於記憶體時間(53.825 ms),論文公式的 2.40 倍仍成立。

每次迭代 75.35 ms,產出 1 到 5 個 token;平均每個 token 22.42 ms,一般步驟則是 53.82 ms。chunk 間隔拉長、平均 TPOT 下降,兩者都要回報。

讀懂目前結果

步驟下限:記憶體與矩陣運算
53.825 / 4.480 ms
每個目標步驟產出的 token
3.3616
加速:論文公式 → 計入驗證成本
2.40× → 2.40×

量化只縮小權重項,KV 讀取仍是 BF16。驗證的 batch × (γ + 1) 個位置一旦讓矩陣運算時間超過記憶體時間,論文分母中的「+ 1」就會變大,加速隨之下降。這裡的 α 是假設的平均值,不是實測接受率。

本文假設的 70B 模型下限:HBM 3.0 TB/s、稠密 BF16 1,000 TFLOP/s、BF16 KV、草稿成本 c = 0.1。實際 kernel 只會比下限慢。

本篇如何銜接前後內容

本篇在學習路線中的位置

帶入前作:拆解一次 LLM 推論請求:請求時間軸與指標計算器、管理 KV 快取:KV 區塊容量計算表

本篇新增:單一 token 成本計算表。請將假設、來源 ID 與失敗案例連同成果一併留存。

後續用途:將模型分配到多張 GPU 與量測推論容量將沿用這些成果。完整閱讀路線依序列出所有學習路徑,並標出來自其他路徑的先備課程。

學習目標與實作準備

開始之前:先備知識與學習成果

請先完成拆解一次 LLM 推論請求與管理 KV 快取。你應該已經清楚 TTFT、TPOT 與 ITL 的差異,會計算 GQA 模型每個 token 的 KV 位元組數,也知道 decode 為何通常受記憶體頻寬限制,而 prefill 通常受運算能力限制。

讀完本篇後,你將能夠:

  1. 根據權重位元組、KV 位元組、峰值頻寬與峰值運算能力,算出 decode 步驟時間的下限,並標明這是下限而非實測值;

  2. 說明 FP8 E4M3 與 E5M2、INT8 與僅權重 INT4 的差異,活化值的離群值為何讓單純的 INT8 量化難以奏效,以及 GPTQ、AWQ 與 SmoothQuant 各自改變了什麼;

  3. 區分量化能省下的部分(權重記憶體與搬移的位元組),以及它不會自動帶來的效果(更快的 prefill、缺少 kernel 支援時的加速,以及維持不變的品質);

  4. 說明推測式解碼的接受規則為何能保留目標模型的分布,並根據 α、γ 與草稿成本,算出每次目標步驟的期望 token 數與加速比;

  5. 解釋批次變大時推測式解碼的效益為何縮小,以及推測式解碼如何改變 ITL;以及

  6. 說明 FlashAttention、FlashAttention-2 與 FlashAttention-3 各自改變了什麼,以及它們為何對長 prefill 最關鍵。

你將建置的成品

你會產出 token-cost.json,內容包含:

  • 範例模型、硬體假設,以及各格式的每權重位元數;

  • 各格式的權重位元組數與 GiB 數;

  • 各格式在批次 1 下、只計權重的頻寬下限;

  • 每條序列帶 2,048 個 token 的 KV 時,批次 32 的 decode 步驟下限;

  • 一組推測式解碼設定下,每次目標步驟的期望 token 數與加速比;以及

  • 一項驗證成本檢查,指出在批次 32 下推測式解碼何時不再近乎免費。

成品中的每個時間值,都是從峰值硬體規格推得的下限。它告訴你哪些設定值得做基準測試,而不是預告基準測試的結果。

凍結模型與硬體

模型沿用〈拆解一次 LLM 推論請求〉的 80 層 GQA 模型,並採用〈將模型分配到多張 GPU〉的 700 億參數、隱藏維度 8,192 與模型版本 dense-70b-fixture-r1,讓數字可以跨篇沿用:

輸入

假設

模型

密集型 decoder-only Transformer,700 億參數,版本 dense-70b-fixture-r1

層數/query head 數/KV head 數/head 維度

80/64/8(GQA)/128

KV 資料型別

BF16,每個 token 327,680 位元組

Decode 快照

32 條活躍序列,每條各有 2,048 個 token 的 KV

GPU 記憶體

80 GiB,其中 12 GiB 預留給執行期與 KV

HBM 峰值頻寬

3.0 TB/s(十進位)

密集 BF16 矩陣運算峰值吞吐量

1,000 TFLOP/s

一般 decode 步驟

25 ms,即〈拆解一次 LLM 推論請求〉的合成 TPOT

頻寬與運算兩列是假設性的整數值,不是任何產品規格。作為對照,NVIDIA 的 H100 SXM 產品頁列出 80 GB GPU 記憶體與 3.35 TB/s 記憶體頻寬;其 BF16 Tensor Core 數字 1,979 teraFLOPS 標註為「with sparsity」(啟用稀疏性),因此不是密集矩陣乘法的峰值(NVIDIA H100)。重複使用這份計算表之前,請把每一列都換成你自己規格表上的數值。

為何 Decode 通常受記憶體頻寬限制

每次前向傳播都受兩項成本限制。權重與 KV 快取存放在高頻寬記憶體(HBM),每次前向傳播都必須送到運算核心一次,Pope 等人稱之為記憶體時間(memory time);N 個參數的 decoder-only 模型每處理一個 token,還需要約 2N 次矩陣乘法 FLOPs,也就是每個參數一次乘法加一次加法(Pope et al., 2022)。兩者都完成之前,步驟不可能結束,所以兩者中較大的那個就是下限:

plaintext
memory_time  = (weight_bytes + batch × KV_bytes_per_sequence) / HBM_bandwidth
compute_time = 2 × parameters × positions_in_step / peak_FLOPs
step_time   >= max(memory_time, compute_time)

Pope 等人指出,對大型模型而言,注意力的矩陣乘法增加的 FLOPs 相對較少,通常可以不計入;但就記憶體而言,注意力仍然重要,因為每條序列都有自己的 KV 快取。在批次 1、BF16 下,光是權重就決定了下限:

plaintext
weight bytes   = 70,000,000,000 × 2 = 140,000,000,000 bytes
memory_time    = 140 GB / 3.0 TB/s = 46.667 ms
compute_time   = 2 × 70e9 × 1 / 1,000e12 = 0.140 ms
intensity      = 140 GFLOP / 140 GB = 1 FLOP/byte
hardware ratio = 1,000 TFLOP/s / 3.0 TB/s ≈ 333 FLOP/byte

運算單元每收到一個位元組,需要約 333 FLOPs 才能保持忙碌;批次 1 的 decode 只提供 1。批次處理能把權重位元組分攤到多條序列,卻分攤不了 KV 位元組,因為每條序列都帶著自己的快取。在 32 條序列的快照下:

plaintext
KV per sequence = 327,680 × 2,048 = 671,088,640 bytes
memory_time     = (140e9 + 32 × 671,088,640) / 3.0e12 = 53.825 ms
compute_time    = 2 × 70e9 × 32 / 1,000e12 = 4.480 ms

這個步驟仍然受記憶體頻寬限制,記憶體時間約為運算時間的 12 倍。每多一條 2,048-token 的序列,就多出 0.224 ms 的 KV 讀取,矩陣乘法時間卻只多 0.140 ms,所以在這個範例中,單靠加大批次永遠不會讓一般 decode 步驟變成受運算能力限制。Pope 等人也描述了同樣的轉變:小批次、短序列時,記憶體時間以載入權重為主;批次與序列變大後,則以載入 KV 為主(Pope et al., 2022)。

這些是下限,不是實測值。Chen 等人直接寫出只計權重的版本:模型大小除以所有晶片的總記憶體頻寬,就是自迴歸取樣速度的硬性上限(Chen et al., 2023)。這個下限假設每個位元組都以峰值頻寬搬移,關鍵路徑上也沒有其他工作,因此實測的步驟只會更慢。

權重量化

格式

量化以較少的位元儲存每個數值,再搭配縮放因子(scale),把低精度數值對應回原本的範圍。

  • FP8 有兩種編碼。E4M3(4 個指數位元、3 個尾數位元)的最大正規數值為 448,建議用於權重與活化值;E5M2 可達 57,344,建議用於梯度。數值在轉型前先乘上縮放因子,才能放進這麼窄的範圍(Micikevicius et al., 2022)。

  • INT8 以 8 位元整數儲存,縮放因子可以按張量、按通道或按向量設定;Micikevicius 等人指出,權重按通道、活化值按張量設定縮放因子,是 INT8 常見的做法。權重與活化值都採 INT8(W8A8)時,SmoothQuant 的作者指出,矩陣乘法這時可以改用 INT8 kernel,相較於 FP16,可讓 GPU 記憶體用量減半、矩陣乘法吞吐量接近加倍;他們自己量到的端對端結果是最高 1.56 倍加速,記憶體用量減為一半(Xiao et al., 2023)。

  • 僅權重 INT4(W4A16)把權重存成 4 位元整數,每一組共用一個縮放因子;AWQ 全篇論文都採用 128 的分組大小。由於硬體沒有 INT4 × FP16 的乘法指令,kernel 會先把整數反量化為 FP16 再做矩陣運算,因此算術仍是 16 位元(Lin et al., 2024)。

為何離群值讓單純的 INT8 難以奏效

整個張量共用一個縮放因子時,縮放因子由必須表示的最大絕對值決定。Dettmers 等人發現,模型規模到約 67 億參數時,隱藏狀態中會出現數值大小最多達其他維度 20 倍的特徵,遍及所有 transformer 層,但在這個規模下只集中在 6 個特徵維度,一般的 8 位元方法也隨之失效。LLM.int8() 讓這些離群維度以 16 位元相乘,其餘超過 99.9% 的數值仍以 8 位元相乘,藉此維持 16 位元的準確度(Dettmers et al., 2022)。想看出問題所在,可以拿有 127 個正值刻度的對稱 INT8 格點為例:如果由一個大小為典型值 20 倍的特徵決定縮放因子,典型值只能落在約 127 / 20 ≈ 6 個刻度上。SmoothQuant 的作者這樣概括其中的不對稱:權重容易量化,活化值不容易(Xiao et al., 2023)。

GPTQ、AWQ 與 SmoothQuant

  • GPTQ 是一次性(one-shot)的訓練後權重量化方法,利用近似的二階資訊逐層選擇量化值。在作者的評估中,它能把權重壓到 3 或 4 位元,準確度損失可以忽略,量化 1,750 億參數的模型約需四個 GPU 小時(Frantar et al., 2023)。

  • AWQ 觀察到,只要保護約 1% 的顯著權重,就能大幅降低量化誤差,而且顯著通道應該依活化值的分布找出,而不是看權重大小。它在量化前以等價轉換放大這些通道,不需要反向傳播或重建(Lin et al., 2024)。

  • SmoothQuant 以 W8A8 為目標。它在離線階段把活化值除以按通道設定的平滑因子,再把這個因子併入權重;這是數學上等價的轉換,把量化難度從活化值轉移到權重,讓兩者都能使用 INT8 矩陣乘法(Xiao et al., 2023)。

量化省下什麼,又省不下什麼

低精度權重能減少一個副本所需的記憶體,以及每個 decode 步驟讀取的位元組,而後者正是上述下限中的主導項。GPTQ 的 decode kernel 說明了這個機制:它以量化矩陣乘上全精度向量,在運算當下即時反量化權重,不必量化活化值。反量化會多花一些運算,但 kernel 讀取的記憶體少得多,作者也把幾乎所有加速都歸功於這些 kernel(Frantar et al., 2023)。

量化不會自動省下的部分:

  • Prefill 運算。僅權重格式會在矩陣乘法前反量化為 16 位元,因此受運算能力限制的 prefill 仍在做 16 位元算術。SmoothQuant 的作者指出,僅權重方法靠減少資料載入來加快生成,在批次 1 時尤其明顯,W8A8 則可能更適合批次處理與上下文階段(Xiao et al., 2023);Pope 等人同樣指出,int8 權重能縮短載入權重的時間,在小批次時尤其有用(Pope et al., 2022)。

  • Kernel 支援。截至 2026 年 11 月,vLLM 的 FP8 W8A8 指南說明,FP8 運算需要 NVIDIA compute capability 8.9 以上(Ada Lovelace、Hopper、Blackwell),Turing 與 Ampere 則透過 Marlin kernel,以僅權重的 W8A16 方式執行 FP8 檢查點(vLLM FP8 W8A8);vLLM 的量化總覽頁另有按硬體列出的相容性表(vLLM Quantization)。Dettmers 等人也指出,對 67 億參數以下的模型,LLM.int8() 的額外開銷可能讓推論比 FP16 基準更慢(Dettmers et al., 2022)。

  • KV 位元組。權重量化不會改變 KV 快取本身的資料型別。KV 快取量化是另一項決策,有自己的準確度風險,管理 KV 快取已經討論過。

  • 品質。每一種格式、分組大小與校準資料集,都會改變模型的數值行為。在採信已發表的準確度表格之前,請依照評估 AI 系統的做法,用你自己有版本編號的黃金測試集與迴歸閘門重新量測任務品質;vLLM 的 FP8 指南也把準確度評估列為量化流程的最後一步。

有一項已發表的結果,正好說明引用時必須一併保留哪些條件。GPTQ 在批次大小 1、生成 128-token 序列的條件下,量測 OPT-175B 每個 token 的平均延遲:3 位元模型在一張 A100 80GB 上為 71 ms,FP16 模型在五張 A100 80GB 上為 230 ms,延遲相差 3.24 倍(Frantar et al., 2023)。這項比較同時改變了精度與 GPU 數量,而且只涵蓋批次 1 的 decode。

完整範例:各格式的權重位元組與下限

GPTQ 估計分組大小為 128 時,每個權重約多出 0.15 位元(光是每 128 個權重配一個 16 位元縮放因子,就多 0.125 位元),所以計算表以每權重 4.15 位元計算 INT4。FP8 若每列 8,192 個權重配一個 32 位元縮放因子,每個權重增加不到 0.004 位元,計算表予以忽略。這裡假設全部 700 億個參數都經過量化;如果檢查點把嵌入層之類的層保留為較高精度,請按它們實際的位元寬度計算。

plaintext
BF16      70e9 × 16   / 8 = 140.0000 GB = 130.39 GiB
FP8 E4M3  70e9 × 8    / 8 =  70.0000 GB =  65.19 GiB
INT4 g128 70e9 × 4.15 / 8 =  36.3125 GB =  33.82 GiB

batch-1 bound  = weight_bytes / 3.0e12
batch-32 bound = (weight_bytes + 32 × 671,088,640) / 3.0e12

格式

權重能否放進 68 GiB 可用空間?

批次 1 權重下限

批次 32 步驟下限

批次 32 相對 BF16 的改善

BF16

否

46.667 ms

53.825 ms

1.00×

FP8 E4M3

是

23.333 ms

30.492 ms

1.77×

INT4,分組 128

是

12.104 ms

19.262 ms

2.79×

有三點值得注意。第一,光是 BF16 權重(130.39 GiB)就超過一張 80 GiB 的 GPU,這也是下一篇要把權重切分到多張 GPU 的原因;FP8(65.19 GiB)放得進扣除 12 GiB 預留後剩下的 68 GiB,在計入量化中繼資料與活化值之前約有 2.8 GiB 餘裕。這一欄只計權重。批次 32 的快照還需要 32 × 640 MiB = 20 GiB 的 KV,超過 12 GiB 的預留空間,所以在一張 80 GiB 的 GPU 上只有 INT4 放得下(33.82 + 20 = 53.82 GiB);BF16 與 FP8 的批次 32 列應視為每一步的頻寬下限,而不是單張 GPU 的可行配置。第二,從 BF16 到 INT4 的位元比是 16 / 4.15 = 3.86×,批次 32 的下限卻只改善 2.79 倍,因為 21.47 GB 的 BF16 KV 讀取並沒有縮小。第三,若只計權重,要在批次 1 下以 FP8 達到 25 ms 的合成 TPOT,必須持續維持峰值頻寬的 23.333 / 25 = 93%;實際 kernel 能否做到,要靠量測回答。

推測式解碼

先起草,再驗證

推測式解碼讓目標模型搭配一個較便宜的草稿模型。草稿模型以自迴歸方式提出 γ 個 token,目標模型在一次平行前向傳播中替它們全部評分,接受其中一段前綴,並一定再補上一個自己產生的 token,因此每次目標前向傳播會產出 1 到 γ + 1 個 token(Leviathan et al., 2023)。這樣做划算,是因為 decode 受記憶體頻寬限制時,多評分幾個位置,權重與 KV 快取一樣只讀一次。Chen 等人逐一分析各元件:草稿很短時,線性層仍受記憶體頻寬限制;KV 快取不隨草稿長度增加;all-reduce 仍受延遲限制(Chen et al., 2023)。

為何接受規則能保留目標分布

以 p(x) 表示目標模型的下一個 token 分布,q(x) 表示草稿模型的分布,兩者都已套用 temperature、top-k 或 nucleus(top-p)取樣調整。對於從 q 抽出的草稿 token x:

plaintext
accept x with probability min(1, p(x) / q(x))
on rejection, sample from p'(x) = max(0, p(x) - q(x)) / Σ max(0, p(x) - q(x))

重新推導輸出為何服從 p。起草出 x 並接受它的機率是 q(x) × min(1, p(x) / q(x)) = min(p(x), q(x))。對所有 x 加總,接受的機率是 β = Σ min(p, q),所以拒絕的機率是 1 - β。由於 Σ max(0, p - q) = Σ (p - min(p, q)) = 1 - β,先拒絕再抽到 x 的機率是 (1 - β) × max(0, p(x) - q(x)) / (1 - β) = p(x) - min(p(x), q(x))。兩條路徑相加恰好是 p(x)。Leviathan 等人以 p 表示目標、q 表示草稿,證明了相同的結果;Chen 等人用的是同一套方法,只是兩個字母對調(Leviathan et al., 2023;Chen et al., 2023)。

分布相同不代表 token 相同。Chen 等人指出,數值計算與亂數種子處理的差異,意味著輸出不會完全一致,他們主張的是「在硬體數值精度範圍內」恢復目標分布(Chen et al., 2023)。截至 2026 年 11 月,vLLM 的指南稱其推測式取樣「在硬體數值的精度限制內,理論上無損」,並提醒批次大小的變化可能讓 logprobs 與輸出機率出現差異(vLLM Speculative Decoding)。

每次目標步驟的期望 Token 數

Leviathan 等人把 α 定義為目標模型接受一個草稿 token 的期望機率。假設每次接受彼此獨立且同分布,每次目標前向傳播產出的 token 數就服從截斷的幾何分布:

plaintext
E[tokens per target step] = (1 - α^(γ+1)) / (1 - α)

重新推導:每次前向傳播一定產出一個 token;目標模型接受第一個草稿 token 時(機率 α)產出第二個;前兩個都接受時(α²)產出第三個;依此類推,最多 γ + 1 個。期望值是 1 + α + α² + ... + α^γ,這個等比級數的和就是上式。

草稿成本與加速比

令 c 為草稿模型執行一次的時間除以目標模型執行一次的時間。一輪迭代的成本是 γc + 1 個目標步驟單位,論文給出的期望實際耗時改善為:

plaintext
speedup = (1 - α^(γ+1)) / ((1 - α) × (γc + 1))

這個定理建立在作者明言的假設上:γ + 1 次目標模型評估可以平行執行,而不增加實際耗時。只要目標步驟受記憶體頻寬限制、還有運算餘裕,這個假設就成立。論文也指出,這個結果假設生成長度夠長(Leviathan et al., 2023)。

為何批次變大時效益縮小

批次變大時,驗證前向傳播要處理批次 × (γ + 1) 個位置,運算餘裕隨之耗盡。EAGLE 的作者描述了這個機制,也實際量測:在 MT-bench、temperature 0 的條件下,他們回報 LLaMA2-Chat 70B 相對一般自迴歸解碼的加速比,從批次大小 1 的 3.01 倍,降到批次大小 2、3、4 的 2.81 倍、2.50 倍與 2.40 倍,同一節也說明 70B 的吞吐量測試是在四張 A100 40GB GPU 上以 FP16 執行(Li et al., 2024)。截至 2026 年 11 月,vLLM 的指南把推測式解碼定位為在中低 QPS、受記憶體頻寬限制的工作負載下降低 token 間延遲的方法(vLLM Speculative Decoding);TurboSpec 的作者在 vLLM 上實作時也觀察到,如果沒有針對實際流入的請求與推測方法調校就直接加入,推測式解碼可能降低推論服務效能(Liu et al., 2024)。

變體:EAGLE 與 Medusa

  • EAGLE 在特徵層級起草:一個小型 head 以目標模型倒數第二層的特徵做自迴歸,並輸入提前一步的 token 序列,再由目標模型以樹狀注意力(tree attention)驗證草稿樹,同時保留其輸出分布(Li et al., 2024)。

  • Medusa 在目標模型上加裝額外的解碼 head,以樹狀注意力一次驗證多個候選延續。Medusa-1 在凍結的骨幹模型上訓練這些 head,可做到無損加速;Medusa-2 連骨幹一起微調,等於改變了模型;可選用的典型接受(typical acceptance)機制則刻意不再與原始分布一致(Cai et al., 2024)。

推測式解碼對 ITL 的影響

現在 token 會一次抵達好幾個。每輪迭代最多產出 γ + 1 個 token,因此以 chunk 計的 ITL 會跟著迭代時間走,而迭代時間比一般 decode 步驟長;以 token 計的 TPOT 則會下降。量測推論容量說明了 chunk 間隔與每 token 平均值為何使用不同的分母,也把接受率與接受長度列為啟用推測式解碼時應記錄的訊號。絕對不要把啟用推測式解碼時量到的 chunk ITL 與一般 decode 的 ITL 當成同一種量測來比較。

完整範例:一次推測式解碼試算

取 α = 0.8、γ = 4、c = 0.1,也就是一次草稿步驟的成本是目標步驟的十分之一。這些是教學用的數值,不是任何模型組合的實測值。

plaintext
E[tokens/step] = (1 - 0.8^5) / (1 - 0.8)
               = (1 - 0.32768) / 0.2
               = 3.3616 tokens per target step

speedup        = 3.3616 / (4 × 0.1 + 1)
               = 3.3616 / 1.4
               = 2.4011×

套用到〈拆解一次 LLM 推論請求〉中旅行 Copilot 的京都飯店請求,該篇合成軌跡中的一般 decode 步驟為 25 ms:

plaintext
iteration time = 25 × (4 × 0.1 + 1) = 35 ms
average TPOT   = 25 / 2.4011 = 10.41 ms/token
149 post-first-token outputs ≈ 149 × 10.41 ≈ 1,551 ms (versus 3,725 ms)

平均值改善 2.40 倍,但串流 chunk 之間的間隔從 25 ms 拉長到 35 ms,每個 chunk 帶 1 到 5 個 token。α 是平均值,獨立性假設也只是建模上的簡化,所以敏感度比單一的代表數字更重要:α = 0.6 時,同樣的 γ 與 c 只得到每步 2.3056 個 token、1.65 倍;草稿較慢、c = 0.5 時,α = 0.8 只有 1.12 倍;α = 0.2 時,推測式解碼比一般 decode 還慢,只有 0.89 倍。

接著用下限模型檢驗「驗證免費」的假設。在 32 條序列的快照下,驗證要處理 32 × 5 = 160 個位置,運算時間為 2 × 70e9 × 160 / 1,000e12 = 22.4 ms。BF16(53.825 ms)與 FP8(30.492 ms)下,記憶體時間仍占主導,驗證依然近乎免費。INT4 下記憶體時間只有 19.262 ms,驗證因此變成受運算能力限制,需要 22.4 ms,約為一般步驟的 1.163 倍;把分母中的「+ 1」換成這個比例,加速比就從 2.40 倍降為 3.3616 / (0.4 + 1.163) = 2.15×。這是我們在 c 維持 0.1 的前提下對論文模型所做的延伸,不是論文本身的結果,但它點出一個真實的交互作用:權重位元組減少後,推測式解碼不再免費的批次大小也跟著降低。

注意力 Kernel

FlashAttention:精確注意力,更少 HBM 往返

標準注意力會把 N × N 的分數矩陣與機率矩陣寫入 HBM,再讀回來。FlashAttention 是 IO 感知的精確注意力演算法:它以分塊(tiling)方式把 Q、K、V 切成放得進晶片上 SRAM 的區塊,跨區塊逐步計算 softmax,並把各步驟融合成單一 kernel,讓 N × N 矩陣完全不必進出 HBM。論文證明其 HBM 存取次數為 O(N²d²/M),其中 d 為 head 維度、M 為 SRAM 大小,標準注意力則為 Ω(Nd + N²),並顯示在常見數值下存取次數最多只有標準注意力的 1/9(Dao et al., 2022)。同一篇論文中,只有區塊稀疏(block-sparse)延伸版本屬於近似計算。

FlashAttention-2 與 FlashAttention-3

FlashAttention-2 保留原本的演算法,重新分配工作:減少非矩陣乘法的 FLOPs,即使只有單一 head 也沿序列長度平行化,並改善 warp 之間的工作分配,相較 FlashAttention 約快 2 倍,在 A100 上達到理論最大 FLOPs/s 的 50%–73%(Dao, 2023)。FlashAttention-3 針對 Hopper:以 warp 特化(warp specialization)讓運算與資料搬移重疊,交錯執行區塊式矩陣乘法與 softmax,並以區塊量化搭配非相干處理(incoherent processing)讓 FP8 堪用。在 H100 80GB SXM5、FP16 輸入、序列長度 512 到 16k、每次執行總計 16k 個 token 的條件下,作者回報前向傳播比 FlashAttention-2 快 1.5–2.0 倍,最高 740 TFLOPs/s;FP8 路徑接近 1.2 PFLOPs/s,在作者含離群特徵的測試中,數值誤差只有逐張量量化標準 FP8 注意力的 1/2.6(Shah et al., 2024)。

為何 Kernel 對長 Prefill 最關鍵

分數矩陣中,每一組 query 位置與 key 位置的配對都對應一個元素。prefill 一次處理所有提示位置,所以若不分塊,32,768 個 token 的提示在每個 head 上都要建出 32,768 × 32,768 的分數矩陣,以 BF16 計為 2 GiB;decode 每條序列只有一個 query,分數列的長度就只等於上下文長度。這就是 FlashAttention 的節省主要出現在長 prefill 的原因。decode 的注意力則以讀取 KV 快取為主,Chen 等人指出,取樣時注意力機制的記憶體頻寬用量大多來自 KV 快取(Chen et al., 2023)。Flash-Decoding 的作者補充,FlashAttention 沿批次大小與 query 長度平行化,所以在批次大小 1、只有一個 query 時,用到的 GPU 不到 1%;Flash-Decoding 則把 key 與 value 切成多個小塊平行處理(Dao et al., 2023)。FlashAttention-3 的論文把針對 LLM 推論的最佳化列為未來工作(Shah et al., 2024)。請依工作負載中占主導的階段選擇 kernel,並針對推論引擎實際選用的注意力後端做基準測試。

建立單一 Token 成本計算器

存成 token_cost.py,再執行 python token_cost.py > token-cost.json:

python
import json

PARAMETERS = 70_000_000_000
LAYERS, KV_HEADS, HEAD_DIM, KV_BYTES_PER_ELEMENT = 80, 8, 128, 2
HBM_BYTES_PER_SECOND = 3.0e12
PEAK_DENSE_FLOPS = 1.0e15
CONTEXT_TOKENS, BATCH = 2048, 32
BITS_PER_WEIGHT = {"bf16": 16.0, "fp8_e4m3": 8.0, "int4_g128": 4.15}
ALPHA, GAMMA, DRAFT_COST = 0.8, 4, 0.1
PLAIN_STEP_MS = 25.0

if not (0 <= ALPHA < 1 and GAMMA >= 1 and DRAFT_COST >= 0 and BATCH >= 1):
    raise ValueError("invalid speculation or batch setting")

kv_bytes_per_sequence = (2 * LAYERS * KV_HEADS * HEAD_DIM
                         * KV_BYTES_PER_ELEMENT * CONTEXT_TOKENS)

def step_bound_ms(weight_bytes, batch, positions_per_sequence):
    memory = (weight_bytes + batch * kv_bytes_per_sequence) / HBM_BYTES_PER_SECOND
    compute = 2 * PARAMETERS * batch * positions_per_sequence / PEAK_DENSE_FLOPS
    return 1000 * max(memory, compute)

expected_tokens = (1 - ALPHA ** (GAMMA + 1)) / (1 - ALPHA)
speedup = expected_tokens / (GAMMA * DRAFT_COST + 1)
formats = {}
for name, bits in BITS_PER_WEIGHT.items():
    weight_bytes = PARAMETERS * bits / 8
    plain_ms = step_bound_ms(weight_bytes, BATCH, 1)
    verify_ratio = step_bound_ms(weight_bytes, BATCH, GAMMA + 1) / plain_ms
    formats[name] = {
        "bits_per_weight": bits,
        "weight_gb": weight_bytes / 1e9,
        "weight_gib": weight_bytes / 2**30,
        "batch1_weight_bound_ms": 1000 * weight_bytes / HBM_BYTES_PER_SECOND,
        "batch_step_bound_ms": plain_ms,
        "verify_cost_ratio": verify_ratio,
        "speculative_speedup_at_batch": expected_tokens / (GAMMA * DRAFT_COST + verify_ratio),
    }

artifact = {
    "scope": "lower bounds from peak figures; not measurements",
    "parameters": PARAMETERS,
    "hbm_bytes_per_second": HBM_BYTES_PER_SECOND,
    "peak_dense_flops": PEAK_DENSE_FLOPS,
    "batch": BATCH,
    "context_tokens": CONTEXT_TOKENS,
    "kv_bytes_per_sequence": kv_bytes_per_sequence,
    "formats": formats,
    "speculation": {
        "alpha": ALPHA,
        "gamma": GAMMA,
        "draft_cost": DRAFT_COST,
        "expected_tokens_per_target_step": expected_tokens,
        "speedup_free_verification": speedup,
        "iteration_ms": PLAIN_STEP_MS * (GAMMA * DRAFT_COST + 1),
        "average_tpot_ms": PLAIN_STEP_MS / speedup,
    },
}
print(json.dumps(artifact, indent=2))

預期的核心數值:

plaintext
kv_bytes_per_sequence = 671088640
bf16:      weight_gb = 140.0,   batch1 ≈ 46.667 ms, batch32 ≈ 53.825 ms, verify_cost_ratio = 1.0
fp8_e4m3:  weight_gb = 70.0,    batch1 ≈ 23.333 ms, batch32 ≈ 30.492 ms, verify_cost_ratio = 1.0
int4_g128: weight_gb = 36.3125, batch1 ≈ 12.104 ms, batch32 ≈ 19.262 ms, verify_cost_ratio ≈ 1.1629
expected_tokens_per_target_step = 3.3616
speedup_free_verification ≈ 2.4011
int4_g128 speculative_speedup_at_batch ≈ 2.1509
iteration_ms = 35.0
average_tpot_ms ≈ 10.4117

刻意破壞的案例

「INT4 一定快 4 倍」

4 位元對 16 位元的 4 倍比例,只適用於原始權重資料量。分組縮放因子讓 INT4 增加到約 4.15 位元(3.86 倍),批次 32 快照中 21.47 GB 的 BF16 KV 讀取並不會縮小(下限只改善 2.79 倍),而且下限也不是實測值。僅權重 INT4 下的 prefill 仍在跑 16 位元矩陣乘法,而在你的 GPU 上缺少快速 kernel 的格式,可能比 BF16 還慢。

「推測式解碼會改變輸出」

在拒絕取樣規則下,輸出分布在硬體數值精度範圍內與目標模型相同,前面已經重新推導過;不同的是你抽到哪一個樣本,因為亂數種子與數值計算不同。這個說法只對有損的變體成立:Medusa 的典型接受、像 Medusa-2 那樣微調過的骨幹,或 vLLM 的 FLy 驗證,vLLM 指南已標明後者不保留分布(vLLM Speculative Decoding)。

「推測式解碼一定有幫助」

在 α = 0.2、γ = 4、c = 0.1 時,每次目標步驟平均只產出 1.2496 個 token,迭代成本卻是 1.4 個目標步驟,所以推測式解碼的速度只有一般 decode 的 0.89 倍。在這組 γ 與 c 下,損益兩平點約在 α ≈ 0.287;批次變大時,「+ 1」的驗證成本也會增加,INT4 批次 32 的檢查已經顯示這一點。

「FlashAttention 是近似注意力」

FlashAttention 計算的是精確注意力,它靠避免 N × N 矩陣在 HBM 來回搬移來節省時間;只有同一篇論文中的區塊稀疏變體才是近似計算(Dao et al., 2022)。除了加總順序不同造成的微小浮點捨入差異,如果換了 kernel 之後品質改變,原因應該是精度路徑不同(例如 FP8 注意力)或程式錯誤,而不是分塊本身。

「量化對 Prefill 與 Decode 的加速一樣多」

小批次 decode 受位元組數限制,權重位元組變少就直接有幫助。prefill 通常受運算能力限制,而僅權重格式會在矩陣乘法前反量化為 16 位元,所以 prefill 的算術量不變,還多了反量化的工作。只有矩陣乘法本身以較低精度執行的格式,例如在支援的硬體上使用 FP8 或 INT8 W8A8,才可能減少 prefill 的運算,而且仍需要做品質檢查。

動手實作

擴充計算器,完成以下項目:

  1. 在 γ = 4、c = 0.1 下,讓 α 依序取 0.3、0.6、0.8 與 0.9;

  2. 在 α = 0.8、c = 0.1 下,找出 1 到 8 之間讓加速比最大的 γ;

  3. 以較慢的草稿模型(c = 0.5),重新計算 α = 0.8、γ = 4 的情況;

  4. 針對每種格式,找出在 2,048-token 上下文下,每條序列驗證 γ + 1 = 5 個位置時,讓下限變成受運算能力限制的最小批次;以及

  5. 以理想化的 1 位元組 FP8 KV(忽略 KV 縮放因子),重新計算 INT4 批次 32 的步驟下限。

推測式解碼各情況的參考值(皆假設驗證免費):

情況

每步期望 token 數

加速比

α 0.3、γ 4、c 0.1

1.4251

1.0179×

α 0.6、γ 4、c 0.1

2.3056

1.6469×

α 0.8、γ 4、c 0.1

3.3616

2.4011×

α 0.9、γ 4、c 0.1

4.0951

2.9251×

α 0.8、γ 6、c 0.1(最佳 γ)

3.9514

2.4696×

α 0.8、γ 4、c 0.5

3.3616

1.1205×

BF16 在批次 98、FP8 在批次 49、INT4 在批次 26 時,驗證會變成受運算能力限制。改用 1 位元組 KV 後,INT4 批次 32 的步驟下限從 19.262 ms 降到 15.683 ms。請把這些記錄為下限模型的輸出,再寫出用來檢驗它們的基準測試計畫:以你自己的提示量得的接受率、在你的硬體上量得的草稿成本,以及實際批次大小下的 chunk ITL、TPOT 與有效吞吐量(goodput)。

可量測的完成標準

計算表符合以下條件才算通過:

  • 模型、參數量、每權重位元數、KV 資料型別、批次大小、上下文長度、峰值頻寬與峰值運算能力都已明列,硬體數字標明為假設值或引用規格表;

  • BF16、FP8 與 4.15 位元 INT4 的權重位元組分別為 140.0、70.0 與 36.3125 GB;

  • 批次 1 權重下限分別為 46.667、23.333 與 12.104 ms,批次 32 步驟下限分別為 53.825、30.492 與 19.262 ms;

  • 每個時間值都標明為下限,而非實測值;

  • INT4 在批次 32 的改善記為下限改善 2.79 倍,而不是 4 倍;

  • α = 0.8、γ = 4、c = 0.1 得出每次目標步驟 3.3616 個期望 token 與 2.4011 倍加速比,並寫明獨立性與驗證免費兩項假設;

  • INT4 批次 32 的驗證比例(1.163)與調整後加速比(2.15 倍)標明為對論文模型的延伸;

  • 推測式解碼的執行結果分別回報接受率、chunk ITL 與 TPOT;

  • 每一種量化設定都附上你自己評估集的品質結果;以及

  • 每個引用的論文加速數字,都寫明其模型、硬體、工作負載、比較基準與指標。

提取練習

  1. 在範例模型上,批次 1 的 BF16 decode 步驟為何至少要 46.667 ms,即使它的矩陣乘法在峰值下只需 0.140 ms?

  2. 批次處理為何能分攤權重讀取,卻分攤不了 KV 讀取?

  3. 活化值的離群值為何讓按張量設定的 INT8 難以奏效?SmoothQuant 又如何因應?

  4. 僅權重 INT4 為何對 decode 的幫助大於 prefill?

  5. 證明推測式解碼的接受規則產出 token x 的機率是 p(x)。

  6. 推測式解碼為何能降低 TPOT,同時拉長 chunk ITL?它的效益為何在大批次時縮小?

  7. FlashAttention 省下了什麼?為何這對 32k-token 的 prefill 比對 decode 更重要?

解答

  1. 每個步驟都必須從 HBM 讀出全部 140 GB 的 BF16 權重,在峰值下需要 140 GB / 3.0 TB/s = 46.667 ms,而 2 × 70e9 次 FLOPs 在 1,000 TFLOP/s 下只需 0.140 ms;這個步驟每個位元組只提供 1 FLOP,硬體比例卻約為 333。

  2. 同一批次中的所有序列共用同一份權重,每個步驟只讀一次;但每條序列的 KV 快取各自獨立,必須分別讀取。在 2,048 個 token 下,每條序列每步多出 671,088,640 位元組,也就是 0.224 ms。

  3. 整個張量共用的縮放因子由最大絕對值決定,所以一個大小為典型值 20 倍的特徵,會讓典型值只剩下 INT8 的 127 個正值刻度中約 6 個。SmoothQuant 把活化值除以按通道設定的平滑因子,再把因子併入權重,以等價轉換讓兩者都能量化為 INT8。

  4. 小批次 decode 受搬移的位元組數限制,而僅權重 INT4 正好減少這部分;prefill 通常受運算能力限制,W4A16 kernel 又會在矩陣乘法前把權重反量化為 16 位元,所以算術量不會縮小。

  5. 起草出 x 並接受的機率是 q(x) × min(1, p(x) / q(x)) = min(p(x), q(x));拒絕的機率是 1 - β,其中 β = Σ min(p, q);接著從殘差分布抽樣,貢獻 p(x) - min(p(x), q(x));兩條路徑相加為 p(x)。

  6. 每輪迭代的成本是 γc + 1 個一般步驟(範例中為 35 ms),平均卻產出 3.3616 個 token,因此 TPOT 降到 10.41 ms,串流 chunk 之間的間隔則拉長到 35 ms。批次變大時,批次 × (γ + 1) 個驗證位置會耗盡公式所假設的運算餘裕,驗證成本因此超過一個一般步驟,加速比隨之縮小。

  7. 它以分塊方式在晶片上 SRAM 中計算,避免把 N × N 分數矩陣寫入 HBM 再讀回,同時仍計算精確注意力。32k-token 的 prefill 若不這麼做,每個 head 都要建出 32,768 × 32,768 的矩陣(BF16 下為 2 GiB);decode 每條序列只有一列 query,主要成本反而是讀取 KV。

本文不涵蓋的內容

將模型分配到多張 GPU 會切分單張 GPU 放不下的 BF16 模型,並加入張量平行化在每個步驟帶來的集體通訊成本。〈量測推論容量〉在負載下量測 TTFT、ITL、TPOT 與有效吞吐量,本篇的每個下限都要在那裡驗證。KV 快取量化屬於〈管理 KV 快取〉,品質閘門屬於〈評估 AI 系統〉。剪枝、蒸餾與混合專家模型的稀疏化不在本篇範圍內。


來源與延伸閱讀

  • Pope, R., Douglas, S., Chowdhery, A., et al. "Efficiently Scaling Transformer Inference." arXiv:2211.05102, 2022。記憶體時間與運算時間成本模型、每個 token 約 2N 次矩陣乘法 FLOPs 的近似,以及 KV 載入隨批次大小增加的第一手論文。arxiv.org/abs/2211.05102

  • NVIDIA. "NVIDIA H100 Tensor Core GPU." 產品頁面,查閱日期 2026-11-03。記憶體容量、記憶體頻寬與標註稀疏運算之 Tensor Core 數字的第一方資料。nvidia.com

  • Micikevicius, P., Stosic, D., Burgess, N., et al. "FP8 Formats for Deep Learning." arXiv:2209.05433, 2022。E4M3 與 E5M2 編碼、其數值範圍與建議用途,以及縮放因子的第一手論文。arxiv.org/abs/2209.05433

  • Dettmers, T., Lewis, M., Belkada, Y., and Zettlemoyer, L. "LLM.int8(): 8-bit Matrix Multiplication for Transformers at Scale." NeurIPS, 2022。湧現的活化值離群值與混合精度分解的第一手論文。arxiv.org/abs/2208.07339

  • Frantar, E., Ashkboos, S., Hoefler, T., and Alistarh, D. "GPTQ: Accurate Post-Training Quantization for Generative Pre-trained Transformers." ICLR, 2023。一次性二階權重量化、分組大小額外開銷與批次 1 decode kernel 結果的第一手論文。arxiv.org/abs/2210.17323

  • Lin, J., Tang, J., Tang, H., et al. "AWQ: Activation-aware Weight Quantization for LLM Compression and Acceleration." MLSys, 2024。依活化值找出顯著通道並加以放大、分組大小 128 與 W4A16 反量化的第一手論文。arxiv.org/abs/2306.00978

  • Xiao, G., Lin, J., Seznec, M., et al. "SmoothQuant: Accurate and Efficient Post-Training Quantization for Large Language Models." ICML, 2023。W8A8 平滑轉換,以及僅權重與 W8A8 量化取捨的第一手論文。arxiv.org/abs/2211.10438

  • vLLM Project. "Quantization." 現行文件,查閱日期 2026-11-03。支援的量化方法清單與按硬體列出之相容性表的官方說明。docs.vllm.ai

  • vLLM Project. "FP8 W8A8." 現行文件,查閱日期 2026-11-03。FP8 的 compute capability 需求、僅權重 Marlin 備援路徑,以及量化後準確度評估的官方說明。docs.vllm.ai

  • Leviathan, Y., Kalman, M., and Matias, Y. "Fast Inference from Transformers via Speculative Decoding." ICML, 2023。接受規則、每次目標步驟期望 token 數與實際耗時改善公式的第一手論文。arxiv.org/abs/2211.17192

  • Chen, C., Borgeaud, S., Irving, G., et al. "Accelerating Large Language Model Decoding with Speculative Sampling." arXiv:2302.01318, 2023。修正版拒絕取樣、記憶體頻寬上限,以及評分短草稿為何只需約一個步驟時間的第一手論文。arxiv.org/abs/2302.01318

  • Li, Y., Wei, F., Zhang, C., and Zhang, H. "EAGLE: Speculative Sampling Requires Rethinking Feature Uncertainty." arXiv:2401.15077, 2024。特徵層級起草,以及加速比隨批次大小變化的第一手論文。arxiv.org/abs/2401.15077

  • Cai, T., Li, Y., Geng, Z., et al. "Medusa: Simple LLM Inference Acceleration Framework with Multiple Decoding Heads." ICML, 2024。解碼 head、樹狀注意力與典型接受機制的第一手論文。arxiv.org/abs/2401.10774

  • Liu, X., Park, J., Hu, L., et al. "TurboSpec: Closed-loop Speculation Control System for Optimizing LLM Serving Goodput." arXiv:2406.14066, 2024,2025 年修訂。說明在 vLLM 中未經調校的推測式解碼會因工作負載而降低效能的第一手論文。arxiv.org/abs/2406.14066

  • vLLM Project. "Speculative Decoding." 現行文件,查閱日期 2026-11-03。適用範圍、數值精度注意事項與支援之推測方法的官方說明。docs.vllm.ai

  • Dao, T., Fu, D. Y., Ermon, S., et al. "FlashAttention: Fast and Memory-Efficient Exact Attention with IO-Awareness." arXiv:2205.14135, 2022。分塊、IO 感知的精確注意力及其 HBM 存取分析的第一手論文。arxiv.org/abs/2205.14135

  • Dao, T. "FlashAttention-2: Faster Attention with Better Parallelism and Work Partitioning." arXiv:2307.08691, 2023。FlashAttention-2 工作分配改進與 A100 使用率的第一手論文。arxiv.org/abs/2307.08691

  • Shah, J., Bikshandi, G., Zhang, Y., et al. "FlashAttention-3: Fast and Accurate Attention with Asynchrony and Low-precision." arXiv:2407.08608, 2024。Hopper 非同步執行、FP8 注意力與 H100 基準測試條件的第一手論文。arxiv.org/abs/2407.08608

  • Dao, T., Haziza, D., Massa, F., and Sizov, G. "Flash-Decoding for Long-Context Inference." Stanford CRFM, 2023-10。作者說明在 decode 注意力中切分 key 與 value 的技術文章。crfm.stanford.edu

閱讀順序

完整閱讀路線

第 6.4 課

你在這裡81%
  1. 1.1
  2. 1.2
  3. 1.3
  4. 1.4
  5. 2.1
  6. 2.2
  7. 2.3
  8. 3.1
  9. 3.2
  10. 3.3
  11. 3.4
  12. 4.1
  13. 4.2
  14. 4.3
  15. 4.4
  16. 5.1
  17. 5.2
  18. 6.1
  19. 6.2
  20. 6.3
  21. 6.4
  22. 6.5
  23. 6.6
  24. 6.7
  25. 6.8
  26. 6.9

查看先備知識、分支路線與共用實作 →

分享這篇文章
XLinkedIn

Huang Tzu Lin

富宇資訊科技創辦人。撰寫 AI 系統設計、證據與工程取捨的文章。

透過 RSS 訂閱

使用慣用的 RSS 閱讀器追蹤最新的 AI 系統實作教學。

開啟 RSS Feed

支援任何 RSS 閱讀器,新文章將自動送達。

回到頂端