Qwen3.8 27B / 4×A100 调参记录
目标是把 4 张 A100 上的 Qwen3.8-27B INT8 服务调到更高的实际吞吐。下面只记改动、测量和决定,后续实验继续往下追加。
现在跑什么
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 |
| 批处理 / KV | max-num-batched-tokens=8192;fp8_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 / TTFT | TP4 prefill / decode / TTFT |
|---|---|---|
| 1K | 4,249 / 89.6 / 0.323s | 6,825 / 99.6 / 0.229s |
| 8K | 5,150 / 74.3 / 1.673s | 9,012 / 82.2 / 0.988s |
| 32K | 4,642 / 96.6 / 7.141s | 8,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 prefill | TP2+DP2 decode | TTFT | 相对 TP4 的 decode |
|---|---|---|---|---|
| 1K / 1 | 3,278.6 | 64.0 | 391ms | 基本相同 |
| 1K / 2 | 6,036.4 | 136.4 | 317ms | 约 +10% |
| 8K / 1 | 2,859.0 | 53.8 | 2,944ms | 略低 |
| 8K / 2 | 8,332.0 | 136.7 | 1,838ms | 约 +23% |
| 32K / 1 | 4,637.6 | 64.3 | 7,144ms | 接近 |
| 32K / 2 | 8,946.4 | 114.0 | 7,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/s | decode 总 tok/s | 单请求 decode | TTFT |
|---|---|---|---|---|
| 1K / 1 | 3,710.5 | 64.4 | 64.4 | 358ms |
| 1K / 4 | 3,815.3 | 208.8 | 66.1 | 867ms |
| 1K / 8 | 772.4 | 224.5 | 61.3 | 4,992ms |
| 8K / 1 | 3,327.8 | 61.7 | 61.7 | 2,544ms |
| 8K / 4 | 9,894.7 | 171.7 | 49.3 | 3,260ms |
| 8K / 8 | 4,136.0 | 210.8 | 72.0 | 8,127ms |
| 32K / 1 | 4,516.0 | 75.5 | 75.5 | 7,338ms |
| 32K / 4 | 8,998.4 | 116.6 | 54.1 | 12,030ms |
| 32K / 8 | 5,398.0 | 91.6 | 68.9 | 24,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 变化必须重新验证。
还要做什么
- 同一份固定 prompt 和 seed,MTP2 / MTP3 各跑至少三次;
- nightly 与稳定版各重跑三次,确认 decode 回退是不是稳定现象;
- 继续增加 2、4 路并发测试,但
max-num-seqs暂时不超过每个 replica 2; - 分别记录单请求延迟和多请求总吞吐,不再混成一个“更快”。