GE 多流自动寻优样例

English

GE 的自动多流提供多种流分配策略(LoadBalanceMainStreamWeightedLoadBalancecv), 不同策略与流数组合对端到端耗时影响显著,且与模型结构、芯片型号强相关,只能实测选优。

本样例提供最小可用的寻优闭环:用环境变量下发候选配置 → 自定义 Pass 写入根图属性 → GE 打点输出每步耗时 → 驱动脚本统计排名并给出推荐配置

能力边界

本样例做的事:

  • 提供一个通用自定义 Pass,把候选配置写入根图属性,被测样例无需改代码;
  • 提供一个寻优驱动脚本,遍历候选、反复执行、解析 STEP 日志、排名并推荐配置;
  • 支持两种执行方式:在线(本机跑被测命令)与离线(编译机逐候选编 OM → 传到目标机 → 远端执行 → 回传 plog → 统一解析);
  • 提供一个最小被测样例,用于验证整条链路是否打通。

本样例不做的事:

  • 不校验各候选的计算结果一致性(需要时在被测样例里自行比对);
  • 不支持断点续跑,中断后需重跑(可用 --configs 拆批降低损失);
  • 不管理 CANN 环境安装与设备独占。

目录结构

multi_stream_autotune/
├── README.md / README_en.md
├── ge_ms_autotune.py        寻优驱动:遍历候选、解析 STEP、排名推荐
├── sample_run.py            最小被测样例:多分支静态图 + Session 反复执行
└── custom_pass/             通用自定义 Pass:环境变量 → 根图属性
    ├── CMakeLists.txt
    └── src/ge_ms_autotune_pass.cpp

原理

配置下发链路:

ge_ms_autotune.py  --(GE_AUTO_MULTISTREAM_PARALLEL_MODE=LoadBalance:4)-->  被测进程
                                                                              │
                              custom_pass 在 kBeforeInferShape 阶段读取环境变量  │
                                                                              ▼
                                        根图属性 ge.autoMultistreamParallelMode = LoadBalance:4
                                              _auto_multistream_tuning_mode  = LoadBalance:4
                                                                              │
                                                                              ▼
                                        GE 按该属性做流分配,并对每次执行输出 STEP 打点

几个关键点:

  • GE 不读取 GE_AUTO_MULTISTREAM_PARALLEL_MODE,该变量只是本样例 Pass 与驱动脚本之间的约定, 作用是在不改被测样例的前提下逐候选切换配置。
  • 图属性优先于同名 optionge.autoMultistreamParallelMode 图属性存在时, 会覆盖 Session/ATC 传入的同名 option。
  • _auto_multistream_tuning_mode 是调测身份属性,只有它存在时 GE 才输出 STEP 打点。 该属性随 GeModel 保存进 OM,因此离线执行同样能打点。
  • 打点含同步等待(执行器按实际 stream 同步界定完成边界),只用于寻优调测, 生产态不要开启。
  • default 也是一个候选,表示不启用自动多流的基准,同样由图属性下发,用于算加速比。

前置条件

  • 与被测 GE 配套的 CANN,且包含图属性自动多流与 STEP 打点能力;
  • 可用的昇腾 NPU 设备,测试期间关闭 profiling、尽量独占设备;
  • 编译 Pass 需要 CMake 3.13+、支持 C++17 的编译器,以及 $ASCEND_HOME_PATH/include/register/register_custom_pass.h
  • 驱动脚本只依赖 Python 3.7+ 标准库。
source /path/to/cann/set_env.sh
export ASCEND_HOME_PATH=/path/to/cann
test -f "$ASCEND_HOME_PATH/include/register/register_custom_pass.h"

步骤一:编译并安装寻优 Pass

cd examples/multi_stream_autotune
cmake -S custom_pass -B build -DASCEND_HOME_PATH="${ASCEND_HOME_PATH}"
cmake --build build --parallel

# 安装到 GE 扫描的自定义 Pass 目录(vendors 下的目录名可自取)
PASS_DIR="${ASCEND_OPP_PATH:-$ASCEND_HOME_PATH/opp}/vendors/ge_ms_autotune/custom_fusion_passes"
mkdir -p "${PASS_DIR}"
install -m 750 build/libge_ms_autotune_pass.so "${PASS_DIR}/"

注意:

  • 该目录下若已有其他会写多流图属性的 Pass,请先移走,否则属性会被互相覆盖;
  • 寻优结束后请删除该 so,避免调测打点与强制属性带到生产环境: rm -f "${PASS_DIR}/libge_ms_autotune_pass.so"

