双 NVIDIA L20 上 Qwen3.8-27B-FP8 + MTP 调优测试报告

双 NVIDIA L20 上 Qwen3.8-27B-FP8 + MTP 调优测试报告

测试日期:2026-08-18
测试服务器:172.16.41.251
模型:Qwen/Qwen3.8-27B-FP8
推理框架:vLLM OpenAI-Compatible Server

一、结论摘要

本轮在同一模型、同一镜像、同一双卡 TP2 拓扑和同一测试负载下,完成了 MTP2/MTP3/MTP4、O2/O3、max-num-batched-tokens 4096/8192/16384、MTP4+4096 组合以及 L20 专用 M=1 FP8 Kernel 的受控 A/B。

主要结论如下:

  1. 均衡生产配置仍建议 MTP3 或改为 MTP4,保留 O2 和 8192。
    • MTP3/O2/8192 双次基线平均为 67.53 decode tok/s
    • MTP4/O2/8192 为 71.37 decode tok/s,提升 **5.70%**;缓存输入也提升约 **4.76%**,4 路吞吐均值下降约 **3.39%**,小于部分并发轮次波动。
  2. 如果只追求单请求速度,MTP4/O2/8192 是最简单、最均衡的调整。
  3. max-num-batched-tokens=4096 也把单流提高到 71.74 tok/s,但 4 路吞吐下降约 **9.84%**,不如 MTP4 均衡。
  4. O3 对单流没有收益:67.50 vs 67.53 tok/s;4 路仅提高约 **0.91%**,不值得引入额外优化复杂度。
  5. 16384 没有提升本轮 4 路负载,不能假设“更大的 batch token 上限一定更快”。
  6. 专用 M=1 FP8 Kernel 将 Decode 提高到 77.05 tok/s(+14.10%),但冷输入和缓存输入分别下降 **32.63%28.94%**。它是“长输出优先”实验配置,不是均衡生产配置。
  7. 输入侧最大的实际收益来自稳定前缀。 基线中约 3.5K Token 输入从冷前缀的 924 ms / 3,784 tok/s 攌善到缓存前缀的 314 ms / 11,038 tok/s:TTFT 下降约 **66%**,有效输入吞吐约为 2.92 倍

即使采用本轮输出最快的 Kernel 配置,Qwen3.8 稠密模型仍未达到先前 Qwen3.6-35B-A3B MoE 的约 152 decode tok/s。软件调优可以改善 Qwen3.8,但无法消除 27B 稠密模型与约 3B 激活 MoE 的架构差距。

二、服务器配置

项目 配置
操作系统 Ubuntu 22.04.5 LTS,Linux 5.15.0-186-generic
CPU 2 × Intel Xeon Gold 6530
CPU 规格 64 物理核心,128 线程
内存 251 GiB
GPU 2 × NVIDIA L20
单卡显存 46,068 MiB
总显存 约 92 GiB(标称 96 GB)
NVIDIA Driver 580.173.02
GPU 拓扑 GPU0 位于 NUMA0,GPU1 位于 NUMA1,GPU 间为 SYS
Docker 29.4.1
Docker Compose v5.1.3

SYS 表示 GPU 间通信需要经过 PCIe 和 CPU UPI/QPI,而不是 NVLink。Qwen3.8 使用 TP2 时,每层通信都会受到这一拓扑约束。

三、模型与部署基线

模型路径:

1
/data/ai/models/Qwen3.8-27B-FP8

部署目录:

1
/home/qwen

镜像:

1
2
3
4
vllm/vllm-openai:qwen38
RepoDigest: sha256:4a2f33a884222f7049b983263ad9976f89452bb81affecf5b67d89ad35c1bc31
Image ID: sha256:a0facb04caccd4de927face0cf4f0ca535d8dd3c9cf72144ab57a1c0d731c111
vLLM: 0.1.dev19754+g3a0914114

运行容器的实际基线参数为:

1
2
3
4
5
6
7
8
9
10
--tensor-parallel-size 2
--disable-custom-all-reduce
--dtype auto
--max-model-len 32768
--gpu-memory-utilization 0.90
--max-num-seqs 8
--max-num-batched-tokens 8192
--optimization-level 2 # 默认 O2
--enable-prefix-caching
--speculative-config {"method":"mtp","num_speculative_tokens":3}

