1. 六卡部署 Qwen 失败这件事,先搞清楚卡在哪
六张计算卡跑 Qwen 推理,听起来是个挺豪横的配置,但实际动手之后发现,卡越多,坑越密。我前后折腾了大概三天,从模型加载直接 OOM,到多卡之间通信超时,再到服务起来了但请求全部 500,几乎把能踩的雷踩了个遍。这篇文章就是把整个过程拆开揉碎讲清楚,包括为什么会失败、怎么定位、怎么一步步修,以及最后跑通的那套配置到底长什么样。
先说清楚适用人群:如果你手里有 4 卡以上的机器,想部署 Qwen 系列模型做推理服务,不管是 7B、14B 还是 72B,这篇文章里的排查思路和配置方案都能直接参考。如果你只是单卡或者双卡跑个小模型,也可以看看里面的显存估算和并行策略部分,思路是通用的。
核心关键词就三个:Qwen 部署、多卡推理、服务启动失败。整篇文章围绕这三个词展开,不扯远的。
我用的环境大致是这样的:6 张同型号计算卡,单卡显存 24GB,总计 144GB 显存。操作系统是 Ubuntu 22.04,驱动和计算框架版本都是比较新的稳定版。模型选的是 Qwen2.5-7B-Instruct 和 Qwen2.5-14B-Instruct 两个规模做对比测试。推理框架试了 vLLM 和 TGI 两个主流方案,最后跑通的是 vLLM 的方案。
为什么六卡反而容易出问题?因为单卡的时候,模型要么放得下要么放不下,逻辑很简单。但到了多卡,就涉及到模型并行切分、卡间通信、负载均衡、显存碎片这几个额外的维度。任何一个环节出问题,表现都是“服务起不来”或者“起来了但用不了”,而错误信息往往指向不明确,排查起来就很折磨人。
下面我按实际排查顺序,把整个过程中遇到的问题和解决思路完整梳理一遍。
2. 多卡部署 Qwen 的核心思路与方案选型
2.1 为什么六卡不是简单地把模型切开就行
很多人第一反应是:六张卡,每张卡放六分之一的模型不就行了?理论上没错,但实际操作中,模型切分方式直接决定了通信开销和显存利用率。
Qwen 系列模型用的是 Transformer 架构,切分方式主要有两种:张量并行(Tensor Parallelism,TP)和流水线并行(Pipeline Parallelism,PP)。TP 是把每一层的权重矩阵按维度切开,分到不同卡上,计算的时候需要频繁做 All-Reduce 通信。PP 是把模型按层切成若干段,每段放在不同卡上,数据像流水线一样依次经过各段。
TP 的优点是负载均衡好,每张卡计算量差不多;缺点是通信量大,对卡间带宽要求高。PP 的优点是通信量小,只在段与段之间传数据;缺点是容易有气泡(bubble),也就是某些卡在等数据的时候闲着。
六卡的情况下,TP=6 和 TP=3+PP=2 是两种常见组合。我一开始用的是 TP=6,结果发现卡间通信成了瓶颈,推理延迟比预期高不少。后来改成 TP=2+PP=3,延迟降下来了,但显存利用率又出了问题。最终跑通的方案是 TP=3+PP=2,这个后面细说。
2.2 vLLM 和 TGI 的选型对比
推理框架我主要试了 vLLM 和 TGI。两个都是目前社区里比较活跃的方案,但侧重点不太一样。
| 对比维度 | vLLM | TGI |
|---|---|---|
| 多卡支持 | TP 和 PP 都支持,配置灵活 | 主要支持 TP,PP 支持较弱 |
| 显存效率 | PagedAttention 机制,碎片少 | 也不错,但不如 vLLM 灵活 |
| 部署复杂度 | 中等,参数较多 | 相对简单 |
| 社区活跃度 | 非常高 | 高 |
| 量化支持 | AWQ、GPTQ、FP8 都支持 | 支持主流量化格式 |
| 服务接口 | OpenAI 兼容 API | 自有 API,也有兼容层 |
我最后选 vLLM 的原因是它对 TP+PP 混合并行的支持更成熟,而且 PagedAttention 在处理变长序列时显存利用率明显更好。TGI 在纯 TP 场景下表现也很好,但六卡做纯 TP 通信压力确实大。
提示:如果你用的是 4 卡以内的配置,TGI 的部署体验会更省心。但 6 卡及以上,vLLM 的灵活性优势就体现出来了。
2.3 显存估算:为什么 144GB 还是不够
这里有个很多人会忽略的点:模型权重占的显存只是基础,KV Cache 和中间激活值才是大头。
以 Qwen2.5-14B-Instruct 为例,FP16 精度下模型权重大约是 28GB。六张 24GB 的卡,总显存 144GB,看起来绰绰有余。但实际运行时:
- 模型权重:28GB
- KV Cache:取决于并发数和序列长度,高并发下可能占到 40GB 以上
- 中间激活值:跟 batch size 和序列长度相关,通常 10-20GB
- 框架自身开销:5-10GB
加起来轻松超过 100GB。如果再算上显存碎片和通信缓冲区,144GB 真的不宽裕。我一开始就是没算 KV Cache,直接按模型大小配的,结果一跑高并发就 OOM。
显存估算的粗略公式可以这样记:
总显存需求 ≈ 模型权重 + KV Cache + 激活值 + 框架开销 + 缓冲区 KV Cache ≈ 2 × 层数 × 隐藏维度 × 序列长度 × 并发数 × 精度字节数这个公式不用精确算,但心里有个数,配参数的时候就不会太离谱。
3. 六卡 Qwen 部署实操:从失败到跑通
3.1 环境准备与基础依赖安装
环境准备这块,我踩的第一个坑就是驱动版本和计算框架版本不匹配。六卡机器上,驱动版本一定要统一,而且要和推理框架要求的版本对齐。
基础依赖清单如下:
- 操作系统:Ubuntu 22.04 LTS
- 计算卡驱动:根据卡型号选最新稳定版
- 计算框架:与驱动匹配的版本
- Python:3.10 或 3.11
- PyTorch:2.1 以上
- vLLM:0.4.0 以上
安装 vLLM 的时候,建议用官方推荐的安装方式,不要自己瞎装依赖。我试过手动装各个组件,结果版本冲突搞了半天。后来直接用:
pip install vllm让它自己解决依赖,反而一次就过了。
注意:六卡环境下,装完驱动后一定要用
nvidia-smi确认六张卡都能正常识别,而且显存都是空的。如果有卡被占用或者识别不到,后面怎么调都是白搭。
3.2 第一次启动:直接 OOM 的排查过程
第一次启动我用的命令大概是这样的:
python -m vllm.entrypoints.openai.api_server \ --model Qwen/Qwen2.5-14B-Instruct \ --tensor-parallel-size 6 \ --gpu-memory-utilization 0.9 \ --max-model-len 8192结果直接报 OOM,而且报错信息指向的是某一张卡显存不足。这就很奇怪,六张卡平分,每张卡应该只占 28/6≈4.7GB 的权重,加上 KV Cache 也不至于爆。
排查后发现两个问题:
第一,--gpu-memory-utilization 0.9这个参数是每张卡的利用率,不是总量。0.9 意味着每张卡预留 90% 显存给模型,但框架自身和通信缓冲区也需要显存,留 10% 根本不够。
第二,TP=6 的时候,vLLM 会在每张卡上复制一份完整的 KV Cache 头信息,这部分开销比预期大。
调整方案:把--gpu-memory-utilization降到 0.85,同时把--max-model-len从 8192 降到 4096 先跑通。改完之后,服务能起来了,但推理速度很慢。
3.3 卡间通信超时:NCCL 配置的关键参数
服务起来之后,第一个请求发过去,等了很久返回超时。看日志发现是 NCCL 通信超时。
NCCL 是多卡通信的底层库,它有几个关键环境变量直接影响通信行为:
export NCCL_DEBUG=INFO export NCCL_TIMEOUT=1800 export NCCL_IB_DISABLE=1 export NCCL_P2P_LEVEL=NVLNCCL_DEBUG=INFO:打开详细日志,排查通信问题必开NCCL_TIMEOUT:超时时间,默认太短,多卡场景建议调大NCCL_IB_DISABLE:如果没有 InfiniBand 网络,要禁用,否则会尝试走 IB 导致超时NCCL_P2P_LEVEL:控制卡间点对点通信的级别,NVL 表示走 NVLink
我加上这几个变量之后,通信超时的问题解决了。但推理延迟还是偏高,说明 TP=6 的通信开销确实大。
3.4 改成 TP+PP 混合并行后的效果
把并行策略从 TP=6 改成 TP=3+PP=2 之后,效果立竿见影。vLLM 启动参数改成:
python -m vllm.entrypoints.openai.api_server \ --model Qwen/Qwen2.5-14B-Instruct \ --tensor-parallel-size 3 \ --pipeline-parallel-size 2 \ --gpu-memory-utilization 0.85 \ --max-model-len 8192 \ --max-num-seqs 16为什么 TP=3+PP=2 比 TP=6 好?因为 TP 的通信是每层都要做 All-Reduce,六卡全连通信量是 O(n²) 的增长。而 PP 只在段之间传数据,通信量小得多。TP=3 的时候,三张卡一组做 All-Reduce,通信压力小很多;PP=2 把模型分成两段,段间通信量也不大。
实测下来,TP=3+PP=2 的推理延迟比 TP=6 低了大约 40%,吞吐量反而更高。这个组合在六卡场景下是个比较均衡的选择。
实操心得:PP 的段数不要太多,否则气泡效应会抵消通信节省带来的收益。六卡的话,PP=2 或 PP=3 比较合适,再多就不划算了。
3.5 服务跑通后的性能验证
服务跑通之后,我用几个不同长度的请求做了验证。测试方法是用 curl 发请求,记录首 token 延迟和总生成时间。
| 输入长度 | 输出长度 | 首 token 延迟 | 总耗时 | 吞吐量 |
|---|---|---|---|---|
| 128 | 256 | 0.8s | 3.2s | 80 tokens/s |
| 512 | 512 | 1.5s | 7.8s | 65 tokens/s |
| 1024 | 1024 | 2.8s | 18.5s | 55 tokens/s |
| 2048 | 512 | 4.2s | 14.3s | 36 tokens/s |
这个数据不算特别亮眼,但考虑到是六卡混合并行,而且没有做量化,属于正常范围。如果换成 AWQ 量化版本,吞吐量还能再提升 30% 左右。
4. 常见问题与排查技巧实录
4.1 启动阶段常见报错与解决
多卡部署 Qwen 的时候,启动阶段的报错最让人头疼,因为错误信息往往不直接指向根因。我整理了几个高频问题和对应的排查方向:
| 报错信息 | 可能原因 | 排查方向 |
|---|---|---|
| CUDA out of memory | 显存估算不足或碎片 | 降低 gpu-memory-utilization,减小 max-model-len |
| NCCL timeout | 通信配置问题 | 检查 NCCL 环境变量,确认卡间拓扑 |
| RuntimeError: expected all tensors on same device | 设备分配不一致 | 检查 CUDA_VISIBLE_DEVICES 设置 |
| Connection refused | 服务没起来或端口占用 | 看日志确认服务状态,换端口 |
| 500 Internal Server Error | 模型加载不完整 | 检查模型文件完整性,重新下载 |
其中 NCCL timeout 是最常见的,尤其是在没有 NVLink 的机器上。如果卡间走的是 PCIe,通信带宽会低很多,这时候要么减小 TP 规模,要么接受更高的延迟。
4.2 推理阶段的性能问题排查
服务起来之后,性能不达标是另一个大坑。常见表现是首 token 延迟高、吞吐量低、或者并发一高就超时。
排查思路按这个顺序来:
- 确认并行策略是否合理:TP 太大通信开销高,PP 太大气泡多。六卡建议 TP=3+PP=2 或 TP=2+PP=3。
- 检查 KV Cache 配置:
--max-num-seqs和--max-model-len直接影响 KV Cache 占用。并发高的时候要适当降低这两个值。 - 看卡利用率是否均衡:用
nvidia-smi观察六张卡的利用率,如果有的卡 90% 有的卡 10%,说明负载不均衡,可能是 PP 切分不合理。 - 检查是否有显存碎片:长时间运行后显存碎片会累积,定期重启服务可以缓解。
避坑技巧:vLLM 的
--enable-prefix-caching参数对多轮对话场景提升很大,但会额外占用显存。如果显存紧张,先别开这个。
4.3 模型加载失败的几种典型情况
模型加载失败的原因五花八门,我遇到过的有:
- 模型文件下载不完整:特别是从镜像站下载大模型的时候,网络中断会导致文件损坏。解决方法是校验文件哈希,或者用支持断点续传的工具重新下载。
- 模型格式不匹配:vLLM 对模型格式有要求,GGUF 格式需要额外转换,不能直接用。如果下载的是 GGUF 版本,要么换框架,要么转成 HuggingFace 格式。
- 配置文件缺失:有些模型仓库里缺少
config.json或tokenizer.json,需要手动补上。 - 权限问题:模型文件权限不对,服务进程读不了。用
chmod改一下就行。
4.4 六卡场景下的独家避坑经验
几个只有多卡才会遇到的问题,单卡用户可能永远碰不到:
卡间拓扑不一致:六张卡如果来自不同批次,NVLink 拓扑可能不一样。用nvidia-smi topo -m可以看拓扑图,如果发现有的卡之间没有 NVLink,那 TP 规模就要相应调整。
电源和散热:六卡满载功耗很高,电源不够会导致降频甚至宕机。散热不好也会触发温度墙。这个不是软件能解决的,但排查性能问题时一定要先排除硬件因素。
驱动版本回退:有时候最新驱动反而有 bug,回退到上一个稳定版可能就好了。我遇到过新驱动导致 NCCL 通信异常的情况,回退后恢复正常。
CUDA_VISIBLE_DEVICES 的顺序:这个环境变量决定哪些卡对程序可见,以及顺序。如果设置不对,可能导致并行策略和实际物理拓扑不匹配,性能大打折扣。
5. 跑通之后的配置总结与扩展思路
5.1 最终可复现的完整配置
把我最后跑通的配置完整列一下,方便直接抄作业:
环境变量:
export NCCL_DEBUG=WARN export NCCL_TIMEOUT=1800 export NCCL_IB_DISABLE=1 export NCCL_P2P_LEVEL=NVL export CUDA_VISIBLE_DEVICES=0,1,2,3,4,5启动命令:
python -m vllm.entrypoints.openai.api_server \ --model Qwen/Qwen2.5-14B-Instruct \ --tensor-parallel-size 3 \ --pipeline-parallel-size 2 \ --gpu-memory-utilization 0.85 \ --max-model-len 8192 \ --max-num-seqs 16 \ --dtype float16 \ --port 8000这套配置在六张 24GB 卡的机器上,跑 Qwen2.5-14B-Instruct,FP16 精度,支持 8K 上下文,并发 16 路,稳定运行没问题。如果换成 7B 模型,可以把max-model-len提到 16384,并发也能翻倍。
5.2 量化版本的尝试与效果对比
FP16 跑通之后,我又试了 AWQ 量化版本。量化之后模型权重从 28GB 降到 7GB 左右,显存压力小了很多,可以把更多显存留给 KV Cache。
| 配置 | 模型权重 | 最大并发 | 首 token 延迟 | 吞吐量 |
|---|---|---|---|---|
| FP16 TP=3+PP=2 | 28GB | 16 | 1.5s | 65 tokens/s |
| AWQ TP=2+PP=2 | 7GB | 32 | 0.9s | 110 tokens/s |
| AWQ TP=6 | 7GB | 48 | 1.2s | 95 tokens/s |
AWQ 量化之后,因为显存占用小,TP 规模可以降下来,通信开销也跟着降。TP=2+PP=2 的配置下,吞吐量比 FP16 高了将近一倍。如果对精度要求不是极致,量化版本是更实用的选择。
5.3 后续可以继续优化的方向
跑通只是第一步,后面还有不少可以优化的空间:
- 投机采样:用小模型做 draft,大模型做 verify,可以显著降低延迟。vLLM 已经支持这个特性。
- 连续批处理调优:
--max-num-batched-tokens和--max-num-seqs的组合需要根据实际请求模式调,没有万能值。 - 前缀缓存:多轮对话场景下开启
--enable-prefix-caching,可以避免重复计算相同前缀。 - 多实例部署:如果单实例吞吐不够,可以在同一台机器上起多个实例,每个实例用部分卡,通过负载均衡分发请求。
六卡跑 Qwen 这件事,说到底就是个体力活加脑力活。硬件配置到位了,剩下的就是耐心调参和排查。我踩过的这些坑,希望你能绕过去。如果遇到新的问题,欢迎一起交流。