步骤二:准备被测样例

被测样例(即寻优时反复执行、用来比较耗时的那个程序)可以是任意在进程内完成编图并反复执行的 命令,需满足:

  • 每次进程启动都重新编图,不复用上一个候选留下的图/模型缓存;
  • 各候选使用完全相同的输入与迭代次数,warmup 之后至少执行 --min-steps 次;
  • 通过退出码反馈成败(非 0 的轮次不参与排名)。

sample_run.py 是一个参照实现:构造四条互不依赖的 pointwise 分支的静态图(多流下可分派到 不同流),预热 1 步后执行 --steps 轮:

python3 sample_run.py --steps 12 --dim 512

步骤三:运行寻优

python3 ge_ms_autotune.py \
    --run-command "python3 sample_run.py --steps 12" \
    --strategies LoadBalance,MainStream \
    --streams 2,4,8 \
    --repeat 3 \
    --output-dir ./tune_out

驱动脚本对每个候选:注入 GE_AUTO_MULTISTREAM_PARALLEL_MODE 和独立的 ASCEND_PROCESS_LOG_PATH → 执行命令 → 从 stdout 与该轮 plog 中收集 STEP → 校验后统计。控制台输出形如:

候选配置(7 个 × 3 轮):default, LoadBalance:2, LoadBalance:4, ...

[000] 配置=default 第 1 轮:python3 sample_run.py --steps 12
      退出码=0 STEP=13 有效=是 耗时=21.4s
...

寻优结果(按中位耗时升序):
配置                      有效轮次  步数    平均(ms)    中位(ms)    P90(ms)     CV        加速比      结论
LoadBalance:4             3/3       36      12.104      12.088      12.301      0.014     1.243     提升
MainStream:4              3/3       36      13.552      13.489      13.702      0.011     1.114     提升
default                   3/3       36      15.037      15.028      15.311      0.009     1.000     持平

[结论] 推荐配置:LoadBalance:4,相对 default 加速比 1.243,中位耗时 12.088 ms。
[复现] GE_AUTO_MULTISTREAM_PARALLEL_MODE=LoadBalance:4 python3 sample_run.py --steps 12

一阶段扫完后若想细化流数,指定相邻取值再跑一次即可:

python3 ge_ms_autotune.py --run-command "..." \
    --configs default,LoadBalance:3,LoadBalance:4,LoadBalance:5 --output-dir ./tune_out_stage2

参数说明

参数 默认值 说明
--mode online online 本机执行;offline 编译 OM 后送目标机执行
--run-command online 必填 被测命令,整体加引号;按 shell 词法切分后直接执行,不经过 shell
--compile-command offline 必填 编译 OM 的命令,用 {om}(含 .om)或 {om_prefix}(不含后缀)占位输出路径
--target offline 必填 目标机配置 JSON 路径,字段见离线场景
--om-dir <output-dir>/om offline:OM 产物与编译日志的存放目录
--strategies LoadBalance,MainStream 候选策略,逗号分隔,可选 LoadBalance/MainStream/WeightedLoadBalance/cv
--streams 2,4,8 候选流数,逗号分隔,取值 [1,64]cv 策略不带流数
--configs 直接给定候选(如 default,LoadBalance:4),指定后忽略上面两个矩阵参数
--repeat 3 每个候选重复轮数,正式比较建议不少于 3
--drop-first 1 丢弃前若干个 STEP(预热)
--min-steps 5 单轮有效 STEP 数下限,低于该值判为无效
--main-graph 自动 多执行对象时指定主对象:session_id:graph_idmodel:model_id;默认取 STEP 数最多者
--timeout 1800 单轮超时秒数,0 表示不限制
--output-dir ./ge_ms_autotune_output 结果目录,必须不存在或为空

default 基准会自动加入候选列表并排在首位。

输出与结果解读

tune_out/
├── summary.csv / summary.json          候选汇总(含各轮明细与无效原因)
├── om/                                 仅 offline:各候选 OM 与编译日志
├── target_*.log                        仅 offline:远端目录准备、上传、清理日志
└── trial_000_default_r1/
    ├── stdout.log                      被测命令(offline 为远端 ssh 会话)的 stdout+stderr
    ├── steps.csv                       本轮解析出的全部 STEP
    ├── fetch_plog.log                  仅 offline:plog 回传日志
    └── plog/                           本轮 GE 日志(offline 为目标机回传的副本)

