大模型推理加速核心原理:分形方法与四类资源计算公式


本文根据腾讯云开发者社区文章整理与重写,重点对原文中的资源估算公式进行了 LaTeX 化、符号统一和边界补充。文末附有原文链接。

大模型推理优化看起来千头万绪:模型结构、GPU、显存、网络、推理框架、算子和服务目标都会影响最终结果。但如果把问题抽象一层,会发现不同尺度上的瓶颈具有相似结构:某种资源先达到上限,其他资源随之闲置。

因此,可以用一套递进的“分形方法”分析推理系统:

  1. 看清楚:计算或测量模型需要多少算力、显存、HBM 带宽和通信带宽;
  2. 避免浪费:消除重复计算、空泡、无效搬运和多余的 Kernel Launch;
  3. 提升利用率:调整并行、调度、负载均衡与通信重叠,让资源供需更加匹配;
  4. 节约资源:通过量化、低秩压缩和稀疏化,从源头减少资源需求。

这套方法既适用于整个集群,也适用于单机、单卡乃至一个算子。

一、先把问题放进四类资源模型

推理系统中需要优先观察四类资源:

资源典型问题主要观测指标
算力Tensor Core 是否吃满FLOPs、MFU、Kernel 执行时间
显存容量模型和请求能否装下权重、KV Cache、激活、运行时预留
HBM 带宽数据搬运是否压过计算读写字节数、带宽利用率、算术强度
通信带宽跨卡数据是否阻塞前向All-to-All、All-Reduce 流量和尾延迟

判断瓶颈时,不应只看“哪个利用率高”,而要比较不同资源各自完成任务所需的理论时间:

tcompute=FCeff,tHBM=DHBMBHBM,eff,tcomm=DcommBcomm,efft_{\mathrm{compute}} = \frac{F}{C_{\mathrm{eff}}},\qquad t_{\mathrm{HBM}} = \frac{D_{\mathrm{HBM}}}{B_{\mathrm{HBM,eff}}},\qquad t_{\mathrm{comm}} = \frac{D_{\mathrm{comm}}}{B_{\mathrm{comm,eff}}}

其中,FF 是计算量,DD 是数据量,CeffC_{\mathrm{eff}}BeffB_{\mathrm{eff}} 是实测有效能力,而不是产品规格表上的峰值。

粗略地说,三者中最大的那个通常决定下界:

tstepmax ⁣(tcompute, tHBM, tcomm)t_{\mathrm{step}} \gtrsim \max\!\left(t_{\mathrm{compute}},\ t_{\mathrm{HBM}},\ t_{\mathrm{comm}}\right)

如果计算与搬运没有重叠,实际时间会更接近三者之和;如果重叠充分,才可能逼近最大值。

二、示例模型与符号约定

原文以 DeepSeek V3/R1 为例:总参数约 671B,单 Token 激活参数约 37B;共有 61 层 Transformer,前 3 层使用稠密 FFN,后 58 层为 MoE。每个 MoE 层包含 256 个路由专家和 1 个共享专家,每个 Token 激活 8 个路由专家及 1 个共享专家。

下面统一使用这些符号:

符号含义示例值
bbBatch Size由场景决定
sqs_q本次参与计算的 Query Token 数Prefill 时接近输入长度,Decode 时通常为 1
sks_kQuery 能看到的平均 KV 长度由上下文长度决定
NLN_LTransformer 层数61
NMoEN_{\mathrm{MoE}}MoE 层数58
dmodeld_{\mathrm{model}}隐藏维度7168
nhn_h注意力头数128
dhd_h单头维度128
dcd_cMLA KV 压缩维度512
dqcd_{qc}MLA Query 压缩维度1536
dRd_R解耦 RoPE 维度64
dmoed_{\mathrm{moe}}单专家中间维度2048
kk单 Token 激活专家数9
pp每个元素的字节数FP8 为 1,BF16 为 2

本文使用 2302^{30} 把字节换算为 GiB。为了和工程口径保持一致,后文仍简写为 GB。

