图 1:本文封面,一次流式请求的时间轴解剖与三个核心指标。
先说明属性:这是一篇经典题解析,题目出自本地历史面试资料整理,与任何公司的当前招聘无关。「如何衡量大模型的推理速度」这道题,考的不是让你背几个缩写,而是你能不能把「快」这个模糊的词拆成可测量、可对比、可写进 SLA 的指标。模型服务上线前定容量、定体验目标,第一步都是把指标口径统一。
一句话结论:把一次请求的时间轴切开——首 token 延迟看 TTFT,流式体感看 TPOT,服务能力看吞吐,三者各有各的口径,不能混报。
先切时间轴:prefill 与 decode 是两种活。
大模型生成一条回复分两个阶段。prefill 阶段把全部输入 token 一次性并行过一遍前向,产出第一个输出 token;decode 阶段进入自回归循环,每一步只产一个 token,串行推进直到结束。两个阶段对硬件的压力完全不同:prefill 是算力密集,一次大矩阵运算;decode 是显存带宽密集,每步都要把整个 KV cache 读一遍。正因为两阶段性质不同,「这条请求花了 5 秒」是没有信息量的说法——必须分段计量。
三个指标各自的定义与分工。
TTFT(Time To First Token):从请求发出到收到第一个输出 token 的时间,等于排队时间加 prefill 时间。它决定用户按下回车后的「首响体感」,对实时交互场景最重要;批量离线任务反而不在乎它。敏感因素是排队深度与输入长度,优化方向是 prefix 缓存和削峰排队。
TPOT(Time Per Output Token):相邻两个输出 token 的平均间隔,近似等于单步 decode 耗时。它决定流式输出的打字机效果顺不顺——TPOT 是 50 毫秒,用户看到的就是每秒约 20 个 token 的稳定吐出。敏感因素是显存带宽与 batch 大小,优化方向是 KV cache 管理与连续批处理。端到端延迟近似等于 TTFT 加 TPOT 乘(输出 token 数减一),由输出长度主导。
吞吐(throughput):全部请求的输出 token 总数除以墙钟时间,单位 tokens/s。它是容量规划与成本核算的核心数:同样一张卡,吞吐越高,单 token 成本越低。
三个指标互相拉扯:并发实验看张力。
吞吐和延迟不是同向指标——提高并发能摊薄每步 decode 的开销、推高吞吐,但单步变慢、TPOT 变差。用一段模拟把这个张力量化(Node.js 22.15.0,node metrics.js):简化一台服务器,prefill 按输入长度计、单步 decode 随并发放大,扫并发梯度:
function simulate(concurrency, reqsPerCon) {
const inputLen = 512, outputLen = 100;
const prefillPerToken = 0.02; // ms / 输入 token(简化模型)
const decodeStepBase = 8; // ms,单请求时单步 decode
const decodeStep = (c) => decodeStepBase * (1 + (c - 1) * 0.12); // 并发放大单步
const prefillMs = inputLen * prefillPerToken;
const tpots = []; let t = 0, totalOut = 0;
for (let r = 0; r < reqsPerCon * concurrency; r++) {
const tpot = decodeStep(concurrency);
tpots.push(tpot);
t += prefillMs + tpot * (outputLen - 1);
totalOut += outputLen;
}
const wallMs = t / concurrency; // 并发分摊墙钟
const tpotP50 = tpots.slice().sort((a, b) => a - b)[Math.floor(tpots.length / 2)];
return { tpotP50, throughput: totalOut / (wallMs / 1000) };
}
for (const c of [1, 4, 16, 64]) {
const r = simulate(c, 8);
console.log(`并发 ${String(c).padStart(2)}: TPOT(P50)=${r.tpotP50.toFixed(2)}ms 吞吐=${r.throughput.toFixed(0)} tokens/s`);
}
实测输出:
并发 1: TPOT(P50)=8.00ms 吞吐=125 tokens/s
并发 4: TPOT(P50)=10.88ms 吞吐=368 tokens/s
并发 16: TPOT(P50)=22.40ms 吞吐=718 tokens/s
并发 64: TPOT(P50)=68.48ms 吞吐=943 tokens/s
并发从 1 提到 64,吞吐涨了约 7.5 倍,但单 token 间隔从 8 毫秒劣化到 68 毫秒——打字机效果肉眼可见地变卡。这条曲线先陡后平:算力或显存打满后吞吐见顶,再加并发只会纯劣化延迟。容量规划要找的就是拐点前的甜点区。
压测时怎么采集才不算数造假。
三条纪律。第一,扫并发梯度而不是单点压测:只报一个并发下的数字,看不出甜点区在哪。第二,报分位数而不是平均值:TTFT、TPOT 都要带 P50/P99,平均值会被长尾请求掩盖。vLLM 官方的 vllm bench serve 压测工具就内置了这套口径,默认输出 TTFT、TPOT、ITL(token 间延迟)与端到端延迟的分位数,并支持按指标设 SLO 阈值统计达标率(goodput)。第三,控制输入输出长度:吞吐对输出长度敏感,对比两次压测必须先确认数据集的输入输出分布一致,否则数字不可比。
图 2:两阶段时间轴、三指标分工与压测纪律。
面试现场的答题结构。
先给结论「快不是一个数,是三个指标」,然后切时间轴讲 prefill 与 decode 的性质差异,顺势给出 TTFT、TPOT、端到端、吞吐四个定义与各自的用户含义。接着讲指标间的张力:吞吐与延迟互为约束,并发实验说明甜点区的存在。追问「怎么压测」时答三条纪律:扫并发梯度、报 P50/P99 分位、固定输入输出长度分布。最后落到工程:TTFT 定首响 SLA、TPOT 定流式体验、吞吐定容量与成本,报数必须带并发数与分位口径。你们团队的模型服务 SLA 是按哪个指标定的?压测时踩过「平均延迟很好看、P99 崩了」的坑吗?欢迎聊聊。