需要注意:服务器当前 /home/qwen/docker-compose.fp8-mtp.yml 写的是 max-model-len: 49152,而正在运行容器的实际参数是 32768。本轮为保持与真实运行基线一致,所有测试都使用 32768;本轮没有修改该 Compose 文件。

四、测试方法

每个条件只改变一个待测参数;组合项单独标记。任何时刻只加载一个 Qwen3.8 模型实例,两张 L20 由该实例独占。

4.1 输入测试

  • 输入长度:约 3,500 Token。
  • 冷前缀:每次请求的唯一随机标识放在提示词最前面,阻止长前缀复用。
  • 缓存前缀:长前缀保持逐字节一致,动态标识放在末尾;先预热一次,再测 5 次。
  • 每次只输出 1 Token,用首 Token 到达时间衡量输入侧延迟。
  • 有效输入吞吐定义为:逻辑提示 Token 数 / 端到端 TTFT

这里的输入 tok/s 包含 HTTP、排队、Prefill 和第一个输出 Token,并不是单独的 GPU Kernel Prefill 数字;因此必须与 TTFT 一起理解。

4.2 输出测试

  • 输入长度:3,465 Token,使用稳定前缀。
  • 输出长度:固定 512 Token。
  • temperature=0top_p=1seed=42ignore_eos=true
  • 单流:每个条件 5 次。
  • 4 路并发:每轮 4 个请求,共 3 轮。
  • Decode 速度定义为:(输出 Token - 1) / 首 Token 到末 Token 的时间
  • 端到端输出速度定义为:输出 Token / 请求总时间

基线在测试序列前后各跑一次,单流分别为 67.54 和 67.52 tok/s,说明单流测量稳定。并发分别为 220.44 和 227.72 tok/s,报告中的百分比以两次均值 224.08 tok/s 为基准,并对几个百分点的小差异保持谨慎。

五、输入速度对比

条件 冷输入 TTFT 冷输入有效 tok/s 缓存输入 TTFT 缓存输入有效 tok/s 相对基线判断
基线平均:MTP3 / O2 / 8192 924 ms 3,784 314 ms 11,038 基准
MTP2 / O2 / 8192 935 ms 3,749 313 ms 11,078 基本相同
MTP4 / O2 / 8192 944 ms 3,709 300 ms 11,563 冷输入 -1.99%,缓存输入 +4.76%
MTP3 / O3 / 8192 939 ms 3,730 313 ms 11,053 基本相同
MTP3 / O2 / 4096 931 ms 3,760 314 ms 11,018 基本相同
MTP3 / O2 / 16384 937 ms 3,739 314 ms 11,049 基本相同
MTP4 / O2 / 4096 945 ms 3,707 302 ms 11,488 缓存输入快,但输出组合失败
MTP3 / O2 / 8192 + M=1 Kernel 1,373 ms 2,549 442 ms 7,843 冷输入 -32.63%,缓存输入 -28.94%

输入侧结论:

  • MTP 数量、O2/O3、4096/8192/16384 对冷 Prefill 的影响都很小。
  • 稳定前缀带来的缓存收益远大于这些服务端参数差异。
  • MTP4 的缓存输入最好,同时没有显著损害冷输入。
  • 只为 M=1 调优 Kernel 会让其他 batch 尺寸使用不合适的最近配置,因此 Prefill 明显变慢。

六、输出速度对比

条件 单流 Decode tok/s 相对基线 单流端到端 tok/s 4 路总吞吐 tok/s 4 路相对基线
基线平均:MTP3 / O2 / 8192 67.53 基准 64.88 224.08 基准
MTP2 / O2 / 8192 63.83 -5.47% 61.45 198.33 -11.49%
MTP4 / O2 / 8192 71.37 +5.70% 68.43 216.48 -3.39%
MTP3 / O3 / 8192 67.50 -0.03% 64.85 226.13 +0.91%
MTP3 / O2 / 4096 71.74 +6.23% 68.75 202.03 -9.84%
MTP3 / O2 / 16384 69.55 +3.00% 66.64 198.38 -11.47%
MTP4 / O2 / 4096 67.23 -0.44% 64.61 196.10 -12.49%
MTP3 / O2 / 8192 + M=1 Kernel 77.05 +14.10% 72.20 227.35 +1.46%

