这篇文章解决什么问题

前两篇 NVIDIA Triton 文章解决了两个基础问题:

  • 服务能否被部署起来。
  • 请求进入服务后,调度层如何通过 dynamic_batchinginstance_group 改变执行方式。

但到这一步,仍然缺最关键的一环:怎样验证配置到底有没有变好。

如果没有一套稳定的压测和解释方法,所谓调优很容易退化成试参数和看感觉。尤其在 Triton 这类服务系统中,很多配置项都会同时影响吞吐和延迟,如果没有统一实验方法,结论往往不可比。

这篇文章要解决的就是这个问题:

如何用 perf_analyzer 建立一套“基线 -> 改一个变量 -> 重测 -> 解读结果”的工程闭环。

重点不只是命令本身,而是:

  • 压测前应固定哪些条件。
  • 输出结果里的关键字段该怎么看。
  • 怎样把一次实验变成可解释、可复用的结论。

为什么 perf_analyzer 很重要

NVIDIA Triton 的调优和普通脚本性能测试有一个很大区别:很多变化不发生在模型内部,而发生在服务层。

例如:

  • 请求是否被合批;
  • 等待队列是否开始积压;
  • 实例副本是否把请求分流得更碎;
  • 协议层和服务层是否已经影响整体吞吐。

这些都不能只靠单个模型前向时间去判断。也正因为如此,Triton 需要一个面向服务行为的压测工具,而 perf_analyzer 就是这套流程里的标准入口之一。

它的价值不在于“能跑压测”,而在于:

  • 它直接面向 Triton 服务协议;
  • 它能按并发级别扫描;
  • 它能输出吞吐和延迟指标;
  • 它适合做配置改动前后的对比。

压测前先固定实验条件

在写任何命令之前,先把实验边界固定住。否则结果很难比较。

至少要固定四件事:

1. 模型版本

模型文件和 config.pbtxt 必须明确是哪一版。只要模型本身变了,前后的结果就不能简单放在同一表里。

2. 输入形状

对 Triton 服务来说,输入形状不仅影响模型执行时间,还会影响 batch 语义和调度行为。压测时必须显式写清楚输入 tensor 形状。

3. 协议

HTTP 和 gRPC 的协议开销不同。压测时需要固定当前测的是哪一种,而不是混着比较。

4. 测量时长

瞬时结果波动很大。需要给每个并发级别一个足够稳定的测量窗口,避免把偶然值当成结论。

如果这四件事没有先固定,后面看到任何吞吐和延迟数字都很难解释。

一条最小压测命令

对上一篇的 simple_mlp 服务,可以从一条最小命令开始:

1
2
3
4
5
6
perf_analyzer \
-m simple_mlp \
-u localhost:8000 \
--protocol http \
--shape input:1,128 \
--measurement-interval 5000

这条命令的作用是:

  • 模型名指定为 simple_mlp
  • 通过 HTTP 访问 localhost:8000
  • 输入 tensor 形状为 [1, 128]
  • 每个测量窗口持续 5000ms

它不是完整实验,只是建立一条最小可用的压测路径。真正有意义的分析,通常要进一步扫描并发区间。

为什么并发扫描比单点压测更有用

如果只测某一个固定并发值,例如 concurrency=1,你只能知道服务在这个点上的表现,却看不到更关键的趋势:

  • 吞吐是否还在持续上升;
  • 吞吐何时进入平台期;
  • 延迟何时开始明显恶化;
  • 当前配置更像是吞吐受限,还是排队受限。

因此,更有用的做法通常是扫描一个并发范围。

例如:

1
2
3
4
5
6
7
8
perf_analyzer \
-m simple_mlp \
-u localhost:8000 \
--protocol http \
--shape input:1,128 \
--concurrency-range 1:16:2 \
--measurement-interval 5000 \
--percentile 95

这条命令的含义是:

  • 从并发 1 扫到并发 16
  • 步长为 2
  • 同时输出 p95 延迟

这样得到的就不再是一个点,而是一整条趋势线。

输出结果里最重要的字段是什么

一次扫描之后,最核心的通常是这几类字段:

  • Concurrency
  • throughput
  • latency
  • p95 / p99

可以把它们分别理解成:

Concurrency

客户端同时在飞的请求数。它不是 batch size,而是并发压测客户端的负载级别。

throughput

每秒完成的推理次数。它回答的是“服务整体干了多少活”。

latency

请求从发出到收到结果的耗时。它通常会包含排队、调度和执行等整个路径的成本。

p95 / p99

尾延迟指标。相比平均值,它们更能反映高负载下的服务体验。

这也是为什么在服务调优里,不能只盯平均延迟。平均值可能还算平稳,但 p95p99 已经开始恶化。

一条更实用的读数顺序

面对一组压测结果,建议按下面顺序解释,而不是直接找“最大吞吐”。

第一步:看吞吐是否还在增长

如果并发继续增加,而吞吐仍然稳定增长,说明系统还没有接近饱和点。

