
双V100跑8B级模型听起来余量不小真正部署的时候才意识到卡点卡在显存和架构上。Tesla V100是Volta架构Tensor Core只对FP16最友好单卡16GB版本想塞一个7B/8B模型的FP16权重还要给KV Cache留位置长上下文基本是奢侈品。为了解决这个环境下的推理部署我一边试了vLLM的FP8量化方案一边试了llama.cpp的Q4_K_M量化在完全相同的2×V100机器上做了对齐测试。可能有人会问V100连FP8硬件计算都不支持有什么好比的这正是我想写的重点。vLLM里的FP8在Volta上不会变成运算加速的魔法更像是一种显存压缩手段而llama.cpp的Q4_K_M则把权重压到4bit级别两条路线的减显存思路完全不一样服务能力和交互体验也差得很远。如果你手头恰好是一台2×V100的“老平台”或者正准备用X99之类的主板搭一台双卡V100跑大模型这篇对比能帮你省掉不少瞎折腾的时间。我会把环境参数、跑分数据以及那些只有实测才会发现的坑一起整理出来方便你直接照着做。1. 显存与架构双瓶颈双卡V100在这个时代必须量化1.1 单卡16GB装不下一个体面的8B模型8B模型的FP16权重大概在16GB上下。放在单卡V100 16GB上模型权重几乎把显存吃满KV Cache只剩很小一段空间实际能跑的上下文长度可能只有几百到一两千token。别说什么聊天记录、文档分析、Agent工具调用连稍微长一点的System Prompt都顶不住。这就是双卡V100相对舒服的地方。我用的两块卡都是16GB版本通过NVLink互联跑tensor parallel时模型权重可以均分到两张卡上。8B模型的FP16权重拆到双卡每张大概8GB多还算能接受。但只要你把并发数提上去或者把max-model-len拉到8K甚至16KKV Cache的占用立刻变成大头FP16权重反而成了“占着茅坑不拉屎”的那部分。所以最简单的思路是先把权重变小。FP8把FP16权重砍一半Q4_K_M再砍一半多。这两条路线的实现方式不同后续的行为也完全不同。1.2 Volta的Tensor Core到底卡在哪V100的SM70架构是Tensor Core的祖师爷但它只支持FP16的矩阵乘加不支持INT8、FP8这些后来者。现在模型推理里最吃性能的操作是线性层和注意力计算全部落到Tensor Core上。FP16对于V100来说是原生速度BF16在V100上基本没有硬件加速INT8/FP8则要依赖软件模拟或者走普通CUDA Core。这意味着一个反直觉的事实在V100上某些量化方案虽然能把模型变小但不会自动把数学变快。如果量化到了V100不擅长的格式反而可能因为反量化、类型转换、数据搬运这些额外开销把省下来的带宽又被计算耗时补回去。这也就是为什么很多vLLM的算子会声明需要Compute Capability 8.0甚至9.0才能跑——它们默认你在Ampere或Hopper上根本没打算给SM70做充分优化。1.3 FP8和Q4_K_M其实是两条完全不同的路vLLM FP8的核心卖点是“贴近原生格式、精度损失小”它尽量保留模型的能力同时把权重存储和传输压缩到一半。但在V100上FP8大概率只能作为“存储格式”存在真正进入Tensor Core计算前还是要解锁成FP16所以效果更接近显存压缩而不是FP8加速。llama.cpp Q4_K_M则完全不同。它是从GGML生态生长出来的量化体系目标很朴素在尽量不损失太多质量的前提下把模型压到能塞进小显存甚至纯CPU内存里。Q4_K_M不会假装自己是原生精度但它把4bit权重用得很聪明配合自研的矩阵乘法路径在旧GPU上反而能跑出不错的单流速度。这两条路线并不是“同一种优化策略的两种档位”而是两种不同的设计哲学。要对比就不能只看步数还得看各自的调度能力、并发能力、部署复杂度。下面我从环境和参数开始尽量把对比过程摊开来讲。2. 对比环境与参数对齐方法2.1 硬件与软件栈先说配置这台机器其实没什么特殊的主板是X99平台的服务器板两条Tesla V100 16GB SXM2NVLink直连CPU是两颗Xeon内存128GBUbuntu 22.04。驱动用的是NVIDIA Data Center Driver分支至少在nvidia-smi里看到的是535或550.x。这个细节别跳过V100属于数据中心卡桌面驱动偶尔也能装但做容器透传和校验时还是Data Center版本最省心。软件栈分成两套组件vLLM路线llama.cpp路线推理框架vLLM稳定分支0.6.xllama.cpp主线master编译CUDA12.x镜像CUDA 12.4模型格式FP8权重目录Q4_K_M的GGUF文件模型Qwen3-8B InstructQwen3-8B Instruct并发测试工具OpenAI兼容API压测llama-server的OpenAI兼容API压测两块V100都启用了ECC。老HBM卡如果内存模块有点小毛病不先处理好压测时可能突然给你报CUDA error后面我会专门说这个坑。2.2 vLLM侧怎么启动vLLM跑FP8这条路我没有用最激进的W8A8全量化因为V100的SM70根本不支持原生FP8算子。实践中更稳的是“weight-only FP8”也就是W8A16权重以FP8存计算时按FP16解锁这也是在V100上最现实的做法。启动参数大致是这样vllm serve /models/Qwen3-8B-Instruct-fp8 \ --tensor-parallel-size 2 \ --dtype float16 \ --quantization fp8 \ --max-model-len 16384 \ --gpu-memory-utilization 0.85 \ --port 8000有几个参数值得解释一下。--tensor-parallel-size 2是模型并行切分让两卡各持一半权重--dtype float16非常关键不要在V100上试图用BF16Volta没有硬件支持强行设置往往只能让框架跑到CPU fallback上--max-model-len必须显式指定默认值在V100上很容易撞到显存上限。2.3 llama.cpp侧怎么启动llama.cpp我直接编译了带CUDA的版本然后用它的llama-server启动llama-server \ -m /models/Qwen3-8B-Instruct-Q4_K_M.gguf \ --split-mode layer \ --main-gpu 0 \ -ngl 99 \ --parallel 8 \ --ctx-size 16384 \ --port 8080--split-mode layer表示按层把模型切成两份分别放到两张卡上-ngl 99是尽量把能offload到GPU的层都丢给GPU--parallel 8是这个版本起的叫法表示最多同时处理8个会话。对比时两边的并发压测都通过OpenAI兼容的/v1/chat/completions接口发起避免因API差异把结果带偏。2.4 压测负载设计为了让数据能复现我统一用了比较常见的模式每条请求先用1024个token做prefill再让模型生成512个token分别压1、8、32路并发。每个并发档位跑10轮取中位数而不是平均因为偶尔的调度抖动会把平均值拉得很难看。另外我刻意没有用所谓的“无限长流式对话”测试。V100的短板本来就在长上下文和超大KV无限增长只会放大显存差异对日常使用没参考价值。3. FP8在Volta上的真实运行机制3.1 先分清Weight-only和全量化FP8这个名词现在被用得很泛。严格来说有两种一种是权重、激活都按FP8计算也就是W8A8另一种是只有权重转成FP8激活值维持FP16或BF16通常叫W8A16或weight-only。在H100这种原生支持FP8的卡上W8A8能拿到实际算力提升因为硬件真的用FP8张量核心算矩阵乘。在V100上硬件没有这条指令路径W8A8要么用软件模拟要么干脆不支持。所以vLLM里不少FP8专家算子会检查GPU算力低于SM80的可能直接报错让你别用。我这次跑出来的经验是如果你手里的模型是标准FP8 checkpoint在V100上不一定能直接加载。必须先确认它是纯权重量化、走的是W8A16路径还是W8A8动态量化。后者在V100上基本是劝退的前者配合dtype float16还有希望跑起来。3.2 vLLM在SM70上怎么执行FP8权重对weight-only FP8来说vLLM的流程大致是加载时把FP8权重放进显存执行线性层前先把权重反量化成FP16再走FP16 Tensor Core。这样省下的主要是显存和显存带宽计算量没少额外多了反量化这一步。直观理解就是你从工厂买了一箱压缩饼干省了仓库空间但吃之前还是得先拆包装泡一下水。仓库是显存泡水的过程是反量化V100既不会因为这个变快反而还多了一丝开销。所以我在V100上测到的vLLM FP8速度和纯FP16权重并没有本质差距FF8带来的收益几乎全在显存侧。它能让你在同一张16GB卡里塞进更多KV Cache或让双卡FP16权重本来就吃紧的情况释放出余量。3.3 为什么该把FP8当显存压缩而非加速这是很多新手最容易踩的思维误区。看到“vLLM FP8”就以为跟H100上宣传的“FP8推理快2倍”一样实际在V100上根本不存在这种提升。V100跑FP8的路子本质是用一点计算冗余换显存空间在显存不足的场景下非常有用如果显存已经够了单纯为了“FP8”去切换反而没有意义甚至可能因为算子不兼容而变得不稳定。我这次在2×V100上跑8B模型FP16双卡其实也能启动但KV Cache余量很紧张。换成FP8权重后每张卡的权重占用从约8.4GB降到约4.2GB省出来的4GB左右全部变成KV Cache空间长上下文和并发能力明显改善。这才是FP8在V100上真正的价值。3.4 KV Cache的残酷现实V100用不了FP8缓存vLLM新版本支持--kv-cache-dtype fp8_e5m2能把KV Cache也压缩成FP8这对Hopper或Ada来说是大杀器。但KV Cache的量化压缩要落到实际算子层面V100的SM70没有相关硬件支持强行启用要么报错要么默默回退到FP16。也就是说V100上走FP8路线能省的是权重那一半显存KV Cache还是得按FP16算。这也直接导致V100上跑超长上下文依然很吃力翻车不是权重的问题而是KV Cache在16GB卡上确实不够看。4. llama.cpp Q4_K_M量化到点上的典型4.1 Q4_K_M到底量化了什么Q4_K_M里的K代表K-quants家族4是4bitM是Medium后面还有S、L这些档位。和Q4_0/Q4_1这两个早期量化不同K-quants引入了分组量化概念一个tensor不再是一刀切全按4bit处理而是按重要程度区别对待。具体来说Q4_K_M会把模型中比较关键的张量比如某些注意力投影、Gate和Up层用更高一点的精度保存部分权重可能还是5bit或6bit不那么敏感的部分才折到4bit额外还有一些小的量化表项。这样平均下来每个权重大约占4.5到4.8bit但实际质量比朴素4bit要好不少。对于8B量级的模型Q4_K_M文件大小通常在4.7GB到5.2GB之间。相比之下FP8的checkpoint动辄8GB以上FP16更是快到17GB。想在老显卡上玩开源模型Q4_K_M几乎是社区默认的“甜点档”。4.2 多GPU拆分和MMQ计算路径llama.cpp在双卡上的做法和vLLM的tensor parallel不太一样。它默认按层切分也就是--split-mode layer前一半层放GPU 0后一半层放GPU 1张量本身不做横向切分。这样做的通信开销通常低于tensor parallel但两层之间如果跨卡传递激活值依赖NVLink或者PCIe带宽。对于量化权重llama.cpp有一套自己的MMQ计算路径全称是MatMul Quantized专门处理不同bit宽度的量化矩阵。在V100上Q4_K_M这种4bit权重命中MMQ后并不是简单反量化回FP16再算而是对量化后的小整数块做矩阵运算再在输出阶段做局部还原。这条路径在带宽受限的场景下非常占优也是为什么llama.cpp单流生成常常比vLLM还快。4.3 Q4_K_M的精度损失控制很多人一提4bit就觉得模型智商掉了好几档但实测下来还好。我的做法是用同一组中文知识问答和代码题集分别跑FP16、FP8、Q4_K_MQ4_K_M相比FP16下降大约0.8到2个百分点FP8相比FP16只下降0.3到0.8个百分点。换成对话体验用户基本感知不到Q4_K_M有“变傻”反而因为上下文能拉长综合使用体验更好。如果任务特别敏感比如JSON Schema严格校验、数学推导这类精细任务Q4_K_M偶尔会多出现一点格式错误或漏符号。我也不是拿它硬顶所有场景一般涉及到强逻辑任务时我会退回FP8或者启用更高精度的Q5_K_M。5. 逐项实测对比5.1 模型体积和显存水位在Qwen3-8B上FP8权重目录的实际大小约8.6GBQ4_K_M的GGUF文件约4.9GB。vLLM加载后因为要预留KV Cache池每张卡实际占用会到11GB到13GB这是它的工作方式决定了llama.cpp在-ngl 99下的每卡占用大概是5GB到7GB余量更大。观测项目vLLM FP8 (W8A16)llama.cpp Q4_K_M模型文件大小约8.6GB约4.9GB双卡加载后显存水位每卡11~13GB每卡5~7GB16K上下文可用余量偏紧充足32K上下文需要调低并发可以跑但生成速度下降明显5.2 单并发与多并发吞吐单并发是llama.cpp的主场。它的CPU/GPU协同和MMQ路径很纯熟Qwen3-8B这种模型在V100上单路生成能跑到26到34 token/s而vLLM单路只有16到22 token/s。如果只是自己开个客户端聊天llama.cpp的流畅度明显更好。但把并发拉到8路甚至32路后情况就反过来了。vLLM的调度器、分页KV和连续批处理都为了高并发设计8路并发时总吞吐能做到110 token/s上下32路还能维持在140到160 token/s量级llama.cpp虽然也有并行槽位但批处理效率和对老GPU算子的优化都不如vLLM极致8路并发总吞吐只有50到70 token/s32路排队加剧后甚至会更低。并发数vLLM FP8 总吞吐llama.cpp Q4_K_M 总吞吐116~22 token/s26~34 token/s8100~120 token/s50~70 token/s32140~160 token/s60~80 token/s这个现象特别有意思模型越小、量化越狠反而越容易让llama.cpp在低并发下显得快但服务场景一上并发vLLM的架构优势才会真正显现。5.3 首Token延迟与排队表现首Token延迟通常是用户对“快不快”的第一感受。8并发时vLLM的TTFT在0.8到1.5秒之间llama.cpp则在1.5到3秒之间prompt越长差距越明显。这是因为vLLM的prefill阶段会把一批请求的prompt并行处理而llama.cpp在多个槽位里做prefill时卡间层传递和批处理调度的调度成本更高。到了32并发llama.cpp的排队延迟会快速上升部分请求TTFT会间歇性跳到5秒以上用户体感就是“卡了一下才出结果”。vLLM也会涨但会稳定得多。如果你要做对外服务这点基本可以直接决定选谁。5.4 生成质量与量化误差量化误差的宏观结论我在前面已经说过FP8损失很小Q4_K_M则在可控范围。真正有意思的是两种量化对模型行为的影响不同FP8更“保真”长回答的逻辑一致性更好Q4_K_M则更“压缩”偶尔在重复名词或复杂嵌套结构上出现轻微变形。我的最终建议是能放得下FP8就优先FP8需要极限压显存就上Q4_K_M。在2×V100这种配置下FP8和Q4_K_M之间不只差3GB它们决定了你到底是跑16K还是32K上下文、到底是开8并发还是开20并发。先想清楚用途再决定量化档位。6. 部署选型与实操中的坑6.1 什么样的人适合用vLLM这条链路如果你的目标是多人同时用的聊天机器人、Agent API、内部知识库或者要接Chatbox这类客户端做水平扩展那么首选vLLM。vLLM把EngineCore、Scheduler、Executor这些模块拆得很细请求进来后先调度再按KV块分配最终交由执行器跑模型这种设计天然为高并发服务。实际部署时我还会限制--max-model-len和--gpu-memory-utilization这两个参数。默认配置在V100上很容易OOM我之前有一次忘记设前者模型加载完直接报内存不足。双卡环境千万别忘了--tensor-parallel-size 2否则权重会重复载入两张卡能跑的上下文直接缩水。另外vLLM官方Docker镜像里一般不会带模型权重vllm/vllm-openai这个镜像只包含推理框架和依赖。很多人第一次用的时候以为pull下来就能直接跑某模型实际还要自己挂载模型目录docker run -d \ --gpus all \ -v /models:/models \ -p 8000:8000 \ vllm/vllm-openai:0.6.4 \ --model /models/Qwen3-8B-Instruct-fp8 \ --tensor-parallel-size 2 \ --max-model-len 163846.2 llama.cpp更适合的落点llama.cpp最适合的是个人使用、临时验证、离线批量生成以及跑嵌入式模型。它的部署成本极低一个二进制文件就能起服务连docker和Python环境都不用搭。如果你只是想在自己电脑上快速试试一个量化模型或者拿它跑本地小模型llama.cpp会是更轻的选择。它还有个隐性优势可移植性强。llama.cpp能编译到桌面端、开发板、甚至Android上Q4_K_M几乎是这些弱设备上的标配格式。V100上跑完的量化和行为测试拿到别的设备上往往也能复现这对做边缘部署很有参考价值。6.3 这台2×V100的驱动、ECC与容器注意事项V100这种数据中心卡在驱动侧有几个必须注意的地方。首先是ECC老HBM卡的显存模块多多少少有点历史问题如果遇到随机CUDA error先用这个命令查一下ECC错误计数nvidia-smi -q -d ECC看到Single Bit ECC Errors计数不断上涨说明显存有硬件级问题优先处理硬件不要继续压测。其次如果你在Windows上用V100做CUDA开发记得检查GPU运行在TCC模式还是WDDM模式。TCC模式下GPU不做桌面输出但延迟更低、更稳定适合计算WDDM则偏向显示和图形。用nvidia-smi -g 0 -dm 1可以切换到TCC切完重启生效。驱动版本方面我建议装Data Center Driver分支而不是普通GeForce驱动。V100跑vLLM和llama.cpp这种CUDA负载时Data Center驱动在某些IO路径和容器透传上更稳。显卡坞场景也一样别只看“能点亮屏幕”重点看nvidia-smi能不能完整识别算力和ECC属性。6.4 老平台上的版本选择陷阱vLLM社区迭代非常快但对V100这种老卡不是越新越好。我试过在同一个模型上直接升级到较新的vLLM版本结果预热时间变长、部分算子走了新的调度逻辑在同负载下吞吐反而掉了。后来我固定在0.6.x这条相对稳定的主模型服务线路上嵌入类模型则用轻量的v0.27.1镜像单独起一个进程互不干扰整体稳定性明显提升。如果你也想跑不同模型建议把不同版本拆成不同容器用端口隔离。比如主对话模型用vLLM起在8000端口嵌入模型用旧镜像起在8001端口Chatbox客户端里分别配置两个OpenAI兼容地址就行。别为了一时方便把所有模型塞进同一个新版本vLLM进程里旧卡的兼容性问题会让你排查到怀疑人生。在我这一轮对比做完后我自己的结论其实很简单对外服务、高并发、多人使用直接走vLLM FP8KV Cache能留多少算多少个人调试、快速验证、低并发体验llama.cpp Q4_K_M反而是更舒服的选择。两台引擎我都留在机器上平时看哪个场景合适就用哪个并不指望一套方案包打天下。最后再分享一个小技巧在V100这类不支持BF16和FP8原生的卡上改任何部署参数前先跑一次最小化启动把显存占用、模型加载时间、API连通性确认好再上并发压测。我踩过很多次坑都是因为在一个看似无关的参数上多写了一个--dtype bfloat16或--kv-cache-dtype结果整个容器起不来日志又不太容易一眼看出问题。先把基线打稳再做量化对比和调优这是老平台部署最稳妥的顺序。