输出侧结论:

  • MTP2 虽有更高的草稿接受率,但每步可接受 Token 更少,最终比 MTP3 慢。
  • MTP4 的平均草稿接受率下降,但平均接受长度增加,单流因此更快。
  • 4096 也能偏向单流 Decode,但会更明显地牺牲并发吞吐。
  • MTP4 与 4096 组合后接受率进一步下降,两个单项收益没有叠加。
  • O3 对当前稠密模型、L20 和跨 NUMA TP2 组合没有单流价值。
  • M=1 Kernel 是输出最快项,但其输入损失意味着它只适合输出很长、首 Token 不敏感的业务。

七、MTP 接受率观察

日志中的末次 SpecDecoding 窗口如下。它们不是全程平均,但可以解释性能方向:

条件 平均接受长度 平均 Draft 接受率 各位置接受率
MTP2 / O2 / 8192 2.75 87.3% 91.3%、83.2%
MTP3 / O3 / 8192 3.38 79.2% 90.1%、78.8%、68.7%
MTP3 / O2 / 4096 3.19 73.0% 85.3%、72.5%、61.2%
MTP4 / O2 / 8192 3.77 69.2% 86.8%、73.6%、62.6%、53.7%
MTP4 / O2 / 4096 3.47 61.7% 84.7%、65.8%、52.9%、43.4%

这说明不能只看“接受率百分比”。MTP4 虽然百分比低于 MTP2/MTP3,但成功时一次接受更多 Token;真正需要比较的是最终 Decode 和业务延迟。

八、L20 FP8 Kernel 调优实验

启动日志确认 Qwen3.8 有 5 个 W8A8 Block FP8 矩阵形状缺少 NVIDIA L20 配置:

1
2
3
4
5
N=17408,K=5120
N=5120,K=3072
N=5120,K=8704
N=7168,K=5120
N=8192,K=5120

镜像没有附带调优脚本,因此下载了 vLLM 官方 benchmark_w8a8_block_fp8.py,并通过包装器将脚本默认的 DeepSeek 形状替换成日志确认的 Qwen3.8 形状。两张 L20 并行搜索 batch=1 的 1,280 组候选配置,约 17 分钟完成。

文件校验:

1
2
3
f9dc7a266da6ea0b94e57ddec9e7994193ac314ca3a84b14a3803ed90efa7d30  benchmark_w8a8_block_fp8.py
fc678e47bd6371059ba5bf578df6ddf2cdb8d76ea1f33e0f0175b937c48eb023 qwen_fp8_kernel_tune_wrapper.py
c76ced52e66dd10db6c8ecf0aebb970834bbfec07855399f06dd6ec50f5fdfe1 qwen_tuning_benchmark.py

测试容器通过 5 个只读单文件挂载加载 JSON,没有覆盖镜像文件。两张 GPU 的日志均确认 Using configuration from ...

限制:本轮只调了 M=1。vLLM 会为其他 batch 尺寸选择 JSON 中最近的配置,因此 3.5K 冷 Prefill 和十几个 Token 的缓存尾部 Prefill 被迫使用 M=1 配置,导致输入退化。若要生产使用,至少还需要调 M=16/32 和 M=2048/4096,再把多组配置合并后重新 A/B。该过程耗时较长,并且配置与 GPU、vLLM Kernel 实现和镜像版本绑定。

九、推荐配置

9.1 均衡、保守生产配置

保持当前:

1
2
3
4
MTP=3
optimization-level=2
max-num-batched-tokens=8192
prefix-caching=on

优点是官方常用 MTP3 路径、并发稳定、维护成本最低。

9.2 单请求优先的推荐配置

只将 MTP3 改为 MTP4:

1
2
3
MTP=4
optimization-level=2
max-num-batched-tokens=8192

