不对称V100组合,27B模型预填充1,376 tok/s
核心结论
我已经把一块16GB和一块32GB的不对称Tesla V100-PCIE组合起来(共48GB),在Proxmox/LXC环境中搭了一个本地推理实验室。目前最实用的配置是:使用最新的CUDA版llama.cpp,加上tensor split、Flash Attention、--numa distribute和大batch。在Qwen3.8 27B Q6_K_M模型上,我测得的性能为:2k上下文时预填充1,376.9 tok/s,4k时1,324.3 tok/s,16k时1,221.5 tok/s,解码39.9 tok/s。我还用Qwen3.8 Flash Next Q4 GGUF(约177B总参数、6B激活的MoE)在同样的两张卡上跑通了,解码约26 tok/s,预填充380 PP。
这里要说明一下:这并不意味着老V100能胜过现代GPU。只是在这些“一颗肾换一块GPU”的疯狂市场行情下,记录一下它们能做到什么。
硬件配置
| 项目 | 配置 |
|---|---|
| GPU 0 | NVIDIA Tesla V100-PCIE,16,144 MiB VRAM,compute capability 7.0 |
| GPU 1 | NVIDIA Tesla V100-PCIE,32,494 MiB VRAM,compute capability 7.0 |
| 服务器拓扑 | NUMA-aware 主机;基准测试使用 numactl --interleave=all 或 --numa distribute |
| 虚拟化 | Proxmox 配合 LXC 推理容器,隔离运行时 |
| CPU | 2 x Xeon E5-2696 v4 @ 2.20GHz |
| RAM | 512 GB DDR4 2400 ECC RDIMM |
| CUDA 栈 | CUDA 12.8;NVIDIA 驱动库按需挂载到 vLLM LXC 容器内 |
基准测试结果
| 负载 | 运行时/模型 | 最佳结果 | 背后的配置 |
|---|---|---|---|
| 最佳 27B 预填充 | mainline llama.cpp,Qwen3.8 27B Q6_K_M | 1,376.87 pp tok/s(2k) | Tensor split;主 GPU = 32GB V100;Flash Attention;Q8 KV;batch/ubatch 2048;20线程;NUMA distribute |
| 最佳 27B 解码 | mainline llama.cpp,Qwen3.8 27B Q6_K_M | 39.88 tg tok/s | 同上 |
| 27B @ 16k 上下文 | mainline llama.cpp,Qwen3.8 27B Q6_K_M | 1,221.49 pp tok/s | 同上 |
| 最佳 Q8 27B 预填充 | mainline llama.cpp,Qwen3.8 27B Q8_K_XL | 1,059.67 pp tok/s(2k) | Tensor split;长上下文运行使用 1:1.5 切分 |
| Q8 27B @ 64k | mainline llama.cpp,Qwen3.8 27B Q8_K_XL | 664.36 pp tok/s | 65,536 token 的 prompt;tensor split 1:1.5 |
| 最佳 ik_llama 27B | ik_llama.cpp,Qwen3.8 27B Q6_K_M | 670.61 pp tok/s(2k);35.43 tg tok/s | Graph split;1:1 切分;batch 更小 |
| 测试过最大的模型 | ik_llama.cpp,Qwen3.8 Flash Next Q4_K_XL | 380.67 pp tok/s(4k);26.29 tg tok/s | 103.68 GiB GGUF;176.94B 总参数 / 6B 激活 MoE;graph split 1:1.75 |
| ninfer-v100 | NInfer,Qwen3.8 27B NVFP4 | 927.54 pp tok/s(8,190) | 19.7 GiB 权重;INT8 KV;MTP |
调优启示
| Qwen3.8 27B Q6_K_M,mainline llama.cpp | pp2048 | tg128 |
|---|---|---|
| Layer split,79线程 | 981.79 | 22.88 |
| Tensor split,20线程 | 1,371.57 | 38.79 |
| Tensor split,主GPU = 32GB V100 | 1,376.87 | 39.88 |
| Tensor split 1:2 | 980.71 | 26.10 |
| 不切分 | 967.36 | 18.05 |
在这个不对称V100组合上,决定性的因素是 tensor split(而非 layer split),以及把 32GB V100 设为主 GPU。最适合日常使用的路径是:mainline llama.cpp + Qwen3.8 27B Q6 + tensor split + Flash Attention + Q8 KV + NUMA distribution。
实测命令
下面的命令是我用来复现 27B Q6 指标的 llama.cpp 基准测试。关键参数包括 -sm tensor(tensor split)、-mg 1(把主 GPU 设为设备 1,即 32GB V100)、-fa on(Flash Attention)、-b 2048 和 -ub 2048(batch / ubatch)、-t 20(线程数)、--numa distribute。
root@llama-cpp:~# numactl --interleave=all /opt/llama.cpp/build/bin/llama-bench -m /path/to/Qwen3.8-27B-UD-Q6_K_M.gguf -ngl 999 -sm tensor -mg 1 -fa on -b 2048 -ub 2048 -t 20 -p 512,2048,4096,16384 -n 128 -r 5 -lm none --numa distribute
输出片段:
ggml_cuda_init: found 2 CUDA devices (Total VRAM: 48638 MiB):
Device 0: Tesla V100-PCIE-16GB, compute capability 7.0, VMM: yes, VRAM: 16144 MiB
Device 1: Tesla V100-PCIE-32GB, compute capability 7.0, VMM: yes, VRAM: 32494 MiB
| model | size | params | backend | ngl | threads | n_ubatch | main_gpu | sm | fa | lm | test | t/s |
| ------------------------------ | ---------: | ---------: | ---------- | --: | ------: | -------: | ---------: | -----: | --: | ---------: | --------------: | -------------------: |
| qwen35 27B Q6_K | 21.49 GiB | 27.32 B | CUDA | 999 | 20 | 2048 | 1 | tensor | 1 | none | pp512 | 1156.94 ± 5.10 |
| qwen35 27B Q6_K | 21.49 GiB | 27.32 B | CUDA | 999 | 20 | 2048 | 1 | tensor | 1 | none | pp2048 | 1376.87 ± 4.59 |
| qwen35 27B Q6_K | 21.49 GiB | 27.32 B | CUDA | 999 | 20 | 2048 | 1 | tensor | 1 | none | pp4096 | 1324.34 ± 7.37 |
| qwen35 27B Q6_K | 21.49 GiB | 27.32 B | CUDA | 999 | 20 | 2048 | 1 | tensor | 1 | none | pp16384 | 1221.49 ± 3.87 |
| qwen35 27B Q6_K | 21.49 GiB | 27.32 B | CUDA | 999 | 20 | 2048 | 1 | tensor | 1 | none | tg128 | 39.88 ± 0.04 |
build: b29c606 (1)
要在同一对 V100 上跑 Qwen3.8 Flash Next Q4_K_XL(103.68 GiB GGUF),我用了 ik_llama 分支。下面的循环分别把主 GPU 设为 0 和 1 对比结果;-sm graph -ts 1/1.75 指定图切分及 1:1.75 的不均匀比例。
root@llama-cpp:~# for MAIN in 0 1; do
echo
echo "===== MAIN GPU ${MAIN} ====="
/usr/bin/numactl --interleave=all \\
/opt/ik_llama.cpp/build/bin/llama-bench \\
\-m /path/to/models/Qwen3.8-Flash-Next-UD-Q4_K_XL-00001-of-00004.gguf -ngl 999 -sm graph -ts 1/1.75 -mg "$MAIN" --fit 1 --fit-margin 1024 -fa 1 -p 4096,8192 -n 128 -b 2048 -ub 1024 -r 10 -o md
done 2>&1 | tee /root/bench-qwen38-main-gpu-final.md
输出中可以看到两个主 GPU 设置下的结果:
===== MAIN GPU 0 =====
ggml_cuda_init: found 2 CUDA devices:
Device 0: Tesla V100-PCIE-16GB, compute capability 7.0, VMM: yes, VRAM: 16144 MiB
Device 1: Tesla V100-PCIE-32GB, compute capability 7.0, VMM: yes, VRAM: 32494 MiB
| model | size | params | backend | ngl | n_ubatch | sm | ts | test | t/s |
| ------------------------------ | ---------: | ---------: | ---------- | --: | -------: | ----: | ------------ | ------------: | ---------------: |
| qwen4exp 125B.A6B Q4_K - Medium | 103.68 GiB | 176.94 B | CUDA | 999 | 1024 | graph | 1.00/1.75 | pp4096 | 356.20 ± 7.68 |
| qwen4exp 125B.A6B Q4_K - Medium | 103.68 GiB | 176.94 B | CUDA | 999 | 1024 | graph | 1.00/1.75 | pp8192 | 361.00 ± 4.72 |
| qwen4exp 125B.A6B Q4_K - Medium | 103.68 GiB | 176.94 B | CUDA | 999 | 1024 | graph | 1.00/1.75 | tg128 | 26.44 ± 0.11 |
===== MAIN GPU 1 =====
ggml_cuda_init: found 2 CUDA devices:
Device 0: Tesla V100-PCIE-16GB, compute capability 7.0, VMM: yes, VRAM: 16144 MiB
Device 1: Tesla V100-PCIE-32GB, compute capability 7.0, VMM: yes, VRAM: 32494 MiB
| model | size | params | backend | ngl | n_ubatch | main_gpu | sm | ts | test | t/s |
| ------------------------------ | ---------: | ---------: | ---------- | --: | -------: | ---------: | ----: | ------------ | ------------: | ---------------: |
| qwen4exp 125B.A6B Q4_K - Medium | 103.68 GiB | 176.94 B | CUDA | 999 | 1024 | 1 | graph | 1.00/1.75 | pp4096 | 380.67 ± 6.25 |
| qwen4exp 125B.A6B Q4_K - Medium | 103.68 GiB | 176.94 B | CUDA | 999 | 1024 | 1 | graph | 1.00/1.75 | pp8192 | 360.75 ± 5.05 |
| qwen4exp 125B.A6B Q4_K - Medium | 103.68 GiB | 176.94 B | CUDA | 999 | 1024 | 1 | graph | 1.00/1.75 | tg128 | 26.29 ± 0.08 |
build: 1a2a8604 (4878)
作者附言
我很想了解更多关于这些 flags 的细节,因为我在这个方面还是新手……感谢你的关注。
社区讨论
—— 高票评论(原帖共 13 条讨论)——
u/FullstackSensei:移除表格格式中的虚线……才能让表格正常显示。
u/BP041:--numa distribute 标志在多 GPU 设置中才是真正的技巧。对于不对称配对,我试过按显存比例切分层而不是原始 tensor split,这样能避免带宽瓶颈。你在 16GB 和 32GB 之间的切分比例是多少?
u/coronafire:感谢分享。那些预填充率看起来特别棒,比我的好多了,不过这可能也跟我的硬件有关。我有两张 V100 16GB,但是插在同一张 PCI 卡上,所以每条总线只有 x8。也有可能是我那个临时拼凑的基准脚本不够严谨。我好玩地把两个 NInfer fork 缝在一起,实现了双 V100 支持,比 llama.cpp 的性能提升明显。你可以在这里试用:https://github.com/andrewleech/ninfer-v100/blob/v100_dual/docs/PREFILL-BENCHMARK.md 我的设置里也有不对称切分,以便在本来配对的两张卡上为 MTP 或视觉模型腾出空间。
u/Traditional_Bell8153:我单张 V100 也有同样的 pp 和 tg 数值。
u/Sitkin_Marrel:从一行到下一行,tg/s 从 22.88 跳到 38.79,预填充也提升了将近 400。即使同时改两个变量,我也会逐字照抄那个配置。
u/Cloudsurfer_90:不错。不对称配对正是手动 tensor split 值得用的地方,因为 llama.cpp 默认会均匀切分,那样 32GB 卡会有一半空闲,而 16GB 卡成为瓶颈。把切分权重偏向大卡而不是 50/50,通常就是白拿性能的地方。另外,在没有 NVLink 的 PCIe V100 上,每个 token 的跨卡流量是真正的天花板,所以让每张卡保留连续的 layer 范围而不是交织排列很重要;KV cache 落在哪里和权重同样重要。一个被 offload 的 cache 如果不断在 PCIe 链路上颠簸,就会吃掉你发布的 decode tok/s。我好奇你最终用的切分比例是多少,以及是否把 KV cache 固定到了某一张卡上。在两块 V100 上跑 27B Q6 做到 40 decode,对这个硬件来说是很扎实的结果。