
说实话把“6卡serve qwen失败”这几个字搜索一遍会看到一大堆帖子但多数都停留在贴报错、求答案的阶段。今天我想把自己从踩坑到跑通的完整过程写下来一台6张RTX 3090的机器试图用vLLM部署Qwen2.5-72B-Instruct折腾了大半天反复CUDA OOM、不断重启。拆开看之后发现所谓的“失败”其实是好几个问题叠在一起既有显存算账没算明白也有并行策略和依赖版本的前置条件没核对。这篇文章默认读者有一定基础但我会尽量把关键计算和排查步骤写全适合准备用多卡做模型serving、以及刚接触vLLM/Qwen部署的人参考。1. 一台6卡机器半天没跑起来先说背景。手上的机器是一台双路服务器插了6张RTX 3090每张24GBCPU是64核系统内存256GBUbuntu 22.04。软件环境一开始是Python 3.10、PyTorch 2.3.0、CUDA 11.8vLLM版本0.6.3.post1transformers 4.44.0。想跑的模型是Qwen2.5-72B-Instruct这是当时新版Qwen系列里参数量比较大的一个意图很简单用6卡张量并行方式提供OpenAI兼容接口供内部工具调用。1.1 首次启动看起来一切正常直到OOM初始化环境后启动命令也很常规就是装好vLLM后一行命令vllm serve Qwen/Qwen2.5-72B-Instruct --tensor-parallel-size 6 --max-model-len 8192日志刚开始很顺模型从模型仓库拉权重然后初始化GPU内核这时候会出现一段比较长的等待大概两三分钟。我一度以为是在加载72B权重还觉得6卡张量并行应该没问题。结果日志从Initializing GPU kernel...跳过去之后直接出现torch.OutOfMemoryError: CUDA out of memory. Tried to allocate 512.00 MiB.更有意思的是这个OOM报错并不是在某一张卡上而是前两张卡依次报第三张卡卡了更久最后全部卡都变成“风扇狂转、显存占用几乎满格”的状态。我特意用nvidia-smi去看确认第0、1、2号卡显存占到了23.4GB左右第3、4号卡只有17GB左右第5号卡还剩不少。这种占用不均的情况让我一度怀疑是vLLM分配bug后来才意识到是权重切分之后加上KV cache预分配的叠加效应导致部分卡超额。1.2 第一次怀疑方向是vLLM版本还是显存不够遇到OOM第一反应是改配置。我把--max-model-len从8192降到4096又把--gpu-memory-utilization从默认的0.9改成0.85重新启动。这次日志往前走了一段加载权重阶段过了但在计算attention的KV cache时再次报错而且这次报错的是NCCL相关的runtime error不是单纯的显存不足。那时候我已经开始怀疑是不是vLLM 0.6.3.post1和CUDA 11.8兼容性有问题甚至想换vLLM 0.5版本重来。不过冷静下来后我做了个最笨但最有效的操作找一张空卡手动加载模型权重验证权重本身、transformers和模型文件是否正常。因为如果权重坏了或者依赖不完整根本不需要讨论多卡并行的问题。我先把一个1.5B的小模型放到单卡上跑通确认vLLM服务本身没有坏然后又单独用transformers在单卡上尝试加载Qwen2.5-72B的fp16权重结果当然是直接OOM——因为单卡24GB压根装不下144GB的权重。这就说明问题根源很可能确实是显存分配而不是框架bug。2. 显存账单算清楚为什么六张卡还是不够很多人一听说“6张卡”就觉得显存应该绰绰有余毕竟4090或3090也有24GB6张就是144GB。但类似Qwen2.5-72B这种级别的稠密模型144GB只是权重的理想下限真正做起serving来要花的远不止这些。2.1 BF16权重一算就露馅先算最基础的模型权重。72B参数量按700亿出头算BF16格式每个参数占2字节裸权重就是72B * 2 bytes 144GB但这里还没算embedding、layernorm、额外的bias项以及模型文件本身的padding和切分对齐。Qwen2.5-72B-Instruct实际在磁盘上的BF16权重接近150GB。6张3090加起来是144GB严格说已经不够所以在权重映射阶段就存在理论上的失败风险。这根本不是优化参数能解决的属于物理账算错了。2.2 KV Cache、CUDA Context 和进程分配的隐性开销如果权重刚好能压线放下还有三笔开销等着CUDA context每个进程初始化时会为GPU保留一部分显存常见在1GB到2GB之间取决于PyTorch和CUDA版本。哪怕什么都不加载先占1.5GB6张卡就额外消耗约9GB。KV cachevLLM默认会把gpu_memory_utilization0.9对应到显存分配策略也就是说它会在一开始给KV cache预留一大块连续空间。如果权重已经吃掉90%KV cache基本无空间可分配于是只能缩减--max-model-len甚至报错。activation和临时bufferattention计算时的中间张量、beam search时的候选张量、以及量化算子变换时的临时buffer都会根据batch size和序列长度动态产生。这些开销单看都不大但叠加之后一张24GB的卡很容易在“看起来应该够”的情况下瞬间超标。我后来用torch.cuda.max_memory_reserved做过统计在纯权重的加载阶段每张卡额外预留的运行时显存大约是1.8GB到2.2GB。2.3 张量并行切分的对齐冗余张量并行不是简单地把权重均分到每张卡。vLLM在切分时会把每个线性层按列或按行切块要求切块数能整除head数、intermediate size等参数。对于某些层实际切分后会有少量冗余。以Qwen2.5-72B为例TP6是可行的但每张卡分配到的参数并不严格等于150/625GB而是在局部层上存在不对齐导致某张卡多占几百MB或更大。这就是我在第一次启动时观察到第0号卡先OOM的原因——它不是最慢加载的而是被分配了更多切片。2.4 显存预算表部署前先自己算一遍后来我做了一张简单的预算表每次部署前都会套一下项目计算公式/建议值72B BF16示例模型权重参数量 × 每参数字节数约150GBCUDA context每卡预留1.0~2.0GB6卡合计约9GBKV cachemax_len × 层数 × 每层KV张量大小视max_len而定activation等临时buffer按batch和seq长度估算若干GB总需求上述四项相加远超144GB算完之后就非常清楚6张24GB卡跑BF16的72B注定失败。如果想继续用这套显卡就必须把权重缩小到“每卡约8GB以下”的程度才可能在保留KV cache和context的前提下稳定运行。3. 排查链路从显存怀疑到量化选型踩坑之后我没有立刻换模型而是做了一套可以复用的排查链路这样以后部署其他模型也能直接用。整个排查过程大概分四步。3.1 先验证服务链路是否正常用0.5B或1.5B的小模型比如Qwen/Qwen2.5-0.5B-Instruct不加张量并行直接单卡启动。这一步的目的是确认vLLM能否正常加载模型、监听端口、响应请求。如果连小模型都报错问题出在环境而非显存。这一步我跑得很顺利也顺手验证了OpenAI接口的/v1/chat/completions能正常返回结果。3.2 检查驱动、CUDA与pytorch的匹配关系vLLM底层依赖NCCL做多卡通信NCCL对CUDA driver和PyTorch的CUDA版本比较敏感。我用命令确认了nvidia-smi # Driver Version: 535.183.06 CUDA Version: 12.2 python -c import torch; print(torch.version.cuda) # 11.8这里出现了一个小坑显卡驱动支持CUDA 12.2但PyTorch编译用的是CUDA 11.8vLLM又是基于PyTorch的运行时来初始化的。虽然多数情况下API能兼容但NCCL在跨卡通信时偶尔会挑版本。保险起见我把PyTorch换成了CUDA 12.1对应的版本升级到torch2.3.1cu121再配合同样的vLLM版本通信错误明显减少。3.3 量化模型是绕不开的解药既然BF16放不下自然想到量化。Qwen官方仓库提供了已经量化好的AWQ版本可以直接下载比如Qwen/Qwen2.5-72B-Instruct-AWQ。AWQ一般按4bit或8bit存储权重4bit大约比FP16少4倍左右。这样72B模型的4bit权重大约37GB到40GB6张卡每张只承担6.5GB左右。当时我还对比了GPTQ和AWQ实测下来在vLLM中AWQ的支持更顺性能和显存占用也符合预期。3.4 实操加载AWQ模型并调整并行参数加载AWQ模型有两种常见方式。一种是让vLLM自动识别模型目录里的quantization配置另一种是显式声明。我习惯显式声明避免版本自动检测出岔子vllm serve Qwen/Qwen2.5-72B-Instruct-AWQ \ --tensor-parallel-size 6 \ --max-model-len 4096 \ --quantization awq \ --gpu-memory-utilization 0.9启动之后六张卡的显存占用基本稳定在19GB到21GB之间已经没有单卡爆满的情况。第一次成功启动后我立刻做了一次并发请求测试16路并发、每路输入512 token、输出256 token整体吞吐大约在每秒280 tokens左右。对于内部工具来说这个数字完全够用。如果想让某一卡的压力再小一点也可以把--gpu-memory-utilization调成0.85但那样会影响KV cache容量导致最大并发数下降需要根据线上流量自己权衡。4. 真正能跑通的三个方案如果你手头的卡不是3090而是A100或者H100显存更大可能不需要走到量化那一步。但如果你和我一样是“小显存多卡”那么下面三个方案基本覆盖了能跑通的所有路径。4.1 方案A4bit量化权重成本最低、见效最快这个方案就是我上面说的AWQ。优点是不用改业务代码模型接口形式不变直接换一个模型路径和一行量化参数即可。缺点是把精度从BF16降到4bit会遇到一点质量损失。以Qwen2.5-72B的基准测试数据看AWQ后的得分下降大多在1到2个百分点内日常问答和代码生成几乎无感知。需要注意vLLM对AWQ和GPTQ的kernel支持版本有要求。如果遇到“Unsupported quantization method”报错优先升级vLLM到较新版本或者确认模型目录下是否存在quantize_config.json。有些用户自己用AutoAWQ量化但量化后的格式和vLLM不完全一致也会导致加载失败。4.2 方案B换MoE或更小的Qwen模型从根源降低显存需求如果你的应用场景并不苛求72B这种体量那更稳的做法是降低模型规模。Qwen2.5系列里有两个更合适的模型Qwen2.5-57B-A14B-Instruct虽然是57B总参数但属于MoE结构每个token只激活14B参数BF16权重大约40GB6张24GB卡完全兜得住推理速度也不错。Qwen2.5-32B-InstructBF16权重约70GB6张卡每卡约12GB留12GB给KV cache和activation基本可以跑8192以上上下文。如果业务方明确要求“必须用72B”那在6张24GB卡上强行BF16就是性价比极低的事情不如直接和业务对齐需求换32B或MoE。但如果只是做技术验证量化的72B依然值得选。4.3 方案C调整vLLM参数、保留FP16的最后抢救还有一个不做量化的办法降低上下文长度和显存利用率同时打开swap空间把KV cache换到CPU内存。比如vllm serve Qwen/Qwen2.5-72B-Instruct \ --tensor-parallel-size 6 \ --max-model-len 1024 \ --gpu-memory-utilization 0.72 \ --swap-space 8这样确实能启动但实践下来非常不推荐。因为序列长度只有1024基本只能做单轮短问答一旦并发请求变多KV cache会频繁写回CPU响应速度会慢到让人怀疑人生。这条路只适合“必须要F16精度、只用短文本、并发极低”的边缘场景正常生产环境别选它。4.4 三个方案怎么挑我把选型逻辑整理成一张表场景推荐方案理由小显存多卡要求最大模型4bit AWQ量化显存释放明显推理性能稳定对输出质量非常敏感换MoE或32B模型保留更高精度规避量化损失仅技术验证不要求并发降低max_lenF16不额外下载量化模型但性能受限生产环境量化大模型或MoE小模型并发、吞吐、延迟都更可控5. 踩坑总结与给后来者的清单这次“6卡serve qwen失败”折腾下来最大的教训不是“不要用6张3090跑72B”而是很多部署问题其实能通过启动前的两分钟计算避免。我把后来一直在用的检查步骤和速查表放在这里希望你不用重复踩这些坑。5.1 部署前必做的三项检查第一先算权重账单。确认目标模型的裸权重大小、每张卡的显存、卡数三者做乘法判断是否留出至少15%的空闲显存。如果是72B BF16和6×24GB这种临界组合直接默认不行即可。第二确认并行规模。--tensor-parallel-size必须是总卡数或能够整除的数值同时模型内部一些维度的配置也需要匹配。vLLM对TP大小有限制例如某些attention head不能整除TP5或7如果遇到“Tensor parallel size should divide”之类的提示不要硬扛改成可用值。第三确认驱动和CUDA版本。用nvidia-smi查看驱动支持的CUDA版本在检查python -c import torch; print(torch.version.cuda)尽量让PyTorch的CUDA版本不高于驱动支持的版本否则NCCL通信会出现玄学报错。5.2 常见报错与解决方案速查表整理一下我遇到以及同事遇到过的几类高频错误现象常见原因解决方向CUDA out of memory加载阶段就报权重运行时开销超过显存换量化模型、换小模型、降max_len启动后卡在Initializing GPU kernels首次加载内核较慢或vLLM/CUDA版本不匹配等待观察检查日志尾部升级vLLMNCCL unhandled system error / timeout多卡通信初始化失败P2P、共享内存、驱动问题检查nvidia-smi topo -m升级CUDA驱动增加NCCL_DEBUGINFOValueError: TP size not supported并行度与模型结构不匹配换为合法TP值例如2、4、8端口被占用或服务启动后无响应容器/进程未退出或绑定失败用ps aux5.3 一些个人经验我在实际部署中还有一个习惯先跑--max-model-len最小值确保服务能起来再去逐步调大。因为一旦max_len过大KV cache会直接挤占权重之外的空间出现“权重加载成功但启动后等待请求时OOM”的诡异现象。锁死GPU也没必要但建议在容器里用CUDA_VISIBLE_DEVICES0,1,2,3,4,5显式指定设备顺序避免多卡编号错乱导致不必要的通信异常。另外如果你需要下载来自ModelScope或Hugging Face的Qwen权重强烈建议直接用各自提供的命令行工具拉取不要在服务器上开代理或者手动拼接下载地址容易毁文件。下载后务必校验模型目录下的文件完整性至少检查权重文件大小是否与config.json中的预期一致。多卡serve Qwen失败绝大多数不是Qwen的问题也不是vLLM的问题而是显存预算和并行配置这两件事没在启动前想清楚。把这篇文章里的步骤走一遍大部分卡脖子的场景都能解开。如果还有更特殊的报错不急着换框架先试着把日志尾部几十行完整贴出来查一下对应的vLLM issue。很多看似无解的问题最后都是因为一些极其基础的版本差异导致的。