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

46 KiB
Raw Permalink 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 执行路径没有把四张卡持续喂满。

七、调度方向验证实验multi-step decode

追加日期2026-07-14

目标

验证 --num-scheduler-steps 4 是否能作为 decode 吞吐提升方向。

由于上一轮实验显示并发升高没有带来 aggregate TPS 增益,且平均 GPU 利用率只有约 29%multi-step scheduling 是最直接的调度侧候选优化:它理论上可以减少每 token 调度往返和 Python/worker 协调开销,让 decode 连续执行多个 step。

实验 1关闭 chunked prefill直接启用 multi-step

启动变体:

  • --num-scheduler-steps 4
  • 不加 --enable-chunked-prefill
  • --max-model-len 100000
  • --max-num-batched-tokens 8192
  • 其它参数沿用完整 parser 服务配置

结果:

  • 服务未启动。
  • 远端日志:/root/work/logs/server_exp_sched4_nochunk_fullparser.log
  • 本地归档:worklogs/remote_results/2026-07-14-scheduler/server_exp_sched4_nochunk_fullparser.log

失败原因:

ValueError: max_num_batched_tokens (8192) is smaller than max_model_len (100000).

解释:

关闭 chunked prefill 后vLLM 要求 max_num_batched_tokens >= max_model_len,否则实际最大可处理序列会被 max_num_batched_tokens 限制。这个配置不能用于官方长上下文,也无法进入 decode 压测。

实验 2decode-only 诊断配置

为绕过实验 1 的限制,临时降低上下文长度,只用于短 prompt decode 诊断。

启动变体:

  • --max-model-len 4096
  • --max-seq-len-to-capture 4096
  • --max-num-batched-tokens 8192
  • --num-scheduler-steps 4
  • 不加 --enable-chunked-prefill
  • attention backend 仍为自动选择

结果:

  • 服务未启动。
  • 远端日志:/root/work/logs/server_exp_sched4_nochunk_len4096_fullparser.log
  • 本地归档:worklogs/remote_results/2026-07-14-scheduler/server_exp_sched4_nochunk_len4096_fullparser.log

关键日志:

Using XFormers backend.
ValueError: Multi-Step not supported for attention backend: xformers.
Set VLLM_ATTENTION_BACKEND to a value from ['flash-attn', 'rocm-flash-attn', 'flashinfer'].

解释:

这说明当前环境不是“chunked prefill 与 multi-step 的组合不支持”这么简单,而是 xformers attention backend 本身不支持 multi-step worker。只要 attention backend 仍然落到 xFormersmulti-step 调度就无法启用。

实验 3强制 FlashInfer backend

启动变体:

  • 环境变量:VLLM_ATTENTION_BACKEND=FLASHINFER
  • --max-model-len 4096
  • --num-scheduler-steps 4
  • 不加 --enable-chunked-prefill
  • 其它参数同实验 2

结果:

  • 服务未启动。
  • 远端日志:/root/work/logs/server_exp_sched4_nochunk_len4096_flashinfer.log
  • 本地归档:worklogs/remote_results/2026-07-14-scheduler/server_exp_sched4_nochunk_len4096_flashinfer.log

关键日志:

TypeError: 'NoneType' object is not callable
...
self._decode_wrapper = BatchDecodeWithPagedKVCacheWrapper(...)

解释:

vllm/attention/backends/flashinfer.py 会导入:

  • flashinfer.BatchDecodeWithPagedKVCacheWrapper
  • flashinfer.decode.CUDAGraphBatchDecodeWithPagedKVCacheWrapper
  • flashinfer.prefill.BatchPrefillWithPagedKVCacheWrapper
  • ixformer.contrib.vllm_flash_attn.flash_attn_varlen_func

当前环境中至少有关键 FlashInfer wrapper 没导入成功,导致 wrapper 为 None,在 profiling 阶段调用时报错。

实验结论

本轮没有进入 c1/c2/c4 decode 曲线压测,因为 multi-step 服务在启动阶段就失败。

结论不是“调度一定无效”,而是:

  1. 当前 xFormers backend 下multi-step 调度不可用。
  2. 当前 FlashInfer backend 依赖不完整或与 CoreX 环境不兼容,不能直接替代 xFormers。
  3. 自动 FlashAttention 也不可用;此前日志已显示 vllm_flash_attn 包缺失,因此自动回落到 xFormers。

因此,调度优化如果要继续推进,前置任务是 attention backend 适配:

  • 路线 A补齐/修复 CoreX 环境里的 FlashAttention 或 FlashInfer backend。
  • 路线 B改造 xFormers backend 或 MultiStepModelRunner使其支持当前 xFormers 路径。
  • 路线 C绕过 multi-step直接 profile xFormers decode、paged attention、MoE 与 TP 通信开销。

对下一步方向的影响

短期内,继续调 --num-scheduler-steps 没意义;它被 backend 卡住了。

