Skip to content

性能优化与容量规划

本页速览 延迟、吞吐、成本是推理服务的三角权衡。本文给出性能指标体系(P50/P99/QPS/TTFT/TPOT)、四层优化手段(模型→引擎→服务→系统)与容量规划方法。

性能优化与容量规划

一句话定义:性能优化是"在给定的延迟预算内把吞吐做到最大",容量规划是"算出要几台机器才能扛住目标流量"——两者共享同一套指标语言:P50/P99、QPS、TTFT/TPOT。

行业洞察:性能优化最容易犯的错是单点思维——"慢就优化模型"、"卡就加机器"。真实系统的瓶颈往往在排队、在序列化、在 IO、在调度,而不是在 GPU。业界经验是先测后调、自底向上:压测定位瓶颈所在层(模型/引擎/服务/系统),一次只动一层,每动一层都要有压测数据支撑。另一个残酷事实:没有容量规划的"弹性扩缩容"等于裸奔——你不知道该扩到几个,HPA 的阈值只是拍脑袋。

一、性能指标体系

1. 延迟分布:P50/P99/P999

指标含义怎么用
P50一半请求在此以下用户体验的中位数
P9999% 请求在此以下服务质量承诺(SLA)的主要锚点
P99999.9% 在此以下长尾故障排查(GC、冷启动、网络抖动)

为什么看 P99 不看平均

平均延迟会被少量极慢请求抬高,掩盖"大多数其实很快"的真相;而 P99 反映的是最差的那 1% 用户的体验。SLO 通常写成"P99 < 100ms,99% 时间达标"。平均延迟几乎从不进 SLA。

