直接回答
LLM 推理 不只是一次模型前向传播。生产请求会依次经过校验、版本化 Chat Template 渲染、Tokenization、排队、Prefill、逐 Token Decode、停止判断、Detokenization、返回或流式发送和计量。模型参数在单次请求中通常不更新,但请求仍依赖 KV Cache、Scheduler Queue、Adapter 和生成参数等可变服务状态。
指标必须从明确边界计算。用户可见的首 Token 延迟(TTFT)可能包含 Gateway、网络、Queue、Tokenization 和 Prefill;Engine 指标可能从更晚位置开始。TPOT、ITL、端到端延迟、Token 吞吐量与满足 SLO 的 Goodput 回答的是不同问题。只有固定模型版本、负载分布、Cache 状态和测量边界,优化结论才可比较。
追踪端到端请求链路
用户看到文本前,请求的数据形态会多次变化:
每个边界都可能失败或增加延迟:
| 阶段 | 输入 | 输出 | 常见失败信号 |
|---|---|---|---|
| API 与 Policy | Messages、Model Alias、生成选项 | 已授权的规范化请求 | 4xx、Quota 拒绝、租户不匹配 |
| Template 与 Tokenizer | Messages、Template Revision | Token ID、Mask、Stop Token ID | 上下文溢出、Tokenizer 漂移、Role 顺序非法 |
| Queue 与 Admission | Token Demand、Priority、Cache Demand | 已调度 Sequence | Queue 长尾、饥饿、容量拒绝 |
| Prefill | 输入 Token | 首次 Logits、初始 KV 状态 | Prompt Latency 高、队头阻塞 |
| Decode | 最新 Token、保留状态 | Next-token Logits、新增状态 | ITL 高、Preemption、OOM |
| 后处理 | Token ID、Finish Reason | 文本 Chunk、Usage | Unicode 边界破损、Stop 不一致、Stream 截断 |
GPU 阶段很关键,但只看 GPU Trace 无法解释 Gateway 延迟、客户端 Buffer 或一次已经重复执行工作的 Retry。
调优前先冻结推理契约
如果两次运行之间的 Execution Identity 改变,性能和输出就失去可比性。不要只记录一个 Model Alias,应保存不可变契约:
{
"modelId": "provider/model-name",
"modelRevision": "immutable-revision",
"tokenizerRevision": "immutable-revision",
"chatTemplateVersion": "chat-v4",
"adapterId": "tenant-a/support-lora@revision",
"engineVersion": "pinned-version",
"generation": {
"maxNewTokens": 256,
"temperature": 0.2,
"topP": 0.95,
"seed": 42,
"stop": ["</final>"]
},
"stream": true
}
还要记录量化格式、Weight/KV Dtype、并行拓扑、Attention Backend、Speculative 配置、Prefix Cache Policy 和硬件。固定 Seed 只能约束一种随机来源,不能保证跨软件版本、Kernel、设备或分布式调度获得 Bitwise Equivalent 输出。
把 Template 与 Tokenization 当作模型输入
Chat Request 不会以 JSON Object 直接进入模型。服务层先通过 Chat Template 序列化 Role、Tool Call、System Instruction 和 Separator,再由 Tokenizer 把文本映射为 ID。Template 或 Tokenizer Revision 会改变:
- Prompt Length 与 Context Window 是否越界;
- Stop Token 行为和控制 Token 是否外露;
- Prefix Cache Identity 与命中率;
- Tool Call Syntax;
- 即使权重不变,输出分布也可能改变。
在 Staging 验证完整 Rendered Prompt 与 Token Count。敏感内容不应默认写入日志;优先保存脱敏元数据、Hash、Revision ID 与长度分布。
Prefill 与 Decode 不等于绝对瓶颈
Prefill 并行处理已知输入位置,并建立初始 KV Cache。对标准 Full-attention Transformer,Attention 部分随 Prompt Length 二次增长,而 Projection 与 MLP 仍包含大量线性项;Sliding Window、Sparse、Recurrent 或 Hybrid Architecture 会改变复杂度。
Decode 是自回归过程:每个已接受 Token 成为下一步输入。启用 KV Cache 后,引擎只为新位置计算 Projection,但仍需读取保留状态与模型权重。小 Batch 时 Decode 常受显存带宽限制;更大 Batch、MoE Routing、长 Attention、通信或 Speculative Verification 都可能改变瓶颈。
| 维度 | Prefill | Decode |
|---|---|---|
| 主要序列工作 | 处理输入位置 | 产生已接受输出位置 |
| 并行性 | 多个已知位置 | 已接受步骤间存在依赖 |
| 状态 | 创建或扩展 KV | 读取并追加 KV |
| 用户侧信号 | TTFT 的重要组成 | ITL 与 TPOT 的重要组成 |
| 常见压力 | Compute、长 Prompt Queue | Weight/KV Read、Batch 与 Scheduler Contention |
| 关键边界 | 不一定始终 Compute-bound | 不一定始终 Bandwidth-bound |
应使用 Profiler 和 Engine Metrics 识别当前约束。“Compute-bound”只是待验证的假设,不能直接当成容量计划。
Decoding Strategy 会改变行为与成本
模型产生 Logits,Decoding Strategy 决定如何选择 Token,以及执行多少 Candidate Sequence 或 Verification Pass。Hugging Face 的 Generation Strategies 区分 Greedy、Sampling、Beam Search 与 Custom Generation Loop:
- Greedy 选择分数最高的 Next Token;
- Sampling 从 Temperature、Top-p、Top-k 等参数变换后的分布采样;
- Beam Search 同时保留多个 Candidate,可能成倍增加状态与计算;
- Speculative Decoding 用另一种方法提出 Token,再由 Target Model 验证,收益取决于 Acceptance Rate 与实现。
生成参数属于产品契约。用 Greedy Benchmark、上线 Sampling,并不代表测量了生产负载。如果更快的配置改变 Structured Output Validity、Task Success、Safety Behavior 或 Finish Reason,就不是等价优化。
调度把独立请求变成共享负载
Continuous Batching 允许引擎在 Iteration 边界接纳新 Sequence、移除已完成 Sequence,而不是等待固定 Batch 全部结束。它可以提高聚合利用率,但不会消除权衡:
- 长 Prefill 可能拖延活跃 Decode Sequence;
- 大 Active Batch 可能提高吞吐,同时抬高 TPOT;
- Preemption 释放容量,但可能引入 Recompute 或 Transfer;
- Priority 保护一个租户时,可能让另一个租户饥饿;
- Prefix Hit 会改变实际计算的 Prefill Token 数;
- Chunked Prefill 可减少阻塞,但会改变调度与延迟分布。
Admission 应基于 Token 与 Memory Demand,而不是请求数。工作进入 GPU 前,必须限制 Prompt Token、请求输出 Token、Parallel Sample、Adapter Residency 与 Tenant Concurrency。
在明确边界定义指标
当前 vLLM Production Metrics 同时暴露 Queue、Prefill、Decode、TTFT、请求级 TPOT、ITL、E2E、Token Count、Preemption 和 KV Usage;其他引擎可能使用不同名称或区间。
| 指标 | 可操作定义 | 容易隐藏什么 |
|---|---|---|
| TTFT | 请求边界到首个已交付输出 Token | Gateway、Tokenization、Queue、Prefill、Buffering |
| TPOT | 请求级 (末 Token 时间 - 首 Token 时间) / (输出 Token 数 - 1) |
单个 Token Gap 的抖动 |
| ITL | 每两个相邻 Token 的交付间隔 | 请求级平均值与输出长度 |
| E2E Latency | 请求边界到终态响应 | 不同输出长度与 Finish Reason |
| Request Throughput | 每秒完成请求数 | Prompt/Output Size 与失败工作 |
| Output Throughput | 每秒生成输出 Token 数 | 延迟长尾与 Prompt 处理 |
| Goodput | 满足既定 SLO 的每秒请求数 | 质量、正确性和业务成功 |
按场景报告 p50、p90、p95 或 p99,不能只有 Mean。必须说明 Timestamp 来自 Client、Gateway、API Process、Scheduler 还是 Model Executor。非流式响应在 Client 侧无法直接观察首 Token 的交付区间。
运行边界明确的 Trace 计算
下面的无依赖示例计算 Client-observed 指标。TPOT 只使用首 Token 之后的间隔;单 Token 响应没有后续间隔,因此返回 0。
from __future__ import annotations
from dataclasses import dataclass
from math import ceil
from statistics import median
@dataclass(frozen=True)
class RequestTrace:
arrived_s: float
first_token_s: float
last_token_s: float
completed_s: float
output_tokens: int
def metrics_ms(self) -> dict[str, float]:
if not (
0
<= self.arrived_s
<= self.first_token_s
<= self.last_token_s
<= self.completed_s
):
raise ValueError("Trace timestamps must be monotonic")
if self.output_tokens <= 0:
raise ValueError("output_tokens must be positive")
ttft = self.first_token_s - self.arrived_s
e2e = self.completed_s - self.arrived_s
decode_intervals = self.output_tokens - 1
tpot = (
(self.last_token_s - self.first_token_s) / decode_intervals
if decode_intervals
else 0.0
)
return {
"ttft_ms": ttft * 1000,
"tpot_ms": tpot * 1000,
"e2e_ms": e2e * 1000,
}
def nearest_rank(values: list[float], percentile: int) -> float:
if not values or not 0 < percentile <= 100:
raise ValueError("Provide values and a percentile in (0, 100]")
ordered = sorted(values)
return ordered[ceil(percentile / 100 * len(ordered)) - 1]
traces = [
RequestTrace(0.0, 0.6, 1.4, 1.45, 5),
RequestTrace(0.2, 1.0, 2.8, 2.85, 10),
RequestTrace(0.4, 1.6, 2.2, 2.25, 4),
]
try:
metrics = [trace.metrics_ms() for trace in traces]
ttfts = [item["ttft_ms"] for item in metrics]
tpots = [item["tpot_ms"] for item in metrics]
e2es = [item["e2e_ms"] for item in metrics]
duration = max(trace.completed_s for trace in traces) - min(
trace.arrived_s for trace in traces
)
output_tps = sum(trace.output_tokens for trace in traces) / duration
print(f"TTFT p50/p95: {median(ttfts):.0f}/{nearest_rank(ttfts, 95):.0f} ms")
print(f"TPOT p95: {nearest_rank(tpots, 95):.0f} ms")
print(f"E2E p95: {nearest_rank(e2es, 95):.0f} ms")
print(f"Aggregate output throughput: {output_tps:.2f} tokens/s")
except ValueError as error:
raise SystemExit(f"Invalid trace set: {error}") from error
# TTFT p50/p95: 800/1200 ms
# TPOT p95: 200 ms
# E2E p95: 2650 ms
# Aggregate output throughput: 6.67 tokens/s
这只是计算示例,不是统计有效的 Benchmark。生产比较需要足够请求量,才能获得稳定 Tail 与 Confidence Interval。
为所有显存消费者建立容量预算
不能把每次 OOM 都归因于 KV Cache。Serving Worker 的 Device Budget 包含:
device_memory =
model_weights
+ kv_state
+ activations_and_workspaces
+ communication_buffers
+ graph_or_compile_reservations
+ adapters
+ allocator_fragmentation
+ safety_headroom
Model Quantization 可以释放 Weight Memory,却不一定缩小 KV State;KV Quantization 修改的是另一块分配,还可能影响质量或 Kernel Support。Tensor Parallelism 可以让模型装入设备,但会引入通信。Offload 用传输延迟换 Device Memory。应在真实 Prompt/Output 分布、并发、Prefix Reuse、Adapter 和 Speculative Branch 下测量 Peak Allocated 与 Reserved Memory。
把症状映射到正确控制项
| 现象 | 应检查的证据 | 候选控制项 | 必需回归门禁 |
|---|---|---|---|
| TTFT 长尾上升 | Client/Gateway/Queue/Prefill 拆分、Prompt Length | Admission、Routing、Prefix Reuse、Chunked Prefill、Prefill Capacity | Output 与 Cache Identity 一致性 |
| ITL 或 TPOT 上升 | Active Sequence、Batch Token、Decode Time、Preemption | Batch Budget、Parallelism、低精度、Speculative Decoding | Token/Logit 或 Task Quality |
| Throughput 停止增长 | Offered Load、Waiting Request、GPU/通信 Profile | Continuous Batching、Replica、Topology、Scheduler | p95/p99 Latency 与 Fairness |
| OOM 或 Eviction | Weight/KV/Workspace Peak、Fragmentation | Token Admission、Paging、Quantization、Offload | 无陈旧或跨租户复用 |
| Output Drift | Revision、Template、Tokenizer、Generation Config | 回滚发生变化的契约组件 | Golden 与 Adversarial Eval |
| Stream Stall | Engine Token Event 与 Gateway/Client Delivery | Buffering、Backpressure、Timeout、Cancellation | 无重复计费或 Retry |
优化依赖负载,不能复制另一种模型、上下文形态、Engine Release 或 GPU 的吞吐倍率。
评测分布,而不是英雄数字
可信 Serving Benchmark 至少记录:
- 不可变 Model、Tokenizer、Template、Adapter、Engine、Driver、Kernel、Hardware 与 Topology Revision;
- Prompt/Output Token 分布、Multimodal Size、生成参数与 Finish Reason;
- Open-loop Arrival Rate 或 Closed-loop Concurrency,包括 Burstiness 与 Warm-up;
- Cold/Warm Model State 和 Prefix Cache Policy;
- TTFT、TPOT、ITL、E2E、Request/Output Throughput、Error、Preemption 与显存分位数;
- Quality、Structured-output Validity、Safety 与 Application Success;
- 随 Offered Load 变化的 SLO-qualified Goodput。
vLLM Benchmark 实现会报告 TTFT、TPOT、ITL、E2E、Request Throughput、Output Throughput、Total-token Throughput、Percentile 与可选 Goodput。CLI 和指标语义随版本演进,结果必须归档精确工具版本。DistServe 论文按同时满足 TTFT 与 TPOT 约束的最大 Request Rate 定义 Goodput;论文倍率只适用于其系统与负载。
保留质量与可复现性
即使 API 仍返回 HTTP 200,推理优化也可能改变输出:
- Weight、Activation 或 KV Precision 会改变 Logits;
- 新 Tokenizer 或 Template 会改变模型输入;
- Sampling 与 Speculative Acceptance 会引入路径依赖;
- Kernel、Compiler、并行 Reduction 与 Hardware 会改变 Floating-point Result;
- Stop Handling 可能截断内容或暴露控制 Token;
- Scheduler 变更可能暴露 Race、Timeout 或 Cancellation 行为。
PyTorch 的 Reproducibility Guidance明确指出:即使 Seed 相同,也不保证跨 Release、Commit、Platform 或 CPU/GPU 完全复现。调试时使用 Pinned Environment 与 Deterministic Control,生产验收仍要执行 Task-level 与 Output-distribution Evaluation。
强制执行失败与安全边界
- Queue Admission 前授权 Model 与 Adapter;
- 按兼容 Revision、Modality 与 Tenant Scope 隔离 Prefix/KV Reuse;
- 服务端强制限制 Prompt、Output、Parallel Sample、Timeout 与 Cost;
- Client Disconnect 时取消 GPU 工作,除非契约明确为异步 Job;
- 在 Billing 与 Job Layer 保证 Retry Idempotency;断开的 Stream 可能已经消耗 Token;
- 记录 Finish Reason、Token Count、Revision Identity 与 Trace ID,默认不记录敏感 Prompt;
- 测试非法 Role Sequence、超大请求、Stop String 边界、Unicode Chunk、Cancellation 与 Partial Failure。
生产决策清单
上线前确认:
- Execution Identity 是否不可变且可在 Trace 中观察?
- Client 与 Engine 的计时边界是否有文档?
- Load Test 是否复现 Prompt/Output Length、Arrival Rate、Burstiness 与 Cache State?
- p95/p99 TTFT、TPOT/ITL、E2E、Goodput、Error、Preemption 与 Memory 是否通过门禁?
- Task Quality、Structured Output、Safety 与 Finish Reason 是否匹配基线?
- Tenant Isolation、Quota、Cancellation、Retry 与 Billing 是否经过测试?
- 能否独立恢复上一版 Engine、Model、Template、Tokenizer、Adapter 与 Scheduler Config?
来源与相关内容
- Hugging Face:Generation Strategies — Greedy、Sampling、Beam 与 Custom Decoding 行为。
- Hugging Face:Generation Features — Streaming 与面向应用的生成能力。
- vLLM:Production Metrics — Queue、Prefill、Decode、TTFT、TPOT、ITL、E2E、Token 与 Cache 指标。
- vLLM:Metrics Design — Event Boundary、Preemption 与 Interval Calculation。
- DistServe — Prefill/Decode 分离下的 TTFT/TPOT SLO 与 Goodput。
- PyTorch:Reproducibility — Seed、Deterministic Algorithm、Platform 与 Version 边界。
- KV Cache 详解 — 状态容量、Prefix Cache、Paging、Quantization 与 Isolation。
- 分离式 LLM Serving — 何时及如何拆分 Prefill 与 Decode Pool。
- 模型量化指南 — Weight Precision、Format、Hardware 与 Quality Trade-off。