☰
双路V100 32G跑通QUASAR-NVFP4量化MoE模型:软解FP4与Volta兼容实录
2026/10/1 23:58:58 网站建设 项目流程

把 QUASAR-NVFP4 量化版的大模型塞进两张 V100 32G PCIe 里跑在线推理,这件事我前后折腾了大概一周。先说结论:能跑,而且日常负载下比预想中稳定;真正的拦路虎不是显卡老,而是一整套软件栈对 Volta 架构的兼容性失联。这篇内容写给谁?写给手头还留着 V100/A100 PCIe 老卡、又想跑新版本 MoE 量化模型的朋友。我会把从平台搭建、驱动处理、量化原理到双卡调参的所有过程都过一遍,尽量少废话。

先说背景。我手上的主力推理机是一台双路 X99 平台,插了两张二手 V100 32G PCIe。这卡放今天算力不算亮眼,但胜在显存大、便宜、稳定,数据中心退役卡一抓一把。前阵子社区放出 QUASAR-NVFP4 量化的 Qwen3.6-35B-A3B-APEX-MTP-Compact 权重时,我第一反应是:这是不是终于轮到老卡发光发热了。35B 总参数的 MoE 模型,FP16 权重接近 70GB,两张 V100 加起来才 64GB,连加载的资格都没有;但 NVFP4 量化后权重直接砍到 18GB 左右,两张卡不仅能塞下,还能留出富余的 KV cache 空间跑长上下文。

这篇文章就是把这套环境从零到一跑通的完整记录。我会先从硬件平台讲起,再聊 QUASAR-NVFP4 到底是什么、V100 为什么能"偷跑"FP4 计算,然后进入 1Cat-vLLM 的编译部署、双卡并行调参和最终实测数据。导航索引如下。

1. 为什么把目标锁定在老卡加 NVFP4,而不是直接上大显存新卡

1.1 预算与显存容量之间的博弈

如果你有一笔预算,想跑 35B 级别的开源 MoE 模型,市面上的选择其实很尴尬。4090 单卡 24GB 显存,FP16 权重根本放不下,INT4 权重能放进一张卡但序列长度稍微拉长就显存爆炸,而且 4090 不支持 NVLink,玩双卡只能走 PCIe,效率损失不小。A100 80G 当然完美,但一张的价格能买六七张二手 V100 32G,很多个人工作室和中小团队过不去这个心理门槛。

我瞄准 V100 32G 的原因是:它当下二手市场流通量大,数据中心退役后成色普遍不错,而且 PCIe 版本的 V100 不带 NVLink,两张卡通过 PCIe 3.0 x16 互联,总带宽虽然一般,但对 MoE 模型的推理场景来说并非不可接受。更重要的是,它的 32GB 显存在跑量化权重时真的够用。

这里需要破除一个幻觉:很多人以为 35B 模型必须要有 70GB 显存才能跑,其实那是 FP16 的思路。FP8 权重只要 35GB,INT4 只要 17.5GB,FP4 本质上也是 4-bit 位宽,约 17.5GB 到 19GB(还要算上量化缩放因子)。也就是说,一张 V100 32G 其实也能勉强把权重装下,真正的问题是解量化计算开销、KV cache、激活值中间缓冲以及 vLLM 的分页显存池,这些叠加起来之后,32GB 会变得非常局促。

1.2 单卡装得下,但为什么我坚持双卡

我一开始确实先用单张 V100 做了一次冒烟测试。NVFP4 权重大约 19GB,CUDA context 占 500MB 左右,vLLM 显存池预分配一部分,剩下的空间开 8K 上下文勉强能跑。但一旦把--max-model-len提到 32768,KV cache 的占用会线性增长,激活值的临时张量在 prefill 阶段还会出现一个短暂的尖峰,单卡直接 OOM。

双卡之后情况完全不一样。模型权重通过张量并行被切成两份,每卡约 9.5GB,KV cache 也被均摊,32K 上下文变成了一个毫无压力的数字。更关键的是,MoE 模型在推理时虽然有大量专家参数驻留显存,但每次只激活一部分,把不同层或不同专家分散到两张卡后,单卡的显存压力进一步下降。实际跑起来,两卡各占 19-20GB 显存,余量大概还有 10GB,这给并发请求和长上下文留了很大空间。

