Tuning log / GPU inference

Qwen3.8 27B / 4×A100 调参记录

目标是把 4 张 A100 上的 Qwen3.8-27B INT8 服务调到更高的实际吞吐。下面只记改动、测量和决定,后续实验继续往下追加。

开始于 2026-08-17持续更新

现在跑什么

30001 现在使用 4 张 A100-SXM4-80GB,拆成两组 TP2,也就是 TP2 + DP2。模型是 Qwen3.8-27B INT8 W8A16,服务镜像是 vLLM floating nightly。30000 上原有的 397B 服务没有动。

当前值
并行TP=2, DP=2, PP=1
上下文 / 并发max-model-len=128000;每个 replica max-num-seqs=2
批处理 / KVmax-num-batched-tokens=8192fp8_e4m3
推测解码MTP=3
缓存chunked prefill / prefix cache 开;PR #48375 启动补丁;编译缓存持久化
通信NVLink / NCCL P2P 开;vLLM custom all-reduce 关

调参流水

1. 先跑通稳定版:TP2 / 8192 / MTP3

改动:用 vLLM v0.26.0-cu129 启动 compressed-tensors 模型,保留模型卡的 FP8 KV、8192 batched tokens 和 MTP3。

遇到:vLLM custom all-reduce 在这台机器报 CUDA invalid argument。GPU 0-3 的 NVLink 和双向 peer access 已确认正常,所以没有照搬 3090 配置去关 NCCL P2P,只加了 --disable-custom-all-reduce,让通信走 NCCL。

决定:保留 NVLink/P2P;保留 --disable-custom-all-reduce

2. TP2 和 TP4 单流对照

方法:本地 llama-benchy 调 OpenAI-compatible API;每格一次,输出固定 512 tokens,prefix cache 关闭。

输入TP2 prefill / decode / TTFTTP4 prefill / decode / TTFT
1K4,249 / 89.6 / 0.323s6,825 / 99.6 / 0.229s
8K5,150 / 74.3 / 1.673s9,012 / 82.2 / 0.988s
32K4,642 / 96.6 / 7.141s8,436 / 77.8 / 3.963s

决定:这一轮先留 TP4。它明显改善长输入 prefill 和 TTFT;32K decode 的单次结果变差,后面继续看并发吞吐。

3. batched tokens 从 8192 提到 16384

结果:8K TTFT 从 1.673s 变成 1.710s;32K prefill 从 4,642 降到 3,797 tok/s,TTFT 从 7.141s 变成 8.711s。

决定:回退到 8192。这台机器当前没有从 16384 得到收益。

4. 稳定版切 floating nightly

改动:换到 0.27.2rc1.dev122+g8efa13b70,其他 benchmark 条件不变。

结果:长输入 prefill 基本接近稳定版,但六个对照格的 decode 都下降。单流下降 13%~36%,双流总 decode 下降 9%~23%。没有 OOM、重启或请求错误。

决定:nightly 暂时保留用于 Qwen3.8 兼容性验证;不能把它写成性能升级,decode 回退仍需继续查。

5. MTP2 / MTP3 acceptance 排查

MTP2,默认采样:8K/1024 单流为 1,076 / 1,942 = 55.41%;固定 prompt 三次合计 52.74%。

MTP2,greedy 诊断:8K/512 为 585 / 878 = 66.63%。这说明采样策略会明显改变 accept,但不代表线上应该改成 greedy。

MTP3 旧窗口:混合流量累计约 49.70%。模型卡的 65.5% 没有 prompt、seed 和原始计数,当前无法做一比一复现。

决定:恢复 MTP3。accept 只用同一 prompt、同一采样下的 counter delta 比较;最终仍以 decode tok/s 为准。

6. TP4 改成 TP2 + DP2,同时把上下文降到 128K

改动:四张卡拆成两个 TP2 replica;max-model-len 从 262144 降到 128000;MTP3、batch 8192、FP8 KV 不变。

输入 / 并发TP2+DP2 prefillTP2+DP2 decodeTTFT相对 TP4 的 decode
1K / 13,278.664.0391ms基本相同
1K / 26,036.4136.4317ms约 +10%
8K / 12,859.053.82,944ms略低
8K / 28,332.0136.71,838ms约 +23%
32K / 14,637.664.37,144ms接近
32K / 28,946.4114.07,149ms约 +26%

决定:当前保留 TP2 + DP2。它没有改善单请求速度,但在至少两条独立请求时,aggregate decode 吞吐更高,这更符合现在的服务目标。

7. 当前 MTP3 计数快照