下一步建议改为两条线并行:

  1. backend 可用性线

    • 检查 flashinferixformer.contrib.vllm_flash_attn 在服务器上的实际导入错误。
    • 确认官方镜像/包中是否本应包含 vllm_flash_attn
    • 如果能补齐依赖,再重跑 multi-step decode 曲线。
  2. 代码 profiling 线

    • 直接在当前可用 xFormers 路径插桩。
    • 重点记录每 token decode 中 attention、MoE、sampler、TP 同步的耗时。
    • 当前平均 GPU 利用率低profiling 比继续盲调参数更有价值。

2026-07-14解除强制 eager / custom all-reduce 禁用验证

修改内容

本轮先处理 vllm/engine/arg_utils.py 中两个会掩盖真实性能路径的硬编码:

enforce_eager=True
disable_custom_all_reduce=True

改为尊重 CLI / dataclass 参数:

enforce_eager=self.enforce_eager
disable_custom_all_reduce=self.disable_custom_all_reduce

同时更新 qwen3_6_scripts/patch_xformers_sdpa_seq.py,让后续重新执行 patchops 时也会保留该行为。服务器实际运行路径已确认:

PYTHONPATH=/usr/local/corex/lib/python3/dist-packages:/usr/local/corex/lib64/python3/dist-packages

因此运行时同时 patch 了:

  • /usr/local/corex/lib/python3/dist-packages/vllm/engine/arg_utils.py
  • /usr/local/corex/lib64/python3/dist-packages/vllm/engine/arg_utils.py

远端运行时备份:

  • arg_utils.py.bak_20260714_eager_allreduce

实验 1CUDA Graph + custom all-reduce 同时开启

启动命令不再带 --enforce-eager,也不带 --disable-custom-all-reduce

日志确认配置已生效:

disable_custom_all_reduce=False
enforce_eager=False
use_async_output_proc=True

结果:服务未能完成启动,长时间卡在 CUDA Graph capture 阶段。

关键日志:

Capturing the model for CUDA graphs.
[W CUDAGraph.cpp:145] Warning: Waiting for pending NCCL work to finish before starting graph capture.

判断:当前 Iluvatar BI-V100 + CoreX + xFormers + TP=4 路径下CUDA Graph capture 不可直接启用。它没有快速报错,而是卡在 graph capture / NCCL pending work 阶段,风险比普通参数不兼容更高。短期不建议继续沿 CUDA Graph 方向盲试。

本地归档:

  • worklogs/remote_results/2026-07-14-eager-allreduce/server_exp_graph_allreduce_seq2_b8192.log

实验 2仅开启 custom all-reduce继续 eager

启动命令保留 --enforce-eager,但不再带 --disable-custom-all-reduce

日志确认:

enforce_eager=True
disable_custom_all_reduce=False

服务可以正常启动并通过 /health

decode microbench 结果:

配置 成功率 TTFT P90 Output TPS P10/req Aggregate Output TPS 对比旧结果
short c1, 256 tok, custom AR on 100% 3.47s 8.47 7.94 旧 full-parser c1 为 P10 8.74 / aggregate 8.34,略降
short c2, 128 tok, custom AR on 100% 1.46s 4.22 8.07 旧 parser-off c2 aggregate 7.23,略升但口径不完全相同

本地归档:

  • worklogs/remote_results/2026-07-14-eager-allreduce/decode_eager_custom_ar_short_c1_t256_r3.json
  • worklogs/remote_results/2026-07-14-eager-allreduce/decode_eager_custom_ar_short_c2_t128_r4.json
  • worklogs/remote_results/2026-07-14-eager-allreduce/server_exp_eager_custom_ar_seq2_b8192_retry.log

结论

  1. 之前的硬编码确实屏蔽了真实配置,本轮已经解除,并确认修改落在实际运行的 CoreX site-packages 路径里。
  2. CUDA Graph 当前不兼容或存在严重启动卡死问题,不适合作为短期主优化方向。
  3. custom all-reduce 可以启动和推理但收益有限c2 聚合吞吐有小幅提升c1 无提升。
  4. 当前 decode 吞吐仍在 8 tok/s 左右,距离 Output TPS P10 >= 20 仍有明显差距,瓶颈不只是 all-reduce 开关。

下一步 profiling 方向

优先进入代码级 profiling而不是继续调 CLI 开关:

  1. ModelRunner.execute_model 统计模型 forward、logits、sample 的阶段耗时。
  2. 在 Qwen MoE 层统计 attention、MoE expert、MoE gate、TP all-reduce 的耗时占比。
  3. 在 fused MoE 路径统计 topk、expert kernel、activation、sum 的耗时。
  4. ENGINEX_PROFILE_DECODE=1 这类环境变量控制插桩,只在短压测时开启,避免污染正式结果。

初步判断custom all-reduce 不是第一大瓶颈;更可能的主战场是 xFormers decode attention、MoE 小 batch kernel、以及 TP 下大量小 kernel / 同步造成的低 GPU 利用率。

2026-07-14代码级 profiling 与第一轮 MoE 优化

profiling 插桩

新增环境变量控制的 profiling

  • ENGINEX_PROFILE_DECODE=1:开启 profiling。
  • ENGINEX_PROFILE_EVERY=N:每 N 次 model forward 打印一次累计统计。
  • ENGINEX_PROFILE_SYNC=1:每段计时前后 torch.cuda.synchronize(),用于定位 GPU 时间。

