3.4 显存预算实战:从公式到调参


文档摘要

3.4 显存预算实战:从公式到调参 3.2 节我把显存预算的方法论讲了一遍,3.3 节给了启动参数的取值经验。但真到生产现场,你会发现自己反复在算同一笔账:换了一批硬件(A100 换成 H100)、换了一个量化版本(BF16 换成 FP8)、业务平均序列长度变了(从 2k 涨到 4k),都得重新算一遍「这张卡到底能扛多少并发」。如果每次都临时推导,既慢又容易算错。 这一节我把显存预算从「方法论」落地成「可重复使用的算账流程」:先把公式彻底拆开(每个因子为什么在那里、什么场景会变),再用三个真实硬件场景把公式跑一遍,最后讲那些公式算不准的「暗坑」——激活峰值、MoE 专家路由、PagedAttention 碎片。

3.4 显存预算实战:从公式到调参

3.2 节我把显存预算的方法论讲了一遍,3.3 节给了启动参数的取值经验。但真到生产现场,你会发现自己反复在算同一笔账:换了一批硬件(A100 换成 H100)、换了一个量化版本(BF16 换成 FP8)、业务平均序列长度变了(从 2k 涨到 4k),都得重新算一遍「这张卡到底能扛多少并发」。如果每次都临时推导,既慢又容易算错。

这一节我把显存预算从「方法论」落地成「可重复使用的算账流程」:先把公式彻底拆开(每个因子为什么在那里、什么场景会变),再用三个真实硬件场景把公式跑一遍,最后讲那些公式算不准的「暗坑」——激活峰值、MoE 专家路由、PagedAttention 碎片。读完之后你应该能在 10 分钟内对任意硬件 + 模型组合给出一份显存预算,而不是在压测里一次次 OOM 才学到。

显存总账:四块,谁是大头先抓谁

一张 GPU 卡在推理时的显存去向,归纳起来是四块:

总显存占用 = 模型权重 + KV Cache + 激活值 + 框架/上下文开销

但「四块并列」这个说法有误导性——它们的体量完全不在一个量级,调优时抓大放小:

  • 模型权重:常驻、固定。和参数量、精度强相关,BF16 下每十亿参数约 2GB;FP8 约 1GB;INT4 约 0.5GB。MoE 模型要把全部专家算进总参数,不是只算激活的。
  • KV Cache:可变、是高并发的大头。和并发数、序列长度、精度强相关,是 99% 的 OOM 来源。
  • 激活值:可变、但通常不是大头。和 batch size、序列长度正相关,预留 5%~10% 一般够。
  • 框架/上下文开销:零头。PyTorch caching allocator、CUDA context、CUDA Graph 录制缓冲,加起来约 1~3GB。
```mermaid pie title 单卡显存典型构成(80GB 卡,高并发 MoE 推理) "模型权重(含专家)" : 50 "KV Cache 池" : 40 "激活值" : 7 "框架/上下文开销" : 3 ```

记住这个比例:权重和 KV Cache 是两头大象,激活和框架是两只老鼠。调优时几乎全部精力都在「怎么让权重小一点(量化)、怎么让 KV 池大一点(utilization)」上,激活和开销除非异常否则不用专门处理。

KV Cache 公式:每个因子都要会拆

KV Cache 是大头,公式必须吃透。原始公式:

KV Cache = 2 × n_layers × n_kv_heads × head_dim × seq_len × batch × dtype_bytes

逐因子拆解,每一个都可能踩坑:

2(K 和 V):每个 token 在每层都要存 Key 和 Value 两份张量,所以乘 2。这个 2 永远在,不要漏。

n_layers(层数):Transformer 的层数。DeepSeek V4 这类大模型层数 60+,意味着 KV 成本天然就高。层数是模型结构定的,你改不了,但要记住「层数 ×2」这一项让大模型 KV 比 7B 小模型贵一个量级。

n_kv_heads(KV 头数)这是最容易算错的因子。在 GQA(Grouped Query Attention)/MQA(Multi-Query Attention)架构下,KV 头数远小于 query 头数。比如一个模型有 96 个 query 头、8 个 KV 头(GQA 分组),那 n_kv_heads = 8,不是 96。DeepSeek V4 这类用了 GQA/MQA 的模型,这一项可能只有 query 头数的 1/8 甚至 1/16——这就是为什么 GQA 能大幅压缩 KV。查这一项一定要看模型 config 的 num_key_value_heads,不要用 num_attention_heads

head_dim(每个头的维度):通常 64/128/256。和模型结构绑定,从 config 的 hidden_size / num_attention_heads 算出来。

seq_len(序列长度)这是业务侧唯一能动的因子,也是 max-model-len 设值的依据。包含 prompt + 生成的最大长度。注意:公式里要用业务峰值长度算成本上限,用平均长度算容量上限——两者差一倍很常见。

batch(并发数):这就是你要算的「能扛多少并发」的未知数。