三、算力:先算 FLOPs,再谈卡数

1. 非注意力部分

线性层的一次乘加通常按 2 FLOPs 计算。设非注意力部分每个 Token 实际激活的参数量为 PactiveP_{\mathrm{active}},则:

Fnon-attn2PactivebsqF_{\mathrm{non\text{-}attn}} \approx 2P_{\mathrm{active}}\,b\,s_q

若用 GFLOPs 表示:

Fnon-attn(GFLOPs)2Pactivebsq109F_{\mathrm{non\text{-}attn}}^{(\mathrm{GFLOPs})} \approx \frac{2P_{\mathrm{active}}\,b\,s_q}{10^9}

这里必须使用“单 Token 实际激活参数量”,而不是 MoE 模型的总参数量。

2. 注意力部分

按原文的估算口径,传统多头注意力的主体计算量可写成:

FMHA2NLbsqsknh(2dh+dR)F_{\mathrm{MHA}} \approx 2N_L\,b\,s_q\,s_k\,n_h\left(2d_h+d_R\right)

采用 MLA 的低秩 KV 表示后,可近似写成:

FMLA2NLbsqsknh(2dc+dR)F_{\mathrm{MLA}} \approx 2N_L\,b\,s_q\,s_k\,n_h\left(2d_c+d_R\right)

于是两个阶段的总计算量可以统一表达为:

FPrefillFnon-attn+Fattn,PrefillF_{\mathrm{Prefill}} \approx F_{\mathrm{non\text{-}attn}}+F_{\mathrm{attn,Prefill}} FDecode/tokenFnon-attn+Fattn,DecodeF_{\mathrm{Decode/token}} \approx F_{\mathrm{non\text{-}attn}}+F_{\mathrm{attn,Decode}}

需要注意:MLA 是模型结构的一部分,并不是只在 Decode 阶段存在。上面的 MHA/MLA 区分是原文用于资源估算的简化口径,不能理解成同一个已训练模型在 Prefill 与 Decode 时任意切换注意力结构。

3. 从计算量估算最低耗时

如果一张卡的峰值算力为 CpeakC_{\mathrm{peak}},实测 MFU 为 η\eta,并行后每卡分到的计算量为 FcardF_{\mathrm{card}},则:

tcomputeFcardηCpeakt_{\mathrm{compute}} \approx \frac{F_{\mathrm{card}}}{\eta C_{\mathrm{peak}}}

这解释了为什么“理论算得过来”不代表线上一定达标:形状太小、路由不均衡或调度空泡都会让 η\eta 明显下降。

四、显存:权重、KV Cache、激活与预留

单卡显存可以拆成四部分:

Mtotal=Mweight+MKV+Mact+MruntimeM_{\mathrm{total}} = M_{\mathrm{weight}}+M_{\mathrm{KV}}+M_{\mathrm{act}}+M_{\mathrm{runtime}}

1. 权重显存

设稠密部分的 FP8 权重大小为 SdenseS_{\mathrm{dense}},MoE 部分的 FP8 权重大小为 SMoES_{\mathrm{MoE}};稠密部分使用 TP,专家部分使用 EP,并把 MoE 权重量化为 INT4,则原文给出的单卡近似值为:

MweightSdenseTP+SMoE2EPM_{\mathrm{weight}} \approx \frac{S_{\mathrm{dense}}}{TP} +\frac{S_{\mathrm{MoE}}}{2EP}

分母中的 2 来自 FP8 到 INT4 的理论位宽减半。真实占用还会受到量化 scale、对齐、分片和框架元数据影响。

2. KV Cache 显存

MLA 每个 Token、每层需要保存约 dc+dRd_c+d_R 个元素,因此:

MKV=NLb(sinput+soutput)(dc+dR)pKV230GBM_{\mathrm{KV}} = \frac{N_L\,b\,(s_{\mathrm{input}}+s_{\mathrm{output}})\,(d_c+d_R)\,p_{\mathrm{KV}}}{2^{30}} \quad \mathrm{GB}