插桩位置:

  • vllm/worker/model_runner.py
    • attn_state.begin_forward
    • model_forward
    • compute_logits
    • sample
  • qwen3_6_scripts/qwen3_5.py
    • full_attention: qkv projection / norm+rope / paged attention / gate+o_proj
    • linear_attention: GatedDeltaNet 整层
    • MoE: gate / routing topk / routed experts / shared expert / TP all-reduce
    • DecoderLayer: norm / attention / MLP

注意profiling 强制同步会显著拖慢请求,因此 profiling 结果只用于定位瓶颈,不作为真实性能分数。

本地归档:

  • worklogs/remote_results/2026-07-14-code-profile/server_profile_mode_eager_custom_ar_seq2_b8192.log
  • worklogs/remote_results/2026-07-14-code-profile/profile_mode_eager_custom_ar_short_c1_t24_r1.json

关键 profiling 结果

短请求 c1, max_tokens=24修正标签后rank0 在 step=24 的主要累计耗时:

模块 总耗时 平均单层/次 次数 判断
prefill.moe.routed_prefill_experts 5239.90ms 65.50ms 80 首 token 慢的最大来源
prefill.layer.linear_attention 2944.76ms 49.08ms 60 prefill 第二大来源
decode.layer.mlp 1456.57ms 1.66ms 880 decode 最大来源
decode.layer.linear_attention 942.63ms 1.43ms 660 decode 第二大来源
decode.moe.routed_total 785.65ms 0.89ms 880 MLP 中 routed expert 为主
decode.moe.routed_decode_experts 540.24ms 0.61ms 880 单 token MoE expert 计算
decode.layer.full_attention 322.06ms 1.46ms 220 full attention 不是第一瓶颈
decode.moe.tp_all_reduce 265.92ms 0.30ms 880 通信有成本,但不是最大项

ModelRunner 粗粒度:

decode.model_forward avg ~= 145.6ms
decode.compute_logits avg ~= 1.17ms
decode.sample avg ~= 1.01ms

结论decode 慢主要发生在模型 forward 内部logits 和 sampler 不是主瓶颈。

为什么 GPU 算力打不满

当前路径的 GPU 利用率低,不是因为单个大矩阵乘算不过来,而是因为每 token 被拆成大量小工作:

  1. MoE 没有真正 fused kernel

    • 注释里已经说明 BI-V100 上缺少 vllm_moe_topk_softmax / vllm_invoke_fused_moe_kernel
    • 当前 routed expert 是纯 PyTorch 实现。
    • prefill 通用路径按 expert 做 Python 循环,长 prompt 下会产生大量小 GEMM 和 CPU/GPU 同步点。
  2. decode batch 太小

    • max_num_seqs=2 时每步只有 1-2 token。
    • 小 batch 下矩阵乘规模小kernel launch、Python 调度、TP 同步成本占比很高。
  3. 模型结构有大量 GatedDeltaNet linear_attention 层

    • profiling 显示 linear_attention 在 prefill 和 decode 都是大头之一。
    • 这部分不是标准 paged attention不能靠换 xFormers attention backend 直接解决。
  4. TP all-reduce 不是第一瓶颈,但放大了小 kernel 问题

    • decode MoE all-reduce 单次约 0.30ms。
    • 单次看不大,但每层一次、每 token 多次累积,且会让 rank 间等待更明显。

第一轮针对性优化MoE tiny-batch fast path

发现:原代码只有 T == 1 的 MoE decode fast path。一旦并发 decode T == 2,会落入通用 routed_prefill_experts 路径:

unique_eids = topk_ids.view(-1).unique().tolist()
for eid in unique_eids:
    ...

这条路径适合大 prefill但不适合 max_num_seqs=2 的小批量 decode。

本轮新增 T <= 2 fast path

  • T * top_k 个选中 expert 展平。
  • 用两次 batched torch.bmm 计算 gate/up 和 down。
  • 避免 Python per-expert loop。
  • 保留 T == 1 原 fast path 不变。

优化结果

对比同口径 short c2, max_tokens=128, requests=4

版本 成功率 TTFT P90 per-request Output TPS P10 Aggregate Output TPS
优化前 custom all-reduce on 100% 1.46s 4.22 8.07
tiny-batch MoE fast path 100% 4.42s 5.95 10.60

decode 聚合吞吐提升约 31%。TTFT 变差可能来自冷缓存/加载后首次请求抖动,后续需要用多轮 warmup 后再复测。

本地归档:

  • worklogs/remote_results/2026-07-14-code-profile/decode_tiny_batch_moe_short_c2_t128_r4.json
  • worklogs/remote_results/2026-07-14-code-profile/server_tiny_batch_moe_eager_custom_ar_seq2_b8192.log

下一步优化方向