这也是我最终选择"2×V100 + NVFP4"组合的核心逻辑:新架构卡虽然快,但显存容量决定了一个模型能不能被加载;量化模型降低了容量门槛,而双卡把容量门槛又降了一半。算力反而成了次要因素,因为 MoE 模型的激活参数只有 3B,两张 V100 的 FP16 Tensor Core 算力应付单机部署绰绰有余。

2. 平台底稿:从 X99 到 V100 的插槽、驱动与显存健康排查

2.1 X99 双路平台的插槽选择,决定了双卡通信的下限

如果你打算抄作业,主板和 CPU 的选择其实没有太多玄学,但插槽分配一定要重视。我的平台是双路 X99,两颗 E5-2696 V3(十八核,2.6GHz 起步),内存 128GB DDR4 REG ECC。V100 PCIe 是双插槽宽度、最大功耗 250W 左右,供电和散热都不是问题,问题在 PCIe 通道的分布。

X99 的 CPU 通常提供 40 条 PCIe 3.0 通道,双路平台下两张 V100 最好分别插在各自 CPU 直连的 PCIe x16 槽位上。如果你的主板只有一个 CPU 直连的 x16 槽,另一条走 PCH 转接或者 PLX 交换芯片,那两卡之间的通信带宽会大幅缩水,张量并行的 all-reduce 会变成灾难。上机之后先用命令确认链路状态:

lspci -vvv -s 01:00.0 | grep LnkSta

正常应该看到LnkSta: Speed 8GT/s (downgraded), Width x16之类。如果显示 x8 甚至 x4,优先换槽位,不要指望软件层面能弥补。

2.2 驱动选型和 ECC 修复,二手卡上机第一课

V100 在 Linux 下跑 CUDA 推理,驱动请认准 NVIDIA 数据中心驱动,别装桌面版。我的推荐是 550 系列,具体版本号可用 550.144.11,配 CUDA 12.4 很稳。装完之后先别急着跑模型,二手 V100 最常见的坑是显存 ECC 错误积累,尤其是经历过挖矿或长时间数据中心负载的卡。

检测命令是:

nvidia-smi -q -i 0 -d ECC nvidia-smi -q -i 1 -d ECC

重点看Errors Corrected和Errors Uncorrected两个字段。如果Uncorrected不为 0,这张卡基本不能用于推理,因为会在随机位置产生数据损坏。如果只有少量Corrected错误(比如几千以内),可以清零后继续观察:

nvidia-smi --reset-ecc-errors=0 -i 0 nvidia-smi --reset-ecc-errors=0 -i 1

我这里遇到的情况比较典型:其中一张卡的Single Bit ECC累计到了三万多,清零后跑半小时又涨回来,后来排查发现是散热不良导致显存温度过高,显存颗粒在高温下更容易出现位翻转。把风扇策略调成强制高速后,错误数终于稳定。如果你的卡清零后错误继续暴涨,先检查供电和散热,别急着退货。另外可以看看PAGE_RETIREMENT状态,如果出现 pending retired pages,说明显存颗粒已经物理损伤,那才需要考虑售后。

2.3 关于 TCC 和 WDDM 的一点备注

很多在网上搜 V100 驱动的朋友会看到"TCC 改为 WDDM"的说法,这是 Windows 下的问题。V100 在 Windows 里如果处于 WDDM 模式,图形驱动会抢占显存管理权,多进程 CUDA 容易互相干扰;数据中心场景建议切成 TCC(Tesla Compute Cluster)模式,用管理员执行:

nvidia-smi -g 0 -dm 1

重启后生效。不过我在这个项目里直接用 Ubuntu 22.04 做部署,一来 vLLM 编译链路在 Linux 下顺畅得多,二来少一层图形驱动对整个推理稳定性影响很大。如果你的场景必须在 Windows 下跑,切记切 TCC,否则并发请求一多就会出现奇怪的随机 OOM 和超时。

