负载测试LLM-API


文档摘要

负载测试LLM-API 本节摘要:传统负载测试工具不是为流式响应、可变输出长度、token 级指标和 GPU 饱和设计的。两个陷阱咬住大多数团队。GIL 陷阱(GIL trap):Locust 的 token 级测量在 Python GIL 下跑分词,与请求生成在高并发下争用,分词积压把报告的 token 间延迟(ITL)撑大——瓶颈是你的客户端,不是服务器。提示一致性陷阱(prompt-uniformity trap):循环里发同一个提示只测了 token 分布上的一点;真实流量长度多变、前缀匹配多样,LLMPerf 用 + 修正。2026 工具映射:LLM 专用的(GenAI-Perf、LLMPerf、LLM-Locust、guidellm)拿 token 级精度;k6 v2026.1.

负载测试LLM-API

本节摘要:传统负载测试工具不是为流式响应、可变输出长度、token 级指标和 GPU 饱和设计的。两个陷阱咬住大多数团队。GIL 陷阱(GIL trap):Locust 的 token 级测量在 Python GIL 下跑分词,与请求生成在高并发下争用,分词积压把报告的 token 间延迟(ITL)撑大——瓶颈是你的客户端,不是服务器。提示一致性陷阱(prompt-uniformity trap):循环里发同一个提示只测了 token 分布上的一点;真实流量长度多变、前缀匹配多样,LLMPerf 用 --mean-input-tokens + --stddev-input-tokens 修正。2026 工具映射:LLM 专用的(GenAI-Perf、LLMPerf、LLM-Locust、guidellm)拿 token 级精度;k6 v2026.1.0 + k6 Operator 1.0 GA(2025 年 9 月)——流式感知、通过 TestRun/PrivateLoadZone CRD 原生 K8s 分布式、最适合 CI/CD 门;Vegeta 做 Go 恒定速率饱和;Locust 2.43.3 仅在配 LLM-Locust 扩展时用于流式。负载模式:稳态、爬坡、尖峰(测自动伸缩)、浸泡(测内存泄漏)。

对应原课程:Phase 17 · Lesson 22 · 22-load-testing-llm-apis(原英文 phases/17-infrastructure-and-production/22-load-testing-llm-apis/docs/en.md)。

学习目标

阅读完本节,你应当能够:

  1. 解释让通用负载测试器在 LLM API 上「撒谎」的两大反模式(GIL 陷阱、提示一致性陷阱)。
  2. 按用途选工具:LLMPerf(基准跑)、k6 + 流式扩展(CI 门)、guidellm(大规模合成)、GenAI-Perf(NVIDIA 参考)。
  3. 设计四种负载模式(稳态、爬坡、尖峰、浸泡),并说出每种抓的失败模式。
  4. 用输入 token 的均值 + 标准差构造真实提示分布,而非固定长度。

一、问题与直觉

你用 k6 把 LLM 端点压到 500 并发用户。扛住了。你上线了。生产里只有 200 真实用户,服务却趴了——P99 TTFT 爆炸,GPU 钉死。

两件事发生了。第一,k6 发了 500 个一模一样的提示——请求合并与前缀缓存让你「看起来」在处理 500 路并发解码,实际上只处理了 1 路。第二,k6 不会像人眼体验那样追踪流式响应的 token 间延迟;它看到的是一条 HTTP 连接,不是 500 个 token 以不同间隔到达。

LLM 的负载测试是它自己的学科。

二、核心概念

GIL 陷阱(Locust)

Locust 用 Python,客户端分词跑在 GIL 下。高并发时分词器排在请求生成后面。报告的 ITL 包含了客户端的分词积压。你以为服务器慢,其实是测试器慢。

修正:LLM-Locust 扩展把分词挪到独立进程,或用编译语言工具(k6、用 tokenizers.rs 的 LLMPerf)。

提示一致性陷阱

所有已知负载器都只让你配一个提示。循环 1 万次里每次发完全相同的提示——服务器每次看到同一个前缀,前缀缓存命中率逼近 100%,吞吐看起来很美。

