资讯动态

大模型显存去哪了?训练推理显存开销与容量规划全解析

发布时间:2026/10/20 5:11:44 来源:尧图企业网站定制
最近调一个长文本对话服务的部署碰到的第一个坎不是模型效果而是显存。模型权重只占了一半出头剩下的空间被一堆看不见的中间产物填满服务一上并发就报“out of memory”你问它到底缺在哪它只管甩给你一行错误码。这一讲的话题就是被很多同学催了很久的显存去哪儿了。这一讲我们把大模型训练和推理的显存开销拆成一笔一笔能落地的账权重、梯度、优化器状态、激活值、KV Cache、运行时开销每一项的公式、量级和优化手段都会讲到同时把FLOPs白板手算和KV Cache容量规划串起来让你拿到任何一张模型卡都能在纸上提前算出显存预算而不是等报错之后再去猜。适合正在做模型训练、推理服务部署、或者刚入大模型底层原理没多久的读者从头到尾跟着手算一遍比背十篇博客都管用。1. 先建总账显存账单的六个抽屉和两把尺子1.1 训练和推理显存账单的画风完全不同大模型时代显存就是硬通货。但训练一个模型要多少显存和推理这个模型要多少显存根本不是一回事。训练时你同时要养着参数的“原稿”、参数的“草稿”梯度和优化器的“台账”三套东西都在显存里活着推理时参数只需要一份“只读副本”剩下的显存大头变成了随上下文长度动态增长的KV Cache。这个差异不搞清楚你会踩两种典型翻车现场拿训练的思路去规划推理服务把KV Cache整个漏算或者拿推理的估算方式去做训练发现连优化器状态都装不下训练压根没法起步。我习惯把显存理解成一个有六个抽屉的柜子而不是一整块空地。程序申请显存时总是从某个抽屉里拿但这个抽屉是哪个、里面装了多少东西错误信息不会告诉你。这六个抽屉分别是模型权重、梯度、优化器状态、激活值、KV Cache、运行时与通信缓冲区。训练和推理最大的区别就是这六个抽屉里到底哪几个被塞满了——训练几乎六个全占推理通常只占权重、KV Cache和运行时三个另外两个是瞬时客人。1.2 六个抽屉训练和推理各占了谁先给一张总体框架表后面每一节都是在给这张表填具体数字显存抽屉训练阶段量级推理阶段量级说明模型权重参数量 × 2字节FP16/BF16参数量 × 2字节量化后可压到1字节或0.5字节推理常用INT8/INT4做体积换质量梯度参数量 × 2字节无反向传播才产生优化器状态参数量 × 12字节Adam无主权重、动量和方差各4字节激活值与batch、序列长度、层数成正比预填充阶段短暂存在训练里最容易爆的隐藏项KV Cache无batch × 上下文长度 × 层数 × 隐藏维度 × 2字节长上下文的大敌运行时/通信1~3GB起1~3GB起分配器缓存、通信缓冲区、驱动的context两把尺子可以先背下来混合精度训练一个参数总共约16字节——FP16权重2字节、FP16梯度2字节、FP32主权重4字节、动量和方差各4字节推理一个参数至少2字节——FP16/BF16量化之后压到1字节甚至0.5字节。第一把尺子会让很多人震惊7B模型按FP16权重算只有14GB看起来一张24GB卡随便装但Adam训练全量是16×7112GB一张80GB的卡都装不下。为什么会有这么大的膨胀下一节拆给你看。2. 训练账单拆解为什么Adam训练要把参数撑到16字节2.1 混合精度训练里的五份拷贝训练阶段模型权重本身其实只占很小一部分真正吃显存的是优化器状态。原因在混合精度训练的设计上前向和反向用FP16/BF16跑省显存、算得快但更新参数时必须用FP32否则梯度太小直接变成零模型根本训不动。于是每个参数在显存里同时存在多份拷贝精度每参数字节数作用权重训练态FP16/BF162前向和反向计算用梯度FP16/BF162反向传播攒出来的更新量主权重FP324Adam更新时用保证精度动量mFP324Adam一阶矩方差vFP324Adam二阶矩加起来就是2244416字节。所以“训练7B模型要112GB显存”不是某个框架的毛病而是Adam混合精度训练的结构性开销。如果你用SGD优化器状态只有主权重4字节总开销变成8字节每参数直接减半。很多大模型预训练切换到AdamW更聪明的分区策略核心目标就是把12字节的优化器状态这口井给挖小。2.2 7B模型手算从112GB到每卡26GB拿一个7B参数的稠密模型举例参数量按70亿算权重7 × 2 14GB梯度7 × 2 14GB优化器状态7 × 12 84GB合计14 14 84 112GB一张80GB的卡装不下更何况还要留激活值和通信缓冲。业界最主流的解法是ZeRO式分片把优化器状态甚至梯度、权重按卡数切散。设GPU数量为P8则方案每卡参数部分显存计算式不分片112GB14 14 84只分片优化器ZeRO-138.5GB14 14 84/8分片优化器梯度ZeRO-226.25GB14 14/8 84/8权重也分片ZeRO-314GB112/8顺手写一个估算函数以后任何模型套进去就行def estimate_train_memory(params_b, gpus8, zero_stage2): # 单位为GB十进制实际按GiB分配会多约7% weight params_b * 2 # FP16权重 grad params_b * 2 # FP16梯度 adam params_b * 12 # FP32主权重 动量 方差 if zero_stage 0: return weight grad adam if zero_stage 1: return weight grad adam / gpus if zero_stage 2: return weight grad / gpus adam / gpus if zero_stage 3: return (weight grad adam) / gpus return 0 print(estimate_train_memory(7, gpus8, zero_stage2)) # 26.25注意ZeRO-3虽然账面上最省但权重每次用之前都要全卡通信拉回来通信量暴涨小规模集群不一定划算。实操中8卡规模训7BZeRO-2通常是甜点位单卡训7B也不是不可能但必须上ZeRO-3加CPU offload速度感人。这里的数字只是参数部分下一节讲的激活值才是压死骆驼的最后一根稻草。2.3 激活值训练里最容易被漏算的隐性开销激活值是前向传播过程中每一层的中间结果反向传播算梯度时要拿它回算所以不能丢。由于激活值和参数量没有直接关系一个3B模型和一个70B模型在小batch下激活值可能差不多大这是很多人栽跟头的地方以为模型小了显存就稳结果调大batch直接OOM。激活值的大小约等于“每层需要保留的中间矩阵数 × 序列长度 × 隐藏维度 × 字节数 × 层数 × batch”。以7B、隐藏维度4096、32层、序列长度2048、batch为8来估算每层保守按12份s×d规模的矩阵加注意力得分矩阵算大约每层176MB32层合计约5.6GB加上注意力矩阵的s²项总共奔着6.5~7GB去。这个量级看起来还能接受但你把batch从8提到64激活值直接翻到50GB往上参数部分还没动显存先被激活值吃光了。应对手段是激活重计算activation checkpointing前向时不存中间结果只存每层输入反向传播时重新算一遍把“存”换成“算”显存能从几十GB掉到1~2GB。代价是约三分之一的算力换显存。另外要澄清一个常见误区梯度累积并不能减少激活值显存。梯度累积只是把每次前向的batch拆小、多次累加再更新但显存里的激活值按真实batch算所以靠调“梯度累积步数”救不了显存得真改batch或者开重计算。3. FLOPs白板手算先算清计算量再谈显存预算3.1 6ND和2N两个公式的身世显存和算力是一对孪生兄弟你选了某种并行策略显存降下来了计算效率可能跟着掉算力算不清显存规划也容易拍脑袋。白板手算FLOPs只需要记住两个公式训练总计算量 ≈ 6 × 参数量N × 训练token数D推理每token计算量 ≈ 2 × 参数量N推导不复杂每token每参数做一次矩阵乘乘法和加法各算一次FLOP所以前向是2N后向要对权重求梯度再对激活求梯度一共4N前向加后向就是6N。推理没有反向只有前向所以每token是2N。嵌入层、输出logits这些边角料在小模型里有影响模型一上7B基本可以忽略。3.2 注意力FLOPs序列变长时平方项开始咬人上面6N是整模型的口径但注意力部分的FLOPs有自己的结构。每个token每层的自注意力去掉常数后大约是8d² 4sd其中d是隐藏维度s是序列长度。d²项来自QKV投影和输出投影sd项来自注意力得分矩阵。看两个极端场景s每层每token注意力FLOPsd4096占整模型比例解码态生成一个token1约134M几乎可忽略预填充处理4096长prompt4096约201M约46%整模型每token算力是2N14GFLOPs序列4096时注意力部分占了将近一半而且这一半随s线性上涨。换句话说长上下文场景下算力瓶颈从权重矩阵悄悄转移到注意力矩阵。这就是为什么长序列训练普遍要用到稀疏注意力、Flash类注意力这些优化——它们本质上都是在动4sd这一项。手算时如果只套6N会严重低估长序列场景的耗时。3.3 算力换时间单卡4年1024卡4天的算术有了总FLOPs再除以卡数和单卡算力就能估算训练时间。设单卡峰值算力312 TFLOPS即每秒3.12×10^14次FLOP7B模型训1万亿token总FLOPs 6 × 7×10^9 × 10^12 4.2×10^22单卡100%效率耗时 4.2×10^22 ÷ 3.12×10^14 ≈ 1.35×10^8秒 ≈ 4.3年实际MFU按40%算单卡要约10.7年用1024卡并行则约10.7年 ÷ 1024 ≈ 3.8天配置单卡100%效率单卡MFU 40%1024卡MFU 40%7B训1T tokens约4.3年约10.7年约3.8天MFU就是“实测有效计算量÷峰值计算量”大规模集群调得好能到40%~50%极端优化能冲60%以上但很少有人真按峰值排期。显存规划也一样别按理论值满打满算通信、等待、框架开销都会吃掉实际余量。接下来的KV Cache规划同样要带着这个“留量”的心态来做。4. 推理账单拆解KV Cache才是长上下文真正的吞金兽4.1 KV Cache公式与7B模型手算推理阶段没有梯度和优化器状态但每生成一个token都要拿历史所有token的Key和Value去算注意力。如果每次重新算一遍时延会随序列长度线性爆炸。所以主流框架的做法是把历史K和V缓存下来这就是KV Cache。KV Cache的公式要写准确KV字节数 batch × 序列长度 × 层数 × 每层KV维度 × 字节数 × 2其中“×2”是K和V各一份。对MHA多头注意力每层KV维度等于隐藏维度d对GQA分组查询注意力要乘一个压缩比kv头数÷query头数。先算7B模型隐藏维度4096、32层、MHA、FP16精度。每token每层的KV字节 2 × 4096 × 2 16KB32层合计512KB/token。这个数字看着不大乘上序列长度立刻变脸上下文长度batch1batch81024512MB4GB40962GB16GB81924GB32GB3276816GB128GB一张24GB的卡跑8并发、8K上下文权重14GB加KV Cache 32GB妥妥超了。这就是为什么小模型也能把大卡吃爆——问题不在参数在KV Cache。对应的估算函数也很简单def kv_cache_bytes(batch, seq_len, layers, hidden, kv_ratio1.0, precision2): # kv_ratioMHA为1.0GQA为kv头数/query头数比如8/640.125 per_token 2 * hidden * kv_ratio * precision * layers return batch * seq_len * per_token # 字节 # 7BMHAbatch8seq8192 print(kv_cache_bytes(8, 8192, 32, 4096, 1.0, 2) / 1e9, GB) # 约32.2GB4.2 72B模型32K上下文推演MHA到GQA到FP8再看一个更狠的场景72B模型、80层、隐藏维度8192、目标上下文32K。如果按MHA算每token每层 2 × 8192 × 2 32KB80层合计约2.5MB/token单用户32K上下文 2.5MB × 32768 ≈ 85.9GB也就是一个用户就把一张80GB卡的KV Cache份额吃掉还不止跟权重144GB一样夸张。这时候架构层面的优化就不得不上了第一刀是GQA/MQA。GQA让多个query头共享一组KV头。假设64个query头、8个KV头压缩比8/640.125KV Cache直接降到10.7GB。第二刀是KV Cache量化FP16换FP8字节数再砍一半到5.4GB。两刀下去32K上下文单用户从85.9GB降到5.4GB这才有商业上线的可能性。方案72B单用户32K的KV CacheMHA FP16约85.9GBGQA(8组) FP16约10.7GBGQA(8组) FP8约5.4GB所以你会发现很多新发布的模型都宣称用GQA不是因为它能提精度而是它实打实把长上下文服务的显存成本砍到十分之一。选模型时看架构参数别只看总参数量。4.3 省KV Cache的三种思路结构、精度、窗口除了GQA和量化还有第三类手段窗口化。滑动窗口注意力只保留最近若干token的KV加上一个“锚点”token守住全局信息这样KV Cache的增长率从“随序列长度线性”变成“到窗口大小封顶”。代价是模型对超出窗口的历史记忆变弱适合对话、摘要这类对近期内容更敏感的任务。另外一个容易忽略的点是KV Cache的分配时机。很多框架会按“最大上下文 × 预分配batch”一次性把KV空间占好无论实际请求多短都按最大算也有框架按需分配、动态扩容。做容量规划时建议直接按“最大并发数 × 最大上下文长度”做最坏情况预算然后拿这个数去对比服务的并发目标别拿平均请求长度的账来糊弄流量一抖直接就炸。预填充阶段还要额外留出激活值的临时空间处理一条32K的prompt每层的中间矩阵会短时间占用几个GBdecode阶段反而没有这个峰值。所以推理服务的显存账本要写两行一行是常驻的权重KV一行是prefill瞬时激活。5. 容量规划实操一张纸从模型参数推到显存预算5.1 训练场景推演7B模型在8张32GB卡上的完整预算表纸上谈兵到此为止我们做两个真实推演。场景一8张32GB卡训练7B模型序列长度2048batch希望跑到8。参数部分用ZeRO-226.25GB/卡激活部分批次8、序列2048、开启激活重计算约1~2GB/卡不开重计算则6.5GB左右运行时和通信缓冲区约2~3GB/卡项目估算GB/卡备注权重ZeRO-2后14.0FP16梯度分片后1.75FP16优化器状态分片后10.5Adam激活重计算后1.5batch8, seq2048运行时/通信2.5取中间值合计约30.25GB贴近32GB上限结论很明确这个配置能跑但几乎贴满。我会建议把batch降到4梯度累积补到8步或者开启更激进的重计算给自己留出10%的呼吸空间。32GB的卡一旦实际峰值冲到31GB以上分配器还按块向上取整很容易莫名OOM。5.2 推理服务推演72B模型做32K上下文服务的完整预算表场景二72B模型做32K上下文的对话服务希望支持8并发。先算权重FP16是144GB两张80GB卡张量并行后每卡72GB权重INT8量化后每卡36GBINT4再减半到18GB。KV Cache按GQA加FP8估算单用户32K约5.4GB8并发是43.2GB加上权重两张80GB扛FP16权重完全没戏72 43 80必须INT4权重或更多卡分摊4张80GB卡做张量并行每卡权重36GBFP8的KV摊到每卡约10.8GB加运行时约3GB总计约50GB/卡留量充足。部署方案每卡权重每卡KV合计是否可行2×80GBFP16权重72GB10.8GB约85GB不可行2×80GBINT4权重18GB10.8GB约31GB可行4×80GBFP16权重36GB5.4GB约44GB可行这就是为什么市面上推理服务普遍上量化和多卡张量并行不是为了炫技是账算下来只有这条路能走。INT4对质量的影响要看实际评测但72B模型压到INT4之后显存占用只有原来的四分之一服务并发能力提升不是一点半点。5.3 真OOM了先按这四步排查纸面预算再漂亮线上还是会碰到显存不足。我排查的顺序固定是四步每一步都能排除一大片第一步纸面账超了没。按上面公式套一遍如果权重加KV已经超过物理显存直接进入降载方案别浪费时间调参数。第二步是不是碎片化。显存分配器有缓存机制申请过的块即使释放了也可能不还给驱动导致“显存看着还有但申请大块失败”。这种场景重启进程、降低动态申请频率通常立竿见影。第三步是不是预填充瞬时峰值。prefill阶段激活值会突然跳高长prompt尤其明显把峰值打下来要用激活重计算或限制单请求长度。第四步看看是不是别的进程在占卡。一张卡上挂了两个服务、或某个残留进程没杀干净都会让显存凭空少几GB。查进程、清环境比优化代码快十倍。5.4 留余量和几个容易被忽略的细节最后讲几个我在实战里反复踩的细节。第一GB和GiB差约7%。显存标称用十进制GB程序分配按二进制GiB标称80GB实际可用约74.5GiB所以纸面预算最好留10%以上余量不然账面上明明没超实际却爆了。第二多卡显存不能简单相加。每张卡的显存是独立寻址的张量并行才能把权重摊到多卡数据并行摊不开权重——摊开的是数据和优化器状态这个区别直接决定你该用哪种并行。第三分配器向上取整实际占用量往往大于理论估算尤其是小tensor特别多的场景。我自己现在接手任何模型第一件事不是急着装环境而是先拿一张纸把这几笔账算完训练看16字节每参数的尺子推理看2字节加KV公式再按最大并发和最大上下文做最坏情况预算最后留出一成以上的余量。这个习惯养成之后显存相关的坑至少能避开七成。下次再看到“out of memory”先别骂框架把这张账本翻出来对一遍答案通常就在其中一行里。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