Benchmark Report

Qwen3.8-27B 在单张 RTX 5090 上:
llama.cpp 与 vLLM 的实测对比

同一块 32GB 显卡、同一个模型、同一套测量口径。结论不是「谁更好」,而是两条曲线在单流与并发上完全相反地分岔。

作者 艾森 / Eisen 测试日期 2026-08-19 模型 Qwen3.8-27B · NVFP4 量化
单个 HTML 文件 · 离线可读 · 含全部图表与样式
llama.cpp · 单流
105.5tok/s
5 个独立实例,离散 0.6%
vLLM · 单流
57.0tok/s
4 个独立实例,离散 0.3%
单流 · llama.cpp 领先
1.85×
换引擎 1.39× × MTP 1.33×
并发 32 · vLLM 领先
14.7×
1058 vs 72 tok/s 聚合吞吐

最后两格方向相反——这是本报告的核心结论,也是「两个引擎都留着」的理由。

测试平台

一台消费级台式机,非数据中心硬件

CPU
AMD Ryzen 9 9950X3D · 16 核 32 线程
主板
Gigabyte X870E AORUS MASTER
内存
96 GB(系统可见 94 GB)
GPU
NVIDIA GeForce RTX 5090 · 32,607 MiB GDDR7
Blackwell · sm_120 · 驱动 610.88 · 显存带宽 1.79 TB/s · 功耗上限 600 W
系统
Windows 11 专业版 10.0.26200
存储
模型与引擎均放独立 NVMe 数据盘,非系统盘
WSL2 分配
16 线程 / 64 GB 内存 / 20 GB swap(仅 vLLM 侧需要)

测试期间 GPU 温度稳定在 32 °C,全程零降频(HW/SW Thermal SlowdownPower Brake 均未触发),因此下列数字不含散热或功耗墙的影响。

可复制的配置

全部取值抓自运行中的机器

llama.cpp(单流最快)

构建
b10502 · Windows CUDA 13.3 官方预编译
Linux 侧无 CUDA 预编译包,需自行编译或走 Windows
模型文件
Qwen3.8-27B-NVFP4-MTP-LOW.gguf · 15.53 GB
MTP 草稿头烘在权重内,无需外挂 drafter
显存占用
约 30.2 GB / 32.6 GB(含 256K 上下文的 q8_0 KV cache)
冷启动
约 40 秒
--model            Qwen3.8-27B-NVFP4-MTP-LOW.gguf
--ctx-size         262144        # 256K;实测与 32K 同速,几乎免费
--flash-attn       on
--cache-type-k     q8_0          # KV 量化,256K 上下文的前提
--cache-type-v     q8_0
--n-gpu-layers     999
--spec-type        draft-mtp     # MTP 投机解码
--spec-draft-n-max 2             # 实测最优(模型卡推荐的 6 是最差档之一)
--spec-draft-p-min 0.75

vLLM(并发最快)

版本
vLLM 0.27.1 · torch 2.13.0+cu132 · flashinfer 0.6.16.post3 · Python 3.13
运行环境
WSL2 Ubuntu 24.04(Windows 主机)· 权重放 ext4,比跨文件系统加载快 6.6×
冷启动
约 165 秒
vllm serve <model-dir> \
    --max-model-len 32768 \
    --gpu-memory-utilization 0.88 \
    --max-num-seqs 32              # 受 Gated DeltaNet 的 Mamba cache 块数硬约束
    --limit-mm-per-prompt '{"image":0,"video":0}'

测量口径

先说怎么测的,数字才有意义

两个引擎用完全相同的基准脚本与判据,唯一差别是 endpoint。单流数字均为:

并发表格用另一口径:端到端聚合吞吐(含首 token 延迟与排队),两个引擎同样一致,因此可横比。

单流解码

交互式使用的决定性指标

