评估大语言模型服务性能时,很多人习惯把“速度快”当成一个整体感受。实际上推理请求的耗时包含两个关键阶段:从发出请求到收到第一个token的等待时间,以及后续token持续生成的速率。前者称为首Token延迟(Time to First Token,TTFT),后者通常用每秒生成token数(tokens per second)来衡量。这两项指标对用户体验的影响完全不同,而且在工程优化上也常常相互制约。理解它们的内涵与测量方法,才能在做模型选型或架构设计时避免走入误区。

下文将分别拆解首Token延迟和生成速度的定义、测量方式与优化策略,并结合实际场景给出选型建议。
首Token延迟:从请求到首个输出的等待时间
首Token延迟(TTFT)指的是客户端发送推理请求后,收到第一个生成token所经历的时间。这个时间包含了网络传输、请求排队、输入token的预填充(prefill)计算以及第一次采样解码的开销。对于长输入提示词,预填充阶段需要一次性处理所有输入token,计算量随输入长度线性增长,因此TTFT对输入长度非常敏感。在批量服务场景下,请求可能还需要等待其他请求组成批次,排队延迟也会被计入TTFT。
从用户体验角度看,TTFT直接决定了交互的“响应感”。在对话式应用中,用户点击发送后如果超过几百毫秒还没有任何输出,就会明显感知到卡顿。很多前端实现会采用流式输出,只要第一个token到达就能开始渲染,因此控制TTFT比总耗时更能影响主观评价。一个典型的阈值是:对于实时对话,TTFT最好控制在300毫秒以内;对于非实时分析类任务,可以放宽到1秒以上。
测量TTFT相对简单,核心是在流式响应中记录第一个有内容的增量数据到达时间。下面这段Python代码演示了如何调用兼容OpenAI协议的本地推理服务,并计算TTFT:
import time
from openai import OpenAI
client = OpenAI(base_url="http://127.0.0.1:8000/v1")
start = time.time()
stream = client.chat.completions.create(
model="local-llm",
messages=[{"role": "user", "content": "介绍大模型推理指标"}],
stream=True,
)
first_token_time = None
token_count = 0
for chunk in stream:
if chunk.choices[0].delta.content:
if first_token_time is None:
first_token_time = time.time() - start
token_count += 1
end_time = time.time()
total_time = end_time - start
ttft = first_token_time
generation_speed = token_count / (total_time - ttft) if total_time > ttft else 0
print(f"TTFT: {ttft:.3f}s, tokens: {token_count}, speed: {generation_speed:.2f} tokens/s")
需要注意的是,实际生产环境中TTFT会因并发量、输入长度分布和硬件负载产生波动。仅测试单次请求得到的数值不能代表线上表现,建议使用压测工具在不同并发下采集多次数据,观察P50、P95和P99分位值。
生成速度:解码阶段的吞吐率
生成速度指在首个token之后,模型持续输出token的速率,单位通常是tokens/s或毫秒/token。这一阶段对应自回归解码过程:每生成一个token,都要以该token作为输入重新计算一次前向传播,因此生成一个token的耗时几乎是固定的,主要由模型参数量、内存带宽和硬件算力决定。与首Token延迟不同,生成速度基本不受输入长度影响(除非使用了某些缓存策略),但对输出长度极其敏感。
生成速度决定了长文本输出的“流畅度”。如果模型每秒只能生成10个token,一段500字的回答需要等待50秒,用户很难接受。而如果生成速度达到50 tokens/s,同样的内容只需10秒,体验会大幅改善。流式输出场景中,只要生成速度足够快,即使用户感知到首Token有一定延迟,后续文字也会快速跟上,整体体验依然可以接受。
测量生成速度需要在排除TTFT之后统计剩余时间内的token数量。上文的代码已经给出了计算方式:用总耗时减去TTFT得到纯生成时间,再用总token数除以该时间。更严谨的测量应该分段统计每个token之间的间隔,取平均值或中位数,以消除个别慢token的影响。下面是分段计时示例:
import time
def measure_decode_speed(generator):
prev_time = None
intervals = []
token_count = 0
for token in generator:
now = time.time()
if prev_time is not None:
intervals.append(now - prev_time)
prev_time = now
token_count += 1
if intervals:
avg_interval = sum(intervals) / len(intervals)
speed = 1.0 / avg_interval
else:
speed = 0
return token_count, avg_interval, speed
这段代码假设generator是一个同步或异步的token生成器。真实推理框架如vLLM、TensorRT-LLM往往会在服务端返回token前进行批处理调度,客户端测得的间隔可能包含网络抖动。为了更准确评估生成速度,最好在服务端记录每个解码步骤的耗时,或使用专门的基准测试工具。
两个指标如何组合影响整体体验
一个推理请求的总耗时可以近似表示为:总耗时 = 首Token延迟 + 输出token数 / 生成速度。这个公式清晰地展示了两个指标的独立作用。对于输出较短的场景(例如单轮问答,输出20个token),TTFT占总耗时的比例很高;对于输出较长的场景(例如生成报告,输出800个token),生成速度的贡献占主导。
以两个假设的模型为例:模型A的TTFT为0.2秒,生成速度为30 tokens/s;模型B的TTFT为0.6秒,生成速度为60 tokens/s。如果平均输出长度是10个token,模型A总耗时0.53秒,模型B总耗时0.77秒,模型A体验更好。如果平均输出长度是200个token,模型A总耗时6.87秒,模型B总耗时3.93秒,模型B明显更快。这说明选型时不能只看单一指标,必须结合业务的输出长度分布。
| 场景 | 典型输出长度 | 优先指标 |
|---|---|---|
| 实时对话机器人 | 20-80 tokens | TTFT |
| 代码补全 | 5-30 tokens | TTFT |
| 长文摘要 | 300-1000 tokens | 生成速度 |
| 内容批量生成 | 500-2000 tokens | 生成速度 |
除了输出长度,用户交互模式也影响权重。在支持流式渲染的界面中,用户看到首Token后会有耐心等待后续内容,因此可以适当牺牲TTFT换取更高的生成速度。而在需要即时反馈的自动补全或搜索建议场景,TTFT必须尽可能低,生成速度反而次要,因为用户每次只接受极短的候选文本。
优化策略与选型建议
降低首Token延迟的常见手段包括:将输入预填充阶段并行化,利用GPU的高并行度一次性处理所有输入token;使用前缀缓存(prefix caching)复用相同系统提示词的计算结果;对模型进行量化或蒸馏以减少计算量;以及优化请求调度,避免高并发下排队时间过长。在框架层面,vLLM的continuous batching和chunked prefill可以把长输入拆分成多个小块,在等待其他请求时交错执行预填充和解码,从而压缩排队和预填充带来的首Token延迟。
提升生成速度则更多依赖硬件与解码算法的优化。推测解码(speculative decoding)使用小模型快速生成多个候选token,再由大模型并行验证,可以在保持精度的同时将解码速度提高2到3倍。KV缓存管理优化可以减少显存碎片和重复分配,提高并发吞吐。选择内存带宽更高的GPU(如HBM3)对解码阶段的提升比单纯增加算力更明显。此外,适当增大批处理规模可以摊薄每次前向传播的固定开销,但会降低单请求的响应速度,需要根据SLA权衡。
实际选型时,建议先明确业务的核心交互模式:如果是面向终端用户的实时对话,优先保证TTFT在300毫秒以内,生成速度达到30 tokens/s以上即可满足大部分体验要求;如果是后台批量生成或离线分析,可以接受更高的TTFT,把生成速度作为主要优化目标。同时要结合成本考虑,不要盲目追求极致指标。通过持续压测和监控,建立两个指标的分位值基线,才能在模型升级或框架切换时做出客观判断。
理解首Token延迟与生成速度的差异,是做好大模型推理服务的基础。两个指标分别对应推理流水线的不同阶段,优化手段和适用场景也各不相同。只有根据实际业务特征进行针对性测试和调优,才能在速度、成本和用户体验之间找到最佳平衡。