Qwen3.8-27B 在 2×V100 上的推理性能测试笔记
测试日期:2026-10-17
所有数字均来自本机实测。每项都标注了测量口径,服务端 slot print_timing 与客户端 wall clock 分开记录。
文末有「未验证项」,明确区分哪些是实测、哪些是推算。
1. 硬件
| 项目 |
值 |
来源 |
| GPU |
2 × Tesla V100-SXM2-16GB |
nvidia-smi |
| 架构 |
Volta, sm_70 |
同上 |
| 单卡显存 |
16384 MiB(驱动报告),llama.cpp 可用 16242 MiB |
同上 |
| 总可用显存 |
约 32.4 GB |
同上 |
| 显存带宽 |
HBM2, 900 GB/s/卡 → 双卡合计 1.8 TB/s(厂商规格) |
NVIDIA 规格 |
| PCIe 链路 |
Gen3 × 4(最大支持 ×16,实际只跑 ×4) |
nvidia-smi --query-gpu=pcie.* |
| NVLink |
6 条 link 全部 active,每条 25.781 GB/s |
nvidia-smi nvlink -s |
| P2P 能力 |
双向 True |
torch.cuda.can_device_access_peer |
| P2P 实测带宽 |
129–131 GB/s 单向 |
torch copy_ 128 MiB × 15 轮 |
| 系统内存 |
30.9 GB |
系统信息 |
| OS |
Windows |
— |
关于 PCIe ×4 的结论:对 decode 无影响。
tensor split 的每层 all-reduce 走 NVLink(实测 130 GB/s,是 PCIe ×4 的 3.94 GB/s 的 33 倍)。
PCIe ×4 只影响三件事:模型加载(实际瓶颈在磁盘)、--cache-ram 的 host 侧换页、CPU offload(那种配置会是灾难)。
2. 软件栈
| 项目 |
值 |
| 二进制 |
C:\Users\xxx\.unsloth\llama.cpp\build\bin\Release\llama-server.exe |
| 版本 |
0.4.0-dev (build 10909, commit 329b6160f) |
| 来源 |
unslothai/llama.cpp(fork,非上游 ggml-org) |
| Release tag |
b10909-mix-bea84f7(-mix- = 自家 patch 标记) |
| 构建 |
CUDA 12.8,cuda12-older bundle,supported_sms = 70,75,80,86,89 |
| 构建时间 |
2026-09-11 |
| 备注 |
含 src/models/qwen35.cpp 与 ggml/src/ggml-cuda/gated_delta_net.cu(327 行,单 kernel,无 tensor core 分支) |
运行环境要求:必须把 CUDA 12 运行时 DLL 目录加进 PATH,否则 --list-devices 返回 (none),
报 LLAMA_SPLIT_MODE_TENSOR needs >= 1 devices。
3. 被测模型
三个模型架构完全相同(model_type=qwen3_5),区别只在权重与量化构成。
| 代号 |
文件 |
大小 |
Q6_K |
Q5_K/Q4_K |
Q8_0 |
IQ4_XS/NL |
BF16 |
| Coder390 |
Qwen3.8-27B-Coder390-EfficientThink-Q6_K-MTP.gguf |
21.58 GiB |
18.59 GB (86.1%) |
– |
2.94 GB (13.6%,MTP 头) |
– |
0.04 GB |
| Q6alt |
Qwen3.8-27B-Q6_K.gguf( 官方) |
20.88 GiB |
20.87 GB (100%) |
– |
– |
– |
– |
| Q5 |
Qwen3.8-27B-UD-Q5_K_S.gguf(unsloth UD 动态量化) |
17.37 GiB |
4.26 GB (24.5%) |
9.56 + 1.83 GB |
0.16 GB |
1.30 + 0.19 GB |
– |
共同架构参数(从 GGUF 元数据读出,三者完全一致):
- 65 层(64 主干 + 1 MTP),
full_attention_interval = 4
- 48 层 GatedDeltaNet(线性注意力)+ 16 层 full attention
- hidden 5120,FFN 17408,Q heads 24 / KV heads 4,head_dim 256
- rope dim 64,freq_base 1e7,原生上下文 262144
- GDN 参数:conv_kernel 4,state_size 128,inner_size 6144,group_count 16,time_step_rank 48
关键推论:因为只有 16 层 full attention,KV cache 不随上下文线性膨胀。
200k 上下文、KV q4_0 下实测 KV 约占 4 GB —— 这是 32 GB 显存能装下 21.6 GB 权重的模型 + 200k 上下文的原因。
若按普通 Transformer 估算会误判为需要 50 GB+。
4. 测试方法(口径)
启动参数(除注明外全程一致)
-c 204800 --parallel 1
--gpu-layers 66 --split-mode tensor --fit off
--flash-attn on --cache-type-k q4_0 --cache-type-v q4_0
--batch-size 2048 --ubatch-size 512
--ctx-checkpoints 4 --cache-ram 8192
--jinja --no-mmproj-auto --load-mode none
--temp 1.1 --top-p 0.8 --top-k 20
decode 测速:单次请求生成 512 token,/v1/chat/completions 非流式。
- 服务端口径:llama.cpp 日志
slot print_timing ... eval time(纯 decode,排除 prompt eval)
- 客户端口径:HTTP 往返 wall clock(含首 token 与传输)
- 两者并列记录,结论优先采信服务端
prefill 测速:构造递增长度的 filler 文本,读服务端 prompt eval time。
并发测速:N 个请求用线程池同时发出,统计总 token ÷ 总墙钟时间。
测试 prompt 的重要缺陷:decode 用的是「按顺序数数字」,属于高度可预测的重复序列。
这会让 ngram 投机严重作弊(实测 mean accepted len 冲到 63,即整段模式命中),
因此 ngram 的数据不可用;MTP 数据只取 ngram 未命中的首轮请求。
5. 性能数据
5.1 加载与显存(204800 上下文 / KV q4_0 / tensor split)
| 模型 |
加载耗时 |
显存占用(GPU0 / GPU1) |
剩余 |
| Coder390 |
33.8 s(首次)、23 s(页缓存热) |
14727 / 14708 MiB |
约 1.5 GB/卡 |
| Q6alt |
40.9 s |
13398 / 13379 MiB |
约 2.8 GB/卡 |
| Q5 |
29.9 s |
14453 / 14453 MiB |
约 1.8 GB/卡 |
显存数值随测量时机波动(加载早期采样会低至 316 MiB),上表为就绪后稳定值。
5.2 decode 速度(512 token,单请求)
服务端 eval time 口径(tok/s,纯 decode)
| 投机模式 |
Coder390 Q6_K |
Q6alt Q6_K |
Q5 UD-Q5_K_S |
| 无投机 |
30–31 |
29–31 |
31.5 |
| draft-mtp |
68–70 |
68.5 |
66–69 |
| ngram-mod |
66.8–71.3 |
— |
32.1 / 127.2 ⚠️ |
| ngram + draft-mtp |
65–67 |
— |
67.6 / 118.5 ⚠️ |
⚠️ = ngram 命中整段重复模式导致数据虚高,不可用于对比。
客户端口径(含首 token):MTP 模式下为 62–70 tok/s,比服务端低 3–6 个点,属正常。
MTP 的关键指标:draft 接受率 98.96%–100%,mean accepted len 3.96–3.99(上限 4)。
三个模型 decode 速度几乎一致。差异远小于量化档位体积差(21.58 vs 17.37 GiB),
说明 decode 速度不由权重大小决定(见第 6 节)。
5.3 prefill 速度(tok/s,投机不参与 prompt eval,数据对各模式通用)
| 区间 |
Coder390 |
Q5 |
Q6alt |
| 0 → 8k |
553 |
620 |
620 |
| 8k → 32k |
503 |
567 |
未测 |
| 32k → 128k |
337 |
379 |
未测 |
| 131k → 196k |
229 |
231 |
未测 |
长上下文可用性验证:单请求送入 196,944 token 成功处理,显存稳定在 15038 MiB/卡,服务未崩溃。
⇒ 200k 上下文确认可用。
5.4 并发吞吐(Coder390,无投机,防止 ngram 干扰)
| 并发 |
总吞吐 |
折算带宽 (21.6 GB/token) |
占 1.8 TB/s |
GPU util |
每请求 |
| N=1 |
29.1 tok/s |
628 GB/s |
35% |
65–72% |
29.1 |
| N=4 |
78.1 tok/s |
1687 GB/s |
94% |
80–83% |
19.5 |
| N=8(4 slot 排队) |
75.7 tok/s |
— |
— |
— |
9.5 |
并发 4 时 -c 204800 被平分,每 slot 只有 51200 上下文(日志 n_ctx_slot = 51200)。
5.5 decode 期间的硬件采样
| 指标 |
单请求(MTP) |
4 并发(无投机) |
| GPU 利用率 |
65–72% |
80–83% |
| 显存带宽利用率(驱动报告) |
33–37% |
42–44% |
| 功耗 / TDP |
170–235 W / 300 W |
240–252 W / 300 W |
| SM 频率 |
1530 MHz(满频) |
1530 MHz |
6. 瓶颈分析
6.1 定量分解
用 MTP 的两组数据反解「每次 forward 的固定开销」:
无投机: F + 1m = 33.3 ms (30 tok/s,每 forward 1 token)
开 MTP: F + 4m = 60.6 ms (66 tok/s,每 forward 4 token)
⇒ m = 9.1 ms/token, F = 24.2 ms
每次 forward 有 24.2 ms 与 token 数无关的固定开销,占单 token 耗时(33.3 ms)的 73%。
6.2 两段瓶颈
第一段(单请求):GPU 并行度不足
- 只用 35% 带宽,GPU util 仅 65–72%,功耗只到 57–78% TDP
- 原因:65 层完全串行(第 n 层依赖 n-1);48 层 GDN 是递归层(state 依赖上一 token,token 维度不可并行);
batch=1/seq=1 时每层 kernel 太小,填不满 SM
- 决定性的反证:8k 与 200k 上下文速度完全一样(30 tok/s)⇒ 与 KV/attention 长度无关
第二段(饱和后):HBM 带宽
- N=4 达到 1687 GB/s ≈ 1.8 TB/s 的 94%,N=8 不再增长
- 78 tok/s 是这套硬件的硬顶
6.3 为什么 MTP 能 +115%
不是加速计算,是在单请求内制造 batch:一次 forward 验证 4 个 draft token,
把并行度从 35% 拉到 80–85%,固定开销被摊薄 4 倍。30 → 66 由此而来。
6.4 换低量化档为什么几乎没用(重要,容易误判)
减小权重只动 F 里那 12 ms 的权重读取部分:
| 档位 |
权重 |
权重读取(1.8TB/s) |
F |
MTP forward |
单请求 tok/s |
提升 |
| Q6_K |
21.6 GB |
12.0 ms |
24.2 |
60.6 ms |
66 |
— |
| Q4 |
18.3 GB |
10.2 ms |
22.4 |
58.8 ms |
68 |
+3% |
| Q3 |
16.1 GB |
8.95 ms |
21.2 |
57.6 ms |
70 |
+5% |
m = 9.1 ms(KV 读取 + attention + GDN state 更新 + 采样)与权重量无关,
F 里另外 12.2 ms 的固定开销(kernel launch / 跨卡同步 / 串行)也不变。
「Q4 ≈ 98 tok/s、Q3 ≈ 112 tok/s」是带宽硬顶,只有多并发时才够得着(Q4 并发预期 90–93)。
6.5 CPU 采样警告不是瓶颈
tensor split 下必现:
W set_sampler: backend sampling not supported with SPLIT_MODE_TENSOR; using CPU
W spec common_specu: backend offload failed for seq_id=0; using CPU sampler
量级估算:每 token logits 回传约 608 KB(vocab 151936 × fp32),走 PCIe ×4 ≈ 0.15 ms,
即使投机多采几份约 0.6 ms,占 33 ms 的 1~3%。
逻辑铁证:若 CPU 采样是瓶颈,「关掉投机」应更快;实测相反(30 vs 66)。
无法消除:唯一能让采样回 GPU 的 --split-mode layer 在此规模下必 OOM。
7. 实测无效 / 不可行的方案
| 方案 |
结果 |
--split-mode layer |
OOM:GPU1 只剩 28 MiB,分配 1002 MiB compute buffer 失败,首个请求崩溃 |
-ctk f16 -ctv q4_0 @200k |
OOM:KV 由 4 GB 涨到 8.2 GB |
-ctk q8_0 -ctv q4_0 @200k |
71.3 tok/s(噪声内),但显存 13558 → 15527 MiB,仅剩 716 MiB,不划算 |
| vLLM |
不可用:V100 不支持 bf16、不支持 FA2、Marlin/AWQ 需 sm_75+、27B FP16=54 GB 双卡装不下 |
CPU offload / -ngl 部分卸载 |
不可行:每 token 过 PCIe ×4(3.94 GB/s) |
上游与第三方优化
ggml-org/llama.cpp PR #20391(GDN kernel 改进):实测仅 1–2%
qr0n/p100-llama-cpp 补丁集:0009 delta-net fusion 每 token 少 480 次 kernel launch,+9.1%,输出 bit-identical;
但该仓库定位 P100 (sm_60),核心三项在 V100 上退化为空操作,V100 只能吃到约 9–10%,需自行编译。
8. 结论
- 200k 上下文在 32 GB 显存上可用,实测 196,944 token 单请求通过。原因是混合线性注意力,KV 只有约 4 GB。
- 三个模型的 decode 速度基本一致(单请求 MTP 66–70 tok/s),与量化档位/文件大小无关。
- MTP 是必需品:无投机 30,开 MTP 66–70(+115%)。日常必须开。
- 不能再往上提升了:78 tok/s 是双卡 HBM 1.8 TB/s 的物理硬顶;
单请求受并行度限制停在 66–70,已是该峰值的 80–85%。
- 换 Q4/Q3 对单请求只有 +3~5%,不值得(还要重下 18 GB + 掉质量)。
- PCIe ×4 与此无关,跨卡通信走 NVLink(实测 130 GB/s)。
- 想再快只有三条路:加并发(换吞吐不换延迟)、换层数更少的模型、等 GDN kernel 被优化。
9. 测量注意事项
- 速度一律以服务端
slot print_timing 的 eval time 为准;客户端 wall clock 只作参考,可能受其他实例干扰
- 起服务后必须确认 PID/端口属于本次实例;
TaskStop 后要 taskkill /F /IM llama-server.exe 清干净再起
- 报数前做物理检查:
tok/s × 权重体积 不得超过 DRAM 带宽上限,超了就说明数据有问题
- 别用重复序列测投机(ngram 会整段命中),MTP/ngram 数据只取首轮(ngram 未命中时)
10. 未验证项(不要当作结论)
- Q6alt 的 32k / 128k / 192k prefill(只测了 8k)
- MTP +
--parallel 4 的组合(78 是 none 模式下的数字,两者叠加未测)
- Q4 / Q3 档的实测速度(本文第 6.4 节是模型推算,非实测)
- 并发下的 GPU 利用率与带宽(单请求采样完整,并发采样只有一次)
- GDN 层与 full attention 层各自的耗时占比(需 Nsight Compute,本机未安装)
附:可复现脚本
| 文件 |
用途 |
|
|
|
serve.sh |
日常启动。`bash serve.sh [coder |
q6 |
q5] [mtp |
none],alias Qwen3.8-27B`,端口 8888 |
start_q6k.sh |
测速用,可控投机模式与 split 模式、可切三个模型 |
|
|
|
bench.py |
单请求 decode / prefill 测速 |
|
|
|
bench_par.py |
并发吞吐测速 |
|
|
|
gguf_types.py |
统计 GGUF 张量量化构成(需系统 Python 3.10 + numpy) |
|
|
|