LLM 部署的挑戰:延遲、吞吐、成本

從延遲、吞吐到成本,拆解 LLM 部署的三大核心指標、它們之間的取捨,以及為什麼 LLM 部署比傳統服務難得多。

LLM 部署的挑戰:延遲、吞吐、成本

前面兩個系列,我們打造了 Skill 系統,也用 Harness 驗證了品質。

現在,你的 Agent 已經「能跑」也「可靠」了。
但當你要把它部署到生產環境,面對真實的使用者與流量時,三個新的挑戰會立刻浮現:延遲、吞吐、成本

這一篇,我們要進入應用三部曲的最後一部:部署與推理優化
我們會拆解 LLM 部署的三大核心指標、它們之間的取捨、延遲的組成、吞吐與批次的關係,以及成本結構,並用具體數據說明為什麼 LLM 部署比傳統服務難得多。

Latency × Throughput × Cost = Deployment Challenge

一、先說一個故事

假設你開發了一個客服 Agent,在測試環境表現優異。
你把它部署到生產環境,期待它服務數千名使用者。

上線第一天,問題就來了:

  • 使用者抱怨:「為什麼回答一個問題要等 10 秒?」
  • 運維抱怨:「GPU 使用率只有 20%,但請求一直在排隊。」
  • 老闆抱怨:「這個月的 API 帳單是上個月的 5 倍。」

你回頭檢查,才發現:

  • 延遲:每次請求要等模型逐字生成,首字延遲(TTFT)就花了 2 秒。
  • 吞吐:GPU 大部分時間在等記憶體,沒有充分利用。
  • 成本:每次請求都要跑完整的模型,沒有快取、沒有批次。

這些問題,就是 LLM 部署的核心挑戰。

傳統服務的部署,關注的是 CPU、記憶體、QPS。
LLM 服務的部署,關注的是延遲、吞吐、成本,以及它們之間的三角取捨。

二、三大核心指標

1. 延遲(Latency)

定義:從使用者送出請求,到收到完整回應的時間。

LLM 的延遲不是單一數字,而是由多個部分組成:

總延遲 = 網路延遲 + 排隊延遲 + 首字延遲(TTFT)+ 生成延遲(TPOT × 輸出長度)
組成說明典型值
網路延遲請求往返的時間10-100 ms
排隊延遲等待 GPU 空閒的時間0-數秒
首字延遲(TTFT)從請求到第一個 token 產出100 ms - 2 s
生成延遲(TPOT)每個後續 token 的產出時間10-50 ms

TTFT(Time To First Token) 是使用者感受最強烈的指標。
當你按下送出,等了 2 秒才看到第一個字,體感就是「很慢」。

TPOT(Time Per Output Token) 決定了整體的等待時間。
如果 TPOT 是 30 ms,生成 200 個 token 就需要 6 秒。

2. 吞吐(Throughput)

定義:單位時間內能處理的請求數或 token 數。

常見的衡量方式:

  • Requests Per Second(RPS):每秒處理多少請求。
  • Tokens Per Second(TPS):每秒生成多少 token。
  • Goodput:在滿足延遲 SLO 的前提下,能處理多少請求。

吞吐與延遲是矛盾的。
提高吞吐通常需要批次處理,但批次會增加單一請求的延遲。

3. 成本(Cost)

定義:每處理一個請求或生成一個 token 的花費。

成本的主要來源:

來源說明佔比
GPU 算力租用或購買 GPU 的費用60-80%
記憶體GPU 記憶體、系統記憶體10-20%
網路頻寬傳輸請求與回應5-10%
儲存模型權重、快取5-10%

成本與延遲、吞吐也是矛盾的。
要降低延遲,需要更快的 GPU;要提高吞吐,需要更大的批次;兩者都會增加成本。

三角取捨

        延遲
       /    \
      /      \
     /        \
吞吐 ———————— 成本

你無法同時最佳化三者。
你必須根據應用場景,選擇優先級:

應用場景優先級說明
即時對話延遲 > 吞吐 > 成本使用者不能等
批次處理吞吐 > 成本 > 延遲可以慢慢跑
大規模服務成本 > 吞吐 > 延遲成本決定可行性

Latency vs Throughput vs Cost

三、為什麼 LLM 部署比傳統服務難?

1. 自回歸生成:一個一個 token 產出

傳統服務:輸入 → 計算 → 輸出。一次完成。

LLM:輸入 → 產出 token 1 → 產出 token 2 → … → 產出 token N。

每一步都依賴前一步的結果,無法平行。

傳統服務:  [輸入] → [處理] → [輸出]        (一次)
LLM:       [輸入] → [t1] → [t2] → [t3] → ... (序列)