统计口径与推荐规则:

  • 每个候选把各有效轮次主执行对象的步骤耗时合并后统计,排名按中位耗时升序;
  • 加速比 = default 中位耗时 / 候选中位耗时
  • ≥1.05 记为「提升」,[0.98, 1.05) 记为「持平」,<0.98 记为「劣化」;
  • 推荐中位耗时最小且加速比 ≥1.05 的候选;没有这样的候选时建议保持 default
  • CV(变异系数)只用于判断数据稳定程度,不参与排名;CV > 0.05 时会提示增大 --repeat 复测。

数据有效性门禁

命中任一条则该轮不参与排名,并在控制台与 summary.json 中给出原因:

检查项 说明
退出码非 0 被测命令失败或超时
日志异常 STEP 行缺字段、字段非整数、cost_us 与时间区间不一致、执行身份缺失或混用
mode 不匹配 STEP 里的 mode 与当前候选不一致,通常说明 Pass 未安装或被其他 Pass 覆盖
ret/sync_ret 非 0 执行接口或同步接口返回失败
有效步数不足 丢弃预热后主执行对象的 STEP 少于 --min-steps
时间区间重叠 主执行对象的 STEP 区间相互重叠,说明并发提交,不能按串行耗时统计

各候选的计算结果一致性不在检查范围内,需要时请在被测样例里自行校验(例如固定输入并比对输出摘要)。

STEP 日志格式

打点位于执行器内部,在线与离线共用同一批位置,均以 model_id 标识执行对象:

[EVENT] GE(pid,proc): [GE_MS_TUNE][STEP] api=NnExecute mode=LoadBalance:4 \
    model_id=7 step=3 start_us=100 end_us=140 cost_us=40 sync_us=0 ret=0 sync_ret=0
字段 说明
api 打点位置,取值 NnExecute/Run(静态 shape)、ModelV2Executor(RT2.0 动态 shape)
mode 本次执行生效的多流配置,用于反查候选是否真的下发成功
session_id+graph_id / model_id 执行对象身份,二选一;当前执行侧统一输出 model_id
step 步骤序号,从 0 开始
start_us/end_us/cost_us 步骤起止与耗时(微秒),cost_us = end_us - start_us
sync_us 其中的同步等待耗时(微秒)
ret/sync_ret 执行返回值与同步返回值,0 为成功

统计口径为「任务下发 → 流同步完成」,不含 H2D/D2H 拷贝与接口层开销,因此数值小于端到端单步耗时。

覆盖范围:静态 shape(DavinciModel,含队列异步 worker)与 RT2.0 动态 shape(ModelV2Executor) 两条执行栈,在线与离线均覆盖;不覆盖 aclmdlExecuteAsyncV2 与 DFlow 执行链路。

已日落的 RT1.0 动态 shape 执行器(HybridModelRtV1Executor)与 RtV2Pipeline 执行器不打点或数值不可信; OM2 路径不支持自动多流,无法下发候选配置,因此也不在寻优范围内。

离线场景(目标机执行)

编译机与执行机分离时用 --mode offline,驱动脚本完成整条链路:

编译机                                                   目标机
  逐候选 atc 编 OM(候选由 Pass 固化进模型)
        │  scp 一次性上传全部候选 OM
        ├──────────────────────────────────────────────▶  <remote_workdir>/om/
        │  每轮 ssh:清 plog → source CANN → 执行 run_command
        │◀──────────────────────────────────────────────  <remote_workdir>/plog/
        │  scp 回传 plog 到本轮 trial 目录
   解析 STEP → 排名 → 推荐(与在线口径完全一致)
        │  结束后 rm -rf <remote_workdir>

目标机配置

{
  "host": "192.168.1.10",
  "port": 22,
  "user": "tester",
  "identity_file": "~/.ssh/id_rsa",
  "remote_workdir": "/home/tester/ge_ms_tune",
  "cann_env": "/usr/local/Ascend/ascend-toolkit/set_env.sh",
  "run_command": "python3 /home/tester/infer.py --om {om} --loop 20"
}
字段 必填 说明
host / user 目标机地址与登录用户
port SSH 端口,默认 22
identity_file 私钥路径,配置后走密钥认证
remote_workdir 目标机工作目录,存放 OM 与 plog;必须是层级不少于两级的绝对路径,寻优结束会被整目录删除
cann_env 目标机 CANN 的 set_env.sh,执行前 source
run_command 目标机上的推理命令,必须包含 {om} 占位符