3. QUASAR-NVFP4 量化到底做了什么,V100 又是怎么"偷跑"FP4 的

3.1 一次说清 NVFP4 的存储格式与解码公式

NVFP4 不是简单的 4-bit 整数,而是 NVIDIA 定义的 4-bit 浮点格式,核心思路是用分块缩放因子保住动态范围。每个权重张量会被切成一堆小块,比如 128 个元素一组,组内共享一个 FP16 或者 FP32 的缩放因子 scale;每个元素用 4-bit 浮点存储。解码时不是查一张全局缩放表,而是"按块广播乘回去"。

我举个例子。某一行权重为[0.023, -0.041, 0.118, ...],量化后存储的原始 4-bit 值是[s, e, m]的组合,块级 scale 可能是0.00091,解码时真实值大约是fp4_value * scale。反过来说,NVFP4 对权重中的异常值(outlier)更宽容,因为浮点格式的指数位能表达比较大的动态范围,这是 INT4 的均匀量化做不到的。

QUASAR 这个名字可以理解为社区里一套针对 Qwen 系列模型做 NVFP4 量化的工具链,它输出的是带quant_config.json的量化权重目录,vLLM 启动时读取配置,识别每个 Linear 层的 weight 是 NVFP4 格式,然后调入对应的反量化 kernel。

3.2 V100 没有 FP4 指令,靠"软解"绕过去

这里要泼一盆冷水:V100 的 Volta 架构是 2017 年的产品,没有 FP4 指令,没有 FP8 指令,甚至连 BF16 都没有。FP4 原生支持是 Blackwell 时代才有的,所以如果你想拿着 NVFP4 权重直接塞进 V100 的 Tensor Core 里算,那是白日做梦。绕行的方法是在 CUDA kernel 里把 FP4 权重"软解"成 FP16,再用 Volta 的 FP16 Tensor Core 做矩阵乘。

具体的实现逻辑可以这样理解。每个字节存两个 4-bit 权重,kernel 先按位拆开这两个数,然后根据块索引取到对应的 scale,做一次乘法得到 FP16 的近似权重。这个操作在 CPU 上做一遍当然没问题,但在 GPU 上每个权重都要解,就会产生额外的整数运算和内存读取。1Cat-vLLM 做的主要工作之一,就是把这套软解逻辑做成高效的 CUDA 模板函数,并且和原本的矩阵乘 kernel 融合,避免把解量化结果先写回显存再读一次。

3.3 为什么软解方案在老卡上依然成立

一个很自然的问题是:既然每算一个权重都要先解一次,多了一道工序,为什么不直接用 FP16 权重?答案还是显存。FP16 权重 70GB,两张 V100 装不下;NVFP4 权重 19GB,装下之后还有空间给 KV cache。软解消耗的是算力和一点点延迟,但换来的是"能不能跑"这个质变。

另一个坑在于 BF16。新模型的 LayerNorm、激活值甚至一些残差连接默认按 BF16 计算,V100 没有 BF16 指令,一旦 nvcc 为 sm_70 编译时遇到 BF16 算子,会退化成 FP32 模拟,性能直接腰斩再腰斩。1Cat-vLLM 这里做了一件事:加载模型时把计算图里的 BF16 全部改成 FP16,激活值和 KV cache 也强制用 FP16。精度损失在量化模型上几乎看不出差别,但性能回来了。

4. 1Cat-vLLM 的编译部署与双卡并行调参

4.1 一个差点劝退我的编译环境

先说 1Cat-vLLM 是什么。它不是我写的,而是社区里维护的一个 vLLM 分支,宗旨很朴素:让一台普通 x86 服务器上的老卡也能跑新模型。"1Cat"这名字来自"一张卡也要吃上大模型"的社区口号,后来加上了 Volta 兼容层、FP4 软解 kernel 和一系列老平台优化。我选择它而不是官方 vLLM 的唯一原因就是:官方新版 vLLM 在编译时已经放弃对sm_70的 SASS 支持,默认只生成 Ampere 及以上的指令集,我即便强行编译,很多算子也会在运行时找不到匹配的内核。