這意味著:

  • 延遲與輸出長度成正比:輸出越長,等待越久。
  • GPU 利用率低:每個 token 的計算量很小,GPU 大部分時間在等。

2. 記憶體密集:模型太大,記憶體是瓶頸

一個 70B 參數的模型,用 FP16 儲存需要 140 GB 記憶體。
這超過單張 GPU 的容量,需要多張 GPU 或多種優化技術。

更麻煩的是:推理過程中,GPU 的算力往往不是瓶頸,記憶體頻寬才是

GPU 算力:100 TFLOPS
記憶體頻寬:2 TB/s

一個 token 的計算需要讀取 140 GB 的權重
→ 記憶體頻寬限制:140 GB / 2 TB/s = 70 ms
→ 算力限制:遠小於 70 ms

結論:記憶體頻寬是瓶頸

這被稱為 Memory Wall(記憶體牆)

3. KV Cache:記憶體殺手

自回歸生成需要快取之前的 Key 和 Value,避免重複計算。

但 KV Cache 的大小與序列長度和批次大小成正比:

KV Cache 大小 = 2 × 層數 × 頭數 × 頭維度 × 序列長度 × 批次大小 × 精度

對於一個 70B 模型,序列長度 4096,批次大小 32:

2 × 80 × 64 × 128 × 4096 × 32 × 2 bytes ≈ 40 GB

這意味著:

  • 批次越大,KV Cache 越大,可能超過 GPU 記憶體。
  • 序列越長,KV Cache 越大,限制了最大上下文長度。

4. 動態工作負載:每個請求長度不同

傳統服務:請求大小相對固定。
LLM 服務:每個請求的輸入長度、輸出長度都不同。

請求 A:輸入 100 tokens,輸出 50 tokens
請求 B:輸入 2000 tokens,輸出 500 tokens
請求 C:輸入 50 tokens,輸出 1000 tokens

這讓批次處理變得複雜:

  • 短請求被長請求拖累。
  • 批次內的請求完成時間不同。
  • 需要動態調度。

5. 成本結構不同

傳統服務:成本主要在 CPU 和網路。
LLM 服務:成本主要在 GPU

一張 A100 GPU 每小時約 3-5 美元。
如果沒有充分利用,成本會非常驚人。

四、延遲的深入拆解

讓我們更具體地拆解延遲。

首字延遲(TTFT)

TTFT 是從請求發出到第一個 token 產出的時間。

組成:

TTFT = 網路延遲 + 排隊延遲 + 預填充時間(Prefill)

預填充(Prefill) 是處理輸入序列的階段。
模型需要讀取所有輸入 token,計算注意力,生成第一個輸出。

預填充的計算量與輸入長度成正比:

Prefill 時間 ≈ 輸入長度 × 每 token 的計算時間

對於一個 2000 token 的輸入,預填充可能需要 500 ms

生成延遲(TPOT)

TPOT 是每個後續 token 的產出時間。

組成:

TPOT = 解碼時間(Decode)

解碼(Decode) 是生成後續 token 的階段。
每一步只需要計算一個 token,但需要讀取整個 KV Cache。

TPOT 主要受記憶體頻寬限制:

TPOT ≈ 模型大小 / 記憶體頻寬

對於一個 70B 模型(FP16,140 GB),記憶體頻寬 2 TB/s:

TPOT ≈ 140 GB / 2 TB/s = 70 ms

這意味著,即使 GPU 算力再強,TPOT 也無法低於這個下限。

總延遲的估算

總延遲 = TTFT + TPOT × 輸出長度
輸出長度TTFTTPOT總延遲
50 tokens200 ms30 ms1.7 s
200 tokens500 ms30 ms6.5 s
1000 tokens2000 ms30 ms32 s

可以看到,輸出長度對總延遲的影響是巨大的

延遲的優化方向

優化方向方法效果
降低 TTFT減少輸入長度、優化預填充
降低 TPOT量化、KV Cache 優化
減少輸出長度更好的 Prompt、限制 max_tokens
減少排隊增加 GPU、優先級調度
串流輸出邊生成邊回傳體感大幅改善

串流輸出(Streaming) 是最有效的體感優化。
即使總延遲不變,使用者看到第一個字就能開始閱讀,體感快很多。

五、吞吐的深入拆解

批次處理:吞吐的關鍵

GPU 在處理單一請求時,利用率往往很低。
批次處理讓多個請求同時計算,大幅提升利用率。

單一請求:GPU 利用率 20%
批次 8:  GPU 利用率 60%
批次 32: GPU 利用率 90%

但批次處理有代價:

  • 延遲增加:請求需要等待批次湊滿。
  • 記憶體增加:KV Cache 與批次大小成正比。

靜態批次 vs 連續批次