优先级从高到低:

  1. MoE prefill 路径

    • 当前 prefill.moe.routed_prefill_experts 是 TTFT 最大来源。
    • 需要把 Python per-expert loop 替换成更批量化的 grouped GEMM / batched GEMM。
    • 官方负载长上下文输入占比极高,提升 prefill 会直接改善 TTFT 和 weighted throughput。
  2. GatedDeltaNet linear_attention

    • decode 和 prefill 都是大头。
    • 需要进一步拆分 projection、conv/update、state update、out_proj确认是 recurrent update 还是投影占主。
  3. 更稳的并发策略

    • tiny-batch MoE 已证明 T=2 能受益。
    • 后续可测试 max_num_seqs=3/4,但受 100K 上下文 KV cache 与 TTFT 影响,需要小心。
  4. TP all-reduce 合并

    • 当前 MoE 每层 routed+shared 后做一次 all-reduce。
    • 如果后续能将若干小通信或 residual 路径合并,可能进一步改善 decode 抖动,但优先级低于 MoE/linear_attention 计算本体。

2026-07-14MoE decode fast path v2tokenwise

背景

上一轮 T <= 2 tiny-batch fast path 使用 batched torch.bmm,将 T * top_k 个 expert 选择展平后批量计算。它已经将 c2 aggregate Output TPS 从 8.07 提升到 10.60。

继续分析后发现,对于极小 batchT=2

  • 第一段 gate/up projection 用 torch.bmm 实际是 T * top_k1 x H 小矩阵乘。
  • T == 1 路径用的是 F.linear(hidden, w13_sel.reshape(-1, H)),会形成一个更大的 GEMM通常更适合 GPU。

因此新增 v2tokenwise tiny-batch 实现。

实现

新增环境变量:

  • ENGINEX_MOE_TINY_IMPL=tokenwise|bmm
  • ENGINEX_MOE_TINY_MAX=4

默认:

ENGINEX_MOE_TINY_IMPL=tokenwise
ENGINEX_MOE_TINY_MAX=4

核心逻辑:

  • T <= 4 的小批量 decode逐 token 走已经验证过的 T == 1F.linear 路径。
  • 每个 token 内仍然把 top-k expert 的 w13 拼成一个大权重,减少第一段 projection 的小 GEMM。
  • 保留上一轮 batched bmm 实现,可通过 ENGINEX_MOE_TINY_IMPL=bmm 回退做 A/B 测试。

代码位置:

  • qwen3_6_scripts/qwen3_5.py
  • Qwen3_5MoeSparseBlock._pure_pytorch_experts

结果

同口径 short c2, max_tokens=128, requests=4

版本 成功率 TTFT P90 per-request Output TPS P10 Aggregate Output TPS
custom all-reduce on优化前 100% 1.46s 4.22 8.07
tiny-batch bmm 100% 4.42s 5.95 10.60
tiny-batch tokenwise v2 100% 4.49s 6.43 11.28

相对上一版 bmmaggregate Output TPS 又提升约 6.4%;相对优化前提升约 39.8%

本地归档:

  • worklogs/remote_results/2026-07-14-code-profile/decode_moe_tokenwise_short_c2_t128_r4.json
  • worklogs/remote_results/2026-07-14-code-profile/server_moe_tokenwise_eager_custom_ar_seq2_b8192.log

结论

这个结果说明:对当前 BI-V100 + PyTorch fallback MoE 路径,将小 GEMM 尽量合并为较大的 per-token GEMM 比把所有 token/expert 都塞进 batched bmm 更好。瓶颈确实集中在 decode MoE routed expert 的小矩阵计算与调度开销。

下一步可继续沿两个方向走:

  1. 测试 max_num_seqs=4,利用 T <= 4 tokenwise fast path看 aggregate Output TPS 是否继续上涨。
  2. 继续优化 shared expert / linear_attention因为 routed expert 已经明显改善decode 下一个大头会逐渐转向 linear_attention 和 shared expert。

2026-07-15平台提交构建/启动配置修复

背景

官方平台仍提示当前提交的 Docker 镜像构建失败。对比一个已能进入 benchmark-agent 阶段的成功案例仓库 fangfangssj/enginex-vllm-bi100-qwen36 后,确认我们的根目录结构没有问题,根目录已有 Dockerfilecomputility-run.yaml

成功案例日志中的 BENCHMARK_DOCKER_IMAGE_NOT_READY 只是镜像正在下载,随后已经创建 benchmark-agent 容器;因此该案例至少通过了代码仓库识别、镜像构建和镜像拉取阶段。

对齐内容

本次将当前仓库的平台提交相关文件对齐成功案例:

  • computility-run.yaml
    • 上下文从 100000 调整为 245000,满足官方说明中 235K+ 输入窗口需求。
    • max_num_batched_tokens8192 调整为 16384
    • 增加 --chat-template /workspace/chat_template_multi_system.jinja,兼容多/乱序 system 消息。
    • 增加 PYTHONPATHLD_LIBRARY_PATHPATH 和五个 native 优化开关环境变量。
  • qwen3_6_scripts/patch_ops.sh
    • 增加 CoreX/OpenMPI 环境变量。
    • 增加 libcusolver.so 软链,避免 torch.linalg.solve_triangular 在容器内找不到库。
    • 增加 multi-system chat template 部署。
    • 增加 patch 后验证输出。
  • 新增/更新成功案例依赖文件:
    • qwen3_6_scripts/chat_template_multi_system.jinja
    • qwen3_6_scripts/xformers.py
    • qwen3_6_scripts/paged_attn.py
    • qwen3_6_scripts/patch_xformers_sdpa_seq.py
    • qwen3_6_scripts/protocol.py
    • qwen3_6_scripts/qwen3_5.py