这一式必须包含层数 NLN_L。如果只写 b(sinput+soutput)(dc+dR)b(s_{\mathrm{input}}+s_{\mathrm{output}})(d_c+d_R),得到的只是单层 KV,而不是整个模型的 KV Cache。

3. 激活显存

未充分融合时,可以把激活近似拆成线性层、注意力分数和 FFN 中间结果:

MactMlinear+Mscore+MFFNM_{\mathrm{act}} \approx M_{\mathrm{linear}}+M_{\mathrm{score}}+M_{\mathrm{FFN}}

对 MLA,可用下面的单层峰值作粗估:

MactMLA1230[bsqdqc+bsq(dc+dR)+2bsqdmodel+2bnhsqsk+2bsqkdmoe]M_{\mathrm{act}}^{\mathrm{MLA}} \approx \frac{1}{2^{30}}\Bigl[ b s_q d_{qc} +b s_q(d_c+d_R) +2b s_q d_{\mathrm{model}} +2b n_h s_qs_k +2b s_q k d_{\mathrm{moe}} \Bigr]

对未做 FlashAttention 式优化的 MHA,可粗略写成:

MactMHA1230[6bsqdmodel+2bnhsq2+2bsqkdmoe]M_{\mathrm{act}}^{\mathrm{MHA}} \approx \frac{1}{2^{30}}\Bigl[ 6b s_q d_{\mathrm{model}} +2b n_h s_q^2 +2b s_q k d_{\mathrm{moe}} \Bigr]

这些式子只能作为上界式估算。算子融合、重计算、FlashAttention、缓冲区复用和数据类型都会改变峰值。

4. 总显存预算

把各部分合起来:

MtotalSdenseTP+SMoE2EP+MKV+Mact+MruntimeM_{\mathrm{total}} \approx \frac{S_{\mathrm{dense}}}{TP} +\frac{S_{\mathrm{MoE}}}{2EP} +M_{\mathrm{KV}} +M_{\mathrm{act}} +M_{\mathrm{runtime}}

原文示例把 CUDA Graph、通信缓冲区、内存池和系统运行时合计预留约 10 GB/卡。这个值与框架、捕获的 Batch 档位和部署参数有关,不能机械照搬。

五、HBM 带宽:Decode 为什么容易变成访存瓶颈

显存“装得下”只回答容量问题;每一步需要从 HBM 搬多少数据,决定了带宽问题。

总 HBM 流量可拆成:

DHBM=Dweight+DKV+DactD_{\mathrm{HBM}} =D_{\mathrm{weight}}+D_{\mathrm{KV}}+D_{\mathrm{act}}

1. 权重流量

设最忙的一张卡实际访问 kcardk_{\mathrm{card}} 个专家,粗略的单步权重读取量为:

DweightSdenseTP+SMoE257kcard2D_{\mathrm{weight}} \approx \frac{S_{\mathrm{dense}}}{TP} +\frac{S_{\mathrm{MoE}}}{257}\cdot\frac{k_{\mathrm{card}}}{2}

最不均衡时,kcardk_{\mathrm{card}} 可能接近 9;均衡分布时则更接近:

kcard1+8EPk_{\mathrm{card}}\approx 1+\frac{8}{EP}

权重读取对 Batch Size 并不线性增长。一次把权重读入片上缓存后,让更多 Token 复用,通常能提高算术强度。这正是 Decode 阶段扩大 Batch 的主要收益之一。

2. KV Cache 流量

Decode 每生成一个 Token,都要读取历史 KV。MLA 下的近似流量为:

DKV/tokenNLbsk(dc+dR)pKV230GBD_{\mathrm{KV/token}} \approx \frac{N_L\,b\,s_k\,(d_c+d_R)\,p_{\mathrm{KV}}}{2^{30}} \quad \mathrm{GB}

它会随 Batch 和上下文长度线性增长,因此长上下文 Decode 即使 FLOPs 不高,也可能被 HBM 带宽限制。