靜態批次(Static Batching):等所有請求到齊,一起處理。

時間軸:
[t=0] 等待請求 1-8
[t=1] 處理請求 1-8
[t=5] 全部完成

問題:短請求要等長請求,浪費時間。

連續批次(Continuous Batching):請求完成就移出,新請求隨時加入。

時間軸:
[t=0] 請求 1-4 開始
[t=1] 請求 5 加入,請求 1 完成
[t=2] 請求 6 加入,請求 2 完成
...

這樣 GPU 幾乎不會閒置。

吞吐的計算

吞吐 = 批次大小 / 每個批次的處理時間

對於一個 70B 模型:

批次大小每批次時間吞吐(tokens/s)
170 ms14
8100 ms80
32200 ms160
64350 ms183

可以看到,批次越大,吞吐越高,但邊際效益遞減

吞吐的優化方向

優化方向方法效果
增加批次連續批次、動態批次
減少計算量化、稀疏化
減少記憶體KV Cache 壓縮、PagedAttention
平行化張量平行、流水線平行
加速解碼推測解碼

六、成本的深入拆解

成本結構

LLM 服務的成本可以拆解為:

總成本 = GPU 成本 + 記憶體成本 + 網路成本 + 儲存成本

其中 GPU 成本佔大頭:

GPU 成本 = GPU 數量 × 單價 × 使用時間

成本的計算範例

假設你用一張 A100(每小時 3 美元)部署一個 7B 模型:

指標數值
GPU 單價$3/小時
每日運行24 小時
每日成本$72
每月成本$2160
每秒成本$0.00083

如果每秒處理 10 個請求:

每請求成本 = $0.00083 / 10 = $0.000083

如果每秒只處理 1 個請求:

每請求成本 = $0.00083 / 1 = $0.00083

吞吐越低,每請求成本越高。

成本優化的方向

優化方向方法效果
提高吞吐批次處理、連續批次
減少 GPU量化、模型壓縮
減少運行時間自動擴縮、按需啟動
快取相同請求快取結果
路由簡單請求用小模型
降級高峰時降級服務

快取的威力

如果 30% 的請求是重複的,快取可以節省 30% 的成本。

def cached_inference(prompt: str, cache: dict, model):
    """帶快取的推理"""
    cache_key = hash(prompt)

    if cache_key in cache:
        return cache[cache_key]

    result = model.generate(prompt)
    cache[cache_key] = result

    return result

模型路由

不是所有請求都需要大模型。

def route_request(prompt: str) -> str:
    """根據複雜度路由到不同模型"""
    complexity = estimate_complexity(prompt)

    if complexity == "simple":
        return small_model.generate(prompt)
    elif complexity == "medium":
        return medium_model.generate(prompt)
    else:
        return large_model.generate(prompt)

這樣可以大幅降低成本,同時保持品質。

七、一個具體的部署範例

讓我們用一個具體的場景,說明三大指標的取捨。

場景:客服 Agent

需求:

  • 每天 10,000 個請求
  • 平均輸入 500 tokens
  • 平均輸出 200 tokens
  • 使用者可接受的延遲:3 秒

選項 A:單一 GPU,無批次

吞吐:1 請求/秒
每日處理:86,400 請求(足夠)
延遲:6.5 秒(超過 3 秒)
成本:$72/天

選項 B:單一 GPU,批次 8

吞吐:8 請求/秒
每日處理:691,200 請求(遠超需求)
延遲:8 秒(超過 3 秒)
成本:$72/天

選項 C:兩張 GPU,批次 4

吞吐:16 請求/秒
延遲:4 秒(接近 3 秒)
成本:$144/天

選項 D:量化 + 批次 4

吞吐:12 請求/秒
延遲:2.5 秒(符合需求)
成本:$72/天(量化後可用較小 GPU)

在這個場景中,選項 D 是最佳解:透過量化降低延遲,同時保持成本。

部署決策清單

1. 確定延遲 SLO(使用者能接受多久?)
2. 估算吞吐需求(每秒多少請求?)
3. 計算成本預算(每月能花多少?)
4. 選擇模型大小(7B / 13B / 70B?)
5. 選擇優化技術(量化、批次、快取)
6. 監控與調優(持續觀察三大指標)

八、監控與調優

部署不是終點,而是起點。
你需要持續監控三大指標,並根據實際情況調優。

關鍵指標

指標說明目標
TTFT首字延遲< 500 ms
TPOT每 token 延遲< 30 ms
RPS每秒請求數滿足需求
GPU 利用率GPU 使用率> 70%
每請求成本平均成本符合預算
錯誤率失敗請求比例< 1%

監控工具