累计:12,093 个 draft tokens,接受 5,861 个,accept 48.47%;三个位置分别为 66.34%、45.52%、33.54%。

11:40 基线后的业务流量 delta:684 个 draft tokens,接受 400 个,accept 58.48%;三个位置分别为 71.49%、57.89%、46.05%。

记录方式:这是两个不同窗口,不合并成一个“当前 accept”。vLLM counter 是进程累计值;下一轮实验前重新记基线,只比较 delta。

8. prefix cache + PR #48375 试开后回滚

改动:没有重建或拉取镜像。30001 在启动时应用 PR #48375 的 9 行 Python 修复,然后打开 prefix cache。

窄验证:原镜像在 drop_eagle_block=true 时,full-attention/Mamba 命中块数是 4/5;应用补丁后是 4/4。6 次相同的 33,012-token 请求也出现了 30,400~32,000 cached tokens。到这里,只能证明文件被修改、cache 能命中。

真实 gate:本地 max-8-stream 无缓存矩阵完成后,prefix-cache 专项在正式测试前就没有通过 coherence:模型对“法国首都”输出连续重复的中文/阿拉伯 token。唯一 cache_salt、thinking-off 和 cached_tokens=0 都不能恢复正确输出。

回滚验证:恢复 prefix-cache-off compose 并重启 30001 后,相同请求立即恢复为包含 Paris 的正常回答,0 error/abort,30000 未动。

当时决定:先关闭 prefix cache 并保留回滚点,继续定位为什么文件已修改却没有保护真实运行路径。

9. 本地 llama-benchy:并发上限跑到 8

方法:prefix cache 关闭,PP=1K/8K/32K,TG=512,流数 1/2/4/8,每格一次。8 流超过两组 replica 合计 4 个同时执行槽,因此结果包含排队。

输入 / 流数prefill 总 tok/sdecode 总 tok/s单请求 decodeTTFT
1K / 13,710.564.464.4358ms
1K / 43,815.3208.866.1867ms
1K / 8772.4224.561.34,992ms
8K / 13,327.861.761.72,544ms
8K / 49,894.7171.749.33,260ms
8K / 84,136.0210.872.08,127ms
32K / 14,516.075.575.57,338ms
32K / 48,998.4116.654.112,030ms
32K / 85,398.091.668.924,258ms

决定:短输入总 decode 在 8 流最高,但 32K 到 8 流已经明显排队并降低总吞吐。当前更合理的工作区间是 4 流;这些仍是单次 smoke 数据。

10. 找到 PR #48375 被绕过的路径,重新打开 prefix cache

根因:补丁文件路径是对的。实际 hybrid cache page 是 1600 tokens,但 compose 强制了 --prefix-match-unit 16。当前 nightly 在 alignment_tokens < block_size 时会走 Mamba fine-grained 分支并提前返回,位置在 PR #48375 的 9 行改动之前。旧单测把 block size 和 alignment 都设成 16,所以给出了假阳性。

改动:保留 PR #48375,删除 --prefix-match-unit 16,让 vLLM 使用 1600-token 公共 page/hash unit。启动 wrapper 现在会拒绝任何显式 prefix-match-unit,并用真实 1600/1600 条件验证:control full/Mamba 为 5/5,MTP drop 后为 4/4。

真实 gate:27K 共享前缀顺序请求在两个 DP replica 预热后每次命中 16,000 tokens,8/8 正确;三轮 8 路并发共 24 个首都回答全部正确。llama-benchy 在 8K 和 32K context、8 路并发下都通过 coherence,32K 测完后再发 8 路 Paris 仍为 8/8。工具调用 4/4 返回正确的 get_weather({"city":"Paris"}),后两次各命中 8,000 tokens。

状态:记录时累计 cache hit 892,800 tokens,error / abort / repetition 均为 0,容器重启 0、无 OOM;30000 未动。

决定:30001 打开 prefix cache。保留 source SHA、vLLM commit 和显式 prefix-match-unit 三道 fail-closed 门禁;后续 floating nightly 变化必须重新验证。

还要做什么

  1. 同一份固定 prompt 和 seed,MTP2 / MTP3 各跑至少三次;
  2. nightly 与稳定版各重跑三次,确认 decode 回退是不是稳定现象;
  3. 继续增加 2、4 路并发测试,但 max-num-seqs 暂时不超过每个 replica 2;
  4. 分别记录单请求延迟和多请求总吞吐,不再混成一个“更快”。
测试数据均来自本地 llama-benchy 调用远端 OpenAI-compatible API。单格一次的结果只用于选下一步参数,不当作正式性能报告。参考:Qwen3.8-27B-INT8-W8A16-MTP 模型卡