3. 激活读写流量

如果中间结果都要落到 HBM,再被下一个 Kernel 读回,那么至少会产生一次写和一次读:

Dact2NL(Mlinear+Mscore+MFFN)D_{\mathrm{act}} \approx 2N_L\left(M_{\mathrm{linear}}+M_{\mathrm{score}}+M_{\mathrm{FFN}}\right)

对未充分融合的 Prefill,可展开为:

DactPrefill2NL230[6bsqdmodel+2bnhsq2+2bsqkdmoe]D_{\mathrm{act}}^{\mathrm{Prefill}} \approx \frac{2N_L}{2^{30}}\Bigl[ 6b s_q d_{\mathrm{model}} +2b n_hs_q^2 +2b s_qk d_{\mathrm{moe}} \Bigr]

对 MLA Decode,可近似写成:

DactDecode2NL230[bsqdqc+bsq(dc+dR)+2bsqdmodel+2bnhsqsk+2bsqkdmoe]D_{\mathrm{act}}^{\mathrm{Decode}} \approx \frac{2N_L}{2^{30}}\Bigl[ b s_qd_{qc} +b s_q(d_c+d_R) +2b s_qd_{\mathrm{model}} +2b n_hs_qs_k +2b s_qk d_{\mathrm{moe}} \Bigr]

这组公式也直接说明算子融合为什么有效:把临时数据留在寄存器、共享内存或 L2 中,可以删除一部分“写回 HBM,再读回来”的往返流量。

六、通信:EP 和 TP 的代价不能只看总带宽

假设 Prefill 使用 TP+EP,Decode 使用 DP+TP+EP,总通信量可拆成:

Dcomm=DEP+DTPD_{\mathrm{comm}} =D_{\mathrm{EP}}+D_{\mathrm{TP}}

MoE 的 All-to-All 需要发送 Token 的隐藏状态。按“激活的其他专家都在远端”这一偏保守假设:

DEPNMoE2bsqdmodel(k1)p230GBD_{\mathrm{EP}} \approx \frac{N_{\mathrm{MoE}}\cdot 2b s_qd_{\mathrm{model}}(k-1)p}{2^{30}} \quad \mathrm{GB}

这里第一个 2 表示 dispatch 与 combine 两个方向。

TP 的 Reduce-Scatter 与 All-Gather 可近似为:

DTPNL2bsqdmodelp230TP1TPGBD_{\mathrm{TP}} \approx \frac{N_L\cdot 2b s_qd_{\mathrm{model}}p}{2^{30}} \cdot\frac{TP-1}{TP} \quad \mathrm{GB}

代入原文示例参数 NMoE=58N_{\mathrm{MoE}}=58dmodel=7168d_{\mathrm{model}}=7168k=9k=9p=2p=2,得到:

DEP0.012bsq GBD_{\mathrm{EP}}\approx 0.012\,b s_q\ \mathrm{GB} DTP0.00163bsqTP1TP GBD_{\mathrm{TP}}\approx 0.00163\,b s_q\frac{TP-1}{TP}\ \mathrm{GB}

这些数值只表示通信量,不等于通信时间。还必须区分数据走的是机内 NVLink/NVSwitch,还是跨机 RDMA:

tcommDintraBintra,eff+DinterBinter,eff+tcollectivet_{\mathrm{comm}} \approx \frac{D_{\mathrm{intra}}}{B_{\mathrm{intra,eff}}} +\frac{D_{\mathrm{inter}}}{B_{\mathrm{inter,eff}}} +t_{\mathrm{collective}}

tcollectivet_{\mathrm{collective}} 包含协议、同步、小包和尾延迟等开销。EP 越大,单卡专家数越少,但跨机概率通常也越高;是否值得,必须把专家负载均衡、计算形状和网络拓扑放在一起测量。

七、第二步:先消除明显浪费

在扩卡或改并行策略之前,应先删除不产生有效 Token 的工作。

1. 避免重复 Prefill