引擎 / 配置decode tok/s 复现GPU 利用率功耗显存带宽
llama.cpp · MTP n=2
105.5
离散 0.6%
5 实例
73%335 W45%
llama.cpp · MTP n=4
96.5
5 实例 95.0–99.8 73%312 W37%
llama.cpp · MTP n=5
95.1
单次 71%315 W40%
llama.cpp · MTP n=3
92.6
单次 72%312 W40%
llama.cpp · 不开 MTP
79.2
单次 96%409 W64%
llama.cpp · MTP n=6 (模型卡推荐值)
63.6
单次 80%251 W28%
vLLM · 无 MTP
57.0
离散 0.3%
4 实例
98%335 W72%
vLLM · MTP n=1
46.7–73.4
不可复现
9 实例,3 次低于基线
88–92%227–299 W38–56%
收益拆解

换引擎贡献 1.39×(79.2 vs 57.0);MTP 再叠 1.33×(105.5 vs 79.2)。值得注意的是:同一个 MTP 功能,在 vLLM 里跨实例乱跳 46.7–73.4,在 llama.cpp 里离散仅 0.6%。选型时,可复现性比峰值更重要

别照抄模型卡

模型卡推荐 --spec-draft-n-max 6,在这块卡上是最差档之一(63.6,比完全不开 MTP 还慢 20%)。投机深度必须在自己的硬件上扫一遍。

并发吞吐

结论与单流完全相反

并发llama.cpp 聚合 vLLM 聚合vLLM 领先
1
61.5
54.9
0.89×
2
72.4
4
73.4
191.2
2.6×
8
72.9
398.6
5.5×
16
72.2
671.9
9.3×
32 (已平台化在 ~72)
1057.8
14.7×

llama.cpp 的聚合吞吐从并发 2 起就压在 ~73 tok/s 不再增长,vLLM 则近乎线性扩展到并发 32。这是设计取向的差异:vLLM 为批处理吞吐而生,llama.cpp 为单流延迟而生。

负面结果:调参救不了

为公平起见把 llama.cpp 的并行槽位加到 --parallel 16 再测,结果反而崩塌:单流 6.5 tok/s、聚合 8.5 tok/s——比默认配置差 12 倍(日志显示 kv_unified=false,每槽位仅剩 16K 上下文)。不要试图把 llama.cpp 调成并发引擎。

屋顶线对照

一个不存在的优化目标

起初的目标是官方标称的 125.9 tok/s,据此以为 vLLM 的 57 有一倍差距可追。这个目标不成立——官方原文注明该数字测于 1× B200、128 并发

平台显存带宽屋顶线上限实测硬件利用率
官方参考基准 · B200 @ batch 1 ~8 TB/s~570 tok/s133.723%
本次测试 · RTX 5090 @ 单流 1.79 TB/s~124 tok/s57.046%

按每 token 读一遍约 14 GB 权重估算,这台机器对硬件的利用率是那份参考基准的两倍。GPU 实测 98% 利用率、335 W(上限 600 W)、32 °C、零降频——vLLM 侧没有 backend 可调,也没有空转可捡。后来的 1.85× 全部来自换引擎与 MTP,与「调后端」无关。

佐证:两个平台的聚合吞吐比值在并发 1 / 8 / 32 三档恒定在 38–42%。恒定比值 = 纯硬件差距;若是配置问题,比值会随 batch 变化。

其他实测

延迟、思考模式、输出质量

首 token 延迟——llama.cpp 的代价

引擎TTFT影响
vLLM(32K 上下文)30 – 45 ms极短问答也无感
llama.cpp(256K 上下文)348 – 553 ms约 10 倍。长输出无感,一问一答式短交互会觉出来

关闭 thinking——与 tok/s 无关的另一条提速路

不是跑得更快,而是少生成 token。传 {"chat_template_kwargs":{"enable_thinking":false}}

任务thinking 开thinking 关提速
一句话解释 TCP 三次握手1.73 s / 48 tok1.65 s / 42 tok
翻译一句话4.67 s / 133 tok0.30 s / 11 tok15.6×
列 3 个内存泄漏原因2.32 s / 75 tok1.52 s / 82 tok1.5×
合计8.7 s3.5 s2.5×

翻译、格式化、抽取这类无需推理的任务收益最大;需要推理的题目照常开着。

