
1. 24 GiB 显存里塞四路 32K 上下文账要先算清楚先把结论摆在前面权重装进 24 GiB 只是入场券真正决定你能不能开四路 32K 的是 KV Cache 的账算不算得过来。很多人拿到一张 24 GiB 的卡比如 4090、3090或者租来的 L4/A10 之类第一反应是模型权重才十几个 G剩下几个 G 开个长上下文应该绰绰有余结果一启动就 OOM或者勉强起来之后并发一上来直接崩。问题几乎从来不在权重而在 KV Cache 这个隐形大户。这篇内容我打算把这件事从头到尾拆一遍KV Cache 到底怎么算、24 GiB 里权重和缓存怎么分账、四路 32K 在什么模型规模下可行、vLLM 里哪些参数直接决定生死、以及实测中那些文档不会告诉你的坑。适合正在做本地推理部署、想用单卡跑多路长上下文、或者被gpu_memory_utilization反复折磨的人。不管你是刚接触 vLLM 的新手还是已经调过几轮参数的老手这里面的账本逻辑都值得重新过一遍。先明确一个前提本文讨论的是推理阶段的显存占用不涉及训练和微调。推理显存主要由三块构成——模型权重、KV Cache、以及框架自身的运行时开销CUDA context、激活峰值、通信缓冲等。前两块是大头第三块经常被忽略但能吃掉 1~2 GiB。把这三块拆开算你才能回答标题里那个问题。我见过太多人把权重装得下等同于能跑然后在长上下文场景里反复翻车。所以第一步不是急着敲命令而是拿纸笔或者计算器把账算明白。下面几节我会把每一块的算法、经验系数、以及 vLLM 里的对应参数全部摊开讲。2. KV Cache 的显存账本公式、系数与常见误算2.1 从注意力机制推导单 token 的缓存开销KV Cache 的本质是把自回归生成过程中每一步算过的 Key 和 Value 张量缓存下来避免重复计算。所以它的显存占用和层数、KV 头数、头维度、精度、序列长度、并发路数直接挂钩。标准公式是这样的KV Cache 显存 2 × num_layers × num_kv_heads × head_dim × seq_len × batch_size × dtype_bytes其中那个2是 Key 和 Value 各一份。dtype_bytes取决于你用的精度FP16/BF16 是 2 字节FP8 是 1 字节INT8 量化也是 1 字节左右。num_kv_heads在用了 GQA分组查询注意力的模型里会远小于num_attention_heads这是省显存的关键设计。拿一个具体的模型举例。假设是 Llama 3 8B 这一档32 层8 个 KV 头GQAhead_dim 128BF16 精度。那么单个 token 的 KV Cache 是2 × 32 × 8 × 128 × 2 131072 字节 ≈ 128 KiB / token注意这个数字——每 token 128 KiB。32K 上下文就是 32768 × 128 KiB ≈ 4 GiB。四路并发就是 16 GiB。这时候你再回头看 24 GiB 的卡权重 8B 模型 BF16 大约 16 GiB加上 16 GiB 的 KV Cache直接 32 GiB超了。这就是为什么权重装得下和能跑四路 32K完全是两码事。2.2 为什么很多人算出来的数字和实际差一截上面是理论值实际占用往往还要再高 10%~30%。原因有几个我逐个说。第一vLLM 的块管理有碎片。vLLM 用 PagedAttention 把 KV Cache 切成固定大小的 block默认 16 个 token 一块按需分配。块内不满也会占满整块所以序列长度不是 16 的整数倍时会有浪费。四路 32K 如果每路都刚好卡在边界上浪费会累积。第二CUDA context 和框架开销。一张卡上跑 vLLM光是 CUDA context、cuBLAS/cuDNN 的 workspace、以及 PyTorch 的缓存分配器起步就是 1~2 GiB。这个数字和驱动版本、CUDA 版本都有关系我实测过同一张卡在不同驱动下能差出 500 MiB。第三激活峰值和临时缓冲。prefill 阶段处理输入 prompt的激活值远大于 decode 阶段尤其是长 prompt。32K 的输入一次性 prefill中间激活可能瞬间吃掉好几个 GiB。vLLM 会做 chunked prefill 来削峰但峰值依然存在。第四精度陷阱。如果你开了 FP8 KV Cache理论上省一半但有些模型/后端组合下 FP8 的 scale 因子、以及和权重的混合精度处理会带来额外开销实际省不到 50%。提示算账时先按理论值 × 1.2 做预算留出安全边际比事后 OOM 强得多。2.3 不同模型档位的 KV Cache 对照表为了让你有个直观感受我把几个常见档位的模型在 BF16、单路 32K 下的 KV Cache 理论值列出来假设都是 GQA 架构head_dim 128模型档位层数KV 头数单 token 开销单路 32K四路 32K7B/8B 级328128 KiB4 GiB16 GiB13B 级408160 KiB5 GiB20 GiB32B 级648256 KiB8 GiB32 GiB70B 级808320 KiB10 GiB40 GiB看这张表就明白了四路 32K 对 7B/8B 级模型是紧巴巴但可能对 13B 以上基本没戏除非上量化 KV 或者砍并发。70B 级想都别想光 KV Cache 就 40 GiB24 GiB 的卡连权重都放不下。这里还有个反直觉的点层数和 KV 头数的影响是乘性的。有些模型层数多但 KV 头少有些反过来。选模型时不能只看参数量得看这两个参数的乘积。比如某些 13B 模型用了更激进的 GQAKV 头数降到 4单 token 开销反而比 8B 的某些版本还低。3. 24 GiB 的分配博弈权重、缓存、运行时怎么切蛋糕3.1 权重占多少参数量 × 精度字节数权重的算法很直接参数量 × 每个参数的字节数。BF16 是 2 字节INT8 是 1 字节INT4 是 0.5 字节实际因为分组量化会有额外开销约 0.55~0.6 字节。8B 模型 BF16约 16 GiB8B 模型 INT8约 8 GiB8B 模型 INT4约 4.5~5 GiB14B 模型 INT4约 8~9 GiB32B 模型 INT4约 18~19 GiB看到没如果你用 INT4 量化把 8B 模型压到 5 GiB24 GiB 里就腾出了将近 19 GiB 给 KV Cache 和运行时。这时候四路 32K 的 16 GiB KV Cache 就装得下了还要扣掉运行时开销实际会比较紧张但可行。这就是量化在长上下文场景里的真正价值——不是为了省权重那点空间而是为了给 KV Cache 让路。但量化有代价精度损失、部分算子不支持、以及某些量化格式在 vLLM 里的兼容性问题。AWQ 和 GPTQ 是目前比较成熟的选择FP8 权重在支持 FP8 的卡上如 L4、H100表现更好。24 GiB 这个档位的卡4090、3090大多是消费级FP8 支持有限所以 AWQ/GPTQ 更现实。3.2 vLLM 的gpu_memory_utilization到底在管什么vLLM 里有个参数叫gpu_memory_utilization默认 0.9。很多人以为它是用 90% 显存其实更准确的理解是vLLM 会先加载权重然后把剩余显存中这个比例的部分划给 KV Cache 池。它的计算逻辑大致是可用 KV Cache 显存 (总显存 × gpu_memory_utilization) - 权重显存 - 运行时开销注意这里有个坑gpu_memory_utilization是乘在总显存上的不是乘在剩余显存上。所以如果你设 0.924 GiB 的卡就是 21.6 GiB 的预算减去权重和运行时剩下的才是 KV Cache 池。设太高比如 0.95容易在 prefill 峰值时 OOM设太低又浪费空间。我的经验是消费级卡设 0.85~0.90专业卡可以到 0.90~0.92因为专业卡的驱动开销更可控。还有一个参数max_model_len它决定单路序列的最大长度。如果你设成 32768vLLM 会按这个长度预留块空间。如果你实际用不到 32K千万别设满设成实际需要的长度能省下大量预留。四路 32K 和四路 8KKV Cache 需求差 4 倍。3.3 一张 24 GiB 卡的实际分配推演我们来做一个完整的推演。目标8B 模型AWQ INT4 量化四路并发每路 32K。项目占用说明权重INT4~5 GiBAWQ 量化后运行时开销~1.5 GiBCUDA context 框架KV Cache 池~15 GiB21.6 - 5 - 1.5四路 32K 需求~16 GiB理论值实际略高结论15 GiB 的池装 16 GiB 的需求差一点点会 OOM 或者被迫降并发。这时候你有几个选择把max_model_len降到 28K、把并发降到 3 路、或者开 FP8 KV Cache 把需求砍到 8 GiB。最后一个选项最优雅但要看硬件和 vLLM 版本支持。如果换成 BF16 权重16 GiB那 KV Cache 池只剩 4 GiB 左右四路 32K 完全不可能连单路 32K 都悬。所以在 24 GiB 卡上做多路长上下文量化几乎是必选项。4. vLLM 参数调优把四路 32K 真正跑起来4.1 启动命令与关键参数逐项拆解假设你已经装好了 vLLM版本建议 0.6.x 以上PagedAttention 和 chunked prefill 都比较成熟下面是一条针对四路 32K 场景的启动命令vllm serve /path/to/model \ --quantization awq \ --dtype float16 \ --max-model-len 32768 \ --max-num-seqs 4 \ --gpu-memory-utilization 0.88 \ --kv-cache-dtype fp8 \ --enable-chunked-prefill \ --max-num-batched-tokens 8192 \ --swap-space 4逐项说--quantization awq告诉 vLLM 权重是 AWQ 量化的它会用对应的 kernel 加载。--max-model-len 32768单路最大长度。这个值直接决定 KV Cache 的预留上限设满 32K 意味着每路都可能占满。--max-num-seqs 4最大并发序列数。这就是四路的来源。设大了会超配设小了浪费。--gpu-memory-utilization 0.88留一点余量给峰值。--kv-cache-dtype fp8KV Cache 用 FP8直接省一半。这是四路 32K 能跑起来的关键开关。--enable-chunked-prefill把长 prompt 的 prefill 切成小块削平激活峰值。--max-num-batched-tokens 8192配合 chunked prefill控制单批处理的 token 数。--swap-space 4CPU 侧的交换空间单位 GiB。当 KV Cache 池不够时可以换出到内存但会拖慢速度。4.2 FP8 KV Cache 的收益与代价--kv-cache-dtype fp8是我最推荐在 24 GiB 卡上开的选项。它把 KV Cache 从 2 字节压到 1 字节四路 32K 的需求从 16 GiB 直接降到 8 GiB一下子就从不可能变成很宽裕。但代价要说清楚第一精度损失。FP8 的尾数位少长上下文下注意力分数的细微差异可能被放大。实测在 32K 场景下FP8 KV Cache 对大多数任务的影响在可接受范围内但对需要精确回忆长文档细节的任务比如大海捞针式的检索召回率可能下降几个百分点。第二硬件支持。FP8 需要较新的 GPU 架构Ada、Hopper 及以上。4090 是 Ada 架构支持3090 是 Ampere不支持原生 FP8vLLM 会回退或者报错。这点一定要先确认自己的卡。第三和权重量化的叠加。权重 AWQ INT4 KV Cache FP8 是常见组合但要注意 vLLM 版本对混合精度的支持情况。老版本可能有 bug建议用较新的稳定版。注意如果你的卡不支持 FP8退而求其次是 INT8 KV Cache省一半但精度损失更明显且部分版本支持不完善。再不行就只能降并发或降长度。4.3 并发与长度的取舍四路 32K 还是八路 16K很多人纠结我要四路 32K这个目标本身。我的建议是先问清楚业务到底需要多少并发、多长上下文。KV Cache 的总量是并发数 × 单路长度的乘积决定的四路 32K 和八路 16K 的总 KV Cache 需求是一样的都是 128K token 的总缓存。所以如果你的业务是多个用户各自问短问题八路 16K 更合理如果是少数用户处理超长文档四路 32K 才对。别为了凑四路 32K这个数字而牺牲实际吞吐。vLLM 的调度器scheduler会动态分配块max-num-seqs只是上限。实际运行时如果只有两路活跃它不会占满四路的空间。所以设max-num-seqs 4不等于一定占 16 GiB而是最多占 16 GiB。这一点让显存利用更灵活但也意味着峰值占用取决于最坏情况做容量规划时要按最坏情况算。5. 实测踩坑那些文档不会写的显存陷阱5.1 启动成功不等于跑得稳prefill 峰值 OOM我最常遇到的坑是服务启动成功日志显示 KV Cache 池分配了多少块看起来一切正常。然后第一个 32K 的长请求进来prefill 阶段直接 OOM。原因是启动时的显存占用是静态的prefill 的激活峰值是动态的。32K 的 prompt 一次性过前向中间激活尤其是注意力矩阵和 FFN 的中间结果可能瞬间吃掉 2~4 GiB。如果你的gpu_memory_utilization设得太满KV Cache 池把空间占光了prefill 峰值一来就没地方放。解决办法就是--enable-chunked-prefill把 32K 的 prefill 切成比如 8K 一块分四次处理峰值降到 1/4。代价是首 token 延迟略增但换来的是稳定性。这个开关在长上下文场景里几乎是必开的。5.2 块碎片为什么实际能用的长度比理论少vLLM 的 PagedAttention 用固定大小的 block 管理 KV Cache默认 block size 是 16。这意味着每个序列的 KV Cache 占用是向上取整到 16 的倍数。32K 刚好是 16 的整数倍没问题但如果你设max-model-len 30000实际会按 30016 预留多出来的浪费在四路并发下会累积。更隐蔽的是块池的碎片化。当序列频繁创建和销毁时块池会出现碎片导致明明总量够但分配不出连续块。vLLM 的块管理做得不错但在高并发长上下文场景下碎片依然会吃掉 5%~10% 的有效容量。所以算账时留 10% 余量不是保守是必要。5.3 版本差异不同 vLLM 版本的显存行为不一样热词里有人提到vllm 新版本性能下降这不是空穴来风。vLLM 迭代很快不同版本在显存管理、调度策略、kernel 实现上都有变化。我实测过同一个模型同一张卡0.5.x 和 0.6.x 的显存占用能差出 1~2 GiB吞吐也有差异。建议是锁定一个经过验证的版本不要盲目追新。部署前在目标硬件上跑一遍基准记录启动后的显存占用、单路 32K 的峰值、四路并发的稳定性。这些数字比任何文档都可靠。如果升级版本务必重新测一遍。另外Docker 镜像的选择也有讲究。官方镜像vllm/vllm-openai是常见选择但要注意镜像里的 CUDA 版本和宿主机驱动是否匹配。驱动太老会导致 FP8 等特性不可用甚至启动失败。5.4 监控怎么知道 KV Cache 池还剩多少vLLM 的日志会打印 KV Cache 的块数和可容纳的 token 数比如GPU KV cache size: 120000 tokens。这个数字除以你的单路长度就是理论上能同时跑几路。但这是静态值运行时的实际占用要看 metrics。vLLM 暴露了 Prometheus 格式的指标其中vllm:gpu_cache_usage_perc直接告诉你 KV Cache 池的使用率。部署时把这个接上监控比事后猜 OOM 原因强得多。当使用率长期在 90% 以上就该考虑降并发或加卡了。6. 结论之外的现实24 GiB 到底能装下什么回到标题的问题权重装进 24 GiB四路 32K 还装得下吗答案取决于三个变量——模型规模、权重量化、KV Cache 精度。8B 模型 INT4 权重 FP8 KV Cache装得下还比较宽裕。这是 24 GiB 卡上四路 32K 的最优解。8B 模型 INT4 权重 BF16 KV Cache很紧张可能差一点需要降长度或降并发。8B 模型 BF16 权重 任意 KV Cache装不下权重就吃掉 16 GiB。13B 以上模型基本没戏除非把并发降到 1~2 路。所以关键不是24 GiB 够不够而是你愿意在精度和量化上做多少妥协。量化不是免费的它用精度换空间。在长上下文场景里这个交换是否划算取决于你的业务对精度的敏感度。我个人在实际部署中的体会是先按最坏情况算账再按实际业务调参。别一上来就追求四路 32K这个极限数字先用两路 16K 跑稳观察实际的 KV Cache 使用率和精度表现再逐步往上加。显存这东西留 10% 余量永远比榨干最后一块 MiB 要明智。踩过几次 OOM 之后你就会明白稳定性比那点极限容量值钱得多。最后分享一个小技巧如果你实在需要在 24 GiB 上跑更大的模型或更长的上下文可以考虑把 KV Cache 换出到 CPU 内存--swap-space虽然慢但能让你在硬件受限时先把功能跑通等有预算了再换卡。这是过渡方案不是长久之计但关键时刻能救急。