影响

这次优先目标是恢复平台可构建、可启动、可进入 benchmark-agent 验证流程。为降低平台构建风险,直接采用成功案例中已验证的一组补丁文件;此前本地 MoE tiny-batch 实验代码可能被成功案例的 native MoE/GDN 优化实现覆盖,后续如果平台构建通过,再基于这套可提交基线继续做性能优化分支。

2026-07-15MoE tiny-batch 矩阵实验

实验过程

远端实验目录:

  • /root/work/logs/moe_tiny_matrix_20260715_codex_05

本地归档:

  • worklogs/remote_results/2026-07-15-moe-tiny-matrix/

前两次启动没有形成有效结果:

  • codex_03:未加 --enforce-eager,服务加载后卡在 CUDA Graph capture/health 一直拒绝连接。
  • codex_04:已加 --enforce-eager,但旧的 codex_03 服务仍占用显存,导致新服务加载 MoE 权重时 OOM。

有效实验为 codex_05,先清理旧进程,再用 --enforce-eager 重跑 5 组矩阵。

固定压测口径:

  • prompt_mode=short
  • concurrency=2
  • requests=4
  • max_tokens=128
  • max_num_seqs=2
  • max_model_len=100000
  • tp=4
  • enable_prefix_caching
  • enable_chunked_prefill

结果

case success TTFT P90 Output TPS P10 Output TPS P50 Aggregate Output TPS Avg GPU util Max GPU util
bmm_max16 100.00% 1.401 6.245 6.381 11.958 53.2 100.0
bmm_max8 100.00% 1.424 6.900 6.957 12.907 54.7 100.0
tokenwise_max16 100.00% 1.312 6.292 6.387 12.003 54.8 100.0
tokenwise_max4 100.00% 1.671 6.377 6.416 11.868 51.4 100.0
tokenwise_max8 100.00% 1.589 6.347 6.357 11.788 54.6 100.0

结论

本轮最佳结果是:

  • ENGINEX_MOE_TINY_IMPL=bmm
  • ENGINEX_MOE_TINY_MAX=8
  • Aggregate Output TPS = 12.907
  • Output TPS P10 = 6.900

相对上一轮 tokenwise_max4 的有效结果 11.28,本轮 bmm_max8 聚合吞吐约提升 14.4%。但要注意两轮测试的启动环境不同:本轮强制 --enforce-eager,且经过多次重启,不能把这个提升完全归因于 bmm 实现。

更重要的是,这个矩阵没有真正验证 T > TINY_MAX 后按块拆分是否有收益。原因是本轮压测固定 max_num_seqs=2decode 阶段 T 基本是 1-2因此 TINY_MAX=4/8/16 都会命中同一 tiny fast path。也就是说

  • tokenwise_max4/8/16 之间的差异主要是重启和运行噪声。
  • bmm_max8/16 之间的差异也主要是噪声。
  • 本轮更有价值的对比是 tokenwisebmm 两种 tiny implementation。

从本轮结果看,在 T≈2 的短输出 decode 场景里,bmm 路径不差于 tokenwise,并且本次最佳是 bmm_max8。下一步如果要验证“T 大于阈值后分块计算”的思路,需要把 max_num_seqs 提到 8 或 16或构造能让单步 decode 聚合更多 token 的压测,否则 TINY_MAX 参数不会真正生效。

2026-07-15Docker build CRLF 根因修复

现象

平台持续提示 Docker 镜像构建失败。将当前 Git HEAD 通过 git archive 打包到远端 Linux 后执行:

cd /root/work/submission_head/qwen3_6_scripts
./patch_ops.sh

首次复现到错误:

./patch_ops.sh: line 11: $'\r': command not found
./patch_ops.sh: line 217: syntax error: unexpected end of file

根因

本地 Git 配置 core.autocrlf=true。在没有 .gitattributes 约束时,git archive 导出的 patch_ops.sh 变成 CRLF 行尾。Linux/Docker 中执行 RUN cd ./qwen3_6_scripts && ./patch_ops.sh 会直接失败。

修复

新增 .gitattributes,强制构建相关文本文件以 LF 行尾导出:

Dockerfile text eol=lf
*.sh text eol=lf
*.py text eol=lf
*.yaml text eol=lf
*.yml text eol=lf
*.jinja text eol=lf

重新 git archive HEAD 后,本地确认 patch_ops.sh 行尾为 LF。再传到远端执行patch_ops.sh 已完整跑通,末尾验证:

[ok] MOE_NATIVE
[ok] _native_experts_sorted
[ok] CHUNK_PARALLEL
[ok] LOOP1_NATIVE
[ok] RMSNORM_NATIVE
[ok] PREFIX_FLASH
[ok] _forward_prefix_flash
[ok] libcusolver.so link
[ok] /workspace/chat_template_multi_system.jinja
[ok] torch.linalg.solve_triangular callable
=> all five optimizations deployed and enabled

对应远端日志:

  • /root/work/logs/patch_head_lf_20260715.log

