
1. 显存账本24 GiB 到底能装下什么先把结论摆在前面24 GiB 显存跑四路 32K 上下文的 Qwen2.5-7B权重本身没问题真正吃显存的是 KV Cache能不能装下取决于你怎么切、怎么量化、怎么配。我最近在两张 24 GiB 的卡上反复折腾 Qwen2.5 系列模型从 7B 到 14B 都试过一轮踩了不少坑也摸清了一些门道。这篇文章就把这笔显存账从头到尾算一遍顺便把 vLLM 里那些跟显存分配相关的参数讲透。先说清楚适用人群如果你手里有一张 24 GiB 显存的卡比如 4090、3090、A5000 这类想用 vLLM 部署 Qwen2.5 做多路并发推理尤其是需要长上下文16K、32K 甚至更长的场景那这篇内容基本就是为你写的。如果你只是单路短对话那随便跑跑就行不用这么较真。1.1 权重占用7B 模型到底吃多少很多人对7B 模型占多少显存有个模糊印象觉得大概是 14 GB 左右。这个数字怎么来的7B 参数FP16 精度每个参数 2 字节7 × 2 14 GB。这个算法没错但实际部署时你会发现显存占用比这个数字要高一些。原因有几个。第一vLLM 加载模型时会有一些额外的 buffer 和临时张量第二embedding 层和 lm_head 层在某些配置下不会量化第三CUDA context 本身就要占几百 MB。实测下来Qwen2.5-7B-Instruct 用 FP16 加载nvidia-smi看到的显存占用大概在 15.2 到 15.8 GB 之间具体取决于你的 CUDA 版本和驱动。那 24 GiB 减去 15.5 GB还剩 8.5 GB 左右。这 8.5 GB 要分给 KV Cache、CUDA Graph、激活值、通信 buffer 等等。其中 CUDA Graph 在 vLLM 里默认会占 1 到 2 GB激活值在 batch size 不大的时候可以忽略通信 buffer 单卡场景基本没有。所以真正能留给 KV Cache 的大概在 6 到 7 GB。如果你用 AWQ 或 GPTQ 做 4-bit 量化权重占用能压到 4.5 到 5 GB 左右那留给 KV Cache 的空间就宽裕多了能到 17 GB 以上。这就是为什么长上下文场景下量化几乎是必选项。1.2 KV Cache 的计算每一路 32K 要吃掉多少KV Cache 的显存占用公式我把它拆成最直白的形式KV Cache 总字节数 2 × 层数 × 序列长度 × 隐藏维度 × 精度字节数 × 并发路数这里的 2 是因为 Key 和 Value 各存一份。以 Qwen2.5-7B 为例它的配置是28 层隐藏维度 3584注意力头数 28KV 头数 4GQA分组查询注意力。注意KV Cache 的大小取决于 KV 头数而不是注意力头数这是 GQA 省显存的关键。单层单 token 的 KV 大小 2 × 4 × 128 × 2 字节 2048 字节 2 KB。这里 128 是每个 KV 头的维度3584 / 28 128。28 层加起来单 token 的 KV 占用 28 × 2 KB 56 KB。那么一路 32K 上下文的 KV Cache 32768 × 56 KB 1,835,008 KB ≈ 1.75 GB。四路就是 1.75 × 4 7 GB。看到这里你应该明白了四路 32K 的 KV Cache 需要大约 7 GB而 FP16 权重占掉 15.5 GB 后只剩 8.5 GB扣掉 CUDA Graph 等开销后实际可用约 6.5 GB刚好差一点点。这就是标题里那个问题的答案——FP16 精度下四路 32K 非常紧张稍微有点波动就会 OOM。如果换成 4-bit 量化权重权重占 5 GB可用空间 19 GB扣掉开销还有 17 GB 以上四路 32K 的 7 GB KV Cache 轻松装下甚至还能再塞几路。1.3 一张表看清不同配置下的显存账我把几种常见配置的显存占用整理成表格方便你直接对照配置权重占用KV Cache4路32KCUDA Graph等开销总计24GiB是否可行FP16 7B15.5 GB7 GB1.5 GB24 GB极限易OOMAWQ 4bit 7B5 GB7 GB1.5 GB13.5 GB轻松GPTQ 4bit 7B5.2 GB7 GB1.5 GB13.7 GB轻松FP16 14B28 GB---装不下AWQ 4bit 14B9 GB7 GB1.5 GB17.5 GB可行从这张表能看出来24 GiB 跑四路 32K 的核心矛盾在权重精度上。FP16 权重把空间吃得太狠留给 KV Cache 的余量太小。只要把权重压到 4-bit整个局面就打开了。2. vLLM 显存分配机制那些参数到底在干什么搞清楚显存账之后下一步是理解 vLLM 怎么分配这些显存。很多人部署时直接默认参数一把梭结果要么浪费显存要么莫名其妙 OOM。vLLM 的显存管理有几个关键参数我逐个拆解。2.1 gpu_memory_utilization不是你想的那个意思gpu_memory_utilization这个参数名字很有迷惑性很多人以为它是GPU 显存使用率上限设成 0.9 就是最多用 90% 显存。这个理解只对了一半。vLLM 的实际逻辑是先加载模型权重然后测量当前显存占用再用gpu_memory_utilization × 总显存 - 已用显存算出可用于 KV Cache 的空间。也就是说这个参数控制的是vLLM 认为自己可以用的显存总量而不是KV Cache 占多少。举个例子24 GiB 的卡设gpu_memory_utilization0.9vLLM 认为可用 21.6 GB。加载 FP16 权重后已用 15.5 GB那么 KV Cache 可用空间 21.6 - 15.5 6.1 GB。这时候如果四路 32K 需要 7 GB就会报错说 KV Cache 空间不足。如果你设成 0.95可用 22.8 GBKV Cache 空间 22.8 - 15.5 7.3 GB刚好够。但这样做的风险是实际运行时的激活值、临时 buffer 可能会把显存顶爆因为 0.95 已经非常接近物理上限了。我的经验是FP16 权重场景下gpu_memory_utilization设 0.92 到 0.94 比较稳妥4-bit 量化场景下设 0.90 就绰绰有余。不要贪心设到 0.98那样 CUDA Graph 捕获阶段就可能失败。2.2 max_model_len 与 max_num_seqs两个必须一起调的参数max_model_len是单条序列的最大长度max_num_seqs是同时处理的最大序列数。这两个参数共同决定了 KV Cache 的峰值需求。vLLM 在启动时会做一个预分配检查它会计算max_model_len × max_num_seqs对应的 KV Cache 大小然后跟可用空间对比。如果不够直接启动失败并报错。这里有个坑很多人以为max_num_seqs4就是四路并发但实际上 vLLM 的调度器可能会同时处理更多序列。因为请求是动态到达的如果四路都在生成第五个请求进来时vLLM 会把它放进 waiting 队列等前面的序列完成后再调度。所以max_num_seqs控制的是同时在跑的序列数上限不是总请求数上限。那四路 32K 应该怎么设max_model_len32768max_num_seqs4。但这样设的话vLLM 会按最坏情况预分配 4 × 32K 的 KV Cache也就是 7 GB。如果你的实际请求不会同时打满 32K可以适当降低max_num_seqs来省显存比如设成 3让第四路排队。2.3 block_size 与 PagedAttention显存碎片怎么治vLLM 的核心技术之一是 PagedAttention它把 KV Cache 切成固定大小的 block 来管理类似操作系统的内存分页。block_size默认是 16意思是每个 block 存 16 个 token 的 KV。这个设计的好处是避免显存碎片。传统做法是给每个序列预分配最大长度的连续显存序列用不满就浪费了。PagedAttention 按需分配 block序列多长就占多少 block利用率高很多。但block_size也不是越小越好。block 太小block 表的开销就大block 太大最后一个 block 的浪费就多。16 是经过验证的平衡点一般不用改。除非你的序列长度都是 32K 这种大块头可以试试 32能减少 block 表的管理开销。实测下来block_size16在四路 32K 场景下KV Cache 的实际利用率能到 95% 以上浪费很少。这也是为什么 vLLM 敢在 24 GiB 上跑长上下文——PagedAttention 把每一块显存都榨干了。3. 实操从零部署四路 32K 的 Qwen2.5理论讲完直接上操作。我以 Qwen2.5-7B-Instruct 的 AWQ 量化版本为例走一遍完整流程。选 AWQ 而不是 GPTQ是因为 AWQ 在 vLLM 上的推理速度通常快 10% 到 15%精度损失也更小。3.1 环境准备与镜像选择vLLM 的版本选择很关键。新版本性能不一定更好我实测过 v0.6.x 和 v0.5.x在某些场景下 v0.5.x 反而更稳。如果你追求稳定建议用 v0.6.1 之后的版本对 Qwen2.5 的支持比较完善。Docker 部署的话直接用官方镜像docker pull vllm/vllm-openai:latest注意官方镜像里不带模型权重模型需要挂载进去或者启动时从 HuggingFace 下载。国内环境建议提前把模型下载到本地挂载进容器避免启动时卡在下载上。模型下载用huggingface-clihuggingface-cli download Qwen/Qwen2.5-7B-Instruct-AWQ --local-dir /data/models/qwen2.5-7b-awqAWQ 版本的模型大小约 5 GB下载比 FP16 版本快很多。3.2 启动命令与参数详解启动命令我调了很多版最终稳定跑四路 32K 的配置是这样的docker run --gpus all \ -v /data/models:/models \ -p 8000:8000 \ vllm/vllm-openai:latest \ --model /models/qwen2.5-7b-awq \ --max-model-len 32768 \ --max-num-seqs 4 \ --gpu-memory-utilization 0.90 \ --block-size 16 \ --enable-prefix-caching \ --disable-log-requests \ --served-model-name qwen2.5-7b逐个参数解释--max-model-len 32768设定了单序列最大长度。这个值不能超过模型本身支持的最大长度Qwen2.5-7B 支持 32K所以设 32768 没问题。如果你只需要 16K设 16384 能省一半 KV Cache。--max-num-seqs 4限制同时处理的序列数为 4。这是四路并发的关键。--gpu-memory-utilization 0.90在 AWQ 场景下足够因为权重只占 5 GBKV Cache 空间很充裕。--enable-prefix-caching开启前缀缓存。如果你的多个请求有相同的 system prompt这个选项能大幅减少重复计算间接省显存。实测在四路并发下开启前缀缓存能降低 20% 到 30% 的 KV Cache 峰值占用。--disable-log-requests关闭请求日志减少 I/O 开销。生产环境建议关掉调试时可以开着。3.3 验证部署是否成功启动后用 curl 测一下curl http://localhost:8000/v1/chat/completions \ -H Content-Type: application/json \ -d { model: qwen2.5-7b, messages: [{role: user, content: 你好}], max_tokens: 100 }如果返回正常说明服务起来了。然后测长上下文curl http://localhost:8000/v1/chat/completions \ -H Content-Type: application/json \ -d { model: qwen2.5-7b, messages: [{role: user, content: $(python3 -c print(测试 * 8000))}], max_tokens: 50 }这个请求会塞进去大约 16K token 的输入看看能不能正常返回。如果能再翻倍测 32K。同时开另一个终端看显存watch -n 1 nvidia-smi重点看显存占用是否稳定有没有持续增长。如果显存一直涨不回落可能是 KV Cache 没有正确释放需要检查 vLLM 版本。4. 踩坑实录那些让我熬夜的问题部署过程中遇到的问题我挑几个典型的讲都是实际踩过的。4.1 启动时报 KV Cache 空间不足最常见的错误信息是ValueError: The models max seq len (32768) is larger than the maximum number of tokens that can be stored in KV cache这个报错的意思是按你给的max_model_len和max_num_seqs算出来的 KV Cache 需求超过了可用空间。排查步骤第一确认权重精度。如果你用的是 FP16 权重24 GiB 跑四路 32K 基本没戏换 AWQ 或 GPTQ。第二检查gpu_memory_utilization。设太低会导致可用空间不足设太高会导致 CUDA Graph 捕获失败。0.90 到 0.94 之间试。第三看是不是有其他进程占了显存。nvidia-smi确认一下有时候之前的容器没退干净显存没释放。第四如果以上都没问题降低max_num_seqs到 3先跑起来再说。4.2 运行中突然 OOM启动成功不代表运行稳定。我遇到过启动正常跑了几轮请求后突然 OOM 的情况。原因通常是激活值峰值超预期。vLLM 在预填充阶段prefill需要一次性计算整个输入序列的注意力激活值占用跟输入长度成正比。32K 输入的 prefill 阶段激活值可能占到 1 到 2 GB。如果gpu_memory_utilization设得太满这部分就没空间了。解决办法把gpu_memory_utilization降到 0.88 到 0.90给激活值留出余量。或者开启--enable-chunked-prefill把长输入切成小块分步计算降低激活值峰值。4.3 并发上不去请求排队严重四路并发听起来够用但实际跑起来发现请求排队很严重。原因是每个请求的生成时间不一样有的请求生成 100 token 就结束了有的要生成 2000 token。长请求占着序列槽不放短请求只能等。vLLM 的调度器是 FCFS先来先服务加抢占。如果长请求太多短请求会被饿死。解决办法有两个一是开启--scheduling-policyfcfs默认就是配合--max-num-batched-tokens控制每批处理的 token 数让调度更公平。二是用--enable-prefix-caching加--num-lookahead-slots让调度器能预判哪些请求快结束了优先调度短请求。实测下来四路 32K 场景下如果请求长度差异大实际吞吐会下降 30% 左右。这是长上下文部署的固有代价只能通过增加并发数来缓解但 24 GiB 的卡加不了太多。4.4 常见问题速查表问题现象可能原因解决方法启动报 KV Cache 不足权重精度太高/并发数太大换 AWQ/GPTQ降 max_num_seqs运行中 OOM激活值峰值超预期降 gpu_memory_utilization开 chunked-prefill请求排队严重长请求占槽开 prefix-caching控制请求长度显存不释放vLLM 版本 bug升级到 v0.6.1推理速度慢block_size 不合适试 block_size32CUDA Graph 捕获失败显存太满降 gpu_memory_utilization 到 0.905. 进阶优化还能再榨出多少空间如果你把上面的配置跑通了还想再优化有几个方向可以试。5.1 KV Cache 量化FP8 的威力vLLM 支持 KV Cache 用 FP8 存储能把 KV Cache 占用直接砍半。开启方式--kv-cache-dtype fp8四路 32K 的 KV Cache 从 7 GB 降到 3.5 GB省出来的空间可以再加两路并发或者把max_model_len提到 64K。但 FP8 KV Cache 有精度损失实测在长上下文场景下困惑度会上升 2% 到 5%。如果你的应用对精度要求不高比如闲聊、摘要可以开如果是代码生成、数学推理建议慎用。5.2 滑动窗口注意力只留最近的 tokenQwen2.5 支持滑动窗口注意力SWA但默认没开。如果你不需要完整的 32K 上下文只需要最近 8K 的信息可以配--sliding-window 8192。这样 KV Cache 只保留最近 8K token显存占用直接降到四分之一。代价是模型看不到更早的上下文。适合对话场景不适合长文档问答。5.3 多卡张量并行24 GiB 不够就上两张如果单卡 24 GiB 实在不够最直接的办法是上两张卡做张量并行--tensor-parallel-size 2两张 24 GiB 加起来 48 GiBFP16 权重 15.5 GB 分到两张卡上各 7.75 GBKV Cache 空间充裕。但张量并行有通信开销推理速度会比单卡慢 10% 到 20%具体取决于卡间互联带宽。NVLink 的话影响小PCIe 的话影响大。我个人建议如果只是四路 32K单卡 24 GiB 加 AWQ 量化就够了没必要上多卡。多卡适合更大模型或更高并发。5.4 实测数据不同配置的吞吐对比我在 4090 24 GiB 上跑了一组对比测试输入 16K token输出 256 token四路并发配置首 token 延迟生成速度显存峰值FP16 权重OOM--AWQ 权重1.8s42 tok/s13.2 GBAWQ FP8 KV1.6s45 tok/s10.1 GBAWQ prefix caching0.9s48 tok/s12.5 GB从数据看AWQ 加前缀缓存是性价比最高的组合首 token 延迟降了一半显存还省了。FP8 KV Cache 省显存效果明显但速度提升有限。6. 我的部署心得折腾了这么久最大的体会是24 GiB 跑四路 32K关键不在卡在权重精度和参数配置。FP16 权重是最大的显存杀手只要换成 4-bit 量化整个局面就活了。另一个体会是vLLM 的参数没有万能配置必须根据你的实际请求模式调。如果你的请求都是短对话max_model_len设 8K 就够了能省大量显存如果都是长文档那就得老老实实按 32K 配。最后分享一个小技巧启动前先用--dry-run模式跑一遍vLLM 会打印出显存分配详情包括权重占用、KV Cache 可用空间、预计并发数。这个信息比看文档有用得多能帮你快速判断配置是否合理。注意--dry-run在部分 vLLM 版本里叫--profile-run具体看版本。如果找不到直接启动看日志也行vLLM 启动时会打印显存分配信息。如果你也在折腾 24 GiB 上的长上下文部署欢迎交流配置。我目前这套 AWQ prefix caching block_size16 的组合跑了两个月没出过 OOM稳定性可以放心。