修正:从提示分布里采样。LLMPerf 用 --mean-input-tokens 500 --stddev-input-tokens 150——长度多样、内容多样。

四种负载模式

  1. 稳态(steady-state) —— 恒定 RPS 跑 30~60 分钟。抓:基线性能回归。
  2. 爬坡(ramp) —— 15 分钟内 RPS 从 0 线性升到目标。抓:容量拐点、预热异常。
  3. 尖峰(spike) —— 突然 3~10 倍 RPS 持续 2 分钟再回落。抓:自动伸缩延迟、队列饱和、冷启动影响。
  4. 浸泡(soak) —— 稳态跑 4~8 小时。抓:内存泄漏、连接池漂移、可观测性溢出。

2026 工具映射

LLMPerf(Anyscale)—— Python 但 Rust 后端分词。均值/标准差提示。流式感知。性能跑的默认选择。

NVIDIA GenAI-Perf —— NVIDIA 参考实现。用 Triton 客户端;指标覆盖全面。注意它的 ITL 不含 TTFT,LLMPerf 的含——同一服务器两工具报出不同的 TPOT。

LLM-Locust(TrueFoundry)—— 修掉 GIL 陷阱的 Locust 扩展。熟悉的 Locust DSL + 流式指标。

guidellm —— 大规模合成基准。

k6 v2026.1.0 + k6 Operator 1.0 GA(2025 年 9 月):

  • k6 本身(Go,编译,无 GIL)加了流式感知指标。
  • k6 Operator 用 TestRun / PrivateLoadZone CRD 做 K8s 原生分布式测试。
  • 最适合 CI/CD 门与 SLA 测试。

Vegeta —— Go,比 k6 简单。恒定速率 HTTP 饱和。不 LLM 感知,但适合网关/限流测试。

Locust 2.43.3 原版 —— 对 LLM 有 GIL 陷阱。只在配 LLM-Locust 扩展时用。

CI 里的 SLA 门

在 PR 上跑 k6:

  • 基线 RPS 下各 30~50 次迭代。
  • 门:P50/P95 TTFT、5xx < 5%、TPOT 低于阈值。
  • 违规即破坏构建。

真实提示分布

从真实流量样本(若有)或公开分布(如聊天用 ShareGPT、代码用 HumanEval)构造。把均值 + 标准差喂给 LLMPerf。不惜一切代价避免「单提示循环」。

你该记住的数字

  • k6 Operator 1.0 GA:2025 年 9 月。
  • k6 v2026.1.0:流式感知指标。
  • 典型 LLMPerf 跑:100~1000 请求,并发 X。
  • 典型 CI 门:每 PR 30~50 次迭代。
  • 四种模式:稳态、爬坡、尖峰、浸泡。

三、从零实现:真实提示分布与一致性陷阱

原课程 code/main.py 模拟带真实提示分布的负载测试,测有效 TPOT,并演示一致性陷阱。下面给最小可读的提示采样与有效 TPOT 讋定骨架。

import random def sample_prompt_lengths(mean_tokens, stddev_tokens, n, seed=0): """从正态分布采样 n 个提示长度,模拟真实流量。 contrast: 一致性陷阱是循环里全发同一长度。 """ rng = random.Random(seed) lengths = [max(8, int(rng.gauss(mean_tokens, stddev_tokens))) for _ in range(n)] return lengths def effective_tpot(generate_times_ms, output_tokens): """有效 TPOT = 总生成时间 / 输出 token 数(含 TTFT)。 generate_times_ms: 每次请求首 token 到末 token 的墙钟 output_tokens: 每次请求的输出 token 数 """ total_time = sum(generate_times_ms) total_tokens = sum(output_tokens) return round(total_time / total_tokens, 3) # ms/token # 案例:对比一致性 vs 真实分布 uniform = [500] * 1000 # 一致性陷阱 real = sample_prompt_lengths(500, 150, 1000) # 真实分布 print(f"一致性长度方差={0}, 真实分布方差≈{(500, 150)}") # 真实分布会暴露前缀缓存失效后的真实解码压力,一致性分布会掩盖它