2. 吞吐与并发

  • QPS(Queries Per Second):每秒处理的请求数,容量规划的基本单位;
  • 并发(concurrency):同时正在处理的请求数。两者关系:并发 = QPS × 平均延迟(Little's Law,容量规划的核心公式,见第四节);
  • GPU 利用率:注意区分"算力利用率"与"带宽利用率"——推理常出现算力利用率低但带宽跑满的情况,这不叫"闲置"。

3. LLM 特有的延迟拆分

  • TTFT(Time To First Token):从请求到首个 token 的时间(用户感知的"反应速度");
  • TPOT(Time Per Output Token):每生成一个 token 的平均耗时(用户感知的"打字速度");
  • 整体延迟 = TTFT + TPOT × 生成长度,LLM 优化必须分开看。详见 大模型推理优化

二、瓶颈定位:先判断谁在拖后腿

1. 四象限分类

text
        算力不足                         带宽不足
   GPU 算力利用率 90%+            GPU 算力利用率低、但带宽/显存传输高
   ─────────────────────────────────────────────────────────────
   计算耗时随 batch 增大超线性      计算耗时随模型体积线性增长

        服务端瓶颈                         IO 瓶颈
   请求大量排队、CPU 打满           网络/磁盘/对象存储耗时占比高
   CPU 火焰图显示序列化/调度开销     nvidia-smi 显示 GPU 空闲

2. 三招定位工具

工具看什么
nvidia-smi / nvidia-smi dmon显存占用、SM 利用率、温度、功耗(确认 GPU 在干活)
perf / py-spy / 火焰图CPU 侧在忙什么(序列化?GIL?调度?)
应用侧打点(OpenTelemetry)各阶段延迟占比(模型 vs 排队 vs 网络,见 监控与可观测性

别被 nvidia-smi 骗了

nvidia-smi 的"GPU-Util"默认是 SM 利用率,推理小 batch 时可能只有 5%,但这不代表卡坏了或没优化空间——它是 memory-bound,带宽才是瓶颈。看带宽要配合 ncu(Nsight Compute)分析。

三、四层优化:模型 → 引擎 → 服务 → 系统

text
第 4 层 系统层:连接池、IO 多路复用、缓存、网卡调优
第 3 层 服务层:batching、结果缓存、并发模型、请求调度
第 2 层 引擎层:图优化、TensorRT/TVM 编译、kernel 特化
第 1 层 模型层:量化、蒸馏、剪枝、模型结构

优化顺序经验:从下往上容易,从上往下便宜。模型层的量化可能带来 2~4 倍收益且改动最小,应该最先做;系统层调优收益常在 10~30%,但边际递减。下面重点讲服务层与系统层(模型层见 量化蒸馏、剪枝与低秩分解,引擎层见 推理:从前向传播到推理引擎)。

1. batching:在线推理的第一大法宝

  • 静态批处理:调用方攒够 N 条再发,简单但延迟不可控;
  • 动态批处理(dynamic batching):服务端把几毫秒窗口内到达的请求合并成一个 batch,交给 GPU 一次算完——Triton 内置特性,小模型吞吐可提升 5~10 倍;
  • 连续批处理(continuous batching):LLM 专用,逐 token 粒度调度,GPU 永不空等,详见 大模型推理优化
text
动态 batching(非 LLM):
t=0ms  请求A │
t=2ms  请求B │──► batch(A,B,C) 一次前向 ──► 三个响应
t=4ms  请求C │         (batch_timeout=10ms)

2. 结果缓存与相似请求合并

  • 精确缓存(model, input) 完全相同的请求直接返回缓存结果。推荐场景命中率可达 30~50%(热门商品/重复查询);
  • 相似合并(dedup):embedding 距离 < 阈值视为相似,取缓存代表值——收益大但需谨慎(影响正确性,要设业务容忍度)。

3. 并发模型:异步是王道

  • 用异步 IO(asyncio)而不是"每请求一线程 + 阻塞调用";
  • 推理调用尽量走服务端 batching,别让一个请求独占一次模型调用;
  • 多模型共享实例时,按模型队列隔离,避免一个慢模型拖垮全部(见 NVIDIA Triton 多模型服务)。

4. 系统层:容易被忽略的 10~30%

  • 连接池复用(HTTP keep-alive / gRPC channel 复用),避免每请求建连;
  • 序列化优化:JSON → Protobuf 或消息压缩(gzip),大 payload 时收益显著;
  • 日志/指标异步化:别让同步日志阻塞请求路径;
  • 网卡/内核调优:TCP BBR、缓冲区调优,跨区域部署时网络常常是 P99 的大头。

四、容量规划:从估算到扩缩容

1. 核心公式(Little's Law)

text
并发(同时在飞请求数)= QPS × 平均延迟(秒)

例:目标 1000 QPS,平均延迟 50ms → 需要的并发 = 1000 × 0.05 = 50

单实例能扛多少并发 → 反推实例数:

text
实例数 = 目标并发 / 单实例实测并发 × 1.3(安全余量 30%)

2. 容量规划的完整流程

text
① 压测:对单实例加压,测出延迟-吞吐曲线(P99 达标时的最大 QPS)
   (压测方法见 /practice/load-testing)
② 换算:目标 QPS ÷ 单实例最大 QPS × 1.3 = 所需实例数
③ 打点监控:实时 QPS、P99、并发,验证公式
④ 扩缩容:HPA 按 CPU/自定义指标伸缩;GPU 服务优先按 QPS 或队列深度
⑤ 预留尖峰:双十一/大促按峰值 QPS 而非均值规划

压测的三个要点

  1. 真实流量分布(生产流量回放),别用均匀假流量;
  2. 测 P99 而非平均——P99 达标才是 SLA;
  3. 压到"P99 开始恶化"的拐点,记录该点 QPS,那就是容量上限。完整方法见 压测与容量规划

3. 弹性策略

伸缩维度指标建议
水平伸缩请求队列深度 / QPS / 自定义指标队列有界、缓慢扩容快速缩容(避免抖动)
GPU 专用vGPU 或整卡模型小可用 MIG/vGPU 分卡
时间规划按流量曲线预设 cron 扩缩大促前 1 小时提前扩容

4. 优化优先级建议

text
第 1 步:压测定位瓶颈层(别猜)
第 2 步:模型层(量化)—— 收益最大、改动最小
第 3 步:服务层(batching、缓存、异步化)
第 4 步:引擎层(TensorRT 编译)—— 深度特化
第 5 步:系统层(连接池、网络、IO)
第 6 步:容量规划 + 弹性伸缩 —— 让资源跟着流量走

每步之后都重新压测,量化收益;一次只动一层,否则收益归因不清。

权衡与取舍

决策点选项怎么选
延迟 vs 吞吐小 batch 保延迟 / 大 batch 保吞吐在线保 P99 延迟,离线放量冲吞吐
优化 vs 加机器调优省钱费时 / 加机器省时费钱先量化+服务层调优,再考虑扩容
P50 vs P99优化中位数 / 优化长尾SLA 锚定 P99,长尾用专项排查
缓存收益 vs 正确性缓存快 / 保证新鲜看业务对新鲜度容忍度
扩缩容快 vs 稳激进缩容省钱 / 保守防抖动缩容要慢(防震荡),扩容要快(防超时)

一句话总结:性能是测出来的,不是调出来的——先立指标、再压测定位、按层优化、用 Little's Law 定容量,让每一分钱花在可衡量的收益上

延伸阅读

参考资料