dtype_bytes(每个元素的字节数):FP16/BF16 是 2 字节,FP8 是 1 字节,INT4 是 0.5 字节。vLLM 支持 KV Cache 量化(--kv-cache-dtype fp8),这一项减半是显存预算里最立竿见影的优化之一——稍后会专门讲。

算账案例 1:DeepSeek V4 671B MoE,BF16,单机 8 卡 H100

这是最常见的旗舰配置。模型参数:n_layers=61n_kv_heads=128head_dim=128,TP=8。业务:平均 seq_len=2000,峰值 8192,目标并发 128,BF16(dtype_bytes=2)。

第一步:单卡权重账。671B 参数 BF16 约 1342GB,TP=8 切到每卡约 168GB。但 H100 只有 80GB,单卡放不下——这就是为什么 671B BF16 必须多机部署,单机 8 卡 H100 装不下。

第二步:换 FP8 重算。FP8 权重每参数 1 字节,671B 约 671GB,TP=8 每卡 84GB——还是放不下 80GB。这时要么 TP 加到 16(双机),要么叠加 EP 切专家。常见做法:单机 8 卡 + EP 切专家,让每卡只常驻部分专家,单卡权重降到 50GB 量级。

第三步:算 KV 池。假设 utilization=0.90,单卡 80GB × 0.90 = 72GB;减权重 50GB,减激活 5GB,减框架 2GB,剩 KV 池 ≈ 15GB

单 token 单卡 KV(TP=8):

= 2 × 61 × (128/8) × 128 × 2 字节 = 2 × 61 × 16 × 128 × 2 ≈ 0.79 MB / token

单请求 KV(平均 seq=2000):0.79 × 2000 ≈ 1.58 GB

并发上限:15GB / 1.58GB ≈ 9——并发只有 9?这显然不够。问题出在哪?KV 头数没切对。TP=8 切 query 头时,KV 头也要按 TP 切,所以单卡视角的 n_kv_heads = 128/8 = 16(上面已经按这个算了)。但单 token KV 0.79MB 在 2000 长度下要 1.58GB,确实只能撑 9 个并发。

这个案例的真启示:671B MoE 即便 FP8+EP,KV Cache 也是显存瓶颈。要么上 KV Cache 量化(FP8 KV 让单 token KV 减半到 0.4MB,并发翻倍到 18),要么牺牲并发数换序列长度(业务峰值砍到 4k),要么扩到双机。这是为什么旗舰 MoE 模型部署成本下不来的根本原因——KV Cache 跟着层数和并发走,量化只能压一半

算账案例 2:DeepSeek V4 中等尺寸(约 30B 等效),INT4 量化,单机 8 卡 A100 40G

中端配置:8 张 A100 40GB(整机 320GB)。模型用 INT4 量化,单卡权重约 12GB。层数 48,n_kv_heads=8(强 GQA),head_dim=128,TP=8。

单卡账:utilization 0.90 → 36GB;减权重 12GB,减激活 4GB,减框架 2GB,剩 KV 池 ≈ 18GB

单 token 单卡 KV:

= 2 × 48 × (8/8) × 128 × 2 字节 = 2 × 48 × 1 × 128 × 2 = 24.5 KB / token

注意这里 KV 头数 8 正好被 TP=8 整除,单卡只剩 1 个 KV 头——这是强 GQA 的福利,KV 极省。

单请求 KV(平均 seq=2000):24.5KB × 2000 ≈ 49 MB

并发上限:18GB / 49MB ≈ 367。这种中等模型 + 强 GQA,单机能扛 300+ 并发,吞吐天花板非常高。

启示:KV 头数对并发的影响是数量级的。强 GQA/MQA 模型(KV 头数 1~8)比传统 MHA(KV 头数=query 头数)的并发能力高 10 倍以上。选模型时如果业务要扛高并发,优先选 GQA/MQA 架构,这比任何调参都管用

算账案例 3:单卡消费级(RTX 4090 24GB)跑小模型

边缘/开发场景:单张 24GB 卡,跑 7B 小模型,BF16。权重约 14GB。层数 32,n_kv_heads=8(GQA),head_dim=128,TP=1。

单卡账:utilization 0.85 → 20.4GB;减权重 14GB,减激活 2GB,减框架 1GB,剩 KV 池 ≈ 3.4GB

单 token KV:

= 2 × 32 × 8 × 128 × 2 字节 = 131 KB / token

单请求 KV(平均 seq=2000):131KB × 2000 ≈ 262 MB

并发上限:3.4GB / 262MB ≈ 13

启示:消费级卡跑小模型,并发能力其实不差(13 个),瓶颈不在显存而在算力(4090 算力远低于数据中心卡,单请求生成速度慢)。所以这种场景的调优重心要从「显存预算」转向「算力/吞吐」,公式算出来的并发上限未必能跑满。

反算:先有硬件和模型,求并发上限

把上面三个案例抽象成一个反算公式,每次新部署都能套用:

max_num_seqs ≈ (单卡显存 × utilization − 单卡权重 − 激活余量 − 框架开销) ─────────────────────────────────────────────────── 2 × n_layers × (n_kv_heads/TP) × head_dim × 平均seq_len × dtype_bytes

实操步骤

  1. 查模型 config,记下 n_layersn_kv_headshead_dimmax_position_embeddings
  2. 算单卡权重(参数量 × 每参数字节 / 并行度)。
  3. 估激活余量(保守取单卡显存的 5%~10%)和框架开销(取 2GB)。
  4. 决定 utilization(生产取 0.85~0.90)。
  5. 估算业务平均 seq_len(采样真实流量 P50,不是峰值)。
  6. 代入公式得 max_num_seqs 理论值。
  7. 生产值 = 理论值 × 0.7~0.8(留余量给长度不均和突发)。

这套流程我在团队里做成了一张 Excel/在线表格,每次新硬件或新模型部署,填 4~5 个参数就能出预算,比每次压测 OOM 再回退快得多。

公式算不准的暗坑

公式是理想化的,生产里有几个坑会让实际能扛的并发比公式低 20%~40%,必须心里有数:

坑 1:激活值峰值。公式里激活按平均预留,但某些层(比如 MoE 路由后的专家合并层)激活会瞬间膨胀,峰值可能是平均的 2~3 倍。如果你按平均预留,峰值时刻会 OOM。对策:激活余量取单卡显存的 8%~10%,不要只取 5%。

坑 2:MoE 专家路由不均。MoE 模型每 token 激活不同专家,热门专家的激活 buffer 比冷门专家大。如果路由不均(某些专家被高频选中),那些专家的激活峰值会异常高。对策:观察 expert_routing_heatmap(部分版本 metrics 暴露),路由严重不均时考虑加辅助损失或路由偏置。

坑 3:PagedAttention 碎片。vLLM 用 block 分页管理 KV,每个 block 固定大小(默认 16 token)。序列长度不是 16 整数倍时,最后一个 block 浪费。碎片率通常 5%~10%,但极端长度分布下能到 15%。对策:在 KV 池总容量上再打 90% 折扣,即公式结果乘 0.9。

坑 4:CUDA Graph 录制缓冲。开 CUDA Graph 后,每个录制过的 shape 会占一块缓冲,batch size 变化越多,缓冲占用越大。对策:生产里限制 max-num-seqs 的取值不要剧烈跳变,或开 eager 模式(牺牲吞吐换显存,权衡取舍)。

```mermaid flowchart LR A[理论 max_num_seqs] -->|× 0.9 碎片折扣| B B -->|× 0.8 突发余量| C C -->|预留 10% 激活| D[实际上线值] style D fill:#cfc ```

把这三层折扣叠起来,你的实际上线值大约是公式原始值的 65%~70%。这就是为什么 3.3 节我反复强调「理论值打 7 折再上生产」——不是保守,是公式本身算的就是理想值,必须留出真实世界的不完美。

一个进阶优化:KV Cache 量化

公式里 dtype_bytes 这一项,BF16 是 2,FP8 是 1——直接减半。vLLM 支持 --kv-cache-dtype fp8,权重保持 BF16/FP8 不变,只把 KV Cache 量化到 FP8。代价是精度略有损失(长序列生成可能轻微质量下降),收益是并发翻倍。

我的实测:DeepSeek V4 在 H100 上开 FP8 KV,吞吐提升约 80%(接近翻倍但受其他瓶颈限制没到 100%),生成质量在常规业务(对话、摘要)下感知不到差异,在超长上下文(>16k)的精细任务(如代码生成)下偶有 token 选择偏差。结论:通用业务大胆开 FP8 KV,精细长上下文任务慎用或做 A/B 评估。

这是一个「不花一分钱硬件就把并发翻倍」的优化,公式里减半那一下对应到生产里就是少买一半的卡,价值极高。

显存预算的最后忠告

把这一节收一收,给三条我每次都会强调的忠告:

第一,算账在压测之前,不在 OOM 之后。先按公式算出预算,再压测验证,能省掉 80% 的 OOM 调试时间。先压测再算账是本末倒置。

第二,公式给出的是上限,不是建议值。生产值永远要打 7 折,留出真实世界的不完美(碎片、激活峰值、路由不均)。搏满公式值的部署,迟早会在某个流量高峰 OOM。

第三,显存预算不是一次性的活,是随业务演进的。平均 seq_len 涨了、并发峰值变了、换了量化版本,都要重算。把公式和算账流程固化成团队资产(脚本、表格),而不是某个人的口头经验——这是把「调参手艺」变成「工程能力」的关键一步。


作者与出处
来源:灏天文库
整理: 灏天文库整理
由灏天文库平台收录,内容或由平台用户上传,仅供学习交流
发布者: 作者: 运行报错的菜鸟的小龙虾 转发
评论区 (0)
U