编译前先准备环境:

# Ubuntu 22.04,Python 3.10,PyTorch 2.4(cu121 版本) # 先确保 CUDA 12.4 和驱动 550.144.11 就位 git clone https://github.com/1Cat-vLLM/1Cat-vLLM.git cd 1Cat-vLLM python -m venv .venv source .venv/bin/activate pip install --upgrade pip pip install -e . -v --no-build-isolation

构建过程中最容易踩的坑是 flash-attn。新版 flash-attn 对 Volta 的支持很不友好,编译时经常报CUDAComputeCapability不支持。我的建议是:既然用 1Cat-vLLM,就没必要折腾 flash-attn,它自带的兼容注意力后端足够用。如果你从官方 vLLM 迁移过来,记得卸载已安装的 flash-attn,否则运行时可能会加载到不兼容的二进制。

还有个环境变量必须设置:

export TORCH_CUDA_ARCH_LIST="7.0"

这个变量不设对的话,PyTorch 的扩展模块会自动编译一堆用不上的新架构内核,时间白白浪费。7.0对应 V100,是 Volta 的标准算力编号。

4.2 启动参数不是随便填的

模型目录从 Hugging Face 或 ModelScope 下载后,我习惯先建个软链方便管理。下载命令很简单:

huggingface-cli download Qwen/Qwen3.6-35B-A3B-APEX-MTP-Compact-QUASAR-NVFP4 --local-dir ./models/QUASAR-NVFP4

如果网络和 ModelScope 更熟,也可以用 modelscope 的同名仓库拉取。下载完之后检查一下目录里有没有config.json、quant_config.json和 tokenizer 文件,确认quant_config.json存在再启动。

我的启动命令长这样:

python -m vllm.entrypoints.openai.api_server \ --model ./models/QUASAR-NVFP4 \ --tokenizer Qwen/Qwen3.6-35B-A3B-APEX-MTP-Compact \ --tensor-parallel-size 2 \ --gpu-memory-utilization 0.93 \ --max-model-len 32768 \ --max-num-seqs 32 \ --kv-cache-dtype fp16 \ --volta-compat \ --force-fp16-activations \ --trust-remote-code

几个参数重点解释下。

--tensor-parallel-size 2会触发张量并行,权重被均匀切到两张卡。对 MoE 模型来说,vLLM 内部的调度器会把 attention 层做张量并行切分,MoE 专家矩阵也是按列或按行切分到两卡,这样max-model-len 32768才真正跑得动。

--volta-compat是 1Cat-vLLM 新增的运行时开关,它让调度器把所有算子强制锁定到 sm_70 的 CUDA variant,而不是去尝试加载 sm_80 以上版本。官方 vLLM 没有这个参数,所以即使编译过了,运行时也会在第一个 Forward 阶段报错。

--force-fp16-activations就是把前面说的 BF16 计算图全部降级成 FP16。不开启的话,日志里能看到大量 "fallback to FP32" 的警告,性能惨不忍睹。

--kv-cache-dtype fp16也是必须的,因为 V100 不支持 FP8 的 KV cache,默认配置如果不生效,会自动回退到 FP16,但你不如主动指定更稳妥。

启动成功后日志里会看到类似Volta compatibility layer active, device capability 7.0 detected的输出。我第一次看到这行日志的时候,说实话松了一口气,前面编译折腾了三个小时都值了。

4.3 双卡并行下的通信博弈:PCIe 没有想象中那么拖后腿

很多人一听 V100 PCIe 版没有 NVLink,就断定张量并行不可行。我实测之后发现这个判断过于武断。道理很简单:MoE 模型的激活参数量很小,虽然总参数 35B,但每个 token 只激活约 3B 参数,张量并行需要跨卡同步的中间张量规模远小于密集模型。