当大量请求共享系统提示词、文档或会话前缀时,可以用分布式 KV Cache 复用已经完成的 Prefill。原文案例采用 vLLM、LMCache 与远端存储组合,并报告在某个“约 20% 输入重复”的场景中减少了约 16% Prefill 计算。

收益是否成立取决于前缀稳定性、块粒度、命中率、远端读取耗时和 KV 格式兼容性。缓存未命中或远端读取比重算更慢时,缓存本身也可能成为负担。

2. 填平 CPU—GPU 空泡

Decode 的单步很短,CPU 调度和 Kernel Launch 很容易暴露在关键路径上。常用方法包括:

  • CUDA Graph 减少重复 Launch;
  • Continuous Batching 保持可执行 Batch;
  • 当前 Step 的 GPU 工作尚未结束时,CPU 提前准备下一步 Metadata;
  • 把采样、停止条件和调度逻辑从 GPU 关键路径移开。

原文案例报告 CPU—GPU 重叠带来 Decode 10% 以上收益,但这不是任何框架、任何负载都能复现的固定数字。

3. 用融合减少 HBM 往返

MoE 路由常包含 GEMM、归一化、Top-K、排序和索引重排。如果每一步都是独立 Kernel,就会产生大量 Launch 和临时张量读写。将它们融合后,收益来自两部分:

ΔtΔNlaunchtlaunch+ΔDHBMBHBM,eff\Delta t \approx \Delta N_{\mathrm{launch}}\,t_{\mathrm{launch}} +\frac{\Delta D_{\mathrm{HBM}}}{B_{\mathrm{HBM,eff}}}

同理,W4A8 的 Pre-Quant 与 Expand Row 如果能在寄存器或片上存储中完成,就不必把量化中间结果反复写回 HBM。

八、第三步:让不同资源相互匹配

1. P/D 分离

Prefill 更偏计算密集,Decode 更容易受 HBM 容量和带宽限制。P/D 分离的核心不是“拆成两个服务”本身,而是允许两类节点使用不同的并行度、Batch、调度和扩缩容策略:

请求 → Prefill 节点 → KV Cache 传输 → Decode 节点 → 流式输出

代价是新增 KV 传输、路由和一致性问题。因此只有当分阶段调优带来的收益大于传输与系统复杂度时,拆分才有价值。

2. KV 传输与计算重叠

如果先算完所有层,再一次性发送 KV,传输会直接进入关键路径。Layerwise 传输把过程改为“算完一层,异步发送一层”,理想情况下:

t_{\mathrm{Prefill+transfer}} \approx \max\left(t_{\mathrm{Prefill}},t_{\mathrm{transfer}} ight)

而不是:

tPrefill+transfertPrefill+ttransfert_{\mathrm{Prefill+transfer}} \approx t_{\mathrm{Prefill}}+t_{\mathrm{transfer}}

但短序列下,过多小包可能让协议开销占主导,因此需要在整块传输、分层传输和小包聚合之间动态选择。

3. 专家负载均衡

MoE 性能取决于最忙的 Rank,而不是平均 Rank。最简单的静态映射可以写成:

GPU_ID=Global_Expert_IDmodNGPU\mathrm{GPU\_ID} =\mathrm{Global\_Expert\_ID}\bmod N_{\mathrm{GPU}}

但热门专家仍可能集中到少数 GPU。生产系统通常还需要专家复制、动态重排、路由约束或跨节点分层放置,并同时观察计算、显存和跨机通信的变化。

九、第四步:从算法层面减少资源需求

1. MLA:压缩 KV Cache

传统 MHA 的 KV Cache 大致随头数和单头维度增长:

MKVMHANLbsknhdhM_{\mathrm{KV}}^{\mathrm{MHA}} \propto N_L\,b\,s_k\,n_h\,d_h

MLA 把 KV 压缩到低维潜变量:

MKVMLANLbsk(dc+dR)M_{\mathrm{KV}}^{\mathrm{MLA}} \propto N_L\,b\,s_k\,(d_c+d_R)

