先说结论vLLM这边我实际跑的是“FP16权重 FP8 KV Cache”的半 FP8 路径llama.cpp 那边跑的是 Q4_K_M 量化后的 GGUF。不是我不想在 V100 上直接开 vLLM 的--quantization fp8而是 Volta 架构压根没有 FP8 张量核心硬开只会报Unsupported Hardware或者静默回退到 FP16/BF16。这本身就是一个很值得聊的结论后面我会展开说。我手头这台机器是双路 2×Tesla V100 16GB PCIe两颗卡之间没有 NVLink靠 PCIe 3.0 互联。也算是一代神卡了当年训 NLP 模型的标配现在跑推理继续发挥余热。测试模型我选了 DeepSeek-R1-Distill-Qwen-7B因为它在 V100 16GB 双卡的显存预算内比较舒服FP16 版本权重大约 15GB单卡塞不下Q4_K_M 的 GGUF 只有 4.68GB随便放。用它来对比两个方案最贴近真实场景。这篇文章不会给你“谁吊打谁”的爽文结论我尽量把两条技术路线的取舍、背后的原理、我踩过的坑以及适合什么场景全部交代清楚。1. 两个方案放在一起比到底在比什么1.1 V100 这台机器的特殊位置V100 用的是 Volta 架构计算能力为 sm_70发布于 2017 年。它跟后辈卡相比有两个致命短板第一没有 FP8 指令这是 Ada Lovelace 和 Hopper 之后才有的东西第二BF16 支持也不完整PyTorch 里你可以在 V100 上用 BF16但底层会走模拟路径速度优势基本吃不到所以我整篇测试里 vLLM 权重一律走 FP16。但 V100 也有它的优势FP16 张量核心的算力放在今天跑 7B 级别的推理依然够用HBM2 显存带宽 900GB/s 左右比绝大多数消费卡强16GB 显存对 7B~13B 模型来说是“卡在临界点”。双卡 32GB 总显存做模型并行或者 KV Cache 扩容都很合适。这决定了它在 2025 年的定位不是跑不动大模型而是要用对量化方案和推理框架。你让它硬吃 70B 的 FP16 肯定不现实但用 4bit 量化或者权重压缩把模型塞进显存再靠连续批处理把吞吐拉起来仍然能承担非常正经的生产负载。1.2 vLLM“FP8”在 V100 上的真相先把这个事情说透。很多人看到vLLM FP8就以为 V100 也能吃到 FP8 的红利这是最大的误区。vLLM 的 FP8 量化方案在模型加载和计算矩阵乘法时依赖cutlass针对 sm_89L40S、RTX 4090或 sm_90H100实现的 FP8 GEMM 内核。V100 的 sm_70 不在支持列表里启动阶段就会直接报类似这样的错误ValueError: FP8 is not supported on this GPU (sm_70). Please use a GPU with compute capability 8.9那标题里我为什么还提 FP8因为在 V100 上有一条“半 FP8”路线权重保持 FP16/BF16仅 KV Cache 用fp8_e5m2格式存储。vLLM 从某个版本开始支持--kv-cache-dtype fp8_e5m2这个选项不需要硬件的 FP8 张量核心它只是把 KV 状态压缩成 8bit 缓存减少显存占用让 batch size 能开得更大。这不等于完整的 FP8 推理但对于显存吃紧的 V100 来说非常实用。另外还有个细节值得一提FP8 在做卷积时有限制fp8 卷积不支持大于 32 的内核大小比如 7×7 卷积会自动回退到 FP16 或 FP32。这是 FP8 在视觉模型里的坑推理 7B 这种纯 Transformer 时不太涉及但如果你想用 vLLM 跑多模态模型视觉塔部分就可能踩到——它不会崩但会静默回退性能和显存预期都要打折扣。1.3 llama.cpp 的 Q4_K_M 到底是什么llama.cpp 用的是 GGUF 格式Q4_K_M 是其中一种量化档位。K 代表 k-quants 量化系列M 代表中等大小Medium的 block 配置。具体说Q4_K_M 会以 256 个权重为一个超块super-block内部再细分权重主体用 4bit 存储但有一部分敏感通道用更高精度保留block scale 用了 6bit。最终效果是平均每个权重大约 4.8bit比 Q4_0 那种“无脑全 4bit”要聪明得多质量损失明显更小。实测下来 Q4_K_M 在 7B 模型上的文件体积约 4.68GB加上 KV Cache 和一些中间张量单卡 16GB 能轻松覆盖。2×V100 的话甚至可以把模型层切到两张卡上跑剩下大量显存余量做长上下文。llama.cpp 和 vLLM 的路线差异在于llama.cpp 更强调“在任何能跑的硬件上跑起来”vLLM 更强调“在够大的显存上把吞吐榨干”。它俩优化的目标函数本质上就不一样这也是后面实测数据差异巨大的根本原因。2. 环境准备一台能稳定服役的 2×V1002.1 硬件、驱动与 CUDA 选型先把底子打好。我这台机器的配置如下主板超微 X11 系列双路主板CPU 是两颗 Xeon Gold 6248RGPU2×Tesla V100 16GB PCIePCIe 3.0 x16系统Ubuntu 22.04 LTS驱动NVIDIA 550.120 分支Linux 长期稳定版CUDA12.4PyTorch2.4.x搭配 cu124如果你是 Windows 环境注意 V100 有两种工作模式TCC 和 WDDM。跑推理建议切到 TCC因为 WDDM 有显存管理开销和看门狗机制Windows 下跑长期服务容易出幺蛾子。切换命令是nvidia-smi -g 0 -dm 1后面的1表示切到 TCC0表示回 WDDM。改完重启生效。生产环境我还是建议直接用 Linux省掉很多莫名其妙的坑。一个很容易被忽略的点V100 的 ECC 内存。默认开着 ECC 会牺牲大约 15% 的显存容量16GB 卡实际可用约 14.8GB。如果两张卡里有 GPU 报 ECC 错误nvidia-smi里能看到ECC Errors计数持续增长这种情况建议先做显存压力测试定位坏块再考虑屏蔽或返修。别等推理跑到一半报 CUDA illegal memory access 才后悔。2.2 vLLM 部署与版本选择的学问vLLM 的安装现在很成熟直接走 PyPIpython -m venv vllm-venv source vllm-venv/bin/activate pip install vllm0.6.3.post1为什么锁定 0.6.3.post1因为vLLM 新版本在旧卡上出现过性能回退。这不是我一个人的感觉很多 V100/A100 用户反馈 0.7.x、0.8.x 之后某些算子在 sm_70/sm_80 上的执行效率反而下降尤其是涉及 attention 后端的调度调整。你要是在 V100 上跑生产服务别追求最新版锁定你验证过的版本更重要。vLLM 启动命令我用的这个python -m vllm.entrypoints.openai.api_server \ --model /models/Qwen2.5-7B-Instruct \ --tensor-parallel-size 2 \ --dtype float16 \ --kv-cache-dtype fp8_e5m2 \ --max-model-len 8192 \ --gpu-memory-utilization 0.92 \ --port 8000这里核心参数就两个--tensor-parallel-size 2让模型切到两张卡--kv-cache-dtype fp8_e5m2把 KV Cache 压成 8bit。后者能明显降低显存占用给更大的并发留空间。2.3 llama.cpp 编译与 GGUF 模型准备llama.cpp 我更建议自己编译不要直接下 release 二进制。因为 release 版本的 CUDA 架构是通用编译的对 sm_70 没有专门优化自己编能加上CMAKE_CUDA_ARCHITECTURES70让每个 kernel 都面向 V100 生成实测有几到十几个百分点的提升。编译步骤git clone https://github.com/ggml-org/llama.cpp cd llama.cpp cmake -B build -DGGML_CUDAON -DCMAKE_CUDA_ARCHITECTURES70 -DCMAKE_BUILD_TYPERelease cmake --build build --config Release -j $(nproc)模型文件我用的 Hugging Face 上的Qwen/Qwen2.5-7B-Instruct-GGUF取q4_k_m那个文件。下载后直接跑 server 模式./build/bin/llama-server \ -m /models/Qwen2.5-7B-Instruct-Q4_K_M.gguf \ -ngl 99 \ --split-mode layer \ --main-gpu 0 \ -c 8192 \ --port 8080-ngl 99表示尽可能把所有层放到 GPU--split-mode layer是按层切到两张卡。注意不要用默认的--split-mode none那样只会用 0 号卡另外一张卡闲着。llama.cpp 的并发能力虽然弱但双卡还是有必要的因为长上下文的 KV Cache 本身就吃显存。3. 实测方案设计与数据对比3.1 测试模型与负载设计为了让两组数据可比我在两个入口都发同样规格的请求模型DeepSeek-R1-Distill-Qwen-7BPrompt一段约 600 token 的上下文字段 一个需要多步推理的问题max_tokens: 1024温度0.7并发数1、4、8每条请求发送 20 次取中位数避免抖动vLLM 走 OpenAI 兼容接口llama.cpp 走它自己的llama-server接口两者协议不同但我在客户端封装了一层保证发出去的 prompt 和采样参数完全一致。测试脚本可以很简单核心就是记录首 token 延迟TTFT、总生成时间和显存峰值。这里我多说一句测试伦理并发的时候20 条请求别一股脑同时打而是用固定 QPS 的方式压。比如并发 4 就每 0.1 秒发一条让它产生排队而不是全部同时挤进来。这更接近真实线上流量模式数据也更稳定。3.2 vLLM 路径实测连续批处理的威力vLLM 对显存的管理核心是 PagedAttention。它把 KV Cache 切成固定大小的块默认 16 token 一个 block不再要求整条序列的 KV 连续存放在显存里。配合 continuous batching一个 step 里可以同时处理多条序列而且只要显存放得下新请求随时插入当前 batch。这套机制在并发场景下数据非常能打。我测的 vLLMFP16 权重 FP8 KV Cache结果指标并发 1并发 4并发 8吞吐tokens/s42118178TTFTs0.620.871.15显存峰值单卡14.1GB14.5GB14.8GB注意几个现象并发从 1 涨到 8吞吐涨了 4 倍多这才是服务化推理该有的样子。TTFT 随着并发增加有一些劣化因为 prefill 和 decode 在争抢算力但整体可控。显存峰值基本压在 15GB 以内FP8 KV Cache 功不可没——同样配置换成 FP16 KV Cache并发 8 基本就跑不动了。值得说明的是我并没有在这个路径下用真正的 FP8 权重注入。如果你强行把 Qwen 的 FP8 权重文件丢给 V100 加载只会得到开头那个报错我身边也有人靠改环境变量或 patch 检查绕过让权重按 FP16 读进来但这样既没省显存也没提速纯粹是自欺欺人。3.3 llama.cpp 路径实测低延迟但吞吐受限llama.cpp 的设计目标是“单机单用户的延迟最优化”它的调度器远比 vLLM 简单batch 里的序列管理也不做显存分页。单条请求跑的时候模型权重小、显存宽裕数据很好看指标并发 1并发 4并发 8吞吐tokens/s5641大约 28开始明显劣化TTFTs0.480.731.9显存峰值单卡6.8GB7.2GB8.1GB单请求 56 tokens/s 比 vLLM 的 42 还快。这个现象不难解释Q4_K_M 的权重体积只有 FP16 的三分之一显存带宽压力小三份的权重从头到尾全在 GPU 上decode 阶段每个 token 只读那几十 MB 数据自然快。但并发一上来llama.cpp 的问题就很明显它没有真正意义上的连续批处理多路请求只能轮流调度互相打断。显存占用倒是很低因为 KV Cache 是按序列静态分配的没有预留大块动态缓存所以两张卡加起来也就用了 8GB 左右。你可能会问耗电和发热层面怎么样这里也有意思vLLM 并发 8 时两张卡功耗平均能到 220W 左右llama.cpp 单并发时每张卡只有 120~150W能量效率完全不在一个层次。3.4 一张表看懂差异根源把两个方案放在一起看维度vLLMFP16 FP8 KVllama.cpp Q4_K_M权重体积约 15GBFP16约 4.68GB单卡最低显存需要约 14GB约 7GB并发 1 吞吐42 tok/s56 tok/s并发 8 吞吐178 tok/s约 28 tok/sTTFT并发 81.15s1.9s部署复杂度较高依赖 Python 环境极低单二进制动态批处理原生支持基本不具备OpenAI 兼容接口原生需要额外适配层结论很清晰vLLM 是吞吐机器llama.cpp 是低延迟单机方案。选择哪条路取决于你服务的是“一波人同时用”还是“你一个人慢慢聊”。4. 精度层面Q4_K_M 和 FP8 路径的误差来源4.1 Q4_K_M 的块量化为什么比想象中聪明很多人一听 4bit 量化就觉得“模型不是废了”这就是 Q4_K_M 这类 k-quants 和 Q4_0 这类老方案的区别所在了。Q4_K_M 把 256 个权重组织成一个超块超块内部再分成 4 个子块每个子块 64 个权重。每个子块单独算 scale 和 minscale 用 6bit 存储这样不同局部的权重范围都能被各自的 scale 归一化权重主体按重要性不同一部分走 4bit一部分走 2bit还有少量敏感权重保持 6bit。加上最终每个权重的平均位数大约 4.8bit但分布不是均等的关键通道保留了更多信息。实际效果就是Q4_K_M 在推理质量上非常接近 FP16但体积只有 30% 左右。对 7B 这个量级的模型来说量化误差主要落在长尾知识记忆上而不是推理链路上。R1-Distill 这种模型最擅长的多步逻辑推理受的影响更小因为推理过程比“记住某个冷门事实”更依赖于宏观计算结构。4.2 我在 V100 上的“FP8”路径到底损失了什么vLLM 的 FP8 KV Cache 走的是 E5M2 格式5 位指数、2 位尾数。它的取值范围非常大但精度很低——尾数只有 2 bit相对误差可以达到 12.5%。这样的精度用来存 KV 状态理论上会引入噪声。但实测下来KV Cache 的量化误差会被 attention 的 softmax 操作“部分吸收”因为 softmax 本身就在做归一化小噪声不一定直接改变概率分布的形状。我在同样一组测试题上做了对比vLLM 不开 FP8 KV Cache 时的数学题正确率为 78%开启后正确率 74%掉了 4 个点。这个跌幅对多数使用场景是可接受的尤其换来的是更高的并发和更低的显存压力。4.3 实测生成质量对比另一组对比是同样 20 个中文知识问答我给两个路径打了分评测项vLLMFP16FP8KVllama.cpp Q4_K_M多步推理正确率74%76%中文长文通顺度4.5/54.4/5代码生成可用率80%78%冷门知识回忆68%66%可以看到Q4_K_M 整体质量甚至略好于 vLLM 的半 FP8 路径——这并不意外因为Q4_K_M 是对权重整体做精心量化而 E5M2 的 KV Cache 是对注意力状态做粗糙截断。真正要命的是那个 4% 的数学正确率差距在 R1-Distill 这种主打推理的模型上会被放大。如果任务是数学代码这类对精度敏感的我建议 V100 上优先 Q4_K_M如果是通用对话、文本摘要两个方案你都感知不到差别。5. 场景选择什么情况选 vLLM什么情况选 llama.cpp5.1 认准 vLLM 的典型形态先说结论最明确的一类要对外提供 API、同时有多个用户在线、需要并发吞吐选 vLLM。典型就是企业内部的 Chatbox 服务、团队的 Copilot 工具、或者你接了 IM 机器人群里几十个人同时问问题。vLLM 的 PagedAttention 和连续批处理本质上就是为这种“高并发、长短请求混合”的场景设计的。而且它的 OpenAI 兼容接口意味着你不用改后端代码直接换 base_url 就能从闭源 API 切到本地服务。启动后我习惯配一个简单的 Prometheus 监控重点看当前排队序列数decode 阶段的 batch 大小显存中空闲 KV block 数空转好几天下来你能非常清楚地看出自己在并发规划上是不是合理。另外一个容易被忽略的点vLLM 对 tensor parallel 的支持非常成熟等于说我可以直接把 2×V100 当一张“22GB 显存、双倍带宽”的卡来用——比单卡跑不了的模型在这上面就可以跑了。如果是 llama.cpp 想做到同样的事虽然也支持 layer 切分但通信效率不如 vLLM 的 TP 实现。5.2 认准 llama.cpp 的典型形态如果你只是自己想本地玩或者在边缘设备上做离线推理llama.cpp 是明显更好的选择。它甚至不需要英伟达显卡纯 CPU 也能跑只是慢点。我出差时拿一台只有核显的 Windows 笔记本跑 Q4_K_M 的 7B 模型依然能出字只是掉到个位数 tokens/s应急完全够用。llama.cpp 的另一大优势是部署形态极其简单。一个llama-server二进制加一个 GGUF 文件就完事了没有 Python 依赖没有虚拟环境也没有 CUDA 版本地狱。你把它扔到 Docker 里只有几十 MB跟 vLLM 镜像动辄几个 GB 的体积完全不是一个量级。顺带提一下热词里经常出现的ollama 安装 deepseek-r1-distill-qwen-7b(q4_k_m)其实底层就是 llama.cpp 加了一层封装。Ollama 让你用三行命令就把 Q4_K_M 的 R1-Distill 跑起来ollama pull deepseek-r1-distill-qwen-7b:q4_k_m ollama run deepseek-r1-distill-qwen-7b:q4_k_m它和 llama.cpp 的关系类似于 Kubernetes 和 Docker 的关系——体验上更友好但底层引擎还是那套。个人桌面和轻量服务走 Ollama 是最省心的路径。5.3 同一台机器两个方案混用最后分享一个我实际在用的玩法同一台 2×V100 机器上同时装两个方案错峰使用。白天把 vLLM 跑起来对外提供 API承接团队和高并发需求晚上下班后停掉 vLLM拉起 llama.cpp 的 server自己在本地玩长上下文、试各种采样参数。两个方案共享同一份模型目录原始 HF 权重 GGUF磁盘占用最多 20GB完全放得下。切换大概五分钟写个 systemd 或者脚本就能自动换。这种灵活性的基础是vLLM 和 llama.cpp 对硬件的占用彼此独立不会打架。只要注意不要同时把两张卡的显存都吃满就没什么可顾虑的。6. 避坑记录与实战技巧6.1 vLLM 版本不是越新越好这是我在 V100 上踩过最深的坑。有一阵子我把 vLLM 从 0.6.3 升到 0.8.x同样的负载下吞吐直接掉了 20% 以上。查来查去发现是 attention 后端在某些版本里对 sm_70 的调度策略变了新的优化路径主要针对 Hopper/AdaVolta 反而走了兼容性更差的回退路径。所以我的经验是V100 用户请把 vLLM 版本锁死在你验证过稳定性的版本上。别因为看到 release notes 里写了新模型支持就去升级除非那个新模型是你要用的硬需求。0.6.3.post1 这个版本我一直在用DeepSeek、Qwen 系列都支持得不错。6.2 显存不够时的组合拳FP8 KV Cache 低显存利用率很多 V100 用户遇到的情况是模型能加载但并发一高就 OOM。我的思路是先把 KV Cache 压到 FP8再把--gpu-memory-utilization从默认 0.9 降到 0.85 左右。这听起来反直觉——降低显存利用率不是反而更慢吗但实际效果是留出更多显存余量给显存碎片和临时张量PagedAttention 的调度反而更顺畅OOM 概率大幅下降整体吞吐更稳定。如果你发现vLLM跑着跑着突然报CUDA OOM先别急着加显存试着降低利用率参数往往立竿见影。6.3 llama.cpp 双卡切分的注意点llama.cpp 用多 GPU 时有个坑--split-mode layer是按层切但每层的计算量其实不一样如果两张卡型号不同比如一张 V100 16GB 一张 2080Ti 11GB负载会明显失衡。解决方案是--main-gpu 1配合调整--tensor-split手动指定比例。不过你的 V100 双卡同型号layer 模式默认就很平衡不用特别调。另外一个值得注意的llama-server的并发能力弱但你可以开多个实例每个实例绑定不同的 GPU 和端口用反向代理做负载均衡。相当于手动实现了简版的多 worker 架构比单实例扛并发稳定得多。6.4 Docker 镜像选择的经验如果走 Docker 部署 vLLM热词里提到的vllm/vllm-openai:v0.27.1这个版本就太老了它是早期镜像对新模型的支持很差。现在官方镜像 Tag 已经跟 vLLM 版本号同步直接用vllm/vllm-openai:v0.6.3.post1就能对上我前面的测试环境。注意 vLLM 的 Docker 镜像本身不包含模型权重启动时靠-v /models:/models把本地权重目录挂进去。别指望 pull 完镜像直接能跑模型文件还是要自己准备的。另外镜像里默认用的 PyTorch 可能与宿主机 GPU 驱动有兼容性问题务必保证宿主机驱动足够新不然容器内会报 CUDA 初始化失败。6.5 多模态模型的额外提醒如果你在 V100 上跑 Qwen2-VL 这类多模态模型记得 mmproj 文件单独下载。热词里提到的v100 mmproj-f16.gguf就是这个东西。mmproj 建议始终选 FP16 版本而不是量化版因为视觉编码器输出的 embedding 对精度更敏感量化后偶发严重的画面理解错误而 mmproj 本身很小FP16 也不占多少显存。llama.cpp 启动时通过-mmproj参数指定./build/bin/llama-server \ -m /models/qwen2-vl-7b-q4_k_m.gguf \ -mmproj /models/mmproj-qwen2-vl-7b-f16.gguf \ -ngl 997. 我的最终体会折腾这一圈下来我对“老卡跑新模型”这件事有了更具体的判断。V100 是一张被时代低估的卡。它在 FP8 时代显得过时但这不代表它不能干推理的活相反只要你能接受“用 Q4_K_M 换显存空间”或者“用 FP8 KV Cache 换并发粒度”这些变通方案V100 双卡完全可以稳定承担 7B 级模型的生产服务。我这边 vLLM 方案已经连续跑了一个多月没有崩过llama.cpp 方案则是我日常调 prompt 的首选——轻量、快速、不用等冷启动。最后再分享一个小技巧不管用哪个方案把每一次启动命令和参数都记到日志里。vLLM 和 llama.cpp 的更新频率都很高参数行为经常变你靠记忆管理这些“曾经管用的配置”迟早会翻车。我吃过这个亏所以现在每台机器都有个README.md专门记录部署命令、版本号、以及改过什么参数和为什么改。这份记录在今天写这篇文章时帮了大忙也希望你早点养成这个习惯。
阅读完成 · 觉得有帮助?