多卡部署 Qwen3-VL-32B:vLLM 死锁与 llama.cpp 实战对比
背景与问题
最近针对 Qwen3-VL-32B 满血版模型进行了一次部署实战。环境是一台配备了 4 张 NVIDIA A30(24GB 显存) 的服务器,总显存 96GB,理论上足以承载 FP16 精度的 32B 模型(约 65GB 权重)。
然而,在使用业界标杆 vLLM 进行部署时,系统陷入了诡异的'死锁'——显存占满,但推理毫无反应,最终超时报错。尝试切换到 Ollama(底层基于 llama.cpp)后,奇迹发生了:不仅部署成功,而且运行流畅。同样的硬件、同样的模型,为何两个主流框架的表现天差地别?
本文将围绕 PCIe 通信瓶颈、Tensor Parallelism(张量并行) 与 Pipeline Parallelism(流水线并行) 展开分析。
硬件环境与拓扑
1. NVIDIA A30 的特性
A30 是一款基于 Ampere 架构的中端推理卡,拥有 24GB HBM2 显存,带宽 933 GB/s。但在多卡互联方面有两个关键点:
- NVLink 的缺失:虽然 A30 规格书支持 NVLink,但在很多通用服务器或云实例中,并没有物理配置 NVLink Bridge。我的服务器上就没有。
- PCIe 的独木桥:当卡与卡之间没有 NVLink 这种'高速私家路'时,所有通信都必须走 PCIe 总线。
实际环境中,显卡连 4 卡 PCIe 都没完全联通,最多只有两卡直连。采用 vLLM 时,如果两卡部署也必须选择 SYS 链接的形式。下面是卡联通情况:
nvidia-smi topo -m

