直接回答

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 状态和测量边界,优化结论才可比较。

追踪端到端请求链路

用户看到文本前,请求的数据形态会多次变化:

flowchart LR A["API 请求"] --> B["校验与授权"] B --> C["渲染 Chat Template"] C --> D["Tokenization"] D --> E["Queue 与准入"] E --> F["Prefill"] F --> G["初始 KV 状态"] G --> H["Decode 与 Sampling"] H --> I{"停止?"} I -->|"否"| H I -->|"是"| J["Detokenize 与交付"] J --> K["Finish Reason、Usage、Trace"]

每个边界都可能失败或增加延迟:

阶段 输入 输出 常见失败信号
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,应保存不可变契约:

json
{
  "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。

python
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 包含:

text
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 至少记录:

  1. 不可变 Model、Tokenizer、Template、Adapter、Engine、Driver、Kernel、Hardware 与 Topology Revision;
  2. Prompt/Output Token 分布、Multimodal Size、生成参数与 Finish Reason;
  3. Open-loop Arrival Rate 或 Closed-loop Concurrency,包括 Burstiness 与 Warm-up;
  4. Cold/Warm Model State 和 Prefix Cache Policy;
  5. TTFT、TPOT、ITL、E2E、Request/Output Throughput、Error、Preemption 与显存分位数;
  6. Quality、Structured-output Validity、Safety 与 Application Success;
  7. 随 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?

来源与相关内容