
1. 压测前先想清楚你到底要测什么很多人部署完 Triton 之后第一反应是“跑一下看看快不快”。但“快”是个很模糊的词。是单次请求快还是高并发下整体吞吐高还是 P99 延迟稳定这三个问题的答案往往互相矛盾。我见过太多团队上线前只测了一个并发数看到吞吐数字还不错就放心了结果生产环境一上量P99 延迟直接飙到几百毫秒用户端开始超时重试雪崩就这么来的。perf_analyzer 是 Triton 官方自带的性能压测工具随 Triton Client SDK 一起分发。它能做的事比大多数人以为的多固定并发压测、固定请求速率压测、扫描并发区间找吞吐天花板、输出延迟分位数报告、生成 CSV 供后续分析、支持 LLM 流式场景的 TTFT/TPOT 指标采集。这篇文章面向的是已经有一个能跑起来的 Triton 服务、准备做上线前性能基线测试的读者。你需要知道模型名、输入 tensor 名称和形状、Triton 的 gRPC 端口默认 8001。如果你还没有可用的 Triton 服务建议先把服务跑通再回来做压测。整篇的节奏是先讲清楚压测的目标和指标含义然后给出可复制的命令和配置接着演示一次完整的压测流程并解读结果最后把常见的坑和排查方法列出来。你可以直接照着操作也可以把命令改一改用到自己的模型上。2. 环境准备与 TaoToken 接入前置2.1 安装 perf_analyzerperf_analyzer 有两种获取方式。第一种是 pip 安装 Triton Client SDKpip install tritonclient[all]安装完成后perf_analyzer 可执行文件会在 Python 的 bin 目录下。如果提示找不到命令检查一下 PATH 是否包含了 pip 的 bin 路径。第二种方式是从 Triton 官方 Docker 镜像中直接使用适合不想在宿主机装依赖的场景docker run --rm nvcr.io/nvidia/tritonserver:24.05-py3-sdk \ perf_analyzer --help这种方式的好处是版本和 Triton Server 对齐避免客户端和服务端协议不匹配的问题。实测下来如果你的 Triton Server 用的是 24.05客户端也尽量用同版本。2.2 确认 Triton 服务可达在压测之前先用简单的 curl 或 tritonclient 确认服务在跑curl -v localhost:8001/v2/health/ready返回 200 就说明 gRPC 端口正常。如果这一步不通后面的压测命令全都会报连接错误。2.3 关于 API 接入与模型调用如果你在压测之外还需要做模型对话验证、coding plan 管理或者 API Key 的创建可以走 TaoToken 的对应入口。压测本身是本地行为不依赖外部 API但如果你要对比线上服务的表现可以先把本地基线跑出来再用同样的输入去线上做对照。模型对话入口https://taotoken.net/models?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_contentmodel_chatCoding Plan 入口https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_contentcoding_planAPI Keys 管理https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_contentapi_keys接入文档https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_contentdoc这些入口在后面的验证和排障环节会用到先放在这里备查。3. 可复制的 perf_analyzer 配置与命令3.1 基础压测命令假设你有一个名为 yolov8s 的模型输入 tensor 叫 images形状是 3x640x640Triton 跑在 localhost:8001。最基础的固定并发压测命令如下perf_analyzer -m yolov8s \ --concurrency-range 4 \ --shape images:3,640,640 \ --input-data random \ -u localhost:8001 \ --measurement-interval 10000这条命令的含义是以 4 个并发请求持续压测 10 秒输入数据用随机数生成。输出会包含吞吐量infer/sec和延迟分位数。3.2 关键参数速查下面这张表把最常用的参数和它们的实际作用列出来方便你按需组合参数作用示例-m模型名称-m yolov8s--concurrency-range并发数范围支持 start:end:step--concurrency-range 1:16:2-bBatch size-b 4--shape输入 tensor 形状--shape images:3,640,640--input-data输入数据生成方式random / zero / file:data.json-uTriton 服务地址-u localhost:8001-i使用 gRPC streaming-i grpc--measurement-interval每次测量持续时间(ms)--measurement-interval 10000--request-rate-range固定请求速率压测--request-rate-range 500:2000:500--percentile报告延迟分位数--percentile 95-f输出 CSV 报告-f results.csv--async异步请求模式--async--streaming流式输出模式LLM--streaming3.3 扫描并发找吞吐天花板单点压测只能告诉你“这个并发下表现如何”但你想知道的是“最优并发是多少”。这时候用 concurrency-range 做扫描perf_analyzer -m yolov8s \ --concurrency-range 1:32:2 \ --shape images:3,640,640 \ --input-data random \ -u localhost:8001 \ --measurement-interval 15000 \ -f latency_vs_concurrency.csv这条命令会依次以 1、3、5、…、31 的并发数各压 15 秒把结果写入 CSV。跑完之后你可以用 pandas 画图import pandas as pd import matplotlib.pyplot as plt df pd.read_csv(latency_vs_concurrency.csv) fig, ax1 plt.subplots() ax1.plot(df[Concurrency], df[Throughput], b-o, labelThroughput) ax1.set_xlabel(Concurrency) ax1.set_ylabel(Throughput (infer/sec), colorb) ax2 ax1.twinx() ax2.plot(df[Concurrency], df[p99 Latency], r-s, labelP99 Latency) ax2.set_ylabel(P99 Latency (us), colorr) fig.legend() plt.show()你要找的是吞吐曲线开始变平缓的那个点。在那之前增加并发能换来吞吐增长在那之后并发增加只会让延迟上升吞吐几乎不变。3.4 对比不同 batch size 和精度如果你在选型阶段可以用脚本批量对比for batch_size in 1 4 8 16; do for fp_mode in fp16 int8; do echo Testing batch$batch_size, $fp_mode perf_analyzer -m resnet_${fp_mode} \ -b $batch_size \ --concurrency-range 8 \ --input-data random \ -f result_b${batch_size}_${fp_mode}.csv done done跑完之后对比各组的吞吐和 P99就能找到性价比最高的组合。3.5 LLM 流式模型的压测LLM 的压测和传统模型不一样因为输出是流式的你关心的不只是总延迟还有首 token 延迟TTFT和每 token 生成时间TPOTperf_analyzer -m llama3 \ --input-data prompts.json \ --streaming \ --measurement-interval 30000 \ --concurrency-range 1:8:2 \ -u localhost:8001 \ --async \ --request-parameter max_tokens:256 \ --request-parameter temperature:0.0这里 prompts.json 需要你自己准备格式是每行一个 JSON 对象包含输入 tensor 的数据。--streaming 开启流式模式后perf_analyzer 会额外报告 TTFT 和 TPOT。4. 验证请求与结果解读4.1 一次完整的压测输出跑完上面的扫描命令后你会看到类似这样的输出*** Measurement Settings *** Service kind: Triton Using GRPC service at localhost:8001 Measurement window: 15000 msec Request concurrency: 8 Request count: 128345 Throughput: 8556 infer/sec Avg latency: 0.934 ms (overhead 0.128 ms queue 0.342 ms compute 0.464 ms) Latency percentiles: p50: 0.921 ms p90: 1.152 ms p95: 1.284 ms p99: 1.623 ms4.2 怎么读这些数字吞吐量 8556 infer/sec 是这个并发下的处理能力。但更值得看的是延迟的构成overhead 0.128ms 是网络和序列化开销queue 0.342ms 是请求在队列里等待的时间compute 0.464ms 是模型实际计算时间。如果 queue 占比明显大于 compute说明请求排队是主要瓶颈增加模型实例数会有帮助。如果 compute 占主导那就要考虑优化 kernel 或者用更高精度的硬件。P99 是 1.623ms是 P50 的 1.8 倍。这个比例还算健康。如果 P99 是 P50 的 5 倍以上说明存在严重的尾部延迟通常和显存碎片、动态 shape 重分配、或者偶发的 cudaMalloc 有关。4.3 用 CSV 做进一步分析加上 --verbose-csv 可以输出每个请求的详细延迟perf_analyzer -m yolov8s \ --concurrency-range 8 \ --latency-report-file latency_report.csv \ --verbose-csv拿到这个 CSV 之后你可以画延迟分布直方图看看长尾到底长什么样。如果 P50 是 3ms 但 P99 是 25ms那 25ms 那些请求就是你要重点排查的对象。4.4 稳定性测试上线前除了看峰值性能还要看长时间运行的稳定性perf_analyzer -m bert_classifier \ --request-rate-range 100 \ --measurement-interval 86400000 \ --periodic-concurrency-range 1 \ -f stability_24h.csv这条命令以固定 100 QPS 跑 24 小时观察吞吐和延迟是否随时间漂移。如果跑着跑着延迟慢慢上升可能是显存泄漏或者连接池耗尽。5. 本篇常见错误排查5.1 连接被拒绝报错信息通常是Failed to connect to localhost:8001。先确认 Triton 服务在跑端口没被防火墙挡。如果你用的是 Docker检查端口映射是否正确。5.2 模型找不到Requested model xxx is not found说明模型名写错了或者模型还没加载完成。用curl localhost:8000/v2/models列出所有已加载的模型。5.3 输入形状不匹配Shape mismatch是最常见的错误之一。你需要确认 --shape 里的 tensor 名称和模型配置文件里的名称完全一致形状也要匹配。如果模型支持动态 shape压测时用的形状要在允许范围内。5.4 压测过程中出现 429 错误Triton 本身不会返回 429但如果你前面有网关或者限流层可能会触发。这时候要么降低并发要么调整限流配置。另外检查一下 Triton 的--max-queue-delay和--max-queue-size参数队列满了也会导致请求被拒。5.5 吞吐不随并发增长如果并发从 4 加到 8 加到 16吞吐几乎不变说明 GPU 已经饱和了。这时候增加并发只会让延迟上升。解决办法是增加模型实例数在 config.pbtxt 里设置 instance_group或者用多 GPU 部署。5.6 P99 延迟突然飙升如果 P99 比 P50 大很多先看是不是动态 shape 导致的 buffer 重分配。可以在 config.pbtxt 里配置 shape bucket 预分配。另外检查是否有其他进程在抢 GPU 资源。5.7 LLM 压测的 TTFT 偏高LLM 的 TTFT 受 prompt 长度影响很大。如果你的 prompts.json 里混了长短不一的输入TTFT 的分布会很宽。建议按 prompt 长度分组压测分别看不同长度下的 TTFT。6. 把压测接入 CI 与后续动作6.1 自动化性能回归性能基线建立之后最怕的是某次提交把性能搞崩了。可以在 CI 里加一个简单的断言脚本#!/bin/bash THROUGHPUT_THRESHOLD8000 P99_LATENCY_THRESHOLD3000 MODEL_NAMEyolov8s perf_analyzer -m $MODEL_NAME \ --concurrency-range 8 \ --input-data random \ -f ci_result.csv THROUGHPUT$(awk -F, NR2 {print $3} ci_result.csv) P99$(awk -F, NR2 {print $7} ci_result.csv) if (( $(echo $THROUGHPUT $THROUGHPUT_THRESHOLD | bc -l) )); then echo FAIL: Throughput $THROUGHPUT $THROUGHPUT_THRESHOLD exit 1 fi if (( $(echo $P99 $P99_LATENCY_THRESHOLD | bc -l) )); then echo FAIL: P99 Latency $P99 $P99_LATENCY_THRESHOLD exit 1 fi echo PASS: All performance metrics within thresholds把这个脚本挂到 CI 流水线里每次提交都跑一遍性能回退就能第一时间发现。6.2 压测之后的下一步拿到基线数据之后你可以做几件事一是根据 queue 和 compute 的占比决定优化方向二是用不同的 batch size 和精度组合找到最优配置三是把压测结果和线上监控数据对照看看本地基线和生产表现差多少。如果你在压测之外还需要做模型对话验证或者管理 API Key可以走前面提到的 TaoToken 入口。压测本身是本地行为但如果你要对比线上服务的表现可以先把本地基线跑出来再用同样的输入去线上做对照。模型对话入口https://taotoken.net/models?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_contentmodel_chatCoding Plan 入口https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_contentcoding_planAPI Keys 管理https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_contentapi_keys接入文档https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_contentdoc6.3 一个实用的压测习惯每次改完模型配置或者升级 Triton 版本之后先跑一遍固定并发的压测和上一次的 CSV 对比。如果吞吐掉了超过 5% 或者 P99 涨了超过 10%就要查原因。这个习惯能帮你避免很多“上线后才发现变慢”的问题。压测不是一次性的任务而是持续的过程。把 perf_analyzer 的命令固化成脚本把结果存成带时间戳的 CSV时间长了你会发现性能变化的规律也能在出问题时快速定位是哪次变更引入的。