资讯动态

vLLM显存估算与KV Cache容量规划:从公式到实战

发布时间:2026/9/20 3:40:24 来源:尧图企业网站定制
几分钟前朋友发消息说他第一次用 vLLM 部署 Qwen2.5-7B显卡是一张 24G 的 RTX 4090模型权重也就 14G 左右怎么看都该够用。结果vllm serve启动没几秒直接报错说 max seq len 超出了 KV cache 能容纳的 token 数。他一度以为是模型加载出问题后来才明白vLLM 对显存的管理和常规推理框架根本不是一回事——模型权重只是占用的一部分KV Cache 才是真正需要精打细算的大头。这篇文章想把 vLLM 里的内存估算与容量规划这件事讲透。内容包括显存到底被谁吃掉了、KV Cache 怎么算、手把手算一个 7B 模型的例子、从业务并发需求倒推显卡规格以及我踩过的坑和排查思路。适合准备用 vLLM 做生产部署的工程师也适合还在纠结“到底要买多大显存”的朋友。看完你至少能自己算出给定一张卡、一个模型能跑多长上下文、多少并发以及最关键的——怎样配置才能不 OOM。1. 为什么需要一套内存估算方法1.1 部署现场最常见的翻车点很多人第一次部署 vLLM 时跟我朋友一样只看模型权重大小觉得“显存塞得下就行”。这个想法在传统推理框架里问题不大但在 vLLM 里会连续踩坑。第一种翻车是启动时报 KV Cache 容量不足。你会看到类似这样的信息模型配置的 max model len 是 32768但 KV cache 最多只能存 8000 个 token于是 vLLM 直接拒绝启动。第二种翻车是启动成功但 max model len 设置得过大导致请求一来并发稍微一高就 OOM。第三种更隐蔽服务跑起来了但吞吐惨不忍睹因为为了塞进一个长序列我把gpu_memory_utilization调到 0.95预留给临时激活的显存太少每次 prefill 都触发显存抖动速度反而上不去。这三种情况的本质都一样在 vLLM 里容量规划不是“显存够不够放下模型权重”而是“业务需要的并发和上下文长度是否能在 KV Cache 预算内被满足”。1.2 显存的三块去向权重、KV Cache、激活值vLLM 部署一个模型时显存基本被拆成三块。第一块是模型权重这个最好理解。模型参数量乘以每个参数占用的字节数就是权重文件加载到显存后的体积。第二块是 KV Cache也就是推理过程中不断增长的键值缓存。每处理一个 token模型每一层都要保存对应的 Key 和 Value 向量方便后续 token 做注意力计算时复用避免重新计算。它的大小会随序列长度和并发数线性增长是动态变化的。第三块是激活值和临时缓冲区在 prefill 阶段尤其大卷积、归一化、中间注意力分数都会产生临时张量。我习惯用一个简单的比喻整张显卡是一间房子模型权重是固定家具搬进来就占死KV Cache 是入住的客人每个客人按天占一个房间激活值则是走廊上临时堆放的行李高峰期特别占地方但过了就清走。容量规划要做的就是在不算超载的前提下同时安排家具、客人和行李。1.3 PagedAttention 解决了什么从碎片化到按块分配既然 KV Cache 这么占地方那怎么尽可能省着用vLLM 的核心创新 PagedAttention 就是干这个的。传统推理框架维护 KV Cache 时会为每个请求预分配一整块连续内存长度按最大可能序列长度来。问题是请求实际用到的长度往往远小于最大值中间还可能有内存碎片导致大量浪费。PagedAttention 的思路是模仿操作系统里的分页机制把 KV Cache 切成固定大小的 blockvLLM 默认每 block 存 16 个 token用的时候按需分配不用再找连续大块内存。跟容量规划直接相关的结论是因为 KV Cache 按 block 管理显存利用率非常高所以我们在估算时不需要额外再给它留碎片冗余只要算出“每 token 需要多大 KV 空间”再乘上业务要支撑的 token 总数就是理论需要的 KV 显存。这也是我在这篇文章里反复用“每 token KV Cache 字节数”这个单位的原因。2. 显存估算公式逐项拆解2.1 模型权重精度决定字节数权重这块是最好算的公式就一句话模型参数量 × 每个参数占用的字节数。不同精度下每个参数占的字节数差别很大。FP32 是 4 字节FP16/BF16 是 2 字节INT8 是 1 字节INT4 是 0.5 字节。目前主流大模型推理默认用 BF16 或 FP16所以一个 7B 模型权重占用大约是 7 × 2 14GB。14B 模型大约是 28GB72B 模型大约是 144GB。这里说的是纯权重还没算 embedding 或额外的 bias但作为估算已经足够精确了。很多人会忽略另一个细节vLLM 加载权重时并不是只占这么多它还会在显存里放 CUDA context、cuDNN 的 workspace、通信缓冲等这部分通常要额外吃 300MB 到 1GB 左右。如果开了多卡张量并行每卡还要多一些 NCCL 的通信 buffer。这也是为什么我宁愿把临时缓冲预留得宽一点也不要卡在 0.9 的利用率和无限贴近真实占用之间赌运气。2.2 KV Cache真正吃掉显存的大头KV Cache 的估算公式比权重复杂一点但也不难每 token 的 KV Cache 大小 2 × 层数 × KV 头数 × head_dim × 精度字节数。公式里乘的 2是因为每个 token 既要存 Key也要存 Value。层数是模型里的 Transformer 层数KV 头数需要留意如果是 GQA分组查询注意力架构这里用的是 num_key_value_heads而不是 num_attention_heads。head_dim 一般是 hidden_size 除以注意力头数。精度字节数同样看权重精度BF16 就是 2。我拿 Qwen2.5-7B 的配置来算一下层数 28注意力头数 28KV 头数是 4head_dim 是 128BF16 精度 2 字节。那么每个 token 的 KV Cache 是 2 × 28 × 4 × 128 × 2 57344 字节差不多 56KB。一个 8192 token 的请求光 KV Cache 就要吃掉 56KB × 8192 ≈ 458MB。如果是 32 并发同时跑那就是 14GB 以上。这里就能看出 GQA 的威力了。如果 Qwen2.5-7B 用的是 MHA多头注意力而不是 GQAKV 头数就是 28那每 token KV Cache 会变成 2 × 28 × 28 × 128 × 2 ≈ 392KB是 GQA 的 7 倍。同样的 24G 显卡KV Cache 预算会被瞬间打穿。所以针对 GQA 模型做容量规划时千万别把注意力头数当成 KV 头数算。2.3 激活值与临时缓冲容易被忽略的隐性开销激活值这块经常被忽略因为它在推理不同阶段的波动很大。prefill 阶段需要并行处理整个 prompt所以每层的激活张量很大尤其是长 prompt 时中间层的注意力分数矩阵是 batch × head × seq_len × seq_len 这种规模一个 8192 token 的 prompt这部分临时值就可能吃掉几百 MB 甚至 1GB 以上。decode 阶段每次只生成一个 token激活值就小得多。vLLM 通过 chunked prefill分块预填充来限制 prefill 的激活峰值也就是把一个完整 prompt 切成若干小段逐段计算避免一次性把激活张量推到峰值。即便如此我仍然建议在估算时留出至少 1GB 到 2GB 作为激活和临时缓冲的余量。结合上面三块的说明实际用来做容量规划的总公式是KV Cache 可用显存 总显存 × gpu_memory_utilization − 模型权重占用 − 激活与临时缓冲预留。然后你再用这个 KV Cache 可用显存除以每 token 的 KV Cache 大小就得到这个配置下最多能缓存的 token 总数。接下来是设置 max model len还是控制并发就都围绕这个数字来。3. 手把手算一个 7B 模型的显存3.1 从 model.config 里找齐参数说一堆公式不如实际算一遍。我以 Qwen2.5-7B-Instruct 为例跑一遍完整流程。首先找到模型自身的配置。最简单的方式是直接加载 transformers 的 AutoConfigfrom transformers import AutoConfig config AutoConfig.from_pretrained(Qwen/Qwen2.5-7B-Instruct) print(layers:, config.num_hidden_layers) print(attn_heads:, config.num_attention_heads) print(kv_heads:, config.num_key_value_heads) print(hidden_size:, config.hidden_size) print(head_dim:, config.head_dim if hasattr(config, head_dim) else config.hidden_size // config.num_attention_heads) print(max_position_embeddings:, config.max_position_embeddings)Qwen2.5-7B 的实际输出是层数28注意力头数28KV 头数4hidden size3584head dim128由 3584 ÷ 28 得到最大位置编码32768除了这些还需要知道参数量和精度。7B 模型的参数量大约 7.6BBF16 精度下权重约 15GB。算的时候直接用 14GB 到 15GB 这个量级都没问题。3.2 代入公式算 KV Cache 预算假设我用一张 24GB 的显卡gpu_memory_utilization0.9。第一步算出 vLLM 允许用的总显存预算24GB × 0.9 21.6GB。第二步减掉权重占用。BF16 下 7B 模型大约 15GB剩下 21.6 − 15 6.6GB。第三步预留激活与临时缓冲。我习惯预留 2GB那么 KV Cache 可用空间约为 6.6 − 2 4.6GB。第四步算每 token KV Cache 大小。前面算过Qwen2.5-7B 是 56KB/token也就是 57344 字节。第五步KV Cache 可用 token 数 4.6GB ÷ 56KB ≈ 86,000 个 token。有了这个数字再看你业务里每个请求平均多长。如果平均每个请求占用 2048 个 token输入加输出那么理论并发上限大约是 86000 ÷ 2048 ≈ 42。但这是理想值vLLM 还要受max_num_seqs限制并且不同请求长度差异大时会有浪费所以实际上按 60% 到 70% 的利用率来算大约 25 到 30 并发是比较稳的。如果业务要求 32 并发每个请求 2048 token那至少需要 32 × 2048 × 56KB ≈ 3.7GB 的 KV Cache24G 卡还能接受。但如果每个请求平均是 4096 token 并且 32 并发KV Cache 就需要 7.3GB24G 卡就已经很紧张了这时候要么降低max_model_len要么换量化模型减少权重占用要么上多卡。3.3 用 vLLM 启动日志和 nvidia-smi 验证理论算完要用实际启动结果验证因为 vLLM 启动时的日志会直接打印 KV Cache 分配情况。我用下面的命令启动vllm serve Qwen/Qwen2.5-7B-Instruct \ --gpu-memory-utilization 0.9 \ --max-model-len 8192 \ --dtype bfloat16启动日志里重点看几行Model weights take 15.2 GB of GPU memory Maximum concurrency for 8192 tokens per request: 8.00 Available kv_cache memory: 4.80 GB这里显示的 Available kv_cache memory 大约 4.8GB和我前面算的 4.6GB 很接近差异来自驱动的 CUDA context 占用和 vLLM 内部估算方式。Maximum concurrency 是 8意思是如果每个请求都用满 8192 token它最多并发 8 个请求。如果我的业务只需要 4096 token那并发能力大约能翻倍到 16 左右。验证时还可以在另一个终端跑nvidia-smi观察显存占用是否接近 21.6GB而不是卡在模型权重的 15GB。如果看到占用远低于预期很可能是gpu_memory_utilization没有生效或者系统里其他进程占用了显存尤其要注意 Windows 下的 WSL2 环境Windows 侧打开的一些图形程序会占用几百 MB 显存导致 WSL2 里能看到的可用显存变小。4. 容量规划从业务指标倒推硬件配置4.1 并发、吞吐与 KV Cache 的关系容量规划的核心是先有业务指标再反推硬件而不是先买了卡再迁就模型。业务指标一般有两个并发路数和单路吞吐。并发路数是同时有多少个请求在推理单路吞吐是每个请求平均每秒生成多少 token。这两者直接决定 KV Cache 需求也决定要不要上多卡、上几张卡。我用一个通用流程来估算先确定目标在线服务要求支撑 32 路并发平均每个请求占用 4096 个 token输入加输出。模型是 Qwen2.5-7BKV Cache 每 token 56KB。那么 KV Cache 总量需求 32 × 4096 × 56KB ≈ 7.34GB。再加上模型权重 15GB 和激活缓冲 2GB总显存需求大约是 7.34 15 2 24.34GB。一张 24G 卡就非常极限40G 的 A100 或者 48G 的 L40S 会更合适。如果硬要用 24G 卡只能把模型量化到 INT4 让权重降到 4GB 左右或者把单路 token 砍到 2048这样每路 KV 需求减半32 × 2048 × 56KB ≈ 3.67GB24G 卡反而还能挤出余量。这里特别提醒一点很多人只看显卡的“总显存”和“权重大小”就拍板很容易出问题。KV Cache 才是跟业务并发直接挂钩的变量务必把它当第一优先级来算。4.2 多卡张量并行怎么分配显存单卡放不下时最常见的是多卡张量并行也就是 vLLM 里的--tensor-parallel-size。它把模型权重拆分到多张卡上每张卡只存权重的一部分同时注意力计算也按头拆分到多张卡这样每卡就能腾出更多空间给 KV Cache。以 2 × 24G 显卡部署 Qwen2.5-7B 为例BF16 权重 15GB张量并行 2 后每卡权重约 7.5GB。每卡 21.6GB 预算减掉 7.5GB 权重、再减 2GB 激活缓冲每卡 KV Cache 可用约 12GB。因为 KV Cache 的注意力头也切分到了两张卡上所以整体可支撑的 token 数会显著提升但不是简单乘 2每张卡都还有通信 buffer 和额外的碎片开销。实际看日志最靠谱启动时会打印每个 rank 的 KV cache memory 和峰值内存。这里还有一个取舍多卡并行能增加 KV Cache 总容量但通信开销也会拖慢单请求的 decode 速度。如果只是并发高、单请求长度长多卡是合适的如果追求单路低延迟优先考虑单卡大显存型号比如 A100 80G 或 H100。反正我的习惯是先算单卡能不能压进去压不进去再考虑并行。4.3 低显存场景和纯 CPU 模式不是所有人都有 A100低显存场景其实也能跑 vLLM关键是要学会取舍。第一招砍 max model len。如果你的场景不需要 32K 上下文把它设成 8192 或 4096KV Cache 压力立刻小很多。很多新手一上来就跟风设置 32768结果把 KV Cache 全占了并发能力几乎归零。第二招量化。INT4 或 FP8 权重能大幅减少显存占用给 KV Cache 腾地方。7B 用 INT4 量化后权重约 4GB24G 卡上 KV Cache 可用能到 13GB 以上并发能力翻倍。第三招用--cpu-offload-gb把一部分权重放到内存代价是速度变慢适合发布前测试或非实时场景。还有一个极端选择纯 CPU 模式也就是--device cpu。这种情况下不存在显存概念权重和 KV Cache 都放在内存里。我用一台 32GB 内存的服务器跑 Qwen2.5-7BBF16 权重 15GBKV Cache 预留 8GB总共约 23GB勉勉强强能跑但速度非常慢生成一个 token 都要几百毫秒。它适合在没 GPU 的机器上做功能调试或者验证代码路径不适合作为生产方案。如果真要 CPU 推理还是换成量化模型更实际。顺带说一句Ollama 这类工具把部署门槛做得很低但它默认留给 KV Cache 的空间比较保守也不好调并发适合个人研究和尝鲜SGLang 的思路和 vLLM 相近内存估算逻辑基本通用但优先还是按目标框架自己的日志为准。便携一键部署包也同理能快速跑起来但生产环境里我建议还是自己控制启动参数方便做容量规划。5. 常见问题与排查技巧实录5.1 启动就报 KV Cache 容量不足这是 vLLM 部署最常见的启动报错典型信息长这样ValueError: The models max seq len (32768) is larger than the maximum number of tokens that can be stored in KV cache (8192). Try increasing gpu_memory_utilization or decreasing max_model_len.意思很直白模型配置或你手动指定的 max model len 太大了当前显存里能分给 KV Cache 的空间撑不起这么长的序列。解决办法按优先级来排先降低--max-model-len比如从 32768 降到 8192如果还不行再考虑提高--gpu-memory-utilization提不上去的时候只能换成量化模型或者上多卡。注意如果显存里已经有其他进程占着几 GB单纯调高 utilization 可能是无效的因为 vLLM 会基于实际可用显存来计算上限被总显存锁死。排查时先跑一遍nvidia-smi确认没有别的进程占卡再看权重和 KV Cache 是否匹配最后再决定动哪个参数。5.2 运行时 OOM 和显存碎片启动没问题请求一多就 OOM这种更让人头疼。原因通常是激活值瞬时峰值和 KV Cache 一起超过了显存预算。prefill 阶段如果 prompt 特别长哪怕 vLLM 内部已经用 chunked prefill 做了分块某些极端长度下仍可能触发峰值。排查思路先从--max-num-seqs下手把它从默认值降到 16 或 8限制内存中并发序列的数量再检查是不是个别请求的 prompt 异常长导致激活峰值最后把gpu_memory_utilization从 0.9 调到 0.85多留一点缓冲。另外Windows 下通过 WSL2 跑 vLLM 时显存碎片问题比纯 Linux 环境严重。Windows 侧驱动管理显存的方式和 Linux 不太一样偶尔会出现nvidia-smi看到显存空闲但申请失败的情况。我在 WSL2 里遇到过好几次重启 WSL2 或退出 Windows 侧占用显存的程序后恢复正常。生产环境还是建议直接上 Linux少折腾。5.3 gpu_memory_utilization 的调参经验最后聊聊这个参数的调参经验。默认 0.9 看着安全但实际并非所有场景都适用。如果业务里长请求多、max_num_seqs也高0.9 会让 KV Cache 把显存占得过满激活峰值一来就 OOM。我在这里整理了一份经验值表可以直接参考使用场景建议 gpu_memory_utilization说明单用户调试、模型测试0.9 到 0.95并发低激活峰值小可以压得狠一点在线 API 服务短请求为主0.85 到 0.9请求长度可控留少量余量在线 API 服务长请求混入0.8 到 0.85需要消化 prefill 激活峰值余量要足多模型共存在一张卡按比例分配单个模型不超过 0.5多个服务共卡要手工算总额WSL2 / 虚拟机共享 GPU0.75 到 0.85驱动和宿主机占用不确定保守一点有一个细节很值得注意gpu_memory_utilization并不是设得越高KV Cache 就一定越多。如果显存里已经有一部分被其他东西占用vLLM 计算可用显存时会取“总显存 × 利用率”和“实际剩余显存”两者的较小值。所以有时候把 0.9 提到 0.95 并没有用反而可能压缩了系统给激活值的缓冲空间。与其盲目拉满不如把模型换成量化版本把权重压下来效果立竿见影。我自己做容量规划时的习惯是先写一个小脚本把权重、KV Cache 和激活预留都算一遍然后启动服务读日志里的 Available kv_cache memory跟理论值对比如果偏差超过 15%就去找是不是有额外显存占用。等这两步都确认了再开始压并发测试逐步把max_num_seqs往上调直到出现 OOM 的前一步再往回调 20% 作为安全余量。这套流程虽然不复杂但每次部署都能省掉我很多试错时间。下次再遇到启动报错或者线上 OOM先别急着改参数回到公式里把每一项重新算一遍大概率问题就出在权重、KV Cache、激活这三者中间的某一块上。

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

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

免费获取报价