2026-07-15官方相似负载基线与启动稳定性

配置

本轮使用官方相似合成数据集:

  • 远端数据集:/root/work/logs/synthetic_cumulative_8_s3_cap40k.jsonl
  • 请求数8
  • 并发1
  • max_tokens=256
  • --max-model-len 100000
  • --max-num-seqs 1
  • --max-num-batched-tokens 16384
  • --enable-chunked-prefill
  • --enable-prefix-caching
  • --chat-template /workspace/chat_template_multi_system.jinja
  • --enforce-eager
  • Native 优化环境:MOE_NATIVE=1 CHUNK_PARALLEL=1 PREFIX_FLASH=1 RMSNORM_NATIVE=1 LOOP1_NATIVE=1

本地归档结果:

  • worklogs/remote_results/2026-07-15-official-like-baseline/eager_c1_r8_t256_cap40k.json
  • worklogs/remote_results/2026-07-15-official-like-baseline/patch_head_lf_20260715.log

结果

指标 当前值 目标 结论
Success Rate 100.00% >= 99% 通过
Cache Hit Rate 74.17% >= 50% 通过
TTFT P90 9.803s <= 5s 未通过
Output TPS P10 5.825 >= 20 未通过
Weighted Token Throughput 883.109 >= 8000 未通过
Aggregate Output TPS 5.789 - 参考
Aggregate Uncached Input TPS 178.330 - 参考
Aggregate Cache TPS 512.015 - 参考

观察

官方相似负载下,当前瓶颈不再是模板兼容或缓存命中率,而是长上下文 prefill/缓存读取吞吐和 decode routed expert 吞吐都偏低。8 条请求中,短输出 decode 约 5.8-8.5 tok/sTTFT P90 被第二条长请求拉到 9.8s。

不加 --enforce-eager 的 CUDA Graph 路径在 BI-V100 当前环境中启动 OOM典型日志为

torch.cuda.OutOfMemoryError: Tried to allocate 256MiB. GPU 1 ...

因此提交平台配置先加入 --enforce-eager,目标是保证镜像启动和评测稳定。后续如果要重新打开 CUDA Graph需要先解决图捕获阶段额外显存开销否则平台会直接启动失败。

异常处理

尝试 --max-num-batched-tokens 32768 后服务启动 OOM并留下 GPU1-3 约 30GB 显存残留。ixsmi 进程表为空,但 ps 发现上一次 worker 残留为 multiprocessing.spawn 子进程。清理残留进程后,显存恢复到每卡约 257MiB。

结论:后续每次 OOM 后必须先确认:

ps -ef | grep -E 'multiprocessing.spawn|resource_tracker|vllm.entrypoints|api_server'
ixsmi

若存在残留 worker需清理后再继续实验否则会把脏显存误判为参数不可行。

下一步

  1. --enforce-eager + max_num_batched_tokens=16384 作为稳定提交基线。
  2. 在干净显存下重新验证 max_num_batched_tokens=32768 是否必然 OOM。
  3. 对官方相似负载做参数矩阵:max_num_batched_tokensgpu_memory_utilizationmax_model_lenmax_num_seqs
  4. 若参数调优收益不足,继续深入 PREFIX_FLASH 和 MoE routed expert 的 native/kernel 路径 profiling。

2026-07-15245K 平台等价配置验证

重要启动细节

远端手工测试时,不能从仓库根目录启动:

cd /root/data-disk-1/enginex-vllm-bi100-qwen36
python3 -m vllm.entrypoints.openai.api_server ...

这样会优先导入仓库内未补丁的 vllm/,导致:

KeyError: 'invalid tool call parser: qwen3_coder (chose from { hermes,internlm,llama3_json,mistral })'

平台 Dockerfile 只复制 qwen3_6_scripts 并执行 patch_ops.sh,不会复制仓库根目录的 vllm/,因此平台会使用补丁后的 corex vLLM。远端手工测试需要从 /root/workspace 等非仓库目录启动,以匹配平台行为。

245K 启动结果

/root 启动平台等价配置成功:

  • --max-model-len 245000
  • --max-seq-len-to-capture 245000
  • --enforce-eager
  • --max-num-batched-tokens 16384
  • --max-num-seqs 1
  • --tool-call-parser qwen3_coder
  • --reasoning-parser qwen3
  • --chat-template /workspace/chat_template_multi_system.jinja

启动后显存约为:

  • GPU0: 28.4GB
  • GPU1: 28.3GB
  • GPU2: 28.3GB
  • GPU3: 28.2GB

说明 --enforce-eager 后 245K 正确性配置可以启动,但剩余显存空间已经很紧。

245K 官方相似压测

本地归档:

  • worklogs/remote_results/2026-07-15-official-like-baseline/eager_245k_c1_r8_t256_cap40k.json
指标 100K 基线 245K 配置 目标
Success Rate 100.00% 100.00% >= 99%
Cache Hit Rate 74.17% 74.17% >= 50%
TTFT P90 9.803s 24.886s <= 5s
Output TPS P10 5.825 5.532 >= 20
Aggregate Output TPS 5.789 4.631 -
Uncached Input TPS 178.330 139.628 -
Cache TPS 512.015 400.894 -
Weighted Throughput 883.109 693.102 >= 8000

