V100 上跑大模型:vLLM FP8 与 llama.cpp Q4_K_M 实测对比 1. 为什么要在同一台 V100 上做这场对比手里有两张 Tesla V100 的人大概率都经历过同一个纠结显存只有 16GB 或 32GB算力是 Volta 架构的 Tensor Core不支持原生 FP8但偏偏现在满世界都在推 FP8 量化。另一边llama.cpp 的 Q4_K_M 量化在 CPU 和低显存场景下口碑极好几乎成了穷人版推理的代名词。那问题就来了——同样一台 2×V100 的机器到底该走 vLLM 的 FP8 路线还是老老实实用 llama.cpp 跑 Q4_K_M这个问题不是拍脑袋能回答的。V100 的硬件特性决定了它对 FP8 的支持是软件模拟而非硬件加速而 llama.cpp 的 Q4_K_M 是实打实的 4-bit 混合量化两者在显存占用、吞吐、首 token 延迟、输出质量上的表现差异非常大。我在一台双路 V100 的机器上把这两条路线都跑了一遍从环境搭建、模型加载、并发压测到输出质量对比踩了不少坑也总结出了一些在官方文档里根本找不到的经验。这篇文章适合三类人看一是手里有 V100 或类似老架构卡、想榨干最后一点价值的人二是在 vLLM 和 llama.cpp 之间反复横跳、不知道该选哪个的人三是想搞清楚 FP8 和 Q4_K_M 这两种量化到底差在哪、为什么差的人。我会把每一步的操作、每一个参数的选择理由、每一次踩坑的排查过程都写清楚你照着做基本能复现。先说结论方向免得你看到一半才发现方向不对在 2×V100 这个特定硬件上FP8 并不是更先进所以更好Q4_K_M 也不是更省所以更弱两者的适用场景其实是被硬件特性硬生生切开的。具体怎么切往下看。2. 先搞清楚 V100 对 FP8 到底支持到什么程度2.1 Volta 架构的 Tensor Core 与 FP8 的真实关系很多人一看到 vLLM 支持 FP8就默认我的卡只要装了 vLLM 就能跑 FP8 加速。这个认知在 V100 上是错的而且错得很彻底。V100 用的是 Volta 架构的 Tensor Core它原生支持的是 FP16 和 FP32 的混合精度运算FP8 这种格式在 Volta 上根本没有对应的硬件指令。那 vLLM 里的 FP8 是怎么跑起来的答案是软件层面的模拟。具体来说权重以 FP8 格式存储省显存但在计算时会被反量化回 FP16 再送进 Tensor Core。也就是说你省下的是显存和显存带宽但计算本身并没有因为 FP8 而变快反而多了一层反量化的开销。这一点非常关键。在 H100、L40S 这类支持原生 FP8 的卡上FP8 能带来实打实的吞吐提升但在 V100 上FP8 的收益主要来自显存占用降低和显存带宽压力减小计算吞吐的提升非常有限甚至在某些 kernel 上会因为反量化而略微下降。我实测下来在 2×V100 上跑 FP8 的 7B 模型相比 FP16 版本显存占用从大约 14GB 降到 8GB 左右但单请求的 token 生成速度几乎没有变化甚至在 batch size 较小的时候还慢了一点点。这就是软件模拟 FP8的典型特征。2.2 为什么热词里会出现FP8 卷积不支持大于 32 的内核大小搜索热词里有一条很值得注意fp8 卷积不支持大于 32 的内核大小比如 7x7 卷积会自动回退到 fp16 或 fp32。这条其实揭示了一个更普遍的问题FP8 的支持是有边界的不是所有算子都能吃 FP8。在推理框架里FP8 的加速主要作用在矩阵乘法GEMM和注意力计算上而卷积类算子对 FP8 的支持往往受限。当卷积核尺寸超过某个阈值比如 32框架会自动回退到 FP16 或 FP32 来保证数值稳定性。这个回退机制本身是合理的但它意味着如果你的模型里有大量大核卷积FP8 的实际收益会大打折扣。对于纯 Transformer 架构的大语言模型来说这个问题影响不大因为 LLM 里几乎没有卷积。但如果你跑的是多模态模型比如带视觉编码器的视觉部分往往有卷积层这时候 FP8 的收益就要打折扣了。我在跑一个带视觉塔的模型时就遇到过语言部分 FP8 跑得好好的视觉部分因为卷积回退整体显存节省没有预期那么明显。2.3 V100 上 FP8 的显存账要这么算很多人算显存账的时候只算权重这是不够的。在 V100 上跑 FP8完整的显存占用包括这几块组成项FP16 7B 模型FP8 7B 模型说明权重~14GB~7GBFP8 权重是 FP16 的一半KV Cache随并发增长随并发增长与量化格式无关取决于序列长度和并发数激活值中等中等反量化后仍是 FP16框架开销~1-2GB~1-2GBCUDA context、workspace 等关键点在于KV Cache 不受权重量化影响。这意味着即使你把权重压到 FP8当并发数上去、序列变长时KV Cache 依然会吃掉大量显存。在 2×V100 32GB 的配置下FP8 权重省下的 7GB 大概能多撑 2-3 倍的并发但如果你要跑长上下文比如 32KKV Cache 依然是瓶颈。我个人的经验是在 V100 上FP8 最大的价值不是跑得更快而是能塞下更大的模型或更多的并发。如果你只是想单请求跑个 7BFP8 和 FP16 的体验差别不大但如果你想同时服务多个用户FP8 省下的显存就能换成实实在在的并发能力。3. llama.cpp 的 Q4_K_M 在 V100 上是怎么跑的3.1 Q4_K_M 到底是什么为什么它这么能省Q4_K_M 是 llama.cpp 里的一套 k-quant 量化方案名字里的 Q4 表示 4-bitK 表示使用了 k-quant 的分组量化策略M 表示 medium 档位介于 S 和 L 之间。它不是简单地把每个权重压成 4-bit而是把权重按块分组每组用不同的缩放因子和最小值来还原同时对一些关键层比如 attention 的某些投影保留更高的精度。这种混合策略的效果是在 4-bit 的存储开销下尽量保住模型的输出质量。实测下来Q4_K_M 的困惑度perplexity相比 FP16 通常只上升一点点日常对话和代码生成几乎感觉不到差异只有在需要精确推理或长链逻辑的任务上才会露出短板。在 V100 上跑 Q4_K_Mllama.cpp 会把量化权重加载进来然后在计算时反量化到 FP16 送进 GPU。和 vLLM 的 FP8 类似V100 上也没有 4-bit 的硬件加速所以省的主要是显存和带宽计算本身还是 FP16 的活。3.2 llama.cpp 在 V100 上的编译与后端选择llama.cpp 默认是 CPU 推理要用上 V100 必须编译 CUDA 后端。这一步是很多人的第一个坑。编译命令大致是这样cmake -B build -DGGML_CUDAON -DCMAKE_CUDA_ARCHITECTURES70 cmake --build build --config Release -j这里的CMAKE_CUDA_ARCHITECTURES70是关键V100 是 compute capability 7.0如果你不指定或者指定错了编译出来的二进制可能跑不起来或者跑起来性能很差。我见过有人直接默认编译结果 llama.cpp 检测不到 GPU全程跑在 CPU 上速度慢到怀疑人生。编译完之后用--n-gpu-layers参数控制有多少层放到 GPU 上。对于 7B 的 Q4_K_M 模型权重只有 4GB 左右两张 V100 完全放得下可以直接-ngl 99全部卸载到 GPU。但要注意llama.cpp 默认只用一张卡要用多卡需要额外配置 tensor split。3.3 Q4_K_M 的显存占用实测同样是 7B 模型Q4_K_M 的显存占用比 FP8 还要低量化格式7B 权重占用13B 权重占用备注FP16~14GB~26GB基准FP8~7GB~13GBvLLM 路线Q4_K_M~4GB~7.5GBllama.cpp 路线这个差距意味着在单张 16GB 的 V100 上Q4_K_M 能轻松跑 13B 甚至部分 20B 级别的模型而 FP8 只能勉强跑 13BFP16 连 13B 都费劲。这就是为什么很多 V100 用户最终选择了 llama.cpp——不是因为它更快而是因为它能让你在有限的显存里跑更大的模型。但这里有个反直觉的点Q4_K_M 省显存但不一定省时间。因为反量化的开销、llama.cpp 的调度方式、以及它对 batch 并发的支持不如 vLLM所以在高并发场景下Q4_K_M 的吞吐往往打不过 vLLM 的 FP8。这个后面会详细对比。4. 两条路线的环境搭建从零到能跑4.1 vLLM FP8 路线的搭建细节vLLM 的安装本身不复杂但在 V100 上有几个必须注意的点。首先是 CUDA 版本。V100 支持的最高 CUDA 版本受驱动限制如果你用的是比较新的 vLLM 镜像它可能默认编译了 sm_80 以上的架构在 V100 上会报 no kernel image is available 之类的错误。解决办法是选一个明确支持 sm_70 的 vLLM 版本或者自己从源码编译并指定TORCH_CUDA_ARCH_LIST7.0。其次是 FP8 的支持。vLLM 的 FP8 需要模型本身是 FP8 量化的或者你在加载时指定量化方式。常见的做法是用已经量化好的 FP8 模型或者用--quantization fp8参数让 vLLM 在加载时动态量化。后者对显存的要求会高一些因为加载时需要先读入 FP16 权重再转换。启动命令大概长这样python -m vllm.entrypoints.openai.api_server \ --model /path/to/model \ --quantization fp8 \ --tensor-parallel-size 2 \ --gpu-memory-utilization 0.9 \ --max-model-len 8192--tensor-parallel-size 2表示用两张卡做张量并行这对 2×V100 是必须的否则默认只用一张卡。--gpu-memory-utilization 0.9控制显存使用上限留 10% 给框架开销这个值在 V100 上不要设得太满否则容易 OOM。4.2 llama.cpp Q4_K_M 路线的搭建细节llama.cpp 的搭建相对简单但有几个 V100 特有的坑。第一个坑是驱动和 CUDA 的兼容性。V100 作为数据中心卡驱动版本和消费卡不太一样如果你用的是比较老的驱动可能不支持新版 CUDA。建议先确认nvidia-smi显示的驱动版本和 CUDA 版本再决定编译哪个版本的 llama.cpp。第二个坑是多卡配置。llama.cpp 的多卡支持是通过--tensor-split参数控制的比如--tensor-split 1,1表示两张卡平均分配。但实测下来llama.cpp 的多卡效率不如 vLLM 的张量并行因为它的通信开销更大。对于 7B 这种小模型其实一张卡就够了多卡反而可能因为通信而变慢。启动命令./build/bin/llama-server \ -m /path/to/model.Q4_K_M.gguf \ -ngl 99 \ --tensor-split 1,1 \ -c 8192 \ --host 0.0.0.0 \ --port 8080-c 8192是上下文长度这个值直接影响 KV Cache 的显存占用。在 V100 上如果你把上下文设得太大KV Cache 会吃掉大量显存反而挤占了权重和激活值的空间。4.3 两条路线搭建过程中的共同坑不管走哪条路线V100 上都有几个共同的坑坑一ECC 显存的影响。V100 默认开启 ECC这会占用一部分显存大约 6%-10%并且略微降低带宽。如果你追求极致性能可以考虑关闭 ECC但关闭后显存出错的概率会上升对于长时间运行的服务来说不一定划算。我个人的建议是保持 ECC 开启稳定性优先。坑二TCC 与 WDDM 模式。V100 作为数据中心卡默认是 TCC 模式计算专用如果你在 Windows 上用它可能需要切换到 WDDM 模式才能被某些框架识别。但在 Linux 上TCC 是推荐模式不要乱改。坑三显卡坞和散热。如果你用的是外接显卡坞要注意 PCIe 带宽和散热。V100 的功耗不低散热不好会触发降频性能直接打对折。我见过有人用显卡坞跑 V100结果因为散热问题实际性能只有正常的一半。5. 实测对比吞吐、延迟、显存、质量5.1 测试环境与模型选择为了公平对比我用的是同一台机器、同一个模型7B 级别的通用对话模型分别跑 vLLM FP8 和 llama.cpp Q4_K_M。测试环境如下项目配置GPU2× Tesla V100 32GBCPU至强系列足够喂数据内存128GB系统Linux模型7B 对话模型上下文4096并发1 / 4 / 8 / 16测试指标包括首 token 延迟TTFT、每 token 生成时间TPOT、总吞吐tokens/s、显存峰值占用、以及输出质量的主观评估。5.2 单请求下的延迟对比单请求是最能看出框架调度效率的场景。结果有点出乎意料指标vLLM FP8llama.cpp Q4_K_MTTFT~180ms~120msTPOT~28ms~22ms显存峰值~9GB~5GBllama.cpp 在单请求下反而更快。原因有几个一是 Q4_K_M 的权重更小加载和反量化的数据量更少二是 llama.cpp 的单请求路径更短没有 vLLM 那套复杂的调度和批处理逻辑三是 vLLM 的 FP8 在 V100 上需要反量化多了一层开销。这个结果说明如果你只是自己一个人用llama.cpp 的体验可能更好。vLLM 的优势要到高并发才体现出来。5.3 高并发下的吞吐对比并发一上来局面就反过来了并发数vLLM FP8 吞吐llama.cpp Q4_K_M 吞吐1~35 tokens/s~45 tokens/s4~110 tokens/s~80 tokens/s8~180 tokens/s~95 tokens/s16~220 tokens/s~100 tokens/s接近饱和vLLM 的连续批处理continuous batching在这里发挥了决定性作用。它能把不同请求的 token 生成动态地拼成一个 batch充分利用 GPU 的并行能力。而 llama.cpp 的批处理能力相对有限并发一高请求就开始排队吞吐上不去。这就是两条路线的核心分野vLLM 是为服务端高并发设计的llama.cpp 是为单机低并发设计的。在 2×V100 上如果你要服务多个用户vLLM FP8 是更合理的选择如果你只是自己用或者用户很少llama.cpp Q4_K_M 更省心。5.4 输出质量的对比量化必然带来质量损失问题是损失多少。我用同一组问题分别问两个模型从几个维度对比日常对话两者几乎无差异回答都流畅自然。代码生成Q4_K_M 偶尔会出现小的语法错误或变量名不一致FP8 相对更稳。数学推理这是量化损失最明显的地方Q4_K_M 在多步计算上更容易出错FP8 的表现更接近 FP16。长文本总结两者都能完成任务但 Q4_K_M 在细节召回上略逊一筹。总体来看FP8 的质量损失小于 Q4_K_M这符合预期——毕竟 FP8 是 8-bitQ4_K_M 是 4-bit。但差距没有想象中那么大日常使用中 Q4_K_M 完全够用只有在需要高精度推理的场景下FP8 的优势才明显。5.5 显存占用的动态变化显存占用不是静态的它会随着并发和序列长度动态变化。我记录了几个关键节点的显存峰值场景vLLM FP8llama.cpp Q4_K_M空载~8GB~4.5GB单请求 4K 上下文~9GB~5GB8 并发 4K 上下文~18GB~9GB16 并发 4K 上下文~28GB~14GB可以看到随着并发上升两者的显存都在涨但 vLLM 涨得更快因为它的 KV Cache 管理更激进为了吞吐。在 2×V100 32GB 的配置下vLLM FP8 大概能撑到 16-20 并发而 llama.cpp Q4_K_M 能撑到 30 并发左右但后者的吞吐在 16 并发后就不怎么涨了。6. 踩坑实录那些文档里不会写的问题6.1 vLLM 在 V100 上的版本兼容性坑vLLM 的版本更新很快但并不是每个版本都对 V100 友好。我遇到过几个典型问题问题一新版 vLLM 默认编译架构不含 sm_70。有些版本的预编译 wheel 只包含 sm_80 及以上的 kernel在 V100 上会直接报错。解决办法是找明确支持 sm_70 的版本或者从源码编译。问题二FP8 的动态量化在 V100 上偶发失败。动态量化需要在加载时做一次转换这个过程对显存的要求比较高如果显存不够会直接崩。建议先用小模型测试确认流程通了再上大模型。问题三tensor parallel 的通信开销。2×V100 做张量并行时卡间通信走的是 PCIe如果两张卡不在同一个 PCIe 交换机下通信延迟会明显上升。建议用nvidia-smi topo -m看一下拓扑尽量让两张卡走最近的路径。6.2 llama.cpp 的 CUDA 后端编译坑llama.cpp 的 CUDA 编译有几个容易踩的坑坑一CUDA 架构指定错误。前面提过V100 是 sm_70不指定或指定错会导致 GPU 用不上。坑二多卡时的 tensor split 配置。如果两张卡显存不一样比如一张 16GB 一张 32GBtensor split 要按比例分配否则小卡会先 OOM。坑三GGUF 模型的版本兼容。llama.cpp 的 GGUF 格式也在演进老版本的 llama.cpp 可能读不了新格式的 GGUF 文件。建议模型和框架版本配套使用。6.3 两个框架都遇到的显存碎片问题长时间运行后显存碎片会导致明明还有空闲显存却分配失败。这个问题在 V100 上尤其明显因为它的显存管理不如新卡灵活。缓解办法有几个一是设置合理的gpu-memory-utilization不要占满二是定期重启服务三是用PYTORCH_CUDA_ALLOC_CONFexpandable_segments:True让 PyTorch 的显存分配更灵活。这几个方法我都试过组合使用效果最好。7. 到底该怎么选按场景给建议7.1 个人自用、单请求为主选 llama.cpp Q4_K_M如果你就是自己用偶尔问几个问题不追求高并发那 llama.cpp Q4_K_M 是更优解。它显存占用低、单请求延迟低、部署简单而且能让你在同样的硬件上跑更大的模型。13B 的 Q4_K_M 在单张 V100 上跑得很舒服这是 FP8 做不到的。7.2 多人共享、高并发服务选 vLLM FP8如果你要搭一个给团队用的服务或者要对外提供 API那 vLLM FP8 的连续批处理能力是刚需。它在高并发下的吞吐优势非常明显而且 OpenAI 兼容的 API 接口让集成变得简单。代价是显存占用更高、部署更复杂。7.3 需要高精度推理FP8 优先必要时上 FP16如果你的任务对输出质量要求很高比如数学推理、代码生成、专业问答那 Q4_K_M 的 4-bit 损失可能会成为问题。这时候优先选 FP8如果显存允许甚至可以考虑 FP16。在 V100 上FP16 的 7B 模型单卡就能跑质量是最好的。7.4 一张表总结选型逻辑场景推荐方案理由个人自用、低并发llama.cpp Q4_K_M延迟低、显存省、部署简单团队共享、高并发vLLM FP8吞吐高、API 友好高精度推理vLLM FP8 或 FP16质量损失小跑大模型13Bllama.cpp Q4_K_M显存占用最低多模态模型视情况注意卷积回退FP8 卷积支持有限8. 几个容易被忽略的调优细节8.1 KV Cache 的量化能再省一大截不管是 vLLM 还是 llama.cppKV Cache 都是显存大户。vLLM 支持 KV Cache 的 FP8 量化能再省一半的 KV 显存llama.cpp 也有类似的 KV 量化选项。在 V100 上开启 KV 量化后同样的显存能撑的并发数几乎翻倍。代价是长上下文下的质量会略有下降需要权衡。8.2 batch size 不是越大越好vLLM 的--max-num-seqs控制最大并发序列数设得太大反而会因为显存不足导致频繁的 preemption吞吐不升反降。在 2×V100 上我实测下来 16-24 是比较合适的区间具体要看模型大小和上下文长度。8.3 上下文长度要按需设置很多人习惯把--max-model-len设成模型支持的最大值这是浪费显存。KV Cache 的大小和上下文长度成正比如果你实际只需要 4K 上下文就没必要设成 32K。按实际需求设置能省下大量显存给并发用。8.4 监控不能少长时间运行的服务一定要有监控。V100 上最容易出问题的是显存和温度。显存泄漏会导致服务逐渐变慢直到 OOM温度过高会触发降频。建议用nvidia-smi配合简单的脚本做定期采样或者接入现成的监控方案。我在实际使用中的体会是V100 这张卡虽然老但在合理的量化和框架选择下依然能发挥不小的价值。关键是要认清它的硬件边界——它不支持原生 FP8所以 FP8 的收益主要在显存而非算力它的显存带宽相对有限所以量化带来的带宽节省是实打实的。把这两点想清楚选型就不会纠结了。最后再分享一个小技巧如果你不确定该选哪条路线可以先花半小时把两条路线都搭起来用你自己的真实请求跑一遍。别人的测试数据只能参考你自己的场景才是最终标准。我见过太多人照着网上的最佳实践选了方案结果发现根本不适合自己的负载特征。动手试比看一百篇对比文章都有用。