Files
qwen36_01/worklogs/decode_analysis_report_2026-07-14.md

9.2 KiB
Raw Blame History

Decode 吞吐专项实验分析报告

日期2026-07-14

一、结论摘要

本轮实验确认:当前服务的主要短板是 decode 阶段吞吐,而不是 prompt prefill、tool parser 或 reasoning parser。

核心证据:

  • 短 prompt、单并发、256 token 输出时Output TPS P10 只有 8.3-8.7 tok/s,明显低于官方目标 >=20 tok/s
  • 去掉 --enable-auto-tool-choice--tool-call-parser--reasoning-parserdecode TPS 没有提升,反而略低。
  • 加并发后没有获得 batch 增益:并发 1 聚合约 8.00 tok/s,并发 2 聚合约 7.23 tok/s,并发 4 聚合约 5.08 tok/s
  • 并发 2 时硬件采样显示平均 GPU-Util 只有 28.9%,平均功耗约 49.7W / 250W,说明 GPU 没有被持续打满。

初步判断:瓶颈更像是 decode 路径中的调度、TP 同步、MoE kernel/专家路由、paged attention/kernel launch 开销,或 xFormers 后端限制,而不是 API parser 层。

二、实验环境

远端服务:

  • 模型:/root/public-storage/models/Qwen/Qwen3.6-35B-A3B
  • APIvllm.entrypoints.openai.api_server
  • GPU4 x Iluvatar BI-V100, 32GB
  • 当前主要实验配置:
    • -tp 4
    • --gpu-memory-utilization 0.95
    • --max-num-seqs 2
    • --max-num-batched-tokens 8192
    • --enable-chunked-prefill
    • --enable-prefix-caching

本地脚本:

  • worklogs/decode_microbench.py

远端脚本:

  • /root/work/decode_microbench.py

本地原始结果:

  • worklogs/remote_results/2026-07-14-decode/

三、实验结果

1. 纯短 prompt decode 基线

完整 parser 配置,短 prompt单并发3 请求,每请求 max_tokens=256

结果文件:

  • 远端:/root/work/logs/decode_full_parser_short_c1_t256_r3.json
  • 本地:worklogs/remote_results/2026-07-14-decode/decode_full_parser_short_c1_t256_r3.json

指标:

指标 数值
成功率 100%
TTFT P90 1.85s
Output TPS P10 8.74 tok/s
Output TPS P50 8.74 tok/s
聚合 Output TPS 8.34 tok/s
总 completion tokens 768
reasoning tokens 768

解释:

短 prompt 下 TTFT 已经较低,但 decode 速度仍只有约 8.7 tok/s。这说明长上下文不是唯一问题,短输出生成本身就偏慢。

2. Tool/parser 触发场景

完整 parser 配置,携带 29 个 tools单并发3 请求,每请求 max_tokens=128

结果文件:

  • 远端:/root/work/logs/decode_full_parser_tool_c1_t128_r3.json
  • 本地:worklogs/remote_results/2026-07-14-decode/decode_full_parser_tool_c1_t128_r3.json

指标:

指标 数值
成功率 100%
TTFT P90 4.98s
Output TPS P10 8.70 tok/s
Output TPS P50 8.70 tok/s
聚合 Output TPS 7.38 tok/s
prompt tokens 6639
cached tokens 4416
completion tokens 384

单请求细节:

  • 第 1 个请求TTFT 5.97scached tokens 0
  • 第 2/3 个请求TTFT 约 1.0scached tokens 2208

解释:

工具 schema 会明显影响首次 prefill/TTFT但前缀缓存命中后 TTFT 恢复。Output TPS 仍约 8.7 tok/s,与纯短 prompt 基本一致,所以 tool parser 不是 decode TPS 主瓶颈。

3. Parser-off 对照

重启服务,去掉:

  • --enable-auto-tool-choice
  • --tool-call-parser qwen3_coder
  • --reasoning-parser qwen3

其余参数保持一致。

短 prompt单并发3 请求,每请求 max_tokens=256

结果文件:

  • 远端:/root/work/logs/decode_noparser_short_c1_t256_r3.json
  • 本地:worklogs/remote_results/2026-07-14-decode/decode_noparser_short_c1_t256_r3.json

指标:

指标 完整 parser parser-off
成功率 100% 100%
TTFT P90 1.85s 3.18s
Output TPS P10 8.74 8.30
Output TPS P50 8.74 8.33
聚合 Output TPS 8.34 7.87

解释:

关闭 parser 没有改善 decode。parser/reasoning/tool 相关启动项不是当前 decode 吞吐低的主因。

4. 并发曲线

parser-off 服务,短 prompt每请求 max_tokens=128

结果文件:

  • decode_noparser_short_c1_t128_r4.json
  • decode_noparser_short_c2_t128_r4.json
  • decode_noparser_short_c4_t128_r4.json

指标:

并发 请求数 成功率 TTFT P90 Output TPS P10/请求 聚合 Output TPS
1 4 100% 0.79s 8.30 8.00
2 4 100% 0.92s 3.68 7.23
4 4 100% 51.60s 2.57 5.08

解释:

并发提高后没有形成有效 batch 增益。并发 2 时单请求 TPS 近似减半,聚合 TPS 也略降;并发 4 时出现明显排队TTFT P90 被拉到 51.6s

