
版权与内容来源声明本文为原创整理。文中涉及官方文档、开源仓库、论文与公开报道的内容均在附表 A 中标注来源引用官方原文保持原样不作改写。文中命令、版本号与界面截图以本文成文时的实测/核验结果为准标注「待验证」的部分请以你本地环境实际输出为判断依据。本文不推荐任何不合规的软件获取方式也不对任何收益结果作承诺。转载请注明出处。第 1 章 显存不是一个数是四笔账第一次把模型拉到本地跑的人多半都经历过同一个瞬间看一眼参数规模觉得卡够用把上下文调长一点、或者同时来了几个请求程序直接被系统杀掉。这个现象叫显存溢出OOM显存不够用了运行中的进程被强制终止。问题出在一个默认假设上——很多人把「模型多大」当成了「显存占多少」。这两件事差得很远。显存其实是被四笔账分掉的权重、KV Cache、激活值与临时缓冲、框架与运行时开销。前三笔随你的用法变化第四笔几乎完全不受你控制但一直在那儿。1.1 四块账各自由谁决定先把四笔账摆到一张表里后面每一章再拆开讲。这一块账主要由谁决定随上下文长度涨吗随并发 / 批量涨吗最容易在哪里成为瓶颈权重参数量 × 每个参数的字节数不涨不涨模型刚加载完就快占满KV Cache层数、KV 头数、头维度、序列长度、精度线性涨线性涨上下文一拉长就崩激活值与临时缓冲批量大小、序列长度、模型深度、隐藏层维度涨涨加大批量或跑长序列时崩框架与运行时开销CUDA 上下文、CUDA 图、预分配余量、显存碎片不涨不涨「算着能跑、实际跑不了」1.2 为什么「装得下」是个错觉把模型权重加载进去、还没开始处理任何请求时占用确实只有第一笔账。这时候很多人会下一个结论「还有一半显存空着稳了。」但剩下那三笔账是在工作过程中才长出来的KV Cache 随着上下文变长和并发数增加而增长激活值与临时缓冲在每一层计算时起起落落框架的预分配和碎片则从头到尾都在那儿占着位置。所以「装得下」只证明了第一笔账能付没证明四笔账都付得起。第 2 章 第一笔账权重最死板的那一块权重就是模型训练完存下来的那一堆数字推理时它们不变。它是四笔账里最好算的因为公式只有一步乘法。2.1 粗算口径参数量 × 每个参数的字节数每个参数占多少字节取决于用什么数值精度存它。Hugging Face 的官方文档《Model memory anatomy》里写得很直接混合精度训练时权重需要两份拷贝一份 FP1616 位浮点占 2 字节用于前向和反向一份 FP3232 位浮点占 4 字节作为稳定更新的主拷贝合计6 字节/参数。推理场景通常只保留低精度那一份所以口径退化成一句乘法权重字节数 ≈ 参数量 × 每个参数的字节数。参数量一般在模型名或模型卡里就有比如模型名里的 7B 表示约 70 亿个参数。每个参数多少字节则由精度决定。常见叫法PyTorch 里的数据类型位数每个参数占的字节数FP32torch.float3232 位4 字节FP16torch.float1616 位2 字节BF16torch.bfloat1616 位2 字节INT8 / FP8torch.int8 / torch.float8_e4m3fn8 位1 字节INT4torch.float4_e2m1fn_x2打包存放4 位0.5 字节这张表的位数来自 PyTorch 官方文档《Tensor Attributes》页面更新于 2026-05-08它逐个列出了各数据类型的位宽字节数就是位数除以 8。有了口径和精度表粗算就是一次乘法。7B 的模型用 FP32 存大约是 28 GB用 FP16 或 BF16 存大约是 14 GB用 INT8 存大约是 7 GB换成 70B同样三种精度分别大约是 280 GB、140 GB、70 GB。这些数按 1 GB ≈ 10 亿字节 粗算只算权重、不含其它三笔账真实值还会因嵌入层大小、是否绑定词表而略有出入。2.2 为什么这笔账最「死」权重这一块的特点是不随你的输入变化上下文再长、并发再多它都不动。所以它只会造成两种结果——要么一开始就装不下要么装下之后就稳定占着不再是你后面崩掉的原因。也正因为它是静态的它才是第一个该被算清楚的数如果连权重都装不下后面三笔账不用讨论了直接换精度或分片。减少这笔账的手段就是把参数存得更省比如用低精度存权重但这属于另一个话题本文只把它算进账里不展开。第 3 章 第二笔账KV Cache随上下文线性增长的那一块模型「能装下」却「跑不动」绝大多数时候是这一笔账在作祟。它是四笔账里唯一会随着上下文长度线性上涨的一块。3.1 KV Cache 是什么大模型生成文本是一个 token 一个 token 往外吐的。每生成一个新 token都要拿它去和前面所有 token 做注意力计算。这种「靠注意力直接建立全局依赖」的思路源头是 Transformer 架构的原始论文《Attention Is All You Need》arXiv:1706.037622017-06-12 首次提交最新修订版为 2023-08-02 的 v7。如果每生成一步都把前面所有 token 的 key 和 value 向量重算一遍代价会随长度不断累积。工程上的做法是把每个 token 算出来的 key 和 value 向量缓存起来下一步直接取用。缓存下来的这一大块就叫KV Cache键值缓存模型为避免重复计算而缓存下来的注意力键值向量。vLLM 官方博客对这块的描述很直白原文是The KV cache isLarge:Takes up to 1.7GB for a single sequence in LLaMA-13B.Dynamic:Its size depends on the sequence length, which is highly variable and unpredictable.意思是一个序列的 KV Cache 就能到 1.7GB 这个体量而且它的大小取决于序列长度变化非常剧烈、难以预测。vLLM 论文的摘要里也是同样的定性——每个请求的 KV cache 占用很大而且是动态涨落的。注意「随序列长度变化」这几个字。权重是固定的一张账单KV Cache 是一张跟着你输入变长而不断加长的账单。3.2 它到底随什么涨把影响因子拆开一共六个。前五个决定「每个 token 存多少」第六个决定「存多少个 token」。影响因子为什么它会推高 KV Cache层数每一层都各自存一份 K 和 V层数越多份数越多KV 头数用分组查询的模型里KV 头数少于查询头数按 KV 头数算头维度每个注意力头向量的元素个数维度越大每个向量越长序列长度提示词加生成内容每一个 token 都要存一份并发序列数每个同时在跑的请求各存一份互不共用精度字节数每个元素占几个字节用 FP16 就是 2写成一句话单个序列、单个 token 的 KV 占用 ≈ 2 × 层数 × KV 头数 × 头维度 × 每个元素的字节数。开头那个 2 是「键 值」两份不是经验系数——KV Cache 这个定义本身就是把 key 和 value 一起缓存下来所以必然是两份。这个式子是按定义推导出来的不是官方文档里的原句附表 A 里注明了推导依据。3.3 为什么上下文一长就崩拿一组常见的配置代入32 层、8 个 KV 头、头维度 128、用 FP16 存2 字节。单 token 占用 2 × 32 × 8 × 128 × 2 131072 字节正好 128 KiB。上下文长度单个序列KV Cache 占用8K约 1.07 GB32K约 4.29 GB128K约 17.2 GB关键在最后一行。一个 7B 模型用 FP16 存权重是 14 GB 左右而单条128K 上下文的 KV Cache 就要 17 GB——比权重还大。这还没算并发同时有 4 条这样的序列KV 就是 68 GB 的量。所以「模型能装下长上下文跑不了」不是玄学是算术。你只付了第一笔账第二笔账翻倍地涨上来了。第 4 章 第三笔账激活值与临时缓冲这一块最不显眼但它是「一加大批量就崩」的常见原因。4.1 激活值是什么激活值activation是模型在每一层计算过程中产生的中间结果。训练时这些中间结果要留着给反向传播用所以必须驻留显存推理时大部分可以即算即弃但仍然会有一批临时张量同时存在。Hugging Face 官方文档对这种开销的说明是前向激活的大小随批量大小、序列长度、模型深度和隐藏层维度变化并且「即使模型本身放得下批量大小或序列长度也足以把显存耗光」。这句话正好解释了第 1 章里那个错觉——装得下和跑得动是两回事。4.2 临时尖峰短命但能致命同一份官方文档还提到另一类内存临时张量。它们由 softmax、矩阵乘法这类算子产生算完就释放生命周期很短但原文明确说了——如果单个算子的峰值过于密集就会形成一次临时内存尖峰把显存顶爆。这解释了一种很难查的现象显存监控上看平均占用不高但程序就是会在某一步挂掉。因为压垮它的不是平均值是那一瞬间的峰值。对排查的启示很明确如果你已经算过权重、也算过 KV两边都还宽裕但只要把批量调大就崩那么嫌疑基本就落在激活值与临时缓冲这一块。第 5 章 第四笔账框架与运行时开销前三笔账都和你的模型、你的输入有关这一笔账几乎和你无关但它一直在扣钱。5.1 固定的那部分CUDA 上下文与 CUDA 图CUDA 上下文是运行时为显卡建立的一套执行环境它本身就要常驻一部分显存模型还没开始算就已经被占掉一块。更值得注意的是优化带来的额外占用。CUDA 图是一种把一串计算调用提前固化下来、减少逐次调度开销的加速手段。vLLM 官方文档《Conserving Memory》页面日期 2026-07-09里写着默认情况下会用 CUDA 图来优化推理而 CUDA 图本身会额外占用 GPU 显存文档同时给出了compilation_config和enforce_eager两个开关来权衡速度与显存。也就是说性能优化不是免费的。你把某种加速打开它是拿显存换来的。这部分开销在纸面估算里常常被漏掉。5.2 预分配与碎片为什么「算出来能跑、实际跑不了」预分配是框架为了减少运行中反复申请释放的开销一次性预留一块显存的做法。它按上限来预留——你要支持 32K 上下文它就按 32K 的规模留位置。于是「其实用不到」的那部分也被占住了。显存碎片是另一个方向的浪费显存被切成一堆大小不一、互不相邻的小块之后即使空闲总量够也可能拼不出一个足够大的连续块给下一个请求。这两件事加起来的破坏力有多大vLLM 官方博客给过一个数在这套机制出现之前现有系统由于碎片化和过量预留会浪费掉 60% – 80% 的显存。这就是「按公式算完全够、实际就是起不来」的根源——你算的是需要多少框架面对的是能凑出多少连续空间。5.3 PagedAttention 解决的是哪一个问题PagedAttention是 vLLM 提出的一种注意力算法它要解决的不是「KV Cache 太大」而是「KV Cache 被浪费」。它的做法借用了操作系统的虚拟内存分页思想把 KV Cache 切成固定大小的块逻辑上连续、物理上可以不连续再用一张块表把逻辑块映射到物理块。vLLM 官方博客里对这个类比的原文是one can think of blocks as pages, tokens as bytes, and sequences as processes.即把块看作内存页、把 token 看作字节、把序列看作进程。vLLM 官方设计文档里对「块」的定义是——KV Cache 被切分成块每个块存放固定数量 token 在一个头上的数据。效果是显存浪费只可能发生在一条序列的最后一块官方给出的说法是浪费低于 4%。这块改进让同样的显存能容纳更多并发序列从而把吞吐拉上去。一篇讲显存账的文章最该配一份能照着算的估算清单。我把「参数量 → 权重、序列长度 → KV Cache」的估算口径和查显存用的命令整理在了一起和上面的推导是同一套口径。放在资料包里扫码即可获取第 6 章 可执行的排查顺序账拆完了接下来是顺序问题。顺序错了你会花很多时间在错误的那块账上找原因。正确的顺序是先看权重再看 KV Cache再看激活与批量最后看框架预分配。理由是前三笔按「改动难度」从小到大、按「是否随输入变化」从静到动排列最后一笔你基本改不动所以放最后兜底。6.1 四步排查顺序表步骤先算什么看什么指标什么现象说明就是这一块常规下一步1. 权重参数量 × 每参数字节数模型加载完、还没有请求时的占用此时占用就已接近上限换更省的精度或做分片2. KV Cache2 × 层数 × KV 头数 × 头维度 × 序列长度 × 字节数最大上下文长度与最大并发数短上下文正常、长上下文直接崩缩小最大上下文或最大并发3. 激活与批量批量 × 序列长度 × 模型规模不同批量下的峰值占用小批量正常、大批量崩降批量或开梯度检查点仅训练4. 框架预分配预分配比例与碎片启动时预留了多少平均占用不高但会挂调低预留比例或换分页式 KV 管理6.2 每一步具体看什么第一步看权重只需要一条命令就能看到静态占用。⚠️代码待验证nvidia-smi --query-gpumemory.total,memory.used,memory.free--formatcsv关键动作是在模型刚加载完、还没有任何请求进来时执行一次。如果这一下memory.used就接近memory.total说明第一笔账本身就超了后面几步都不用做。第二步和第三步可以合起来算不需要真正加载模型只要模型目录里有配置文件就行。⚠️代码待验证importjson cfgjson.load(open(模型目录/config.json,encodingutf-8))num_params7_000_000_000# 从模型名或模型卡获取的参数量bytes_per_param2# FP16 或 BF1616 位 ÷ 8 2 字节weight_gbnum_params*bytes_per_param/(10**9)num_layerscfg[num_hidden_layers]num_kv_headscfg.get(num_key_value_heads,cfg[num_attention_heads])head_dimcfg[hidden_size]//cfg[num_attention_heads]seq_len8192batch1kv_bytes2*num_layers*num_kv_heads*head_dim*seq_len*batch*bytes_per_param kv_gbkv_bytes/(10**9)print(f权重约{weight_gb:.2f}GB)print(fKV Cache 约{kv_gb:.2f}GB)字段名以你本地那份config.json为准不同模型可能缺省其中某几个键取不到时按模型卡的说明补齐。第四步是核对精度与字节数的换算关系确认自己没有把位数当字节数用。⚠️代码待验证importtorchforname,dtypein[(FP32,torch.float32),(FP16,torch.float16),(BF16,torch.bfloat16)]:ttorch.zeros(1024,dtypedtype)print(name,t.element_size(),字节/元素)最后是把嫌疑落到参数上。如果用服务框架部署先把最大上下文长度和最大并发数调小看还崩不崩——这两个参数正好是 KV Cache 公式里的两个乘数能帮你把第二笔账和第三笔账区分开。⚠️代码待验证vllm serve 你的模型目录 --max-model-len8192--max-num-seqs4调小之后如果恢复正常说明瓶颈在 KV Cache如果照旧崩再往激活值与临时缓冲那一层找。确认口径之前先回头看框架层启动时预分配了多少、有没有开 CUDA 图这类吃显存的优化以及是不是在「平均占用不高」的情况下挂掉——那就是碎片或临时尖峰的典型特征。第 7 章 把四笔账串起来前面拆开讲这一章合起来算一遍然后说清楚什么时候该停手。7.1 一个完整的手算例子场景7B 模型FP16 存权重头维度按第 3 章的配置想跑 8K 上下文、单序列。账目手算过程结果权重70 亿 × 2 字节约 14 GBKV Cache单 token 128 KiB × 8192约 1.07 GB激活与临时缓冲随批量与序列长度浮动单序列推理时是临时峰值远小于权重但会尖峰框架与运行时CUDA 上下文、CUDA 图、预分配余量通常还要留出一块余量结论一张 24GB 的消费级卡跑这个组合是够的还能留出余量。但如果把上下文拉到 128KKV Cache 一项就从 1 GB 涨到约 17 GB和权重同量级——总账一下子翻倍原来够用的卡就不够了。7.2 什么时候该停手、该换方向判断标准其实只有一条看哪笔账在变。如果权重是最大项而 KV 很小那你的瓶颈是静态的减少权重用更省的精度是有效方向。反过来如果 KV 已经和权重同量级甚至更大,那么再怎么压缩权重收益也有限——因为真正随你的用法膨胀的是 KV 那一块这时候该动的是上下文长度、并发数或者用分页与共享前缀这类减少浪费的机制。对着现象倒推嫌疑对应关系其实很好记模型刚加载完就快占满是权重短上下文正常、长上下文直接崩是 KV Cache单条请求正常、多来几个请求就崩是 KV Cache 里的并发那一份批量调大就崩、调小就正常是激活值与临时缓冲平均占用不高却某一步莫名挂掉是临时尖峰或显存碎片按公式算够用、实际就是起不来则是框架预分配与碎片。排查的顺序清单、估算口径和查显存用的命令我整理成了一份可以直接照着走的版本和正文用的是同一套推导。放在资料包里扫码即可获取附表 A本文引用事实与出处对照表事实出处文档名 发布方 链接本文位置KV Cache 体量大且动态单序列可到 1.7GB大小取决于序列长度《vLLM: Easy, Fast, and Cheap LLM Serving with PagedAttention》vLLM 项目官方博客2023-06-20https://blog.vllm.ai/2023/06/20/vllm.html第 3 章早期系统因碎片化与过量预留浪费 60%–80% 显存同上第 5 章PagedAttention 把 KV Cache 切成固定大小的块浪费只发生在最后一块官方称为低于 4%同上第 5 章块/页/进程的类比原文「blocks as pages, tokens as bytes, and sequences as processes」同上第 5 章块的定义KV Cache 被切成块每块存放固定数量 token 在一个头上的数据《Paged Attention》设计文档vLLM 官方文档页面日期 2026-06-24https://docs.vllm.ai/en/latest/design/paged_attention.html第 5 章每个请求的 KV cache 占用巨大且动态涨落目标是把浪费降到接近零《Efficient Memory Management for Large Language Model Serving with PagedAttention》Kwon 等SOSP 2023arXiv:2309.06180https://arxiv.org/abs/2309.06180第 3 章混合精度训练权重为 6 字节/参数FP16 副本 2 字节 FP32 副本 4 字节《Model memory anatomy》Hugging Face 官方文档https://huggingface.co/docs/transformers/en/model_memory_anatomy第 2 章前向激活大小随批量大小、序列长度、模型深度、隐藏层维度变化模型放得下也可能因批量或序列长度耗尽显存同上第 4 章临时张量由 softmax、矩阵乘法等算子产生单算子峰值过密会造成临时尖峰并导致显存不足同上第 4 章各精度位宽float32 为 32 位、float16 为 16 位、bfloat16 为 16 位、float8 为 8 位、float4 为打包 4 位《Tensor Attributes》PyTorch 官方文档页面更新于 2026-05-08https://docs.pytorch.org/docs/stable/tensor_attributes.html第 2 章默认使用 CUDA 图优化推理CUDA 图会额外占用 GPU 显存可用配置权衡速度与显存《Conserving Memory》vLLM 官方文档页面日期 2026-07-09https://docs.vllm.ai/en/latest/configuration/conserving_memory.html第 5、6 章限制最大上下文长度与最大并发数可进一步降低显存占用同上第 6 章Transformer 架构与注意力机制的原始论文《Attention Is All You Need》Vaswani 等arXiv:1706.03762v1 提交于 2017-06-12最新修订 v7 于 2023-08-02https://arxiv.org/abs/1706.03762第 3 章单 token KV 字节数 ≈ 2 × 层数 × KV 头数 × 头维度 × 精度字节数本文按定义推导非官方原文推导依据vLLM 官方博客对 KV Cache 的定义 vLLM 设计文档对「块」的定义第 3、6 章第 2、3 章各表的 GB 与 KV 数值均为本文按上述口径自行手算非官方给出的数字计算口径见第 2 章与第 3 章第 2、3、7 章框架预分配比例参数如 vLLM 的gpu_memory_utilization的具体默认值待验证本次未核到该默认值的官方文档原文请以你本地安装版本的官方文档为准第 5 章附表 B术语速查表术语一句话解释在本文哪里用到显存溢出OOM显存不够用运行中的进程被系统强制终止第 1 章权重模型训练完存下来、推理时不再变化的那一堆数字第 1、2 章KV Cache为避免重复计算把每个 token 的注意力键值向量缓存下来的那块显存第 1、3 章激活值模型每层计算过程中产生的中间结果训练时要留着推理时多为临时第 1、4 章临时张量softmax、矩阵乘法等算子产生的短命中间数据峰值可能顶爆显存第 4 章CUDA 上下文运行时为显卡建立的执行环境本身要常驻一部分显存第 5 章CUDA 图一种把计算调用序列固化下来以加速的优化手段会额外占用显存第 5 章预分配框架为省去反复申请释放按上限一次性预留一块显存的做法第 5 章显存碎片空闲显存被切成互不相邻的小块总量够却拼不出连续大块第 5 章PagedAttention把 KV Cache 切块、逻辑连续物理可不连续的分页式注意力算法第 5 章KV 头数存放键值的注意力头数量分组查询下少于查询头数第 3 章数值精度每个数字占多少位直接决定每个参数占多少字节第 2 章写在最后这篇用到的资料写这篇文章时把相关的官方文档和源码又翻了一遍顺手也整理了几份配套的东西大模型学习路线图从零基础到能自己动手做 Agent按阶段说明每一步该学什么、哪些可以先跳过《LangChain LangGraph MCP 智能体开发实战》视频课7 个模块从私有化部署、EmbeddingRAG 到 MCPAgent 全流程AI 大模型知识库在线可查Agent Skills 从入门到落地、Claude Skills 完全指南等专题按目录浏览即可640 套 AI 大模型行业报告 经典 PDF 书籍看行业落地案例和别人怎么做的时候用得上大模型零基础到精通教学视频跟着敲一遍比只读文档快得多资料是我自己整理的放在下面这个码上扫码即可获取添加时备注「AI」优先通过。资料按「先路线、再动手、最后查漏」的顺序整理好了建议先看学习路线那一份照着它挑一条适合自己当前基础的路径再往下看。