推理性能优化¶
优化前先确定目标。交互式聊天更关心首 token 时间,批处理更关心总吞吐,Agent 同时受模型生成和工具等待影响。
四个核心指标¶
| 指标 | 含义 | 常见瓶颈 |
|---|---|---|
| TTFT | 请求到首 token 的时间 | 排队、长输入 prefill、冷启动 |
| TPOT | 首 token 后每个 token 的时间 | 显存带宽、批量、跨卡通信 |
| Throughput | 每秒总输出 token 或完成请求 | 批处理、调度、GPU 利用率 |
| E2E latency | 用户看到完整结果的时间 | 输入、输出长度、工具和重试 |
同时记录输入 token、输出 token、并发、模型、量化、硬件和版本。缺少这些维度的 tokens/s 无法复现。
理解 prefill 与 decode¶
- Prefill 一次处理输入上下文,通常偏计算密集。超长提示词会明显拉高 TTFT。
- Decode 逐 token 生成,通常偏显存带宽密集。并发与 KV Cache 决定能装多少活跃序列。
这解释了为什么“缩短上下文”常常比换更快 GPU 更有效,也解释了批量为什么能提升吞吐但可能损害单请求时延。
优化顺序¶
1. 删除无效 token¶
- 不重复发送固定文档、历史对话和工具说明。
- RAG 只放与问题相关、去重后的片段。
- 设置合理最大输出,要求结构化且简洁的结果。
- 对可复用长前缀启用供应商缓存或引擎 prefix caching。
2. 连续批处理¶
使用 vLLM/SGLang 等调度器动态合并不同时间到达的请求。增大批量前同时观察 P95 TTFT,吞吐提升不应把交互请求全部排队。
3. KV Cache 管理¶
- 限制最大上下文和活跃序列。
- 在支持时评测 FP8 KV Cache。
- 按短、长请求分队列,避免长请求阻塞短请求。
- 缓存命中率低时,不要为 prefix cache 保留过多显存。
4. 权重量化¶
| 方法 | 典型场景 | 取舍 |
|---|---|---|
| FP8/INT8 | 数据中心新 GPU、高质量要求 | 约 2 倍权重压缩,质量通常接近基线 |
| AWQ/GPTQ 4-bit | NVIDIA 生产推理 | 约 4 倍压缩,需要校准或预量化权重 |
| GGUF Q4/Q5/Q8 | 本地、CPU/GPU 混合 | 硬件覆盖广,量化等级选择丰富 |
| bitsandbytes 4/8-bit | 快速实验、QLoRA | 易用,但推理加速不保证 |
Hugging Face 量化选型明确提醒:量化后必须在自己的任务和硬件上同时评测质量和速度。显存更小不一定代表延迟更低。
5. 并行策略¶
- Tensor parallel:单层跨多卡切分,适合模型单卡放不下;依赖高速互联。
- Pipeline parallel:不同层放在不同卡,可能产生流水线气泡。
- Data parallel:每卡完整副本,提高独立请求吞吐;需要每卡能放下模型。
- Expert parallel:MoE 专用,专家跨卡分布;通信模式与 dense 模型不同。
优先选择“单卡能放下就单卡”。多卡不是免费显存,通信会增加复杂度和时延。
6. 推测解码¶
用小 draft 模型或多 token 预测提出候选,再由目标模型验证。接受率高时能降低 decode 延迟,任务分布变化时收益可能消失。上线前比较:接受率、总吞吐、P95 和额外显存。
7. 编译和专用内核¶
FlashAttention、CUDA Graph、torch.compile、TensorRT-LLM、Marlin 等可带来收益,但高度依赖模型、GPU、形状与版本。一次只改变一个变量,保存可回退配置。
基准测试矩阵¶
至少覆盖:
| 维度 | 建议点位 |
|---|---|
| 输入长度 | 512、2K、8K、目标 P95、最大允许值 |
| 输出长度 | 32、256、目标 P95、最大允许值 |
| 并发 | 1、2、4、8,直到错误或 P95 超标 |
| 请求类型 | 短问答、长文档、结构化输出、工具调用 |
| 模型配置 | 基线精度、候选量化、缓存开关 |
每轮先预热,报告均值和 P50/P95/P99,不只报告最好一次。
性能排障¶
TTFT 高
-> 是否排队?
-> 输入是否过长?
-> prefix cache 是否命中?
-> prefill 是否被小 batch 或跨卡通信限制?
TPOT 高
-> GPU 利用率与显存带宽是否饱和?
-> batch 是否过小?
-> 量化内核是否真正启用?
-> tensor parallel 是否跨慢速 PCIe?
吞吐低但 GPU 空闲
-> CPU tokenizer、网络、序列化或同步工具是否阻塞?
-> 服务是否错误地串行处理?
-> 请求是否频繁冷启动或加载 adapter?
十步性能实验¶
- 固定模型 revision、引擎镜像、启动参数、GPU 时钟和功率上限。
- 保存真实流量的输入/输出 token 分布,不用单一固定长度代替。
- 单并发预热,确认质量和输出 token 数没有异常变化。
- 建立无量化、无特殊优化的可解释基线。
- 分别扫描并发、输入长度和输出长度,找到第一次违反 SLO 的点。
- 同步采集网关队列、CPU、GPU、显存、网络和引擎 scheduler 指标。
- 每次只改变一个高影响变量,至少重复三轮。
- 对量化和推测解码重新跑质量集,不默认结果等价。
- 计算每成功任务成本,而不是只比较峰值吞吐。
- 保存胜出配置、失败配置和回滚命令。
从指标形状判断瓶颈¶
| 观测 | 更可能的瓶颈 | 下一步验证 |
|---|---|---|
| TTFT 随输入长度近似线性增长 | prefill 计算或输入过长 | 缩短上下文、拆分长短队列 |
| TTFT 随并发陡增,TPOT 稳定 | 排队或调度上限 | 看 queue time、限制 admission |
| TPOT 高且 GPU 显存带宽接近饱和 | decode 带宽 | 测量化、增大有效 batch |
| GPU 利用率低且 CPU 满载 | Tokenizer/序列化 | 并行 tokenizer、批量处理 |
| 多卡比单卡更慢 | 通信或切分不当 | 查看拓扑,降低 TP 或换高速互联 |
| P50 正常但 P99 极差 | 长请求干扰或周期性抖动 | 按长度分队列,关联 GC/缓存/网络 |
实验记录样例¶
experiment: exp-017-fp8-kv
baseline: exp-012-bf16-kv
immutable:
model_revision: "<commit>"
engine_image: "<digest>"
gpu: "H200-SXM"
workload: "traffic-2026-09-01-v3"
change:
kv_cache_dtype: fp8
result:
task_pass_rate_delta_pp: -0.2
ttft_p95_delta_pct: -3.1
throughput_delta_pct: 18.4
peak_vram_delta_gib: -21.7
decision: accept_after_high_risk_review
优化停止条件¶
当候选已满足业务 SLO,继续追求实验室峰值可能增加维护风险。出现以下任一情况应暂停:质量下降触碰门禁;收益小于测量噪声;需要使用未获支持的内核;回滚时间超过目标;运维团队无法观察新增机制。
核心原则:性能问题必须先定位发生在排队、prefill、decode、工具还是网络。把端到端慢统一归因于“模型太大”,会把工程时间花在错误位置。