输出质量抽查

探针结果
TCP 三次握手(中文一句话)正确、简洁 pass
中译英与 vLLM 输出逐字相同 pass
9.11 与 9.9 谁大9.9,正确 pass 经典陷阱题

该 GGUF 的 LOW 档指 LM head / embedding / MTP 头这 10 个「额外张量」的精度;448 个骨干张量在各档间字节相同。抽查未见质量退化。

六个说谎的探针

本报告最值钱的部分

测试过程中有六次,探针给出了自洽但错误的答案。每一次若就此收工,得到的都是错结论。左栏是探针说的,右栏是实际。

探针说

流式分片计数 = 输出 token 数,得 47.3 tok/s

实际

256 个 token 只收到 227 个非空分片,低估 18%。必须取服务端 usage.completion_tokens

探针说

开启 MTP 后 73.4 tok/s,+29% 免费提速

实际

换 9 个实例后落在 46.7–73.4,其中 3 次低于基线。单点对比在漂移条件下无意义,必须交错 A/B

探针说

整台机器变慢了(同配置从 73 掉到 47)

实际

同期另一臂的基线四次都是 57.0 纹丝不动。漂的是那条代码路径,不是机器——靠交错 A/B 才分辨出来

探针说

下载 5.66 MB/s,46 分钟能拉完 15.5 GB

实际

那是 20 MB 突发测速。持续传输仅 0.06 MB/s(约 72 小时)。测可行性必须测持续速率

探针说

某投机参数启动失败,判为「不支持」

实际

实例间只等了 4 秒,上一个的显存还没释放。给足释放时间后正常启动。切换必须轮询显存

探针说

文件写入成功——回显打印了 wrote (912 bytes)

实际

路径被 shell 转义吃掉,写到了别处。回显是脚本自己 echo 的字符串,不是写盘证据

通用判据

做任何「卡住 / 不通 / 不支持 / 已成功」的判断前,先问一句:我这个探针在什么情况下会给出假答案?假的肯定答案比假的否定答案更危险——它不会促使你继续查。

怎么选

两条曲线分岔之后的实用判据

选 llama.cpp

单人交互式使用。快 1.85×,256K 上下文几乎免费,冷启动仅 40 秒。代价是首 token 延迟约 10 倍,且并发不扩展。

选 vLLM

批量任务、多路并发、需要低首 token 延迟。并发 8 时快 5.5×,并发 32 时 14.7×。代价是单流慢、冷启动 165 秒。

照这套配置部署

硬件相同或相近的话,把下面这段整体交给 AI coding agent(Claude Code / Codex / Cursor 等)即可

为什么是提示词而不是脚本

这套东西横跨 Windows、WSL2、计划任务、防火墙四个面,脚本很难覆盖各人机器的差异;而其中真正会卡住人的是六个反直觉的坑——把它们写进提示词,agent 才不会重走一遍弯路。下面这段已包含全部判据。

部署提示词
我有一台 Windows 11 工作站:AMD 9950X3D / 96GB 内存 / NVIDIA RTX 5090 32GB
(Blackwell sm_120),数据盘有 40GB 以上空闲。

请帮我部署 Qwen3.8-27B 本地推理服务。目标:单流解码 100+ tok/s、256K 上下文、
OpenAI 兼容 API。

以下方案已在同款硬件上实测验证,请照此执行,**不要自行改动标 🔴 的取值**。

### 1. 引擎选 llama.cpp,不要用 vLLM
- 同一张卡上 vLLM 单流仅 57 tok/s,llama.cpp 可达 105 tok/s
- 🔴 下载 llama.cpp 官方 **Windows CUDA 预编译包**(Linux 侧没有 CUDA 预编译包)
  以及配套的 cudart 运行时包,解压到同一目录
- 只有在需要「多路并发批处理」时才该用 vLLM;单人交互式用 llama.cpp