💡 一致性陷阱的修复成本几乎为零:把「单提示循环」换成「按均值 + 标准差采样」,你就能从「虚假的乐观」切到「诚实的容量数字」。

四、框架对比:四大场景的工具横向对照

场景 首选工具 为什么 陷阱
基准性能跑 LLMPerf Rust 后端分词、均值/标准差提示、流式感知
NVIDIA 硬件参考 GenAI-Perf 厂商参考、Triton 客户端、指标全 ITL 不含 TTFT,与 LLMPerf 报的 TPOT 不同
CI/CD 门 k6 v2026.1 + k6 Operator Go 编译无 GIL、流式感知、K8s 原生 CRD 分布式 需配真实提示分布,否则落入一致性陷阱
大规模合成压测 guidellm 合成流量、规模大 不 LLM 感知需校准
网关/限流饱和 Vegeta Go、恒定速率、简单 不 LLM 感知
熟悉 Locust DSL 的团队 LLM-Locust 修掉 GIL 陷阱、保留 Locust DSL 原版 Locust 2.43.3 不可用

心法:工具选型先问「测什么」——基准跑选 LLMPerf,CI 门选 k6,大规模选 guidellm,厂商参考选 GenAI-Perf。无论哪个,真实提示分布(均值 + 标准差)是入场券。

五、可复用产物

本节产出 outputs/skill-load-test-plan.md(原课程目录)。给定工作负载与 SLA,它产出:

  1. 工具选择:依场景(基准/CI/大规模/参考)选工具。
  2. 真实提示分布:从真实流量或公开数据集算出输入 token 的均值 + 标准差。
  3. 四种负载模式:稳态、爬坡、尖峰、浸泡各自的参数(RPS、时长、倍数)与目标失败模式。
  4. CI 门阈值:P50/P95 TTFT、5xx 上限、TPOT 上限,违规即破坏构建。

六、练习

  1. 跑通模拟器:运行 code/main.py。对比一致性 vs 真实分布——差距在哪?

  2. 写 CI 门 k6 脚本:100 并发、运行 5 分钟、TTFT P95 < 800ms。写出脚本骨架。

  3. 浸泡诊断:浸泡测试显示内存每小时涨 50MB。列出三种可能原因,以及用以区分它们的探针。

  4. 尖峰恢复:从 10 RPS 尖峰到 100 RPS。若已部署 Karpenter + vLLM production-stack(第 03、18 节),预期恢复时间多少?

  5. TPOT 不一致:同一服务器,GenAI-Perf 报 TPOT=6ms,LLMPerf 报 TPOT=11ms。解释为什么。

本节要点回顾

  1. 通用负载器在 LLM 上撒谎:为流式、可变输出、token 级指标、GPU 饱和设计的才是 LLM 负载测试。
  2. GIL 陷阱:Locust 客户端分词跑在 GIL 下,高并发时分词积压撑大报告的 ITL——瓶颈是测试器。
  3. 提示一致性陷阱:循环发同一提示让前缀缓存命中 100%,吞吐虚高;改用均值 + 标准差采样。
  4. 四种负载模式:稳态抓回归、爬坡抓拐点、尖峰抓自动伸缩、浸泡抓泄漏。
  5. LLMPerf:Rust 后端分词、均值/标准差提示、流式感知——基准跑默认。
  6. GenAI-Perf:NVIDIA 参考,但 ITL 不含 TTFT,与 LLMPerf 报出不同 TPOT。
  7. k6 v2026.1 + Operator 1.0 GA(2025-09):Go 编译无 GIL、流式感知、K8s 原生 CRD 分布式——CI 门首选。
  8. Vegeta:Go 恒定速率,适合网关/限流,不 LLM 感知。
  9. Locust 仅配 LLM-Locust 用:原版 2.43.3 有 GIL 陷阱。
  10. 真实分布是入场券:从真实流量或公开数据集算均值 + 标准差,避免单提示循环。

下一节,我们进入面向 AI 的 SRE——多智能体事故响应、运行手册、预测性检测如何把凌晨三点的排班从 30 分钟压到 5 分钟。


发布者: 作者: Rohit Gupta 转发
评论区 (0)
U