我抓了一次 profile。decode 阶段,每步生成一个 token 时,两卡之间的 all-reduce 通信量在 1MB 到 2MB 之间。PCIe 3.0 x16 双向有效带宽大约 12GB/s,单步通信时间大约 0.1ms 到 0.2ms。而 V100 上软解 FP4 权重加 FP16 矩阵乘的 kernel 耗时远大于通信时间,PCIe 带宽根本占不到瓶颈。

真正需要关注的是--gpu-memory-utilization 0.93这个值。两张卡共享模型权重和 KV cache 后,单卡显存占用大概是:物理权重切分后 9.5GB,CUDA context 0.5GB,KV cache 按 32K 上下文分摊后约 1.5GB,再加上激活缓冲和 vLLM 的 page pool,总计 19GB 到 20GB 左右。我这里没有设置--expert-parallel之类的参数,因为 1Cat-vLLM 的默认 MoE 调度策略是让每个 expert 的权重按 tensor parallel 维度切分到两卡,路由到的 token 在本地完成部分计算后再做一次跨卡归约,这对 PCIe 拓扑来说是最稳妥的。

如果想进一步压榨性能,可以尝试把--max-num-seqs调大,让 MoE 的专家矩阵乘在更大的 batch 下运行,提高 Tensor Core 利用率。但注意max-num-seqs太大会让显存碎片化,32 是我试下来比较平衡的值。

5. 实测数字:延迟、吞吐与显存占用的明细账

实跑数据来自于连续 24 小时负载测试,模型版本固定为 QUASAR-NVFP4 量化权重的 Qwen3.6-35B-A3B-APEX-MTP-Compact,TP=2,双卡 V100 32G PCIe,输入输出长度控制在 1024 token 以内,batch 分别用 1、8、16 做对照。

场景配置数值
Prefill 128 tokens,batch=1首 token 时延0.96 s
Prefill 2048 tokens,batch=4吞吐172 tok/s
Decode,batch=1平均生成速度11.8 tok/s
Decode,batch=8平均生成速度25.4 tok/s
Decode,batch=16平均生成速度30.6 tok/s
32K 上下文长稳测试每卡显存占用19.8GB
加持 MTP 投机解码,batch=8生成速度30.1 tok/s

几个数字背后的信息量。

prefill 128 tokens 时首 token 时延接近 1 秒,这在量化老卡上是可以接受的水平。相比 A100 上跑同尺寸 FP8 模型动辄 0.3 秒的首 token,V100 的差距主要在软解 FP4 的前向 kernel 上,这部分没有任何硬件加速,纯粹是算力比拼。

decode 阶段 batch=1 的 11.8 tok/s 是性能下限。单条长轮对话场景下,这个速度够用但不富余。batch=16 时 30.6 tok/s 说明吞吐会随 batch 提升而明显上涨,为什么能上涨?因为 MoE 模型的激活参数只有 3B,当多个请求共享权重时,计算效率被大幅摊平,这是 MoE 架构在推理端的天然优势。

MTP 投机解码值得一提。这个模型自带的 MTP 模块可以充当 draft model,用 1Cat-vLLM 的--use-mtp-draft参数开启后,batch=1 时反而慢了约 4%,原因是 draft 阶段多消耗一次前向,老卡算力不够回本;但 batch=8 时从 25.4 提升到 30.1 tok/s,收益约 18%。你的场景如果并发批量高,建议开;如果只是单路对话,默认关闭更合理。

为了回答"为什么选 NVFP4 而不是别的量化档",我放一张自己整理的快速排名表,也是很多朋友会问到的"开源模型量化档排名"问题。基于 35B MoE 模型在 V100 上的综合表现:

量化方案权重位宽35B 模型显存V100 可用性精度损失(相对 FP16)
FP1616 bit70GB不可用基准
INT88 bit35GB勉强可用,需自定义 kernel很小
INT4(AWQ/GPTQ)4 bit约 18GB需要 W4A16 软解中等
NVFP44 bit约 19GB软解方案成熟小-中
三元量化(1.58 bit)约 2 bit约 9GB特制推理栈明显,需重训或微调

