推論基準測試、容量、SLO 與可觀測性
- 作者
- Huang Tzu LinFounder
- 發布日期
- 閱讀時間
- 約 21 分鐘
完成本篇後,你應該能夠
- 區分閉迴路 concurrency 測試與開迴路 request-rate 測試;
- 凍結工作負載分布,而不是只測一種方便的 prompt 形狀;
- 分開 cold-cache 與 warm-cache 結果;
- 計算 TTFT、ITL、TPOT、端對端延遲、吞吐量、觀測 goodput 與符合 SLO 的容量;
- 從實測容量與預留比例推導副本及 GPU 數量;
- 選擇請求、佇列、KV、GPU、網路與故障指標;以及
- 證明警示與復原流程在故障注入下確實有效。
完成檢核你能逐項滿足下列所有完成標準嗎?
完整完成標準10
- model、tokenizer、engine、precision、scheduler 與 workload revision 都不可變;
- cold/warm cache 測試分別命名,並回報 hit/eviction 狀態;
- 同時包含閉迴路 saturation 與開迴路 arrival-rate sweep;
- 每個測試點都回報 attempted/completed/failed requests、error rate、P50/P95 latency、throughput、goodput、queue trend 與逐類別成功筆數;
- 每個類別都分別依 request-level TTFT/TPOT cutoff 評估,且至少有 2,000 筆成功觀測;
- 選定容量點的每個類別都至少有 95% joint request-level 達成率、最多 1% 錯誤,且沒有持續 queue growth;
- 副本計算包含 75% utilization 政策,並對範例輸入得到 8 replicas/16 GPUs;
- failure test 記錄 detection/recovery time,而且至少一個已配置 alert 觸發;
- 儀表板能區分 queue saturation、KV pressure、GPU failure 與 network/transfer degradation;以及
- 重複測試揭露變異,而不是只發布最佳一次。
目錄
- 01開始之前:先備知識與學習成果
- 02你將建置的成品
- 03凍結服務系統
- 04凍結工作負載分布
- 05閉迴路與開迴路回答不同問題
- 閉迴路 Concurrency 測試
- 開迴路 Request-Rate 測試
- 06在測試前先定義指標
- 07控制快取狀態,不要把它平均掉
- Cold Cache
- Warm Cache
- 08完整範例:找出符合 SLO 的容量
- 09包含 Headroom 的容量規劃範例
- 10建立 Workload Manifest
- 11建立 SLO 分析器
- 12可觀測性:量測工作、狀態與故障
- 請求與 SLO 訊號
- Scheduler 與 Queue 訊號
- KV 與 GPU 訊號
- 分散式訊號
- 13刻意破壞的 Benchmark
- 14故障注入:證明儀表板有效
- 15動手實作
- 16可量測的驗收標準
- 17提取練習
- 18解答
- 19本文不涵蓋的內容
- 20來源與延伸閱讀
推論基準測試不是比誰印出的 tokens-per-second 最大。它是一個實驗,用來回答:一套版本固定的系統,能否在足夠比例的請求都符合明確延遲與可靠性目標時,持續承受指定工作負載。如果沒有工作負載、抵達過程、快取狀態、分母與百分位數,吞吐量數字就無法回答容量問題。
本篇會把 I-06 的部署配置轉成操作成品。你將凍結混合工作負載,執行飽和與速率型測試,計算請求指標與 goodput(有效吞吐量),推導包含預留空間的副本需求,定義儀表板,並注入一個應該讓服務以安全、可觀測方式失敗的故障。
開始之前:先備知識與學習成果
你應該已理解 TTFT、ITL、TPOT、端對端延遲、連續批次處理、KV 快取,以及 I-06 建立的 TP2 × DP4 部署方式。
完成後,你將能夠:
區分閉迴路 concurrency 測試與開迴路 request-rate 測試;
凍結工作負載分布,而不是只測一種方便的 prompt 形狀;
分開 cold-cache 與 warm-cache 結果;
計算 TTFT、ITL、TPOT、端對端延遲、吞吐量、觀測 goodput 與符合 SLO 的容量;
從實測容量與預留比例推導副本及 GPU 數量;
選擇請求、佇列、KV、GPU、網路與故障指標;以及
證明警示與復原流程在故障注入下確實有效。
你將建置的成品
你會產出一個版本化的 inference-capacity-pack/,包含四個可檢查檔案:
inference-capacity-pack/
├── workload-manifest.yaml
├── requests.csv
├── slo-report.json
└── failure-test.mdworkload-manifest.yaml 凍結系統與流量;requests.csv 每一列代表一筆完成或失敗的請求;slo-report.json 包含百分位數、錯誤率、SLO 達成率與 goodput;failure-test.md 記錄一個刻意注入的故障、觸發的警示與復原時間。
凍結服務系統
計算範例使用以下假設性、但規格完整的設定:
輸入 — 假設
--- — ---:
模型 — dense-70b-fixture-r1,I-06 的密集型 BF16 模型
部署 — 4 個副本,每個 TP2,共 8 張 GPU
引擎與 tokenizer — tutorial-engine-fixture-1.0 與 dense-70b-tokenizer-fixture-r1
串流 — 啟用
前綴快取 — 啟用,但 cold 與 warm 測試分開
Speculative decoding — 停用
P/D disaggregation — 停用
量測邊界 — 從 client 送出到 client 收到
主要 SLO 達成目標 — 每個類別都獨立要求至少 95% 成功請求同時符合 request-level TTFT 與 TPOT cutoff
最大錯誤率 — 1%
容量預留 — 正式操作不得超過實測 SLO 合格容量的 75%
引擎與 tokenizer revision 很重要,因為不同版本可能改變指標行為與 token 數。目前 vLLM 文件也警告,不同工具並沒有完全標準化指標術語,因此名稱旁必須附上公式與量測邊界(vLLM Benchmark CLI)。
凍結工作負載分布
單一平均輸入長度會抹去 prefill-heavy 與 decode-heavy 流量的差異。請明確定義類別:
類別 — 比例 — 輸入 tokens — 輸出 tokens — Request TTFT cutoff — Request TPOT cutoff
--- — ---: — ---: — ---: — ---: — ---:
interactive — 70% — 1,024 — 128 — 500 ms — 40 ms/token
long_context — 20% — 8,192 — 256 — 2,500 ms — 50 ms/token
generation_heavy — 10% — 512 — 1,024 — 800 ms — 45 ms/token
對這個固定比例:
weighted input tokens
= 0.70×1,024 + 0.20×8,192 + 0.10×512
= 2,406.4 tokens/request
weighted output tokens
= 0.70×128 + 0.20×256 + 0.10×1,024
= 243.2 tokens/request加權值可用來驗證 aggregate counter,但每個類別的 SLO 達成率仍須獨立評估。整體百分位數或達成率可能看起來健康,同時低流量的 long-context 類別卻不健康。
Manifest 還應凍結 sampling 參數、停止行為、chat template、前綴分布、請求取消策略、測試時間、client 位置、完整模型與引擎配置,以及 random seed 或 request schedule。目前 NVIDIA AIPerf 文件支援明確的 constant、Poisson、gamma、fixed-schedule 與 concurrency-burst 抵達模式;選擇哪一種抵達模型是實驗的一部分,不是隱藏的 client 細節(AIPerf load-generator options)。
閉迴路與開迴路回答不同問題
閉迴路 Concurrency 測試
閉迴路測試最多維持 N 筆 in-flight 請求。一筆完成後,client 才送出下一筆。延遲增加時,完成速度下降,新的抵達也隨之變慢。這適合找飽和點與吞吐量—延遲前緣,但它會自我節流,因此無法證明服務能吸收獨立的正式抵達率。
目前 AIPerf 把只有 concurrency 的模式稱為 burst 或 saturation 模式:在 concurrency 上限內盡快送出請求(AIPerf arrival patterns)。
開迴路 Request-Rate 測試
開迴路測試依目標速率安排抵達,不受完成速度影響,常使用 Poisson 過程或實際 trace。如果 offered load 超過服務容量,佇列會成長,TTFT 會把超載暴露出來。這才適合驗證外部抵達率 SLO;但必須確認 load generator 沒有因過低的 concurrency 上限,而在暗中阻塞已排定的抵達。
兩種都要使用:
以閉迴路 concurrency sweep 找出飽和候選點;
以開迴路 rate sweep 驗證真實抵達率下的佇列穩定性與 SLO 達成率;以及
有正式抵達與長度分布時,重播帶 timestamp 的 trace。
在測試前先定義指標
對請求 i,client 送出時間為 s_i、第一個非空 token 時間為 f_i、最後一個 token 時間為 e_i、輸出 token 數為 O_i:
TTFT_i = f_i - s_i
E2E_i = e_i - s_i
TPOT_i = (E2E_i - TTFT_i) / (O_i - 1), for O_i > 1若 token 抵達時間為 t_i,1 ... t_i,O,個別 inter-token latency 為:
ITL_i,j = t_i,j - t_i,j-1, for j = 2 ... O_i每筆請求只有一個 TPOT 平均值;ITL 則是所有個別間隔的分布。一般一次一 token 的串流下兩者相近;但一個串流 chunk 內含多個 token 時,包括 speculative decoding,兩者就可能分歧。目前 vLLM 基準測試文件明確區分這兩者(vLLM Benchmark CLI)。
對長度為 T 的量測視窗:
request_throughput = successful_requests / T
output_throughput = sum(successful_output_tokens) / T
error_rate = failed_requests / attempted_requests
observed_goodput = successful_requests_meeting_their_class_request_cutoffs / T
class_attainment_c = passing_successful_requests_in_class_c
/ successful_requests_in_class_c
all_class_SLOs_pass = AND over every class c of:
class_attainment_c >= 0.95
AND successful_requests_in_class_c >= 2,000本篇將 capacity goodput 定義為同時符合以下條件的最高 offered request rate:
每個工作負載類別都獨立達到至少 95% request-level SLO 達成率;
每個類別都有至少 2,000 筆成功觀測;
錯誤率不超過 1%;以及
steady-state 視窗中沒有持續佇列成長。
整體 observed goodput 仍是有用的吞吐量計數器,但單一類別失敗時,它不能讓容量測試點通過。逐類別閘門可防止占 70% 的 interactive 類別掩蓋占 20% 或 10% 的類別退化。
DistServe 使用 goodput 把抵達率與同時成立的 TTFT、TPOT 限制連在一起,而不是只計算原始吞吐量;它報告的改善仍只適用於其評估的模型、工作負載和 SLO(Zhong et al., OSDI 2024)。
控制快取狀態,不要把它平均掉
執行兩組有明確名稱的實驗:
Cold Cache
每個測試點前重啟或明確清除 engine cache;
無法清除時使用不重複前綴;
steady-state 視窗排除 compilation 與 model-load warmup;以及
回報接近零的 cache-hit rate。
Warm Cache
預熱一組版本固定的重複前綴;
不同配置使用相同的前綴頻率分布;
只有在預期快取狀態成立後才開始量測;以及
回報 prefix-cache hit rate 與 eviction rate。
不要把 cold 與 warm 請求混成一個未標記平均值。目前 vLLM 基準測試指南警告,對同一伺服器重跑可重現資料集,可能重用快取內殘留的前綴並膨脹吞吐量(vLLM Benchmark CLI)。
完整範例:找出符合 SLO 的容量
假設每個測試點使用固定 seed 的 Poisson 抵達排程,預先分層為恰好 15,400 筆 interactive、4,400 筆 long-context 與 2,200 筆 generation-heavy 請求,再進行隨機排列,共 22,000 筆。至少量測五分鐘;若任何類別完成後少於 2,000 筆成功觀測,便沿用預先承諾的排程繼續執行。快取策略保持不變。下列是參數化教學資料,不是 OptiVerse 基礎設施的量測:
Offered RPS — Completed RPS — 錯誤率 — 整體達成率 — 最低類別達成率 — 最低類別成功 n — 觀測 goodput RPS — Output tok/s — Interactive P95 TTFT — Interactive P95 TPOT — 佇列趨勢
---: — ---: — ---: — ---: — ---: — ---: — ---: — ---: — ---: — ---: — ---
16 — 16.0 — 0.0% — 99.6% — 99.1% — 2,200 — 15.94 — 3,891 — 180 ms — 24 ms — 平坦
20 — 20.0 — 0.1% — 99.1% — 98.2% — 2,197 — 19.82 — 4,864 — 260 ms — 29 ms — 平坦
24 — 23.9 — 0.4% — 95.4% — 95.1% — 2,191 — 22.80 — 5,812 — 420 ms — 38 ms — 平坦
28 — 25.2 — 2.0% — 86.8% — 78.0% — 2,156 — 21.87 — 6,129 — 980 ms — 62 ms — 成長
28 RPS 測試點的原始 output throughput 最高,但觀測 goodput 低於 24 RPS。它最弱類別的達成率只有 78.0%,也同時違反兩個 interactive 延遲目標、錯誤率過高,而且佇列正在成長。只看原始吞吐量會選錯操作點。
每個類別都至少有 95% 達成率與 2,000 筆成功觀測、整體錯誤率不超過 1%,且佇列保持平坦的最高 offered rate,是四副本下的 24 RPS。其最弱類別在 2,191 筆成功請求上達到 95.1%。因此:
measured SLO-compliant capacity per replica
= 24 RPS / 4 replicas
= 6 RPS/replica只有在工作負載比例、部署、快取狀態、引擎配置與 scaling regime 都相同時,才能做這個除法。它不是通用的每副本常數。
包含 Headroom 的容量規劃範例
假設預測尖峰為 36 RPS,政策預留 25% 容量,因此規劃利用率最多 75%:
required_replicas
= ceil(peak_RPS / (capacity_per_replica × target_utilization))
= ceil(36 / (6 × 0.75))
= ceil(8)
= 8 replicas
required_GPUs = 8 replicas × 2 GPUs/replica = 16 GPUs目前八 GPU 叢集只有四個副本,無法在指定預留比例下承受預測尖峰。這個計算本身不能授權採購:仍須驗證八副本拓撲,因為 load balancer 行為、網路路徑、快取 locality 與共享瓶頸,都可能阻止線性 scaling。目前 Dynamo sizing 文件也明確劃出相同邊界:分析型 optimizer 會提出配置,但不會模擬逐請求 scheduler 或 KV-cache 行為,因此仍必須執行 live benchmark(Dynamo sizing with AIConfigurator)。
Little's Law 可用於檢查穩定視窗的一致性:
average_in_flight ≈ completion_rate × average_E2E_seconds在 23.9 completed RPS、平均 E2E 1.8 秒時,預期平均 in-flight 數為 23.9 × 1.8 = 43.02。Client/server in-flight gauge 若相差很大,代表量測邊界不一致、遙測遺失、重試,或佇列不穩定。Little's Law 無法挽救持續成長的超載佇列,因為穩定系統假設已不成立。
建立 Workload Manifest
建立 inference-capacity-pack/workload-manifest.yaml:
cutoff: 2026-08-29
model:
id: dense-70b-class
revision: dense-70b-fixture-r1
tokenizer_revision: dense-70b-tokenizer-fixture-r1
precision: bf16
engine:
name: tutorial-engine-fixture
version: "1.0"
placement: {tp: 2, pp: 1, dp: 4, replicas: 4, gpus: 8}
prefix_cache: enabled
speculative_decoding: disabled
disaggregated_prefill: disabled
client:
location: loadgen-fixture-a/zone-a
streaming: true
arrival_pattern: poisson
minimum_duration_seconds: 300
initial_scheduled_requests: 22000
request_schedule_seed: 20260829
class_schedule_counts: {interactive: 15400, long_context: 4400, generation_heavy: 2200}
warmup_seconds: 60
workload:
- {name: interactive, mix: 0.70, input_tokens: 1024, output_tokens: 128,
request_ttft_cutoff_ms: 500, request_tpot_cutoff_ms: 40}
- {name: long_context, mix: 0.20, input_tokens: 8192, output_tokens: 256,
request_ttft_cutoff_ms: 2500, request_tpot_cutoff_ms: 50}
- {name: generation_heavy, mix: 0.10, input_tokens: 512, output_tokens: 1024,
request_ttft_cutoff_ms: 800, request_tpot_cutoff_ms: 45}
acceptance:
class_slo_attainment_min: 0.95
min_successful_samples_per_class: 2000
error_rate_max: 0.01
queue_trend_required: flat
target_utilization: 0.75request_*_cutoff_ms 欄位套用到個別請求;P50/P95/P99 仍是要回報的分布統計,不能和這些 cutoff 互換。先產生精確類別筆數,以記錄的 seed 洗牌,再配置 Poisson 抵達間隔。初始排程結束後,若任何類別少於 2,000 筆成功列,必須從同一個版本化排程接續,再做容量決策。
如果使用 AIPerf,可用 --request-rate、選定的 arrival pattern、streaming、duration 與凍結資料集表示 rate sweep。只有 concurrency 的 sweep 應該另命名為 saturation experiment。目前 AIPerf 文件同時提供這兩種控制,並會寫出後續分析所需的成品(AIPerf command-line options)。
建立 SLO 分析器
把 load tool 的 per-request 輸出正規化為 requests.csv,包含以下欄位:
request_id,class,ttft_ms,tpot_ms,e2e_ms,output_tokens,status
r-0001,interactive,182.0,25.1,3370.0,128,ok
r-0002,long_context,1940.0,44.2,13211.2,256,ok
r-0003,generation_heavy,910.0,47.0,48991.0,1024,ok
r-0004,interactive,0,0,0,0,error將以下內容存為 analyze_slo.py:
import csv
import json
import math
import sys
REQUEST_CUTOFFS = {
"interactive": {"ttft_ms": 500, "tpot_ms": 40},
"long_context": {"ttft_ms": 2500, "tpot_ms": 50},
"generation_heavy": {"ttft_ms": 800, "tpot_ms": 45},
}
CLASS_ATTAINMENT_MIN = 0.95
MIN_SUCCESSFUL_PER_CLASS = 2000
ERROR_RATE_MAX = 0.01
def percentile(values, probability):
if not values:
return None
ordered = sorted(values)
index = max(0, math.ceil(probability * len(ordered)) - 1)
return ordered[index]
if len(sys.argv) != 4:
raise SystemExit("usage: analyze_slo.py REQUESTS_CSV DURATION_SECONDS QUEUE_TREND")
path = sys.argv[1]
duration_s = float(sys.argv[2])
queue_trend = sys.argv[3]
if duration_s <= 0:
raise ValueError("duration must be positive")
if queue_trend not in {"flat", "growing"}:
raise ValueError("queue trend must be flat or growing")
with open(path, newline="", encoding="utf-8") as handle:
rows = list(csv.DictReader(handle))
attempted = len(rows)
successful = [row for row in rows if row["status"] == "ok"]
failed = attempted - len(successful)
passed_total = 0
by_class = {
name: {"ttft": [], "tpot": [], "e2e": [], "tokens": 0, "passed": 0}
for name in REQUEST_CUTOFFS
}
for row in successful:
name = row["class"]
if name not in REQUEST_CUTOFFS:
raise ValueError(f"unknown workload class: {name}")
ttft = float(row["ttft_ms"])
tpot = float(row["tpot_ms"])
e2e = float(row["e2e_ms"])
output_tokens = int(row["output_tokens"])
bucket = by_class[name]
bucket["ttft"].append(ttft)
bucket["tpot"].append(tpot)
bucket["e2e"].append(e2e)
bucket["tokens"] += output_tokens
cutoff = REQUEST_CUTOFFS[name]
if ttft <= cutoff["ttft_ms"] and tpot <= cutoff["tpot_ms"]:
bucket["passed"] += 1
passed_total += 1
classes = {}
for name, cutoff in sorted(REQUEST_CUTOFFS.items()):
bucket = by_class[name]
count = len(bucket["ttft"])
attainment = bucket["passed"] / count if count else None
sample_adequate = count >= MIN_SUCCESSFUL_PER_CLASS
p99_sample_adequate = count >= 10000
class_slo_pass = sample_adequate and attainment >= CLASS_ATTAINMENT_MIN
classes[name] = {
"successful_count": count,
"passing_count": bucket["passed"],
"request_ttft_cutoff_ms": cutoff["ttft_ms"],
"request_tpot_cutoff_ms": cutoff["tpot_ms"],
"attainment": attainment,
"sample_adequate": sample_adequate,
"p99_sample_adequate": p99_sample_adequate,
"class_slo_pass": class_slo_pass,
"p50_ttft_ms": percentile(bucket["ttft"], 0.50),
"p95_ttft_ms": percentile(bucket["ttft"], 0.95),
"p99_ttft_ms": percentile(bucket["ttft"], 0.99) if p99_sample_adequate else None,
"p50_tpot_ms": percentile(bucket["tpot"], 0.50),
"p95_tpot_ms": percentile(bucket["tpot"], 0.95),
"p99_tpot_ms": percentile(bucket["tpot"], 0.99) if p99_sample_adequate else None,
"p95_e2e_ms": percentile(bucket["e2e"], 0.95),
"output_tokens": bucket["tokens"],
}
error_rate = failed / attempted if attempted else None
overall_attainment = passed_total / len(successful) if successful else None
all_class_slos_pass = all(item["class_slo_pass"] for item in classes.values())
capacity_gate_pass = (
all_class_slos_pass
and error_rate is not None
and error_rate <= ERROR_RATE_MAX
and queue_trend == "flat"
)
report = {
"attempted": attempted,
"successful": len(successful),
"failed": failed,
"error_rate": error_rate,
"overall_attainment": overall_attainment,
"observed_goodput_rps": passed_total / duration_s,
"request_throughput_rps": len(successful) / duration_s,
"output_throughput_tps": sum(int(row["output_tokens"]) for row in successful) / duration_s,
"queue_trend": queue_trend,
"all_class_slos_pass": all_class_slos_pass,
"capacity_gate_pass": capacity_gate_pass,
"classes": classes,
}
print(json.dumps(report, indent=2, sort_keys=True))執行:
python analyze_slo.py inference-capacity-pack/requests.csv 300 flat \
> inference-capacity-pack/slo-report.json上方四列 CSV 是 schema smoke test,因此每個類別的樣本刻意不足,capacity_gate_pass 必須為 false。在真實測試點,只有每個命名類別都有至少 2,000 筆成功列、joint request-level 達成率至少 95%、錯誤率不超過 1%,且佇列趨勢為 flat 時,分析器才會讓容量通過。只有 scheduler queue-depth time series 顯示沒有持續成長時,才能傳入 flat。這個 script 使用 nearest-rank percentile,確保結果可重現。正式工具可能採用不同 interpolation;記錄演算法,不要盲目比較 percentile 名稱。
可觀測性:量測工作、狀態與故障
正式儀表板需要足夠訊號,才能解釋 SLO 為什麼改變。請使用 model revision、engine version、workload class、replica 和 result 等有界 cardinality 維度。Request ID 應放在 trace/log,不要當成 metric label。
請求與 SLO 訊號
attempted、successful、failed、timed-out、rejected 與 cancelled requests;
依 workload class 區分的 TTFT、TPOT、ITL 與 E2E histogram;
input/output tokens 與 request/output throughput;
observed goodput 與逐類別 SLO attainment;以及
retry count 與 client disconnects。
Scheduler 與 Queue 訊號
waiting 與 running requests;
queue-wait histogram;
batched prompt 與 generation tokens;
preemption、eviction 與 admission rejection counts;以及
引擎支援時的 per-iteration batch composition。
KV 與 GPU 訊號
KV blocks used/total 與 maximum token capacity;
prefix-cache hit 與 eviction rates;
HBM used、compute utilization、memory bandwidth、power 與 OOM events;以及
model load 與 warmup duration。
分散式訊號
依 operation/message bucket 區分的 collective duration 與 errors;
disaggregated serving 的 KV-transfer bytes、latency、throughput 與 failures;
per-replica inflight work 與 router selection;以及
啟用 speculative decoding 時的 speculative acceptance length/rate。
目前 NVIDIA Dynamo 文件會比較 vLLM、SGLang 與 TensorRT-LLM 提供的 Prometheus 訊號,並警告 metric 名稱會隨引擎版本改變;應檢查實際部署的 /metrics endpoint,而不是直接沿用舊儀表板(Dynamo Engine Metrics Comparison)。
刻意破壞的 Benchmark
這個配置能產出亮眼數字,卻幾乎沒有操作證據:
load_mode: concurrency_only
concurrency: 128
request_count: 100
prompts: repeat_the_same_prompt
prefix_cache: enabled
clear_cache_before_run: false
warmup_requests: 0
report: [mean_latency, total_tokens_per_second]
model_revision: latest
engine_version: latest它壞在:
concurrency-only 流量會自我節流,無法驗證獨立抵達率;
100 筆請求幾乎沒有 tail evidence;
重複 prompt 與未清除快取會混合 cold/warm 狀態;
平均值會隱藏 tail latency 與類別失敗;
沒有錯誤、佇列成長、取消與快取狀態;以及
可變的
latest版本無法重現。
修復方式不是再加一個 percentile,而是換成版本固定的 manifest、分離快取狀態、兩種負載模式、per-request 記錄、類別 SLO 與 failure test。
故障注入:證明儀表板有效
讓服務在通過條件的 20 RPS 測試點運行,接著終止四個 TP2 副本中的一個。在 failure-test.md 記錄:
injection_time
detection_time
router_removal_time
first_error_time and last_error_time
TTFT/TPOT/SLO-attainment during incident
queue peak and recovery time
requests retried, rejected, or lost
which alerts fired
whether capacity returned without manual data repair預期行為不是「完全沒有變化」。失去 25% 副本理應降低容量。系統只有在能偵測並移除副本、不再把新工作送給它、誠實暴露錯誤或 backpressure,並回到穩定狀態時才通過。若剩餘三個副本無法在 SLO 內承受 20 RPS,admission control 應拒絕或 shed load,而不是讓佇列無限成長。
之後再分別測試冷啟動 autoscaled replica、降低 KV 容量、request cancellation 與退化的跨節點連線。這些測試針對不同故障機制,不應壓成一個 chaos score。
動手實作
產生或擷取符合三類 manifest 的工作負載。
執行閉迴路 concurrency sweep 找出飽和點。
對 16、20、24、28 RPS 使用固定 seed 的 22,000-request 排程,分別執行至少五分鐘 cold-cache 與 warm-cache Poisson rate sweep。
把每筆 attempted request 正規化到
requests.csv;繼續執行排程,直到每個類別都有至少 2,000 筆成功列。產生
slo-report.json,並畫出 offered RPS 對 raw throughput、observed goodput、逐類別 attainment、P95 TTFT、P95 TPOT 的圖。選擇分析器回報
capacity_gate_pass: true的最高速率。以 75% target utilization 計算 36 RPS 所需副本與 GPU。
在通過條件的測試點注入單副本故障,完成
failure-test.md。
若某類別少於 2,000 筆成功樣本,請把 tail 結果標成探索性,而且不得把該測試點選為容量。分層排程保證占 10% 的類別一開始有 2,200 次嘗試;錯誤發生後,分析器仍會檢查成功筆數。某類別少於 10,000 筆成功觀測時,不要報告精確 P99;否則最高 1% 大約只剩不到 100 筆觀測。
可量測的驗收標準
Capacity pack 只有在以下條件成立時才通過:
model、tokenizer、engine、precision、scheduler 與 workload revision 都不可變;
cold/warm cache 測試分別命名,並回報 hit/eviction 狀態;
同時包含閉迴路 saturation 與開迴路 arrival-rate sweep;
每個測試點都回報 attempted/completed/failed requests、error rate、P50/P95 latency、throughput、goodput、queue trend 與逐類別成功筆數;
每個類別都分別依 request-level TTFT/TPOT cutoff 評估,且至少有 2,000 筆成功觀測;
選定容量點的每個類別都至少有 95% joint request-level 達成率、最多 1% 錯誤,且沒有持續 queue growth;
副本計算包含 75% utilization 政策,並對範例輸入得到 8 replicas/16 GPUs;
failure test 記錄 detection/recovery time,而且至少一個已配置 alert 觸發;
儀表板能區分 queue saturation、KV pressure、GPU failure 與 network/transfer degradation;以及
重複測試揭露變異,而不是只發布最佳一次。
提取練習
為什麼閉迴路測試可能隱藏超載?
TPOT 與 ITL 有什麼不同?
為什麼 cold-cache 與 warm-cache 結果必須分開?
Observed goodput 與 raw request throughput 有何差異?
在計算範例中,為什麼選 24 offered RPS,而不是 28?
在指定預留比例下,36 RPS 需要多少副本與 GPU?
哪些訊號能區分 queue problem 與 KV-memory pressure?
Failure test 能證明什麼健康 benchmark 無法證明的事?
解答
新工作只在先前工作完成後才送出,因此延遲升高會減慢抵達,讓 client 自我節流。
TPOT 是每筆請求的一個平均 decode 時間;ITL 包含串流輸出之間的每一個實際間隔。
前綴重用會降低 prefill 工作並膨脹吞吐量;混合兩種狀態會讓結果不可重現,也無法解釋工作負載。
它每單位時間只計算符合所屬類別 request-level cutoff 的成功請求;容量還要求每個類別都通過自己的達成率與樣本數閘門。
28 RPS 的 raw throughput 較高,但最弱類別達成率只有 78.0%、錯誤率是 2.0%,而且佇列成長。24 RPS 的每個類別都在至少 2,191 筆成功請求上達到至少 95.1%,其他閘門也都通過。
ceil(36/(6×0.75)) = 8個副本;每個 TP2,因此共 16 張 GPU。Queue depth/wait 與 admission metrics 顯示飽和;KV utilization、free blocks、eviction 與 preemption 顯示 KV pressure。GPU 與 request metrics 提供交叉證據。
它證明在已知故障下,偵測、路由移除、load shedding/retry、警示與復原流程都能運作。
本文不涵蓋的內容
本篇定義的是推論操作方法,不是通用 SLO。產品團隊必須依使用者需求與成本/風險限制推導目標。參數化結果不是 OptiVerse 量測,線性副本計算也必須在 scale-out 後驗證。即使基礎設施 SLO 通過,品質評估、模型準確度、安全與應用正確性仍是獨立 gate。
來源與延伸閱讀
vLLM Project. “Benchmark CLI.” 現行文件,存取日期 2026-08-29。Client-observed TTFT、ITL、TPOT、throughput 與 cache-warmth 警告的第一方定義。docs.vllm.ai
NVIDIA. “AIPerf Load Generator Options Reference.” 現行文件,存取日期 2026-08-29。Rate、concurrency、trace 與 user-driven load schedule 的第一方定義。docs.nvidia.com/aiperf
NVIDIA. “AIPerf Command Line Options.” 現行文件,存取日期 2026-08-29。Request-rate sweep、Poisson/gamma/constant arrivals、concurrency、warmup、duration 與 cancellation 的第一方控制說明。docs.nvidia.com/aiperf
Zhong, Y., Liu, S., Chen, J., et al. “DistServe: Disaggregating Prefill and Decoding for Goodput-Optimized Large Language Model Serving.” OSDI, 2024。LLM serving 中 SLO-conditioned goodput 的第一手來源。usenix.org
NVIDIA Dynamo. “Engine Metrics Comparison.” 現行文件,存取日期 2026-08-29。Request、queue、KV、routing、transfer 與 speculation metric 的第一方跨引擎清單。docs.nvidia.com/dynamo
NVIDIA Dynamo. “Size a Local Deployment with AIConfigurator.” 現行文件,存取日期 2026-08-29。估算候選配置後,再對 live endpoint 驗證的第一方指南。docs.nvidia.com/dynamo
NVIDIA. “LLM Inference Benchmarking: Fundamental Concepts.” 2025 年 4 月。Metric 定義、workload length 與 throughput-latency trade-off 的第一方說明。developer.nvidia.com
閱讀順序
推論系統
章節 08 / 08
上一章
MoE 路由與專家平行部署
下一章
尚無可用文章
Huang Tzu Lin
With over eight years in autonomous robotics, there's a strong passion for incorporating cutting-edge technologies and innovative approaches. Dedicated to transforming the latest research and insights into practical applications, this journey pushes the limits of possibility.