245K 配置满足上下文窗口正确性,但对当前小样本性能明显更差。后续优化策略:

  1. 平台提交配置保留 245K以覆盖官方 235K+ 输入上限。
  2. 快速性能迭代优先用 100K/cap40K 官方相似数据,减少单轮成本。
  3. 确定有效优化后,再回到 245K 配置做确认。
  4. 当前离目标最大的差距仍是 prefill/cache 吞吐和 decode MoE 吞吐,而不是缓存命中率或请求成功率。

2026-07-15MoE router top-k softmax 实验

改动

尝试把 MoE router 从:

routing_weights = torch.softmax(router_logits.float(), dim=-1)
topk_weights, topk_ids = torch.topk(routing_weights, self.top_k, dim=-1)
topk_weights = topk_weights / topk_weights.sum(dim=-1, keepdim=True)

改为:

topk_logits, topk_ids = torch.topk(router_logits.float(), self.top_k, dim=-1)
topk_weights = torch.softmax(topk_logits, dim=-1)

数学上这等价于 “full softmax -> topk -> renormalize”但避免了完整 (T, num_experts) softmax。预期收益主要来自减少每层 decode 路由开销。

结果

本地归档:

  • worklogs/remote_results/2026-07-15-official-like-baseline/eager_100k_router_topk_c1_r8_t256_cap40k.json
指标 100K 基线 router top-k softmax 目标
Success Rate 100.00% 100.00% >= 99%
Cache Hit Rate 74.17% 74.17% >= 50%
TTFT P90 9.803s 13.048s <= 5s
Output TPS P10 5.825 5.664 >= 20
Aggregate Output TPS 5.789 5.379 -
Uncached Input TPS 178.330 172.454 -
Cache TPS 512.015 495.142 -
Weighted Throughput 883.109 850.328 >= 8000

结论

该改动没有带来收益,反而略降。可能原因:

  • full softmax 相对 expert GEMM 和 TP all-reduce 占比很小;
  • topk logits 后 softmax 改变了 kernel 调度形态,但没有减少主瓶颈;
  • 小样本中输出 token 数不同,仍需看趋势而不是单条。

操作结论:不保留该代码改动,回退到 full-softmax router。下一步继续围绕 MoE expert GEMM、all-reduce、prefill/cache 路径做实验。

2026-07-15关闭默认 PROF_TRACE

改动

qwen3_5.py_prof() 原本默认开启:

_PROF_TRACE = os.environ.get("PROF_TRACE", "1") != "0"

这会在 decode 阶段输出大量 [PROF] 行,并且每次 flush=True。改为默认关闭:

_PROF_TRACE = os.environ.get("PROF_TRACE", "0") == "1"

需要代码级 profiling 时再显式设置 PROF_TRACE=1

结果

本地归档:

  • worklogs/remote_results/2026-07-15-official-like-baseline/eager_100k_no_prof_c1_r8_t256_cap40k.json
指标 100K 基线 PROF_TRACE 默认关闭 目标
Success Rate 100.00% 100.00% >= 99%
Cache Hit Rate 74.17% 74.17% >= 50%
TTFT P90 9.803s 12.739s <= 5s
Output TPS P10 5.825 5.851 >= 20
Aggregate Output TPS 5.789 5.674 -
Uncached Input TPS 178.330 169.148 -
Cache TPS 512.015 485.652 -
Weighted Throughput 883.109 840.712 >= 8000

结论

关闭默认 profiling 没有带来明显性能收益,说明当前主要瓶颈不是日志 I/O。不过该改动仍应保留在生产提交中

  • 避免评测日志膨胀;
  • 避免 profiling 输出影响长时间 benchmark 稳定性;
  • profiling 仍可通过 PROF_TRACE=1 手动开启。

下一步继续看 MoE routed expert 和 TP all-reduce服务日志中 decode 稳态仍约 5.8 tok/s说明专家计算/通信路径仍是核心。

2026-07-15max_num_batched_tokens=32768 实验

配置

在 no-prof 代码基础上,仅把:

--max-num-batched-tokens 16384

改为:

--max-num-batched-tokens 32768

其余保持 100K 快速基线一致。此前 32768 发生过 OOM但这次在清理残留 worker 和显存后可正常启动,说明之前 OOM 不能作为参数不可行的证据。

结果

本地归档:

  • worklogs/remote_results/2026-07-15-official-like-baseline/eager_100k_b32768_c1_r8_t256_cap40k.json
指标 100K 基线 no-prof 16384 no-prof 32768 目标
Success Rate 100.00% 100.00% 100.00% >= 99%
Cache Hit Rate 74.17% 74.17% 74.17% >= 50%
TTFT P90 9.803s 12.739s 13.005s <= 5s
Output TPS P10 5.825 5.851 5.895 >= 20
Aggregate Output TPS 5.789 5.674 5.556 -
Uncached Input TPS 178.330 169.148 173.430 -
Cache TPS 512.015 485.652 497.947 -
Weighted Throughput 883.109 840.712 857.595 >= 8000

结论