### 2. 模型用带 MTP 头的 GGUF
- HuggingFace 仓库:esatapedico/Qwen3.8-27B-NVFP4-MTP-GGUF
- 文件:Qwen3.8-27B-NVFP4-MTP-LOW.gguf(约 15.5 GB)
- MTP 草稿头已烘在权重内,**不需要**另外准备 draft model

### 3. 启动参数
    --model            <模型路径>.gguf
    --ctx-size         262144
    --flash-attn       on
    --cache-type-k     q8_0
    --cache-type-v     q8_0
    --n-gpu-layers     999
    --host             127.0.0.1
    --port             8080
    --spec-type        draft-mtp
    --spec-draft-n-max 2          # 🔴 必须是 2
    --spec-draft-p-min 0.75

🔴 **`--spec-draft-n-max` 必须是 2。** 模型卡推荐的 6 在这块卡上只有 63 tok/s,
   比完全不开 MTP(79)还慢。实测 2=105 / 3=93 / 4=96 / 5=95 / 6=64。
🔴 **不要加 `--parallel`。** 加到 16 会让单流从 105 崩到 6.5 tok/s(差 12 倍)。
   llama.cpp 的并发吞吐从并发 2 起就不再增长,调它没有意义。

### 4. 进程托管:用「计划任务」,不要用 Start-Process
🔴 通过 SSH 或 Start-Process 拉起的 llama-server,**会随父会话退出而被杀**。
   正确做法是注册一个计划任务,让它在**前台**运行 llama-server(任务持有该进程),
   并把 ExecutionTimeLimit 设为 PT0S(无限),否则默认时限会把服务掐掉。
🔴 包装脚本里 **`$ErrorActionPreference` 不能设 `Stop`**。llama.cpp 把所有日志写
   stderr,PowerShell 在 Stop 下会把它当成终止性错误,服务打印第一行就被自己
   的包装脚本干掉。用 'Continue'。
- 建议把启动参数放进一个独立的 args.txt(一行一个),包装脚本读它再 splat 传入,
  这样改配置不需要重新注册任务。

### 5. 如果要从别的机器访问
🔴 **Windows 会自动为 llama-server 创建 Block 规则**(首次监听时没人点「允许」的
   默认后果,通常有 2 条)。而 **Windows 防火墙里 Block 优先于 Allow**——不先删掉
   它们,你加多少放行规则都不会生效,且现象是「端口不通」,极易误判成配置写错。
- llama-server 用 `--api-key-file <文件>` 加鉴权(比 `--api-key` 好:密钥不进
  命令行、不出现在进程列表)
- 防火墙放行规则要**按来源网段收窄**,不要对所有来源开放
- ⚠️ 注意 `/v1/models` 与 `/health` 不受密钥保护(llama.cpp 的设计),只有推理
  端点受保护

### 6. 验证时请遵守这些判据,否则会得到假结论
🔴 **测 tok/s 不要数流式分片**,要取服务端返回的 usage.completion_tokens。
   数分片会低估约 18%(256 个 token 只对应约 227 个非空分片)。
🔴 **不要用单次测量做横向对比。** 换配置要交错 A/B/A/B,否则任何时间漂移都会
   被误读成配置差异。同一配置至少跨 3 个独立实例复现再下结论。
🔴 **重启换配置时,要轮询等显存真正降下来再启动下一个实例**(等到 4 GB 以下)。
   只等固定几秒会因显存未释放而启动失败,而失败信息看起来很像「该参数不被支持」。
🔴 **不要把命令的回显当作执行成功的证据。** 要用独立命令复核实际状态
   (文件是否存在、进程是否在跑、端口是否在听)。

### 7. 完成后请告诉我
- 实测的单流 decode tok/s(按上面第 6 条的口径)
- 显存占用
- 服务重启后是否仍在运行(验证托管是否真的生效)

期望结果

指标参考值
单流 decode≈105 tok/s
显存占用≈30.2 / 32.6 GB
上下文262,144 token
冷启动≈40 秒
首 token 延迟350–550 ms

若你的显卡不是 32GB,把 --ctx-size 调小即可——实测 32K 与 256K 速度基本相同,上下文长度几乎不影响解码速率,只影响显存占用。