这说明当前配置下 decode 并发能力很弱。由于服务参数 --max-num-seqs=2,并发 4 的后两个请求排队符合预期;但并发 2 聚合吞吐仍不提升,说明 decode 内部没有把双请求 batch 变成更高硬件利用率。

5. 硬件利用率采样

parser-off 服务,并发 24 请求,每请求 max_tokens=128,同时采样 ixsmi

结果文件:

  • 远端:/root/work/logs/decode_noparser_short_c2_t128_r4_monitor.json
  • 远端:/root/work/logs/ixsmi_noparser_short_c2_t128_r4_monitor.json
  • 本地同名文件位于:worklogs/remote_results/2026-07-14-decode/

指标:

指标 数值
成功率 100%
TTFT P90 0.92s
Output TPS P10 3.79 tok/s
聚合 Output TPS 7.40 tok/s
ixsmi records 52
parsed GPU samples 208
平均 GPU-Util 28.91%
最大 GPU-Util 100%
平均显存 30477 MiB
最大显存 30555 MiB
平均功耗 49.67 W
最大功耗 51 W

解释:

GPU 利用率和功耗都偏低。虽然瞬时 GPU-Util 能到 100%,但平均只有约 29%,功耗长期接近空载到轻载水平。这说明 decode 过程中存在大量空泡、同步等待或小 kernel 启动开销GPU 算力没有被持续喂满。

四、瓶颈判断

当前最可能的瓶颈排序:

  1. Decode 调度/后端限制

    • --num-scheduler-steps 4 曾尝试失败。
    • 报错:Multi-Step + Chunked-Prefill not supported for attention backend: xformers
    • 当前服务日志显示使用 XFormers backend
  2. TP 通信或同步开销

    • 模型使用 -tp 4
    • 短 decode 每步都可能涉及多卡同步。
    • 并发 2 聚合 TPS 不升反降,符合小 batch 多卡同步效率差的特征。
  3. MoE decode kernel/专家路由效率

    • Qwen3.6-35B-A3B 是 MoE 模型。
    • decode batch 小时,专家路由和 fused MoE kernel 可能难以形成高利用率。
  4. Paged attention / attention backend 每 token 开销

    • 当前 attention 后端为 xFormers。
    • multi-step decode 被 xFormers + chunked prefill 组合限制。
  5. API/parser 层

    • 本轮实验基本排除其为主瓶颈。
    • tool/schema 影响首次 TTFT但不显著影响 decode TPS。

五、下一步建议

建议下一步不要直接改大段 kernel而是先做两个能明确指向代码修改方向的服务变体实验。

实验 Adecode-only multi-step 变体

目的:验证 multi-step scheduling 是否能明显提升 decode。

做法:

  • 暂时关闭 --enable-chunked-prefill
  • 加回 --num-scheduler-steps 4
  • 保持 max_num_seqs=2
  • 跑短 prompt decode 并发 1/2 曲线

判断:

  • 如果 Output TPS 明显提升,说明 decode 调度是关键方向。
  • 后续要研究如何让 multi-step 与长上下文 chunked prefill 共存,或按场景切换。

风险:

  • 官方长上下文负载仍需要 chunked prefill所以这不是最终配置只是定位实验。

实验 Battention backend 变体

目的:确认是否可以启用兼容 multi-step 的 attention backend。

做法:

  • 尝试设置 VLLM_ATTENTION_BACKEND=FLASH_ATTN
  • 或检查 CoreX/xFormers flash attention 能否被 vLLM selector 选中
  • 如果能启动,再测试 multi-step + chunked prefill

判断:

  • 如果 flash attention 能启动且 multi-step 可用,优先走 backend 配置/适配路线。
  • 如果不能启动,需要看 vllm/attention/selector.pyvllm/attention/backends/* 和 CoreX xFormers 补丁。

实验 C代码级 profiling

目的:把低 GPU 利用率归因到具体模块。

建议插桩位置:

  • vllm/worker/model_runner.py
  • vllm/worker/multi_step_model_runner.py
  • vllm/model_executor/models/qwen3_moe.py
  • vllm/model_executor/layers/fused_moe/*
  • attention.py
  • paged_attn.py

记录每步:

  • model forward 耗时
  • attention 耗时
  • MoE/MLP 耗时
  • sampler 耗时
  • 每步前后同步耗时

实验 D服务参数小网格

目的:确认当前 max_num_seqs=2 是否已经是最优。

建议组合:

参数 候选
max_num_seqs 1, 2, 4
max_num_batched_tokens 4096, 8192
chunked_prefill on/off
num_scheduler_steps 1, 4

优先只在短 prompt decode 上跑,快速筛掉无效组合。

六、当前推荐行动

下一步优先跑:

  1. chunked_prefill=off + num_scheduler_steps=4
  2. 若能启动,跑 decode c1/c2/c4 曲线和 ixsmi 采样
  3. 若 decode TPS 明显提升,再研究如何兼容官方长上下文 prefill
  4. 若没有提升,进入 qwen3_moe / fused_moe / attention 的代码级 profiling

本轮最重要的事实是GPU 平均利用率只有约 29%,所以先不要把问题简单归因为“卡算不动”。更像是当前 decode 执行路径没有把四张卡持续喂满。