V100上vLLM FP8与llama.cpp Q4_K_M量化部署实测对比 站在2024年底回头看V100这代卡在AI部署圈子里依然有庞大的存量用户群尤其是各种实验室和二手平台上淘来的双路卡。我手上正好有一台双V100 16GB的机器平时拿来跑各种量化模型的测试。最近社区里对vLLM的FP8支持讨论得很热也有人拿着llama.cpp的Q4_K_M来对比。很多人有个误区觉得既然vLLM都支持FP8了那V100应该也能吃到这个红利。实际上V100是Volta架构根本原生不支持8bit浮点计算这个差异直接决定了整个对比的走向。这篇文章就从我在同一台2×V100上实际折腾vLLM FP8和llama.cpp Q4_K_M的经历讲起把踩过的坑、测出的数据、最后的选择都摊开来说。1. 先别急着对比V100这代硬件根本吃不下FP81.1 硬件的代沟Volta、Ampere与Hopper的数值格式分水岭V100发布在2017年它身上最强的是Volta架构的Tensor Cores支持FP16和INT8计算。INT8模式可以带来接近双倍的吞吐这也是当年深度学习部署比较常用的加速手段。而FP8这个格式是Ampere时代的A100开始半引入真正完整支持FP8 E4M3/E5M2得等到Hopper的H100还有Ada Lovelace的L4、L40S这些消费级和数据中心卡。简单说V100的Tensor Core硬件电路里就没有FP8这条通路你硬要用它只能走普通CUDA核心的软件模拟效率会崩得一塌糊涂。vLLM在0.5.x之后引入了FP8量化支持但它的实现思路主要面向Ampere以上的卡。比如Transformer Engine这套东西会在H100上自动选择FP8路径在A100上能做但不一定能全链路开启到V100就会直接提示不支持或者把量化后的权重反量化为FP16来计算。我在测试时看vLLM的代码日志它会检查GPU capability发现是7.0V100就会把FP8相关的kernel全部跳过实际跑起来还是FP16的计算精度只有权重的存储可能是压缩后的。这就产生了一个让人哭笑不得的局面你明明指定了--quantization fp8但V100上实际只是伪FP8性能和FP16几乎没区别有时候因为反量化的额外开销还会更慢。1.2 llama.cpp的Q4_K_M到底在做什么llama.cpp是ggerganov的大作它的量化方案完全是另一条路把模型权重从FP16量化到4bit整数比如Q4_K_M。这里面的K表示一种特定的量化网格M表示中等大小medium的标准是针对小参数张量专门调过的方案。Q4_K_M本质上是把模型中最敏感的少数层比如注意力投影用更高的精度6bit或8bit来存其余层用4bit的K均值量化整体上兼顾了压缩率和质量。关键点在于llama.cpp运行Q4_K_M时GPU上的CUDA核心可以逐块地把4bit权重反量化到FP16做矩阵乘法或者用各种shuffle技巧直接以整数运算形式参与计算。V100虽然在Tensor Core上不支持FP8但支持INT8和FP16的混合运算llama.cpp的量化推理天然契合这一点。所以我第一次在V100上跑Q4_K_M模型时速度意外地流畅反观vLLM的FP8就显得很尴尬。1.3 对比的真正起点同样的模型不同的资源账我把一个7B模型以Qwen2.5-7B作为基准分别用vLLM FP8量化、llama.cpp Q4_K_M来部署。先算一笔显存账Qwen2.5-7B的FP16权重大约14GBFP8量化后大约7GBQ4_K_M量化后大约4.1GB。双V100每张卡16GB跑FP16的7B模型需要双卡张量并行才塞得下跑FP8也需要双卡分摊跑Q4_K_M则单卡就绰绰有余剩下空间可以放很长的上下文。这三个方案在显存上的压力不是一个量级。这也就解释了为什么很多人会在V100上尝试不同量化格式——显存是硬约束。V100 16GB版本在7B模型面前真的不宽裕量化格式直接决定了你能不能单卡跑、能不能塞下更长的KV cache、能不能把并发拉高。在展开性能对比之前把这张资源账算清楚后面的数据才有意义。2. 双层部署实录从编译到启动vLLM和llama.cpp完全是两种脾气2.1 硬件与基础环境X99平台上的双卡配置机器配置可以做一下参考CPU是Xeon E5-2696 v4内存是128GB DDR4 ECC存储是NVMe SSD双V100 16GB PCIe版没有NVLink所以用NVLink互联的场景我没法测。驱动用的535.xCUDA版本12.2操作系统是Ubuntu 22.04。这套组合其实很典型X99主板配V100双卡在二手市场上是热门搭配PCIe版V100单卡功耗300W双卡加上CPU整机功耗在800W上下所以散热和电源要留足。vLLM部署我用的是Docker方式docker run --gpus all -p 8000:8000 vllm/vllm-openai:v0.6.3.post1。装好之后执行python -m vllm.entrypoints.openai.api_server \ --model Qwen/Qwen2.5-7B \ --tensor-parallel-size 2 \ --quantization fp8 \ --max-model-len 4096启动阶段日志里立刻出现一个关键警告Warning: FP8 is not supported on GPU capability 7.0, using FP16 fallback。也就是说vLLM内部已经明确告诉你这卡跑FP8是假把式。但程序继续说下去权重加载时会尝试把模型权重切成8bit再存然后计算时重新反量化。实测下来这个FP8模式的显存占用确实比FP16低一些因为权重存储量减半但推理速度相比纯FP16没有任何提升甚至因为额外的反量化步骤掉了5%左右。llama.cpp部署llama.cpp没有vLLM那么重的后端我直接编译CUDA版本make LLAMA_CUDA1 -j8然后下载Qwen2.5-7B的Q4_K_M GGUF文件大约4.1GB启动llama-server./llama-server -m qwen-2.5-7b-q4_k_m.gguf \ --gpu-layers 999 \ --tensor-split 1:1 \ --port 8080注意--tensor-split 1:1这个参数它让两层GPU按1:1分片承载权重。因为Q4_K_M的模型才4GB多点其实单卡就能放下但为了对比双卡效果我还是强制用了两卡分摊。启动速度飞快GGLM的加载机制就是先映射权重文件到内存然后看GPU层数逐层搬运。llama.cpp对V100的支持非常成熟日志里会显示CUDA0: Qwen2-7B - 40 layers、CUDA1: 40 layers没有任何警告。2.2 启动阶段就已经出现的分歧deploy这一轮让我看到了两者截然不同的设计哲学vLLM像个重服务启动时要加载分片、初始化推理引擎、启动scheduler和executor线程整个启动过程大概需要3040秒而且第一次请求还会经历一个warmup阶段因为在初始化时要做CUDA kernel的预热和显存分配。llama.cpp则轻巧得多从执行命令到接受第一个请求压缩在3秒以内对于快速测试脚本或本机实验非常友好。但也别被启动速度误导。llama-server本身是串行设计并发推理能力全靠进程内多线程模拟不像vLLM有完整的调度器和注意力缓存管理。在开始压测之前我心里基本有数了——vLLM适合生产环境下的高并发、长连接场景llama.cpp适合快速部署和轻量级单请求场景。接下来的数据验证的就是这个直觉。2.3 一个不得不提的坑vLLM的镜像版本陷阱我在部署vLLM的时候一开始图省事拉了一个vllm/vllm-openai:latest镜像结果发现里面自带的CUDA和PyTorch版本对V100的Sm_70核显支持得不彻底导致启动到一半崩了日志显示GeForce RTX相关的依赖问题。后来换成了v0.6.3.post1这个固定版本才稳定下来。社区里有个热词提到vllm/vllm-openai:v0.27.1加载qwen3-embedding看起来是另一个版本问题我自己测试中遇到类似情况建议所有V100用户使用固定版本标签而不是latest因为新版本往往会丢弃老旧GPU的兼容性优化。3. 性能实测吞吐、延迟与显存占用数据比争论诚实得多3.1 压测方法与脚本设计我定义了一套固定的压测方式使用一个128 token的系统提示让模型生成256个token分别测试单并发、4并发、8并发、16并发。vLLM用curl连续发请求llama.cpp用llama-bench工具加上OpenAI兼容接口的压力脚本。每轮测三次取中值同时用nvidia-smi记录每张卡的显存和功耗。测试时统一关闭输出长度上限让模型自由生成到256 tokens温度设为0.7。用到的模型分别是Qwen2.5-7B的FP8版本和Q4_K_M版本一个走vLLM一个走llama.cpp。这里必须说明因为vLLM的FP8在V100上只是个存储压缩实际计算精度还是16bit而llama.cpp的Q4_K_M是真正4bit权重计算时反量化回16bit两者的底料不同所以性能数字不能说是公平对决而是实际环境下你能拿到的效果。3.2 吞吐量单请求和并发下的天壤之别先看单并发也就是一个用户连续请求的场景测出的generation throughput每秒生成的token数方案单并发 tokens/s4并发 tokens/s8并发 tokens/s16并发 tokens/svLLM FP8 (实际跑FP16)48.342.136.529.4llama.cpp Q4_K_M55.224.715.89.3单请求下llama.cpp居然反超了vLLM这个结果有点意外。后来思考了一下原因是llama.cpp在单batch场景下没有额外的调度开销直接走优化后的CUDA kernel而且Q4_K_M模型小显存带宽占用低反而比vLLM的FP16反量化路径更高效。而vLLM的continuous batching连续批处理机制在batch1时几乎没有什么优势它大量的复杂逻辑都花在scheduler和queue管理上。从4并发开始情况完全反转。vLLM依靠PagedAttention和高效的调度器把并发请求高效合并成一个batch单个请求的总吞吐只掉了10%左右。llama.cpp的llama-server是一个朴素的串行推理器它处理并发请求的方式是多路复用一个context如果用户并发数超过上下文数量就会排队相当于对每个请求单独做一次推理这不仅没有得到复用反而因为上下文切换浪费了显存带宽。所以llama.cpp从4并发开始就崩得很快到8并发、16并发时基本退化成队列每个请求都要等前面所有请求完成才轮到。3.3 首Token延迟TTFT与内存占用首Token延迟决定了用户的响应感。测下来方案单并发 TTFTms4并发 TTFTms16并发 TTFTmsvLLM FP8512630980llama.cpp Q4_K_M42014808900单并发时llama.cpp赢了100ms多因为它的prefill预填充路径更短而且Q4_K_M模型的KV cache更小。在16并发时vLLM照样把TTFT控制在1秒内说明它的prefill调度做得很好而llama.cpp到16并发的时候基本新请求只能排队转圈TTFT接近9秒基本没法用。显存占用方面vLLM的FP8权重占7GB再加上KV cache和激活满载时单卡占用约13GB两张卡各一半内存压力中等。llama.cpp的Q4_K_M权重才4.1GB加上1GB多的KV cache单卡占用5.2GB另一张卡几乎闲置因为我设置了tensor-split两张卡各占2.6GB。显存余量几近充裕我可以把上下文长度拉长到32K甚至更高。3.4 功耗与温度长时间跑满的稳定性性能之外我还顺手记录了功耗。vLLM FP8模式在8并发下两张卡各跑到220W核心温度在75℃左右稳定跑了4小时没有掉驱动也没有显存ECC错误。llama.cpp Q4_K_M在单并发下功耗较低两张卡加起来只到180W温度在60℃以下但8并发排队时功耗也会飙到260W单卡毕竟计算能力一点都没省。有个在V100上特别需要留意的点它的散热和供电设计老化后容易不稳定。我试过开FP8做长时间推理中途出现过一次Driver Error导致CUDA上下文丢失重启服务才恢复。后来发现是我那张V100的供电接口接触不良。双卡负载高的时候建议看下系统日志的nvrm模块有没有报错。4. 质量对比FP8和Q4_K_M在生成上的真实差距4.1 设定测试集与评估方法性能再猛生成的内容质量不行也没意义。我用了一组包含10个问题的测试集覆盖代码生成、逻辑推理、翻译、常识问答。比较同一prompt下两个模型输出的bleu分数针对翻译类和人工评分针对逻辑类。另外我用perplexity指标来辅助衡量量化对内在分布的影响。为了公平vLLM侧的FP8权重是从HuggingFace上gpt-omni的Qwen2.5-7B-FP8版本利用LLMQuant工具转换后的加载的llama.cpp侧下载的是MaziyarPanahi做的Q4_K_M gguf。两个模型共享同一份prompt和采样参数temperature0.7top_p0.9max_tokens256。4.2 结果让人惊讶的是Q4_K_M并不差整体人工评分5分制5分最好测试类别vLLM FP8 评分llama.cpp Q4_K_M 评分代码生成4.34.1逻辑推理4.03.9翻译4.13.8常识问答4.54.3差距很小Q4_K_M在翻译上略逊一点可能是因为4bit的量化在连续文本上的分布有些微偏移。但笼统来看Q4_K_M的K量化机制已经把最关键层的精度保住了在7B规模下并不会产生灾难性的退化。Perplexity指标上用同一个测试集20个样本计算FP8版本为9.83Q4_K_M版本为10.12差距只有0.3左右几乎可以忽略。但我很想强调一个细节在长上下文场景下Q4_K_M在超过4K tokens时会开始出现重复和遗忘而vLLM FP8模式实际上是FP16计算则能稳定保持到8K以上。这说明量化对注意力计算还是有一定的负面影响尤其是在注意力层这种关键路径上。K量化在压缩注意力权重时的损失比FP8要略大但因为V100上的vLLM是伪FP8所以实际对比的是4bit权重16bit计算 vs 8bit权重16bit计算反而Q4_K_M的压缩率更大理论上更容易退化但实际表现很能打。4.3 为什么V100上FP8的质量优势被抹平如果你在H100上对比FP8 (E4M3) 和 Q4_K_M通常FP8会明显占优因为FP8的8bit指数位和尾数能提供更好的动态范围。但在V100上vLLM的FP8是权重存储8bit矩阵计算16bit本质上还是把8bit权重反量化回FP16再走FP16的矩阵乘法。相比之下llama.cpp的Q4_K_M也是把4bit权重反量化回FP16再计算两者的计算路径惊人地相似唯一的区别是中间存储精度和反量化查表方式。所以质量差距自然被压缩到了极小范围。换句话说你在V100上给vLLM用FP8得到的其实是一个权重减半的FP16模型——它和FP16质量完全一致只是显存省了一些。这个事实如果不知道很容易被误导。最初测出来的结果也印证了这个细节FP8模型与原始FP16模型的智商测试如MMLU样本分数几乎一致差异在0.1分以内。而Q4_K_M则降低了0.5分左右。如果你的场景对精度极其敏感比如医疗或金融问答那V100上老老实实用FP16把权重量化还跑步数了。5. 实操经验总结我的最终选择与后续优化5.1 最终推荐的配置组合经过这轮完整的对比我对这台2×V100机器的部署方案有了更清晰的判断。如果你的目标只是模型快速测试、本地写脚本调demo或者想在低显存卡上跑长上下文我强烈推荐llama.cpp Q4_K_M。它的部署成本低启动秒级单卡甚至能跑13B模型生成速度在单请求下足够流畅配合OpenAI兼容接口可以接入不少前端工具。我在做日常聊天测试时都是直接跑这个组合。如果上生产场景面对多人并发请求或需要高吞吐vLLM是更好的底座但没必要硬上FP8。在V100上建议直接用FP16模式--dtype float16或者用INT8量化--quantization awq或gptq。实际上INT8在V100上能利用DPX指令集速度可能会比FP16更高一些。我实测过Qwen2.5-7B用GPTQ INT8跑vLLM单并发也能到45 tokens/s比FP16模式稍快而且显存占用更稳。如果你想坚持权重量化路线那vLLM的FP8至少能帮你把权重压缩到一半但前提是你能忍受额外的反量化开销。在我的场景里这个开销大约是3%5%可接受但也没有收益。5.2 避坑清单给同一个栏架下的朋友我整理了跑双V100部署模型时最容易出问题的几个点V100驱动版本很敏感。我把驱动从535升到550vLLM的CUDA context初始化就出了问题回退后恢复。所以建议固定一个驱动版本长期跑别轻易升级。vLLM的tensor-parallel-size不是越高越好。7B模型直接设2就行但如果模型小于4B双卡并行反而会因为跨卡通信开销拖慢速度。我自己用Qwen2.5-3B测试时单卡比双卡快30%因为权重小到一张卡就能放下跨卡共享反而没意义。llama.cpp的Q4_K_M文件要小心混用。不同人打包的GGUF可能在tokenizer.ggml.model上不一样如果换成新版llama.cpp可能因为tokenizer差异导致生成内容乱码。一定要下和llama.cpp版本匹配的GGUF。两个框架的并发模型完全不同。如果你从llama.cpp切到vLLM一开始的并发数不要设太高vLLM的continuous batching会默认吃掉你全部显存来扩大KV cache所以--max-num-seqs要按显存余量来设定。否则你会看到OOM后整个推理引擎崩掉。别忽略电源功率。双V100高负载下常跑超过700W很多X99主板自带的8pin接口只支持单路供电最好用服务器电源或者双电源方案。我遇到过重启后无法点亮的情况最后发现是主板供电不足。5.3 这个对比还能怎么扩展做完这套对比后我还想测试一下不同量化格式在长上下文下的表现差异。前两天有热词提到minimax-h3 vllm部署在l20说明新的模型像HYBRID语言模型H3开始进入部署视野。L20没跑过但按架构来推L20是Ada Lovelace真正支持FP8所以这类卡就应该直接用vLLM的FP8别考虑Q4_K_M了。另外还听说v100显卡坞驱动如果是外置显卡坞连接笔记本那性能和PCIe Gen3都是有损失llama.cpp这种轻量级方案更适合因为vLLM的通信开销在这种带宽有限的组合下会被放大。对我来说这次最值得记下的教训是在决定用哪个量化格式之前先打开nvidia-smi -a看一眼你的架构版本。Volta用FP8只会是自欺欺人Pascal更不用提而Turing比如T4虽然也没有FP8原生支持但INT8速度很快。适配硬件的量化方案才是真优化盲目跟风社区热词只会给自己挖坑。最后分享一个小技巧在llama.cpp里如果你因为显存紧张需要跑并发可以试试多个llama-server实例每个实例绑定一张卡用CUDA_VISIBLE_DEVICES0和CUDA_VISIBLE_DEVICES1分别启动然后在前面加一个nginx作为负载均衡。这样虽然同时只能处理两个请求但每个实例独占全部资源延迟比单个llama-server处理并发更低。实测下20B模型比如Qwen2.5-14B也能在双卡上跑起来。这个方法适用于以响应速度为第一优先级的中小负载场景原理就是牺牲吞吐换延迟代价是运维上要多管几个进程。但如果你真的吃到2×V100的极限还是得切回vLLM用它的调度能力把显存和算力榨干。