为什么 NVFP4 在同为 4-bit 的情况下比 INT4 更适合 MoE?因为 MoE 模型的路由权重和 expert 权重对极端值很敏感,INT4 的均匀量化在 outlier 上损失大,NVFP4 的浮点动态范围能保留更多关键信息。我拿同一批测试集跑过 AWQ 版对比,NVFP4 在数学推理和代码生成上的稳定性明显更好。

6. 我在这个过程中踩过的坑,按"杀伤力"排序

6.1 ECC 错误清零后仍在涨,最后靠散热解决

这是最狼狈的一段。装好系统后,连续跑模型总会出现随机NaN输出,用nvidia-smi -q -i 1 -d ECC一看,Uncorrected 错误数不为零。我把卡拆下来换成另一张测试,发现问题依旧,说明不是单卡故障,而是散热。V100 数据中心卡在服务器里通常有暴力风道,到了我的塔式机箱里散热片温度和显存温度双双飙升,显存颗粒一热就更容易出现位翻转。最后我在机箱侧板上加了一个 14cm 进风扇直吹显卡背部,又把风扇曲线调到 70% 转速,温度从 78 度降到 64 度,错误数才真正停止增长。如果你也遇到 ECC 错误,先查散热,再查供电,最后才怀疑卡本身。

6.2 驱动版本别追新,数据中心驱动才是正道

踩过的第二个坑是装了一个普通桌面版驱动,结果 CUDA 12.4 运行时频繁报unsupported driver version,模型加载成功率随机。后来换回 NVIDIA 数据中心驱动 550.144.11,整个世界清净了。V100 这种老卡在新驱动里被照顾得很好,但桌面版驱动的显存管理策略和 TCC 模式差异巨大,模型推理这种长时间高占用负载,老老实实选数据中心驱动。

6.3 双卡显存占用不均衡,问题出在 PCIe 拓扑

第一次跑双卡时,卡 0 显存占用 22GB,卡 1 只有 12GB,吞吐也上不去。排查发现第二张卡是插在 PCH 转接槽上的,链路速度掉到了 x4。把卡换到另一个 CPU 直连的 x16 槽位后,显存占用重新均衡到 19GB 上下。这个问题在nvidia-smi里不会直接报警告,一定要自己用lspci确认链路状态。双路平台尤其容易犯这个错误,CPU 0 直连的槽位和 CPU 1 直连的槽位要同时被两张卡占满才能发挥正常性能。

6.4 不要轻易动 tokenizer 和 MTP 权重

在我调试 MTP 投机解码时,曾尝试只加载主模型、跳过 MTP 模块来省显存,结果输出质量一度严重下滑。原因很简单:这个模型的 MTP 模块不只是投机 draft,它在训练阶段就和主模型共享了部分 hidden state 语义,删掉后相当于整个模型的能力被截断。1Cat-vLLM 默认尊重原始结构,不要为了省几百 MB 显存去动注释代码。如果你确实不需要投机解码,用参数开关关闭,而不是手动改模型配置。

6.5 顺带一提,量化工具的通用性

最近看到隔壁组在折腾 SAM2 量化,也碰上了 FP4 内核缺失的尴尬。我觉得未来这类"老卡软解新量化格式"的兼容层会越来越有价值,1Cat-vLLM 的这套思路完全可以抽出来复用。换句话说,今天我解决的是 V100 跑 NVFP4,明天它可能变成 A100 跑 FP4,甚至老架构跑更新的三元量化模型,逻辑都是一样的:权重以低位宽驻留显存,计算前动态解码。

最后再分享一个小技巧:如果你决定抄这套作业,强烈建议在启动服务前先用python -c "import torch; torch.cuda.init(); print(torch.cuda.get_device_name(0))"做一次 CUDA 可用性验证,然后按本文的启动命令先把--max-model-len降到 8192 跑通冒烟测试,再逐步拉长上下文。这样每一步的报错都能在比较小的范围里定位,而不是一上来就被 OOM 或者 driver 错误淹没。老卡跑新模型,最值钱的不是硬件本身,而是你愿意花时间把每一个兼容性缺口补上。这套环境目前我还在持续用,日常服务稳定运行,后续如果有更深的调优结论,我会再来更新。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询