| 缩写 | 含义 | 典型场景 |
|---|---|---|
| X | 自身(Self) | GPU 内部环路 |
| PXB | PCIe x16 桥接(Direct PCIe Bridge) | 同一 PCIe 树下的 GPU 直接互联 |
| SYS | 系统总线(System Bus) | 通过 CPU/主板 南桥间接连接 |
| PIX | PCIe 交换机(PCIe Switch) | 多 GPU 通过 PCIe 交换机互联 |
2. vLLM 加载模型'卡死'现象
在使用 vLLM 尝试拉起 4 卡推理时,部署脚本如下:
#!/bin/bash
echo "###########start vl by vllm...##########"
export GLOO_SOCKET_IFNAME="enp210s0f0" # 多网卡需要指明
export CUDA_VISIBLE_DEVICES="1,2,3,4"
export VLLM_LOGGING_LEVEL="DEBUG"
export VLLM_ATTENTION_BACKEND="FLASH_ATTN"
vllm serve /model/Qwen3-VL-32B-Instruct \
--gpu-memory-utilization 0.8\
--dtype auto \
--host 0.0.0.0 \
--port 7860\
--tensor-parallel-size 2\
--kv-cache-dtype fp8 \
--max-model-len 10000\
--limit-mm-per-prompt images=4,video=1\
--api-key yourkey
结果遇到了以下现象:
- 初始化阶段:vLLM 成功加载模型权重,显存占用正常上升,甚至直接到快到 100% 就卡死了(直接超时或不动)。
- 推理阶段:发送第一条 Prompt,后台日志显示正在进行 NCCL 通信初始化。
- 死锁:GPU 使用率忽高忽低,最终降为 0,程序长时间无响应,最后抛出 NCCL 错误。
这显然不是显存不足(OOM),而是 通信堵塞。
3. Ollama 的成功验证
随后,使用 Ollama 加载 GGUF 格式(自己下载的高精度的 FP16)模型,默认都是 q4 的,可以去官网找列表参数。脚本 ollama_start.sh 如下:
#!/bin/bash
echo "###########start llm by ollama...##########"
export CUDA_VISIBLE_DEVICES=0,1,2,3
export OLLAMA_NUM_PARALLEL=10
nohup ollama run qwen3-vl-32b-instruct-bf16:latest >/ollama_models/logs/qwen3vl_32.log 2>&1 &
echo "Models started in background. Check logs in /ollama_models/logs/"
- 结果:一次点亮。非常稳定,没有卡顿,每秒有 50 个 token 的样子。
为什么?难道 vLLM 的技术反而不如 llama.cpp 吗?后面仔细分析了背后的根本原因,在于 两者对'并行'的方式完全不同。
深度解析:vLLM 在弱通信环境下为何不好用
vLLM 之所以快,是因为它追求极致的计算密度;而它之所以挂,是因为它默认采用了 张量并行(Tensor Parallelism, TP)。
1. 什么是张量并行(TP)?
为了讲清楚这个问题,我们把大模型想象成一个巨大的 矩阵乘法工厂。假设我们要计算 $Y = X \times W$。
在 TP 模式下,vLLM 是这样分配工作的:
- 它把巨大的权重矩阵 $W$,竖着切成 4 份 ($W_1, W_2, W_3, W_4$),分别放在 4 张 A30 卡上。
- 当输入 $X$ 进来时,4 张卡 同时 计算 $X \times W_1$, $X \times W_2$……
- 关键点来了:要得到最终结果 $Y$,必须把 4 张卡算出来的部分结果 加在一起(All-Reduce)。
2. 致命的 All-Reduce 通信风暴
大模型(Transformer 架构)通常有几十层(Qwen-32B 可能有 60-80 层)。在 vLLM 的 TP 模式下:
- 每一层计算完,都必须进行一次全员通信同步。
- 每生成一个 Token,模型就要跑完所有层。
计算公式: $$\text{通信次数} = \text{层数} \times \text{生成的 Token 数量}$$
假设模型有 80 层,你要生成 100 个字。那么显卡之间需要进行 $80 \times 100 = 8000$ 次通信!
3. A30 跑多卡数据路径分析
在没有 NVLink 支持,且 PCIe 可能不支持 P2P(Peer-to-Peer)直接访问的环境下,这 8000 次通信是怎么走的?
- 路径:GPU A -> PCIe -> CPU 内存 -> PCIe -> GPU B
- 后果:
- 延迟爆炸:每次绕道 CPU 内存,延迟都会增加。在每秒几十次的高频同步下,延迟被放大成无法忍受的卡顿。
- 带宽瓶颈:PCIe Gen4 x16 的带宽(~32GB/s)远低于显卡内部显存带宽。
- NCCL 崩溃:vLLM 依赖的 NCCL 库在检测到这种恶劣的拓扑环境时,往往会因为同步超时而直接挂起(Hang)。
结论:vLLM 是一辆为高速公路(NVLink)设计的法拉利,你把它开进了泥泞的沼泽地(无 P2P 的 PCIe),它不仅跑不快,还会陷进去。
Ollama (llama.cpp) 的玩法
Ollama 能跑通,并非因为它有什么黑科技,而是因为它默认采用了更适合低带宽环境的策略:流水线并行(Pipeline Parallelism) 或简单的 层级切分(Layer Split)。
1. 不同的切分逻辑:切蛋糕 vs 切千层饼
如果说 vLLM 是把每一层蛋糕切成 4 块分给 4 个人吃(TP),那么 llama.cpp 就是把一个 80 层的千层饼,拆成 4 份,每人拿 20 层(PP)。
- GPU 1:负责第 1-20 层。
- GPU 2:负责第 21-40 层。
- GPU 3:负责第 41-60 层。
- GPU 4:负责第 61-80 层。
2. 通信频率的降维打击
在这种模式下,数据是怎么流动的?
- GPU 1 算完前 20 层,把结果(仅是一个很小的 Activation Tensor)发给 GPU 2。
- GPU 1 休息(或者处理下一个请求)。
- GPU 2 接力继续算。
通信对比:
- vLLM (TP):每生成 1 个 token,通信 80 次。
- Ollama (PP):每生成 1 个 token,通信 3 次(1->2, 2->3, 3->4)。
这就是成功的关键:Ollama 将通信频率降低了几个数量级!即使走慢速的 PCIe 总线,这几次数据传输的耗时也是微秒级的,对整体推理速度几乎没有影响。
3. GGUF 格式的助攻
虽然我是为了跑满血版,但 llama.cpp 生态下的 GGUF 格式(即使是 F16)天生对这种层级切分支持得更好。llama.cpp 的 --split-mode layer 参数正是为此而生,它明确告诉程序:'不要搞复杂的张量拆分,简单粗暴地按层分就好。'
实践中的工具链问题
在这次折腾中,我也尝试了 GPUStack 这个非常好用的管理工具,但也失败了。通过分析日志,我发现了其中的生态依赖链问题。
- 默认走 vLLM:GPUStack 为了追求性能,检测到 NVIDIA 显卡时,默认首选 vLLM 后端。这直接触发了上述的'TP 通信死锁'问题。
- 版本滞后:Qwen2-VL 是非常新的模型,引入了特殊的视觉编码器架构。社区版的
llama.cpp更新极快(甚至按小时更新),但 GPUStack 内部集成的llama-box可能还停留在旧版本,无法解析新模型的 Vision Projector,导致报错'架构不支持'。
在开源大模型领域,工具链的更新速度往往追不上模型迭代的速度。 手动编译最新版往往比集成工具更可靠。
避坑指南与架构建议
经过这次'A30 实战',我总结出了一套针对多卡推理的决策逻辑,分享给大家。
1. 选型决策树
- 有 NVLink 吗?
- YES -> vLLM (开启 TP,享受极致速度)。
- NO -> 继续看下一步。
- 是单机多卡 PCIe 吗?支持 P2P 吗?
- YES (如 4x 4090 插在 PLX Switch 上) -> 可以尝试 vLLM,但要做好降速准备;或者 SGLang。
- NO (如 A30/A40 分布在不同 CPU Socket) -> 绝对避坑 vLLM TP 模式。
2. 弱通信环境下的生存法则
如果在没有 NVLink 的机器上(如 A30, 3090, 4090 多卡)部署大模型:
- 首选 llama.cpp / Ollama:利用其天然的层级切分(Layer Split)优势。虽然单 Batch 延迟可能略高,但稳定性无敌。
- 拥抱 GGUF 量化:既然带宽是瓶颈,减小模型体积就是最大的优化。Q4_K_M 量化版的模型,权重体积减半,不仅加载快,而且在某些极端情况下,甚至能塞进 2 张卡里,进一步减少通信需求。
强制 vLLM 走流水线并行(高阶玩法): vLLM 最新版本开始实验性支持 Pipeline Parallelism (PP)。你可以尝试以下配置来'模拟'llama.cpp 的行为:
# 告诉 vLLM 不要切分张量,而是切分层
vllm serve Qwen/Qwen3-32B-VL-Instruct \
--tensor-parallel-size 1 \
--pipeline-parallel-size 4
注:vLLM 的 PP 支持目前尚不成熟,可能会有显存分配不均的问题。
结语
这次 A30 部署 Qwen-VL 的经历,是让显存不足的用户,能充分利用多机多卡,实现大模型自由。
- vLLM 像是一支训练有素的足球队,需要队员之间时刻眼神交流(高频通信),配合无间。一旦场地太大(带宽低)喊话听不见,配合就乱了。
- Ollama 像是一条工业流水线,大家各司其职,做完手里的活儿传给下一个人。虽然流程长了点,但对'沟通'的要求最低。