class LLMMetrics:
    """LLM 服務指標收集"""

    def __init__(self):
        self.ttft_records = []
        self.tpot_records = []
        self.request_count = 0
        self.error_count = 0
        self.total_tokens = 0

    def record_request(
        self,
        ttft: float,
        tpot: float,
        output_tokens: int,
        success: bool = True,
    ):
        """記錄一次請求"""
        self.request_count += 1
        self.total_tokens += output_tokens

        if success:
            self.ttft_records.append(ttft)
            self.tpot_records.append(tpot)
        else:
            self.error_count += 1

    def get_summary(self) -> dict:
        """取得摘要"""
        from statistics import mean, median

        return {
            "total_requests": self.request_count,
            "error_rate": self.error_count / max(self.request_count, 1),
            "avg_ttft": mean(self.ttft_records) if self.ttft_records else 0,
            "p95_ttft": (
                sorted(self.ttft_records)[int(len(self.ttft_records) * 0.95)]
                if self.ttft_records else 0
            ),
            "avg_tpot": mean(self.tpot_records) if self.tpot_records else 0,
            "p95_tpot": (
                sorted(self.tpot_records)[int(len(self.tpot_records) * 0.95)]
                if self.tpot_records else 0
            ),
            "total_tokens": self.total_tokens,
        }

調優策略

問題可能原因調優策略
TTFT 太高輸入太長、排隊太久減少輸入、增加 GPU、優先級調度
TPOT 太高模型太大、量化不足量化、用小模型、KV Cache 優化
吞吐太低批次太小、GPU 閒置連續批次、增加批次、平行化
成本太高吞吐太低、模型太大快取、路由、降級、量化
錯誤率高超時、記憶體不足增加資源、優化記憶體、重試

九、常見的陷阱

1. 只看平均延遲

平均值會掩蓋尾延遲。

# 不好的例子:只看平均值
avg_latency = 1.5  # 看起來很好

# 好的例子:看 P95、P99
p95_latency = 8.0  # 5% 的請求要等 8 秒
p99_latency = 15.0  # 1% 的請求要等 15 秒

2. 忽略 TTFT

TTFT 是使用者感受最強烈的指標。

# 不好的例子:只優化總延遲
# 好的例子:同時優化 TTFT 和 TPOT

3. 沒有串流輸出

串流輸出是最有效的體感優化。

# 不好的例子:等全部生成完才回傳
response = model.generate(prompt)
return response

# 好的例子:邊生成邊回傳
for token in model.stream(prompt):
    yield token

4. 批次太大

批次太大會增加延遲,且邊際效益遞減。

# 不好的例子:批次 128
# 好的例子:批次 8-32,根據實際情況調整

5. 沒有監控

沒有監控,你不知道系統的實際表現。

# 不好的例子:部署後就不管了
# 好的例子:持續監控 TTFT、TPOT、吞吐、成本

6. 忽略成本

只關注效能,忽略成本。

# 不好的例子:用最貴的 GPU,只為降低延遲
# 好的例子:在延遲與成本之間取得平衡

7. 沒有快取

重複的請求重複計算。

# 不好的例子:每次都跑完整模型
# 好的例子:快取相同請求的結果

8. 沒有降級機制

高峰時服務崩潰。

# 不好的例子:所有請求都用大模型
# 好的例子:高峰時降級到小模型,或排隊

十、總結:三角取捨的藝術

讓我們回顧這一篇的核心:

  • 三大核心指標:延遲、吞吐、成本,三者互相矛盾。
  • 為什麼 LLM 部署難:自回歸生成、記憶體密集、KV Cache、動態負載、成本結構。
  • 延遲的拆解:TTFT、TPOT、總延遲的估算。
  • 吞吐的拆解:批次處理、連續批次、吞吐的計算。
  • 成本的拆解:GPU 成本、快取的威力、模型路由。
  • 具體範例:客服 Agent 的四個選項與最佳解。
  • 監控與調優:關鍵指標、監控工具、調優策略。
  • 常見陷阱:只看平均、忽略 TTFT、沒有串流、批次太大、沒有監控、忽略成本、沒有快取、沒有降級。

LLM 部署不是單純的「把模型跑起來」,而是在延遲、吞吐、成本之間找到平衡。
沒有通用的最佳解,只有根據場景的取捨。

理解了三大指標與它們的取捨,你就有了部署 LLM 系統的基礎。
接下來,我們要深入每個優化技術,從最關鍵的開始:KV Cache。


下一篇預告

《KV Cache:為什麼推理需要它》

我們會解釋 KV Cache 的原理、它為什麼是推理的必要機制、它的大小如何計算、它如何成為記憶體瓶頸,以及有哪些優化技術(MQA、GQA、PagedAttention)可以緩解這個問題。