其主要价值不仅是“少占显存”,更是 Decode 时每个 Token 从 HBM 读取的历史状态显著减少。

2. W4A8 / FP8:压缩权重和搬运量

权重量化带来的理想容量比例为:

MINT4MFP848=12\frac{M_{\mathrm{INT4}}}{M_{\mathrm{FP8}}}\approx\frac{4}{8}=\frac{1}{2}

但端到端加速并不会自动达到 2 倍。反量化、scale 读取、Kernel 支持、GEMM 形状和精度回退都会侵蚀收益。原文提到的 W4A8 吞吐提升属于特定 H20 部署和测试条件,不能脱离模型、并行度与服务目标复用。

3. DSA:把二次复杂度中的大维度换成小维度

传统稠密注意力的主体计算近似为:

Fdense2bdmodels2F_{\mathrm{dense}} \approx 2b\,d_{\mathrm{model}}\,s^2

稀疏注意力先用较小的索引维度 dId_I 寻找候选,再只对 Top-KK 位置做完整计算:

Findexer2bHIdIs2F_{\mathrm{indexer}} \approx 2b\,H_I d_I s^2 Fsparse2bdmodelsKF_{\mathrm{sparse}} \approx 2b\,d_{\mathrm{model}}\,sK

在原文的 128K 上下文示例中,取 dI=128d_I=128dc=512d_c=512K=2048K=2048。传统 MLA 需要读取的相对数据量为:

DMLA=128000×512=65,536,000D_{\mathrm{MLA}} =128000\times512 =65{,}536{,}000

DSA 的索引与稀疏读取量为:

DDSA=128000×128+2048×512=17,432,576D_{\mathrm{DSA}} =128000\times128+2048\times512 =17{,}432{,}576

因此相对比例约为:

DDSADMLA26.6%\frac{D_{\mathrm{DSA}}}{D_{\mathrm{MLA}}} \approx 26.6\%

也就是理论读流量下降约 73.4%。这只是该参数组合下的量级估算;候选召回率、索引器成本、Kernel 实现和精度影响仍需实测。

4. mHC:不要只算新增 FLOPs

原文还讨论了 mHC 一类更复杂的残差连接。其额外计算可概括为:

FmHC2ndmodel(n2+3n+3)bs+2(n2+2n)+In2F_{\mathrm{mHC}} \approx 2n d_{\mathrm{model}}(n^2+3n+3)bs +2(n^2+2n) +I n^2

附加状态规模约为:

MmHC2ndmodel+n2+2nM_{\mathrm{mHC}} \approx 2n d_{\mathrm{model}}+n^2+2n

对应的读写量近似为:

DmHC[(8n+2)dmodel+2n2+4n]bsD_{\mathrm{mHC}} \approx \bigl[(8n+2)d_{\mathrm{model}}+2n^2+4n\bigr]bs

这类结构在总 FLOPs 中占比可能不大,却可能受小算子、同步和 HBM 读写影响。评估新结构时,必须同时计算 FLOPs、临时状态和数据搬运,不能只看参数量。

十、把公式变成可执行的优化流程

一套更稳妥的实践顺序是:

  1. 固定模型、精度、输入/输出长度分布和 SLO;
  2. 用公式估算每请求的 FLOPs、显存、HBM 流量和通信量;
  3. 用 Profiler、GPU 指标和网络遥测校正有效算力与有效带宽;
  4. 判断瓶颈属于计算、容量、访存、通信还是调度空泡;
  5. 优先删除重复工作和无效搬运;
  6. 再调整 TP、EP、DP、Batch、Chunk 和专家放置;
  7. 最后评估量化、MLA、稀疏注意力等更深层改动;
  8. 每次优化都重新检查 TTFT、TPOT、吞吐、精度和尾延迟。

公式的价值不是给出一个绝对准确的线上数字,而是帮助我们提前排除不可能的方案、定位最值得测量的环节,并检查实测结果是否符合基本量纲。

参考