本轮收益:

  • Decode:67.53 → 71.37 tok/s,约 +5.70%。
  • 端到端输出:64.88 → 68.43 tok/s,约 +5.47%。
  • 缓存输入:11,038 → 11,563 tok/s,约 +4.76%。
  • 4 路吞吐均值下降约 3.39%,但并发样本波动较大,建议用真实 Agent 请求再跑 30~60 分钟 P50/P95。

9.3 长输出、首 Token 不敏感的实验配置

使用基线 MTP3/O2/8192,加 M=1 L20 Kernel:

  • Decode 可达到 77.05 tok/s。
  • 冷输入 TTFT 增至 1.37 秒,缓存输入 TTFT 增至 0.44 秒。
  • 不建议直接作为通用 Agent 服务;更适合离线生成或很长输出。

十、业务侧输入优化建议

本轮最确定的输入优化是提高 Prefix Cache 命中:

1
2
3
4
5
6
固定 system prompt
固定工具定义及工具顺序
固定 Agent 规则
动态日期、request ID、用户信息
对话历史或摘要
当前请求

动态字段不要放在请求最前面。对于工具较多的 Agent,应只注入当前任务需要的工具,并保持 JSON 字段和工具顺序稳定。

减少 Thinking 和输出长度没有纳入本轮“引擎参数 A/B”,因为它会改变输出内容和 Token 数,无法与固定 512 Token 的纯速度测试混在一起。它仍然可能是业务端到端延迟收益最大的下一步,应使用真实 Agent 任务单独做质量/延迟联合评估。

十一、未推荐项

  • optimization-level=3:单流无提升。
  • max-num-batched-tokens=16384:本轮 4 路吞吐反而下降。
  • MTP4 + 4096:收益不能叠加,单流和并发均退步。
  • 单独启用 M=1 Kernel:输入损失过大。
  • 提高 gpu-memory-utilization 或降低 max-model-len:主要影响容量,不解决单流 Decode 核心瓶颈。
  • 当前重新启用 Custom AllReduce:此前在这套 L20/镜像组合上触发 CUDA Graph invalid argument,本轮继续使用稳定的 --disable-custom-all-reduce

十二、服务器最终状态与安全边界

  • 原容器 qwen38-fp8-mtp 已恢复运行,/health 返回 HTTP 200。
  • 其他原有服务保持运行。
  • 所有测试和调优容器均已停止。
  • 没有删除任何容器、镜像、模型或文件。
  • 模型目录 /data/ai/models 未修改。
  • 新增脚本、结果和 Kernel 配置全部位于 /home/qwen

由于遵守“不删除”约束,服务器保留了成功和失败的测试容器。失败项包括一次优化级别格式校验失败和一次官方调优脚本多进程 pickle 失败;两次都发生在正式推理/Kernel 搜索前,且保护脚本均恢复了原服务。

十三、结果与脚本位置

服务器:

1
2
3
4
5
6
7
/home/qwen/qwen_tuning_benchmark.py
/home/qwen/run_qwen_tuning_suite.sh
/home/qwen/run_qwen_combined_test.sh
/home/qwen/run_qwen_kernel_tuning.sh
/home/qwen/run_qwen_tuned_kernel_test.sh
/home/qwen/kernel-tuning/
/home/qwen/results/tuning-20260818/

本项目:

1
2
3
4
5
6
7
8
QWEN38_TUNING_REPORT.md
qwen_tuning_results/*.json
qwen_tuning_benchmark.py
run_qwen_tuning_suite.sh
run_qwen_combined_test.sh
qwen_fp8_kernel_tune_wrapper.py
run_qwen_kernel_tuning.sh
run_qwen_tuned_kernel_test.sh

十四、最终建议

如果目标是替换现有 Agent 模型并优先改善单请求体感,建议下一阶段在真实业务流量上比较:

  1. 当前 MTP3/O2/8192;
  2. MTP4/O2/8192;
  3. 同一批真实任务下的 Thinking 开/关与输出长度上限。

本轮参数测试的推荐候选是 MTP4/O2/8192。它是唯一同时改善单流输出和缓存输入、且没有明显破坏冷输入的简单配置。若必须追求更高的纯 Decode,下一步应补齐多 batch 尺寸的 L20 Kernel 网格,而不是直接部署只有 M=1 的实验配置。