32768 可以启动但没有带来总体收益Output TPS P10 略升TTFT 继续变差,综合吞吐仍低于原 100K 基线。单纯扩大 chunk 不是主优化方向。下一步尝试 8192验证更小 chunk 是否能改善 TTFT。

2026-07-15max_num_batched_tokens=8192 实验

流程改进

新增可复用远端实验脚本:

  • worklogs/remote_scripts/run_batched_tokens_experiment.sh

脚本负责:

  • 停止旧 api_server
  • 检查启动前 GPU 显存;
  • 按指定 max_num_batched_tokens 启动 100K/no-prof 服务;
  • 等待 /health
  • 运行 8 请求官方相似压测;
  • 写入 driver.logserver.log 和结果 JSON。

这能减少超长 SSH 命令导致的本地 PowerShell/SSH 子进程异常,也让后续实验日志格式统一。

结果

本地归档:

  • worklogs/remote_results/2026-07-15-official-like-baseline/eager_100k_b8192_c1_r8_t256_cap40k.json
  • worklogs/remote_results/2026-07-15-official-like-baseline/driver_b8192.log
指标 100K 基线 no-prof 8192 no-prof 16384 no-prof 32768 目标
Success Rate 100.00% 100.00% 100.00% 100.00% >= 99%
Cache Hit Rate 74.17% 74.17% 74.17% 74.17% >= 50%
TTFT P90 9.803s 13.996s 12.739s 13.005s <= 5s
Output TPS P10 5.825 5.840 5.851 5.895 >= 20
Aggregate Output TPS 5.789 5.513 5.674 5.556 -
Uncached Input TPS 178.330 168.408 169.148 173.430 -
Cache TPS 512.015 483.527 485.652 497.947 -
Weighted Throughput 883.109 834.751 840.712 857.595 >= 8000

结论

8192 没有改善 TTFT反而是本轮 chunk 参数中最弱。8192/16384/32768 三组的 Output TPS P10 都约 5.8-5.9,差别很小,说明当前目标瓶颈不是 chunk 大小,而是 decode 每 token 的层内计算/通信周期,以及长上下文 prefill/cache 路径的底层吞吐。

下一轮方向:

  1. 对 decode 稳态做低开销 profiling只记录少量聚合计时避免 PROF_TRACE 每层每 token 打日志。
  2. 拆分 MLP 时间中 routed expert、shared expert、TP all-reduce 的占比。
  3. 若 all-reduce 占比高,测试 --disable-custom-all-reduce 保持关闭/打开的差异和 TP 通信路径。
  4. 若 routed expert 占比高,继续优化 _native_experts_sorted 中逐 expert 循环和 index_add_

2026-07-15MoE 聚合 profiling

改动

qwen3_5.py 增加默认关闭的聚合 profiling

  • PROF_MOE_SUMMARY=1 开启;
  • PROF_MOE_SYNC=1 时每段前后 torch.cuda.synchronize(),用于获得更可信的 GPU 段时间;
  • PROF_MOE_INTERVAL 控制每多少次 MoE forward 输出一次汇总;
  • 分桶输出 bucket=decode (T=1) 与 bucket=prefill (T>1)。

默认 benchmark 不开启这些变量,因此生产路径不受影响。

归档

  • worklogs/remote_results/2026-07-15-official-like-baseline/eager_100k_b16384_moe_profile_r2_t256_cap40k.json
  • worklogs/remote_results/2026-07-15-official-like-baseline/driver_moe_profile.log
  • worklogs/remote_results/2026-07-15-official-like-baseline/moe_summary_profile.log
  • worklogs/remote_results/2026-07-15-official-like-baseline/eager_100k_b16384_moe_bucket_profile_r2_t256_cap40k.json
  • worklogs/remote_results/2026-07-15-official-like-baseline/driver_moe_bucket_profile.log
  • worklogs/remote_results/2026-07-15-official-like-baseline/moe_bucket_summary_profile.log

关键结果

bucket 分桶的最终平均值如下。数值是每个 worker 进程最终 summary 的均值,单位为 ms / MoE layer call。

Bucket router gate routed expert shared expert all-reduce total
decode (T=1) 0.069 0.768 0.235 0.333 1.416
prefill (T>1) 0.577 77.970 2.078 4.177 84.814

占比:

Bucket router gate routed expert shared expert all-reduce
decode 4.9% 54.2% 16.6% 23.5%
prefill 0.7% 91.9% 2.4% 4.9%

结论

MoE routed expert 是最明确主瓶颈:

  • 对 TTFT/prefillrouted expert 占 MoE 时间约 92%,优先级最高。
  • 对 Output TPS/decoderouted expert 仍是最大项,约 54%all-reduce 约 23%,属于第二优先级。

下一步优先做 routed expert 路径优化:

  1. decode T=1 路径尝试使用 ixformer.functions.act_bias_mm + silu_and_mul 替换 F.linear + torch 激活,保留 bmm down projection。
  2. prefill _native_experts_sorted 重点减少逐 expert Python loop / 小 kernel 串行开销;需要查 ixformer 是否提供 grouped GEMM 类接口。
  3. all-reduce 暂时不是第一主因,但 decode 中占 23%,后续可独立测通信路径。