认证方式:优先用 identity_file 指定的密钥;未配置密钥时,从环境变量 GE_MS_TARGET_PASSWORD 读密码(需要目标机之外的编译机装有 sshpass,密码经 SSHPASS 传递、不出现在命令行与日志里);两者都没有则使用 ssh 默认密钥。密码不要写进 JSON。

目标机上的推理程序

run_command 指向的推理程序由你自己准备并部署到目标机(scp/rsync/镜像/CI 均可), 驱动脚本只上传 OM,不部署程序。该程序需满足:

  • 加载工具传入的 {om}。它会被替换为目标机上该候选 OM 的绝对路径 (<remote_workdir>/om/model_<候选>.om);候选切换完全靠换 OM,程序本身无需感知多流配置;
  • 各候选使用完全相同的固定输入与迭代次数;
  • warmup 之后至少执行 --min-steps 次(默认 5,正式比较建议 20 起);
  • 使用打点覆盖的 ACL 接口 aclmdlExecute/aclmdlExecuteV2/aclmdlExecuteAsync。 走 aclmdlExecuteAsyncV2 不产生 STEP,寻优拿不到数据;OM2 路径不支持自动多流,不能用于寻优;
  • 通过退出码反馈成败,非 0 的轮次不参与排名;
  • 不需要安装寻优 Pass,调测身份属性随 OM 携带。

它在离线侧的角色相当于在线侧的 sample_run.py;离线依赖真实 OM 与设备,样例不提供对应实现。

运行

export GE_MS_TARGET_PASSWORD='...'        # 密钥认证时不需要这行
python3 ge_ms_autotune.py --mode offline \
    --compile-command "atc --model=/data/model.onnx --framework=5 \
                       --soc_version=AscendXXX --output={om_prefix}" \
    --target target.json \
    --strategies LoadBalance,MainStream --streams 2,4 --repeat 3 \
    --output-dir ./tune_out_offline

要点:

  • 不要在 --compile-command 里再传多流 option,属性由 Pass 统一写入,避免两处配置打架;
  • 目标机不需要安装寻优 Pass,调测身份属性随 OM 携带;编译机需要;
  • 每轮执行前会清空目标机的 plog 目录,避免上一轮记录与本轮撞键;
  • --timeout 同时约束编译、远端执行与回传,跨机传输耗时不影响排名(排名用 STEP 里的 cost_us);
  • 单个候选编译失败会立即中止;某一轮远端执行失败只作废该轮,其余继续,结束仍会清理远端目录;
  • 编译机、OM 与目标机的 CANN/GE 版本和芯片型号必须匹配。

落地到生产

寻优结论应固化到业务侧配置,而不是继续依赖本样例的 Pass:

  • 在线:Session 初始化时传入 option ge.autoMultistreamParallelMode=<推荐配置>
  • 离线:atc 编译时传入同名 option;
  • 卸载寻优 Pass(见步骤一),确保 _auto_multistream_tuning_mode 不再写入,关闭调测打点。

常见问题

现象 排查方向
所有候选都提示 mode 与候选不一致 Pass 未安装、装错目录,或被 vendors 下其他 Pass 覆盖
完全没有 STEP 记录 GE 版本不含打点能力;或被测样例走了未覆盖的执行链路(aclmdlExecuteAsyncV2、DFlow)
OM2 模型跑不出结果 OM2 路径不支持自动多流,候选配置下发不进去,无法寻优
提示时间区间重叠 被测样例并发提交多次执行,改为串行执行,或用 --main-graph 指定单一执行对象
候选间耗时差异极小 图本身缺乏可并行分支;或算子粒度过大,多流收益被单算子耗时淹没
CV 偏大、结论不稳定 设备被其他业务占用、profiling 未关闭,或 --repeat/--min-steps 取值过小
ge.enableSingleStream=true 同时配置报参数错误 单流与自动多流互斥,二者只能选一
offline:提示需要 sshpass 编译机未装 sshpass;装上,或改配 identity_file 走密钥认证
offline:ssh 连不上或反复要密码 先手工 ssh -i <key> user@host 验证;驱动使用 BatchMode=yes,不会交互输密码
offline:候选编译失败 <output-dir>/om/compile_<候选>.log;确认 --compile-command{om_prefix} 与实际产物路径一致
offline:STEP 全部缺失 目标机推理程序未走覆盖到的 ACL 接口,或 cann_env 没配导致 plog 落到别处