AI 测试开发面试高频题:"大模型服务怎么做性能测试?和传统压测有什么区别?"
传统接口压测看 QPS 和 RT,大模型压测看的是"用户等多久看到第一个字、每个字隔多久蹦出来"。指标体系换了,容量模型也换了。这篇用一次大促事故讲清楚。
一、真实事故:首字延迟从 0.8 秒飙到 15 秒
某电商智能导购,平时首字延迟 0.8 秒,体验丝滑。大促零点流量涨了 10 倍,监控曲线立刻变形:TTFT(首 token 延迟)从 0.8 秒飙到 15 秒,用户发完问题盯着空白对话框等十几秒,然后骂骂咧咧关掉;更惨的是部分请求排队超时直接 504,重试又加倍了流量,恶性循环。
事后排查,三个问题叠加:一是没做过容量规划,上线时的并发水位全凭拍脑袋;二是推理服务没设并发限制,高并发下 KV cache 被打满,continuous batching 频繁抢占,单请求生成速度(TPOT)也跟着恶化——不只是等得久,连"出字"都变慢了;三是没有降级预案,模型卡死时连一句"稍后再试"都给不出。
复盘会上性能负责人说了一句大实话:这不是模型不行,是我们从来没按大模型的方式压过它。
二、核心代码:流式压测器 + 阶梯找拐点
第 1 步:采集 LLM 专属指标
大模型压测不能只看总耗时,要拆成 TTFT(等待体验)、TPOT(出字流畅度)、吞吐(系统产能)三组:
import asyncio, time, aiohttp
async def one_request(session, sem, question, out):
async with sem:
t0 = time.time(); ttft = None; n = 0
async with session.post(URL, json={
"q": question}) as r:
async for line in r.content:
if not line.startswith(b"data:"):
continue
n += 1
if ttft is None:
ttft = time.time() - t0 # 首 token 延迟
total = time.time() - t0
out.append({
"ttft": ttft, "total": total, "tokens": n,
"tpot": (total - ttft) / max(n - 1, 1), # 每 token 间隔
})
async def run_load(concurrency, n_req, questions):
out, sem = [], asyncio.Semaphore(concurrency)
async with aiohttp.ClientSession() as s:
await asyncio.gather(*[
one_request(s, sem, questions[i % len(questions)], out)
for i in range(n_req)])
return summarize(out) # P50/P95/P99 + 超时率 + 吞吐(tokens/s)
第 2 步:阶梯加压,找拐点定水位
def capacity_test():
curve = []
for c in (1, 5, 10, 20, 40): # 阶梯并发
m = run_until_stable(c)
curve.append((c, m["ttft_p95"], m["tpot_p95"], m["timeout_rate"]))
knee = find_knee(curve) # TTFT 开始非线性上涨的点
assert PROD_CONCURRENCY <= knee * 0.7, \
f"生产水位 {PROD_CONCURRENCY} 超过拐点 {knee} 的 70%,容量不足"
事故服务的拐点在并发 12 左右,而大促实际并发峰值 60——五倍于拐点,不卡才怪。修复动作:限流到拐点 70%、超限请求进排队页、备用小模型兜底短问答,压测报告里这三条都要有对应演练用例。
三、沉淀成方法:LLM 压测指标体系
| 维度 | 传统接口 | 大模型服务 | 阈值参考 |
|---|---|---|---|
| 等待体验 | RT | TTFT P95 | 对话类 < 3s |
| 流畅度 | — | TPOT P95 | < 100ms/字 |
| 产能 | QPS | 吞吐 tokens/s、并发拐点 | 阶梯压测找 |
| 稳定性 | 错误率 | 超时率、排队深度、504 率 | < 0.1% |
三条工程纪律:一是压测流量要混长短文本——全用短问题压出来的容量是假的,长上下文请求吃 KV cache 的速度是短请求的几十倍,用例集要按线上真实长度分布配比;二是压测环境必须含网关和排队层,只压模型裸服务会漏掉网关超时、队列积压这些真实瓶颈;三是每次模型/推理框架升级重跑容量曲线,换个模型版本拐点可能移动 30%,容量结论是有"保质期"的。
四、面试追问,你答得上来吗
- 大模型压测和传统压测的本质区别?——答:传统接口响应是"一次性、字节数小、耗时稳定",大模型响应是"流式、耗时随输入输出长度剧烈变化、GPU 资源有状态(KV cache)"。所以指标从 QPS/RT 换成 TTFT/TPOT/吞吐,容量从"扛多少 QPS"换成"拐点并发是多少",而且同样的并发下长短文本表现完全不同,必须按真实分布压。
- 怎么定容量水位和扩容线?——答:阶梯压测找拐点(TTFT P95 开始非线性上涨的并发数),生产水位压在拐点 70% 以下;告警线设在拐点 50%,给扩容和限流留反应时间。水位不是越高越好——越过拐点后吞吐不升反降,压得越狠死得越快。
- TTFT 正常但 TPOT 恶化,说明什么?——答:说明首 token 前的排队和预填充还扛得住,但解码阶段资源吃紧——典型是 KV cache 不足导致批处理抢占、或显存带宽打满。反过来 TTFT 高 TPOT 正常,则是排队/预填充瓶颈。两个指标分开看,才能定位到推理管线的哪一段。
下一篇预告:《聊着聊着就报错了——上下文溢出与 Token 边界测试》