如果吞吐开始进入平台期,说明再继续加并发,收益已经有限。

第二步:看平台期之后延迟怎么变

吞吐进入平台期并不代表结果已经理想。真正关键的是,这之后的 p95 / p99 是否开始明显恶化。

如果吞吐几乎不再增长,但尾延迟持续上升,说明请求更多是在排队,而不是换来更高的有效处理能力。

第三步:找“工作点”

所谓工作点,不一定是吞吐峰值点,而是某个吞吐和尾延迟之间更平衡的位置。

服务最终采用哪个工作点,取决于业务目标,而不是压测工具本身。

一次最小对比实验应该怎么做

真正的调优不应该是一口气改很多配置再看结果,而应该是“改一个变量、保留其他条件不变”。

一个最小对比实验,至少应包含下面四步。

1. 记录 baseline

例如:

  • 不开 dynamic_batching
  • instance_group.count = 1

这是后续所有判断的起点。

2. 只改一个变量

例如只打开:

1
2
3
4
dynamic_batching {
preferred_batch_size: [ 4, 8, 16 ]
max_queue_delay_microseconds: 5000
}

除此之外,其余条件全部不变。

3. 用同一条压测命令重测

输入形状、协议、并发范围、测量时长都保持一致。否则对比没有意义。

4. 把结果写成对比表

例如:

1
2
3
4
5
6
7
配置A: baseline
配置B: 开启 dynamic batching

比较:
- throughput 是否提升
- avg latency 是否变化
- p95/p99 是否恶化

只有走完这四步,才算完成一次可解释的实验。

dynamic_batching 的结果通常该怎么解读

如果开启 dynamic_batching 后出现下面这种现象:

  • 吞吐明显提升
  • 平均延迟变化不大
  • p95 / p99 上升

这通常说明:

  • 服务确实从合批中获益;
  • 但队列等待已经开始对尾延迟产生影响。

这并不等于配置错误,而是合批机制本来就存在的典型权衡。

相反,如果开启之后:

  • 吞吐几乎没变
  • p95 / p99 反而变差

那通常意味着当前流量模式不足以支撑合批收益,或者等待窗口设置得不合适。

instance_group 的结果通常该怎么解读

如果把 instance_group.count 从 1 改成 2,常见现象可能有两类。

情况一:吞吐上升

这通常说明单实例原本没有把资源利用好,增加实例后并发接纳能力改善了。

情况二:吞吐不升反降,尾延迟还变差

这通常说明请求被分散到更多实例后,每个实例拿到的 batch 反而更碎,资源竞争和请求分流抵消了收益。

这也是为什么前一篇强调过:instance_group 不能孤立调,而要和 dynamic_batching 一起看。

为什么 p95 / p99 往往比平均值更重要

在低负载时,平均延迟可能已经能说明问题。但只要进入更接近饱和的区间,平均值就很容易掩盖尾部问题。

一个典型现象是:

  • 大多数请求还算快;
  • 只有一小部分请求开始排长队;
  • 平均值仍然不算夸张;
  • p99 已经明显恶化。

对在线服务来说,用户往往更容易感知这部分尾部请求,因此 p95 / p99 通常比平均值更适合作为调优判断依据。

一个更完整的工程闭环

把前面这些压缩一下,可以得到一套更稳妥的工程流程:

  1. 明确目标:吞吐优先还是时延优先。
  2. 固定实验条件:模型、输入、协议、测量时长。
  3. 建立 baseline。
  4. 每次只改一个变量。
  5. 重新压测并记录 throughputavg latencyp95p99
  6. 结合调度机制解释现象,而不是只记数字。
  7. 最终给出“在当前场景下更合适的配置”。

这就是一条最小但完整的 Triton 调优闭环。

常见误区

误区一:只跑一次单点压测

单点结果缺少趋势信息,很难看出服务何时饱和、延迟何时恶化。

误区二:一次改很多参数

如果同时改了等待窗口、实例数、批大小偏好,再看到结果变化时,往往无法判断到底是谁起了作用。

误区三:只看吞吐峰值

吞吐峰值并不自动等于最佳工作点。如果尾延迟已经明显失控,这个点可能并不适合真实在线业务。

结论

perf_analyzer 的价值,不只是帮 Triton 服务“跑一下压测”,而是让调优过程从直觉试参变成可解释的工程实验。

真正有用的结果,不是某一个漂亮数字,而是一套明确结论:

  • 在什么输入和协议条件下测得;
  • 改了哪个变量;
  • 吞吐为什么变化;
  • 尾延迟为什么变化;
  • 最终应该选择哪个工作点。

到这里,NVIDIA Triton 这条线也形成了一个完整闭环:

  • 基础部署;
  • 调度机制;
  • 压测与解释。

下一篇会把整个系列收束到项目化表达上,讨论怎样把这套学习过程组织成一个可展示、可讲述的最小项目。

系列导航