6.1 推理耗时:从逐 Token 生成到瓶颈与优化
6.1 推理耗时:从逐 Token 生成到瓶颈与优化
大语言模型(LLM)的推理(inference)通常指:给定输入 Prompt,模型按自回归方式逐步生成输出 Token。推理性能最常用的两个体验指标是:
- TTFT(Time To First Token):从提交输入到生成第一个输出 token 的时间。
- TPOT(Time Per Output Token):生成后续每个 token 的平均耗时。
理解推理耗时,需要把推理拆成两个阶段:
- Prefill / Context 阶段:把整段输入 Prompt 喂给模型,计算所有层的注意力与中间状态,并把可复用的 Key/Value 写入 KV-cache。该阶段决定 TTFT。
- Decoding 阶段:在已有 KV-cache 的基础上,逐 token 生成输出,每次只追加一个 token,并更新 KV-cache。该阶段决定 TPOT。
1. 推理机制:为什么“逐 Token”难以并行
LLM 是自回归模型,输出序列 y=(y1,y2,…,yT)y = (y_1, y_2, \dots, y_T)y=(y1,y2,…,yT) 满足:
p(y∣x)=∏t=1Tp(yt∣x,y<t) p(y \mid x) = \prod_{t=1}^{T} p(y_t \mid x, y_{<t}) p(y∣x)=t=1∏Tp(yt∣x,y<t)
因此第 ttt 个 token 的生成依赖之前所有 token(包括输入与已生成输出),这使得:
- 跨 token 的时间维度强依赖:yty_{t}yt 必须等 yt−1y_{t-1}yt−1 生成后才能计算。
- GPU 可以在单次前向里对矩阵计算做并行,但token 之间难以并行(除非采用 speculative decoding、并行采样等额外机制,这里不展开)。
2. 计算的两大核心:矩阵乘法与 Attention(KV-cache)
在 Transformer 推理中,主要计算来源可以粗分两类:
2.1 矩阵-向量(或矩阵-矩阵)乘法:MLP 与投影层
对每一层而言,前向包含大量线性变换,例如:
- Attention 的 Q,K,VQ,K,VQ,K,V 投影
- 输出投影
- MLP 两层线性(如 SwiGLU)
这些在推理时本质上是(批量化的)矩阵乘法。若将单 token 的某一线性层近似为矩阵-向量乘法:
y=Wx \mathbf{y} = \mathbf{W}\mathbf{x} y=Wx
若 W∈Rd×d\mathbf{W}\in \mathbb{R}^{d \times d}W∈Rd×d,则涉及 d2d^2d2 个权重元素。每个元素至少一次乘加(Multiply-Add),可粗略按 2 FLOPs 计:
- 1 次乘法
- 1 次加法
因此,线性层 FLOPs 近似与权重规模成正比。
2.2 Attention:利用 KV-cache 降低重复计算
标准 self-attention 为:
Attn(Q,K,V)=softmax(QKTd)V \text{Attn}(\mathbf{Q},\mathbf{K},\mathbf{V})=\text{softmax}\left(\frac{\mathbf{Q}\mathbf{K}^T}{\sqrt{d}}\right)\mathbf{V} Attn(Q,K,V)=softmax(dQKT)V
在推理时,如果每生成一个新 token 都重新计算所有历史 token 的 K,VK,VK,V,代价会非常高。于是引入 KV-cache:
- Prefill 时:把 prompt 中每个位置的 K,VK,VK,V 计算出来并缓存。
- Decoding 时:新 token 只需要计算自己的 Q,K,VQ,K,VQ,K,V,并把新的 K,VK,VK,V 追加进缓存;注意力计算复用历史 K,VK,VK,V。
这样 Decoding 阶段避免了对历史 token 的重复前向,但仍需要读取历史的 KV-cache 来参与注意力。
3. 瓶颈分析:推理到底受“算力”还是“带宽”限制?
一个常见经验结论是:
- Prefill(TTFT)更偏计算受限(compute-bound)
- Decoding(TPOT)更偏带宽受限(memory-bandwidth-bound)
原因在于两阶段读取/计算的结构不同。
4. 单 Token 需要读多少数据:权重读取与 KV-cache 读取
4.1 权重读取:每个新 token 都要用到大量权重
以一个约 7B 参数的模型为例,若权重使用 FP16(每参数 2 字节),则权重总大小约为:
WeightBytes≈7.111×109×2 Bytes≈14.2 GB \text{WeightBytes} \approx 7.111\times 10^9 \times 2 \ \text{Bytes} \approx 14.2 \ \text{GB} WeightBytes≈7.111×109×2 Bytes≈14.2 GB
关键点在于:即使模型结构上“同一 token 会重复使用同一套权重”,但硬件缓存容量通常只有几十 MB 级别,远小于 14.2 GB,权重无法常驻缓存。于是每生成一个 token,系统都可能需要从显存(HBM / GDDR)反复取权重数据到计算单元附近。
这直接引出一个下界:推理速度不可能快于显存带宽允许的“搬运速度”。
4.2 KV-cache 读取:随上下文长度线性增长,但通常远小于权重搬运
KV-cache 的大小依赖模型结构(层数、头数、head dim、KV 精度等)。用一个具体的近似设定说明“量级”:
- 每层:key 存储 888 个长度为 128128128 的向量(即 888 个 head,每 head dim 为 128)
- 每层:value 同样存储 888 个长度为 128128128 的向量
- 以 323232 层为例
则每个 token 对应 KV 的元素数近似为:
NKV-elem=32×(8×128+8×128)=32×16×128=65536 N_{\text{KV-elem}} = 32 \times (8\times 128 + 8\times 128) = 32 \times 16 \times 128 = 65536 NKV-elem=32×(8×128+8×128)=32×16×128=65536
若 KV-cache 为 FP16,则每个 token 的 KV-cache 字节数约为:
KVBytesPerToken=65536×2 Bytes≈131072 Bytes≈128 KB \text{KVBytesPerToken} = 65536 \times 2 \ \text{Bytes} \approx 131072\ \text{Bytes} \approx 128\ \text{KB} KVBytesPerToken=65536×2 Bytes≈131072 Bytes≈128 KB
当上下文长度为 PPP(prompt + 已生成输出的总 token 数)时,Decoding 阶段一次注意力需要读取历史 KV 的量级为:
KVRead(P)≈P×128 KB \text{KVRead}(P) \approx P \times 128\ \text{KB} KVRead(P)≈P×128 KB
例如 P=1000P=1000P=1000:
KVRead(1000)≈1000×128 KB≈128 MB \text{KVRead}(1000) \approx 1000 \times 128\ \text{KB} \approx 128\ \text{MB} KVRead(1000)≈1000×128 KB≈128 MB
与 FP16 权重总量 14.2 GB14.2\ \text{GB}14.2 GB 相比,128 MB128\ \text{MB}128 MB 通常小一个数量级以上,因此在许多配置下,单 token 的主要搬运成本仍来自权重读取(当然,当上下文极长或采用更大 KV 结构时,KV-cache 的占比会明显上升)。
5. “理论下限”时延估算:带宽决定 TPOT 的底线
如果 Decoding 阶段近似被显存带宽限制,可以用:
TPOTmin≈需要读取的数据量显存带宽 \text{TPOT}_{\min} \approx \frac{\text{需要读取的数据量}}{\text{显存带宽}} TPOTmin≈显存带宽需要读取的数据量
5.1 示例:RTX 4090 的带宽下界估算
假设显存带宽为 1008 GB/s1008\ \text{GB/s}1008 GB/s,权重为 FP16,总量约 14.2 GB14.2\ \text{GB}14.2 GB,则每生成一个 token,若“有效读取”接近完整权重规模,则有:
TPOTmin≈14.21008 s≈0.0141 s=14.1 ms \text{TPOT}_{\min} \approx \frac{14.2}{1008}\ \text{s} \approx 0.0141\ \text{s} = 14.1\ \text{ms} TPOTmin≈100814.2 s≈0.0141 s=14.1 ms
若改用 8-bit 权重(每参数 1 字节),权重读取量约减半到 7.1 GB7.1\ \text{GB}7.1 GB,则:
TPOTmin≈7.11008 s≈0.0070 s=7.0 ms \text{TPOT}_{\min} \approx \frac{7.1}{1008}\ \text{s} \approx 0.0070\ \text{s} = 7.0\ \text{ms} TPOTmin≈10087.1 s≈0.0070 s=7.0 ms
这类计算给出的是理论下限:实际系统还会受到算子融合、访存效率、并行度、调度开销、attention 实现、量化/反量化开销等影响,通常会慢于该下界。
6. “线性耗时模型”:为什么输出越长越慢得可预测
很多线上系统中,总体延迟可以近似写成:
Latency≈b+kx \text{Latency} \approx b + kx Latency≈b+kx
其中:
- xxx:生成的输出 token 数
- bbb:首 token(TTFT)相关的固定成本(主要来自 Prefill)
- kkk:后续每 token 的平均成本(TPOT)
一个重要经验是:bbb 往往是 kkk 的十几倍甚至更多,并且与 prompt 长度几乎线性相关。直观理解:
- Prefill 要对 prompt 的每个位置做一次“完整的注意力与前向”,计算量随 prompt 长度增长。
- Decoding 每步只新增一个 token,许多中间量复用 KV-cache,因此每步增量更稳定。
7. CoT 与推理耗时:效果提升为什么伴随明显成本
链式思维(CoT)通常会显著增加输出 token 数 xxx。在上面的线性模型下:
Latency≈b+kx \text{Latency} \approx b + kx Latency≈b+kx
当 xxx 增大时,总耗时近似线性增长。于是 CoT 带来一个直接工程后果:
- 更长的推理文本 ⇒\Rightarrow⇒ 更高的延迟与更低的吞吐
7.1 一个具体量级例子
假设某服务在给定 prompt 下:
- b=300 msb=300\ \text{ms}b=300 ms
- k=10 ms/tokenk=10\ \text{ms/token}k=10 ms/token
若不使用 CoT,输出 x=80x=80x=80 token:
Latency≈300+10×80=1100 ms \text{Latency}\approx 300 + 10\times 80 = 1100\ \text{ms} Latency≈300+10×80=1100 ms
若使用 CoT,输出 x=400x=400x=400 token:
Latency≈300+10×400=4300 ms \text{Latency}\approx 300 + 10\times 400 = 4300\ \text{ms} Latency≈300+10×400=4300 ms
同一输入下,输出长度从 80 增加到 400,延迟近似增加约 4 倍。这类线性关系解释了为什么“推理越长越慢”在体验层面非常稳定可见。
8. TPS(Tokens Per Second)怎么计算:把 TTFT 与 TPOT 放进一个公式
部署时常用 TPS 衡量输出吞吐:
TPS=输出 token 总数总延迟(秒) \text{TPS}=\frac{\text{输出 token 总数}}{\text{总延迟(秒)}} TPS=总延迟(秒)输出 token 总数
总延迟由两部分组成:
- TTFT:prefill 阶段造成的首 token 延迟
- TPOT:decoding 阶段每个输出 token 的平均耗时
因此:
Latency=TTFT+TPOT×输出 token 数 \text{Latency} = \text{TTFT} + \text{TPOT}\times \text{输出 token 数} Latency=TTFT+TPOT×输出 token 数
设输出 token 数为 NNN,则:
TPS=NTTFT+TPOT×N \text{TPS}=\frac{N}{\text{TTFT}+\text{TPOT}\times N} TPS=TTFT+TPOT×NN
8.1 为什么短输出时 TPS 看起来更低
当 NNN 很小,分母中 TTFT 占比很大:
TPS≈NTTFT \text{TPS}\approx \frac{N}{\text{TTFT}} TPS≈TTFTN
此时即使 TPOT 很快,TPS 也会被 TTFT 拉低。反过来,当 NNN 很大,TTFT 可被摊薄:
TPS≈NTPOT×N=1TPOT \text{TPS}\approx \frac{N}{\text{TPOT}\times N}=\frac{1}{\text{TPOT}} TPS≈TPOT×NN=TPOT1
因此在长输出场景下,TPS 更接近 decoding 的极限速度。
9. 用 FLOPs 估算 TTFT 与 TPOT:一个工程上常用的“可计算近似”
在一些粗估场景,会用“计算量 / 有效算力”来估 TTFT/TPOT。
9.1 Prefill 阶段 FLOPs 近似
一个常用近似是:
FLOPsprefill≈2×B×L×P \text{FLOPs}_{\text{prefill}} \approx 2\times B \times L \times P FLOPsprefill≈2×B×L×P
其中:
- BBB:batch size
- LLL:prompt length(token 数)
- PPP:模型参数量(按“每参数贡献一次乘加”的粗略估计)
- 系数 2:来自乘加约 2 FLOPs 的口径
9.2 Decoding 阶段 FLOPs 近似
若输出 token 数为 CCC(completion size),则:
FLOPsdecoding≈2×B×C×P \text{FLOPs}_{\text{decoding}} \approx 2\times B \times C \times P FLOPsdecoding≈2×B×C×P
这两个式子忽略了 attention 中 QKTQK^TQKT 与 softmax、以及各类算子常数项,主要用于“量级估算”。
9.3 把 FLOPs 转成时间:引入有效算力
若 GPU 的峰值半精度算力为 FpeakF_{\text{peak}}Fpeak(TFLOPs),实际利用率为 η\etaη,则有效算力:
Feff=ηFpeak F_{\text{eff}}=\eta F_{\text{peak}} Feff=ηFpeak
时间近似为:
t≈FLOPsFeff t \approx \frac{\text{FLOPs}}{F_{\text{eff}}} t≈FeffFLOPs
例如峰值 312 TFLOPs312\ \text{TFLOPs}312 TFLOPs、利用率 η=0.6\eta=0.6η=0.6:
Feff=0.6×312=187.2 TFLOPs F_{\text{eff}} = 0.6\times 312 = 187.2\ \text{TFLOPs} Feff=0.6×312=187.2 TFLOPs
然后用上面的 FLOPs 估计 TTFT(prefill)与总 decoding 时间(再除以输出 token 数得到 TPOT)。需要强调的是:该方法在不同实现与不同硬件上误差可能较大,但作为容量规划与趋势判断仍很常用。
10. 首 token 时延优化:为什么 Prefill 更值得“缓存”
10.1 Prefill 的核心特征:与 prompt 长度强相关
Prefill 阶段需要处理整个 prompt,许多实现里 TTFT 与 prompt length LLL 近似线性相关(特别是在注意力实现已优化、但总体仍需遍历 prompt 的情况下)。
从工程直觉出发,可以把两种下界写成:
-
首 token(prefill)更偏算力下界:
TTFT≳Prefill FLOPs半精度有效算力 \text{TTFT} \gtrsim \frac{\text{Prefill FLOPs}}{\text{半精度有效算力}} TTFT≳半精度有效算力Prefill FLOPs -
后续 token(decoding)更偏带宽下界:
TPOT≳权重字节数(及必要读写)显存带宽 \text{TPOT} \gtrsim \frac{\text{权重字节数(及必要读写)}}{\text{显存带宽}} TPOT≳显存带宽权重字节数(及必要读写)
这解释了为什么很多系统优化优先瞄准 TTFT:用户更敏感“等第一下”的延迟,而 TTFT 又常常是长 prompt 的主要代价。
11. System Prompt Caching:用缓存把 TTFT 直接“砍掉一截”
大量线上应用的 prompt 结构近似为:
Prompt=System Prompt+User Prompt \text{Prompt} = \text{System Prompt} + \text{User Prompt} Prompt=System Prompt+User Prompt
其中 System Prompt(系统提示)往往固定或变化很小(例如产品规则、角色设定、安全策略、输出格式约束)。这给了缓存优化空间:System Prompt 的 Prefill 结果可以复用。
缓存的关键对象不是“文本”,而是 System Prompt 对应的 KV-cache(更准确说,是经过模型各层计算后得到的 key/value 状态)。
当下一次请求使用相同的 System Prompt 时:
- 不需要再次对 System Prompt 做 Prefill 计算
- 直接加载已缓存的 KV
- 只对 User Prompt 做增量 Prefill,然后进入 Decoding
这会显著降低 TTFT,尤其当 System Prompt 很长时。
12. 两种常见缓存形态:Prefix Sharing 与 Prompt Cache
12.1 Prefix Sharing:共享固定前缀
适用场景:System Prompt 在很多请求中完全相同,并且总是位于 prompt 最前面。
- 预先对 System Prompt 做一次 Prefill
- 缓存其 KV
- 每次请求把缓存 KV 作为起点,继续对 User Prompt 做 Prefill
收益:当 System Prompt 占 prompt 的大头时,TTFT 可显著下降。
直观例子:假设
- System Prompt 长度 Ls=1200L_s=1200Ls=1200
- User Prompt 长度 Lu=200L_u=200Lu=200
- Prefill 耗时与长度近似线性
那么不缓存时 Prefill 处理长度为 140014001400;启用 Prefix Sharing 后,每次只需要处理 200200200 的增量部分,Prefill 主体成本约减少到原来的 200/1400≈1/7200/1400 \approx 1/7200/1400≈1/7(实际还需考虑调度与缓存读取开销,但趋势通常成立)。
12.2 Prompt Cache:缓存更一般的 Prompt 片段或整段
Prefix Sharing 假设“共享部分就是前缀”,而 Prompt Cache 更泛化:
- 可以缓存任意 prompt 片段对应的 KV(不一定是严格前缀)
- 可以对整段 prompt 做缓存(例如模板化的输入结构)
- 更适合存在多种模板、重复片段多、或对话历史高度重复的应用
Prompt Cache 通常需要更复杂的管理策略(例如哈希、分块、匹配规则、过期策略、显存占用控制),但本质目标相同:减少重复 Prefill。
13. Session Prompt Cache:多轮对话与 Agent 场景的关键优化
在多轮对话中,第 ttt 轮的输入往往包含大量历史:
Promptt=System+History<t+Usert \text{Prompt}_t = \text{System} + \text{History}_{<t} + \text{User}_t Promptt=System+History<t+Usert
其中 History<t\text{History}_{<t}History<t 与上一轮相比高度重叠,只是新增了少量 token。若不做缓存,每一轮都要对长历史重复 Prefill,TTFT 会随着轮数快速上升。
Session Prompt Cache 的核心就是:在一个 session 内,持续复用历史部分的 KV-cache,只对新增部分做增量计算。
13.1 一个可感知的例子
假设第 1 轮到第 6 轮对话,历史 token 逐轮增长:
- 第 1 轮:L=800L=800L=800
- 第 2 轮:L=1200L=1200L=1200
- 第 3 轮:L=1600L=1600L=1600
- 第 6 轮:L=2800L=2800L=2800
如果不缓存,TTFT 近似随 LLL 线性增长,用户会感到“越聊越慢”。
如果做 Session Cache,则每轮只新增少量 token(例如本轮用户输入与少量系统拼接),Prefill 主要变成“增量成本”,TTFT 将显著更稳定。
14. 组合视角:如何用 TTFT/TPOT/TPS 指导优化决策
可以用下面的延迟分解来判断优化优先级:
Latency=TTFT+TPOT×N \text{Latency} = \text{TTFT} + \text{TPOT}\times N Latency=TTFT+TPOT×N
- 当请求输出很短(小 NNN):TTFT 占主导,优先做 prompt 缓存、前缀共享、减少 prompt 长度、优化 prefill kernel。
- 当请求输出很长(大 NNN):TPOT 占主导,优先做权重量化、提升带宽利用、优化 decoding kernel、减少每步开销。
同时 TPS 公式:
TPS=NTTFT+TPOT×N \text{TPS}=\frac{N}{\text{TTFT}+\text{TPOT}\times N} TPS=TTFT+TPOT×NN
也解释了一个常见现象:
- 在短输出场景,即使 decoding 很快,TPS 也可能不高(TTFT 抬高分母)。
- 在长输出场景,TPS 更接近 1/TPOT1/\text{TPOT}1/TPOT(TTFT 被摊薄)。
15. 小结:把关键结论压缩成三句话
- 推理分为 Prefill(决定 TTFT)与 Decoding(决定 TPOT),整体延迟近似 b+kxb+kxb+kx。
- Decoding 常受显存带宽约束,FP16 7B 量级模型的单 token 时间下界可用 数据量/带宽\text{数据量}/\text{带宽}数据量/带宽 估算。
- 缓存(System Prompt / Prefix Sharing / Prompt Cache / Session Cache)的本质是复用 KV-cache,减少重复 Prefill,从而显著降低 TTFT,特别适合长系统提示、多轮对话与 Agent 工作流。
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐



所有评论(0)