已关闭
[RFC]: GE支持模型编译生成原生可执行代码 #3
ClarkXie创建于  2025年12月27日关闭于  8月20日
ClarkXie
ClarkXie成员
2025年12月27日 创建

Thanks for sending an requirement! Please fill in the following template to help quickly solve your problem.

Backgroud(背景信息)

在基于图模式的深度学习计算中,模型计算流程被建模为一张计算图。GE 图引擎通过对整图进行全局的优化、编译与调度,有效提升模型执行效率。然而,当前 GE 图引擎对用户而言呈现较强的“黑盒”特性,其内部所执行的图优化策略不透明,导致用户在精度调试与性能调优阶段高度依赖华为官方工具链及技术支持,自主可控性较低。

Origin(信息来源)

图灵业务部

Benefit / Necessity (价值/作用)

  • 图模式快速调试:提供类似 PyTorch eager 的图模式调试体验,用户脚本可直接映射到实际执行算子。支持tensor粒度 Dump 而无需重新编译整张图,并提供脚本化调试能力,加速问题定位。
  • 可调试开发模式:结合脚本话算子编排和gdb调试模式,可用于开发阶段的性能分析、策略验证与算子错误定位。
  • 高效的离线推理部署:优化 OM 文件体积和加载性能,引入结构化打包方式,提高模型结构可解释性。
  • 灵活的开发与扩展:由 GE 完成算子编译与 Task 调度,用户可通过 dlopen so 方式进行二次开发(如构建自定义 AclGraph),实现更灵活的部署、调试和性能优化能力。

Design(设计方案)

Om2在线场景流程图

Om2离线场景流程图

likedislike
ClarkXieClarkXie成员
2025年12月27日 修改了issue 的描述
此处折叠了10条事件消息 查看更多
ClarkXieClarkXie成员
2025年12月27日 修改了issue 的描述
wangyuchen
wangyuchen成员
2025年12月27日 评论:

考虑引入结构化打包方式,提高模型结构可解释性

当前 GE 采用的模型归档格式 OM

OM(Offline Model)是华为昇腾 AI 处理器支持的离线模型归档格式,由图编译器对原始模型进行算子融合、算子调度、内存优化等一系列编译优化后生成,用于昇腾 AI 在处理器上实现高性能推理。OM 模型可以通过 ATC 模型转换工具、IR 构图接口生成,部署于 NPU 设备上执行推理。此外,图编译缓存场景,GE 会将会将编译结果持久化为 OM 模型,当再次编译运行相同图时,直接加载缓存的 OM 以减少编译耗时。

随着 CANN 开源开放与生态易用性建设不断推进,现有OM(Offline Model)格式在支持更透明的调试流程、更灵活的开发模式方面,逐渐显现出其架构上的局限性。具体而言:

  • OM 作为图编译产物,其“黑盒”特性导致模型可解释性不足
    OM 模型归档格式内部为主要由模型首部、模型分区表和若干模型分区构成。模型首部长度是固定 256 字节,首部分区记录模型的版本、大小等元信息。模型分区表由多个分区表项构成,每个表项占用24字节,记录对应分区的类型、文件偏移量及长度。模型分区是一段遵循 protobuf 格式或无格式连续内存,记录了模型中算子信息、权重数据等,其解析方式由分区类型决定。
033df551b63d48909b6eb33044c91415.png
  • OM 模型缺乏调试与执行能力,开发模式单一

当前,用户在生成 OM 模型后,仍需基于 ACL/GE 接口进行二次开发以完成模型的加载与执行。该加载与执行过程对用户而言是不透明的,算子编排与下发完全由框架内部接管,用户无法干预下发过程,GE 执行时出现问题需要工程师协助定位。

OM2 的归档结构如下所示:

<model_name>_<timestamp>.om2
+-- manifest.json                             # 元信息:CANN版本、序列化协议、生成方式等
+-- data
|   +-- model_0
|       +-- model_meta.json                   # 模型的基本信息
|       +-- runtime                           # 模型执行
|           +-- <model_name>_<hashid>_omx.cpp
|           +-- <model_name>_<hashid>_omx.so
|       +-- debug                             # [可选] 调试信息
|           +-- ge_model.json                 # 图编译生成的模型信息
|           +-- task_info.json                # 算子的 Task 信息
|   +-- model_1                               # [可选] 支持多模型打包                         
|       ...
|   +-- kernels_<npu_arch>                    # 算子二进制
|       +-- te_add_xxxx.o
|   +-- constants                             # 所有模型的常量与权重数据
|       +-- model_0_constants_config.json     # model_0 的常量与权重数据
|       +-- model_1_constants_config.json     # model_1 的常量与权重数据
|       +-- constants_0
|       +-- constants_1
|       +-- constants_2
|   +-- so_bins                               # [可选] 算子动态库
...                                           # 可扩展,例如二进制复用场景等

OM2 目录结构与 原 OM 分区对照:

OM2 目录 OM 分区 说明
manifest.json OM Header 对应 OM 的首部信息,存放 OM 文件的名称、平台类型、模型分区大小、模型权重、模型数量等信息。
data/model-<index>/model_meta.json MODEL_INOUT_INFO 记录模型输入、输出 tensor 的信息,包含 shape、数据类型、数据格式、档位等。
data/model-<index>/runtime NA 提供可编译执行的 cpp 源码和可直接执行的 python 脚本。
data/model-<index>/debug/ge_model.pbtxt MODEL_DEF 记录图结构、属性信息,按照 protobuf 序列化。
data/model-<index>/debug/task_info.json TASK_INFO Task 定义,Task 即具体下发给 RTS 执行的任务,每个算子是一个Task,在 OM2 中作为 Debug 信息使用。
data/kernels_<npu_arch> TBE_KERNELS 算子二进制。
data/constants WEIGHTS_DATA 模型权重数据。
data/so_bins SO_BINS 动态 shape、tiling 下沉、算力切分等场景依赖的算子动态库需要打包到 OM 文件中。
likedislike
ClarkXieClarkXie成员
2025年12月27日 修改了issue 的描述
此处折叠了8条事件消息 查看更多
ClarkXieClarkXie成员
1月8日 修改标题为 “[RFC]: GE支持模型编译生成原生可执行代码”,原标题为“[Requirement|需求建议]: GE支持模型编译生成原生可执行代码”
wangyuchen
wangyuchen成员
1月23日 评论:

OM2 模型加载性能与体积分析(穿刺方案)

OM2 模型的加载时间和空间性能不能回退。围绕该目标,选取 ResNet(CV 模型)和 debeta(NLP 模型)测试了加载性能与打包体积,并与当前版本的 OM 进行对比。

模型体积拆解

权重数据是模型存储空间的主体,使用ZIP压缩对权重数据的压缩率约为 8%,空间优化收益有限。整体对比表明(下图中左侧为OM,右侧为OM2),在不压缩处理的情况下,OM2 的整体模型体积并未超过OM。

  • 模型各分区构成
model_size.png
  • OM2模型压缩比(STORED vs. DEFLATE)
Model MODEL(CPP&SO) TBE_KERNELS WEIGHTS_DATA JSON Total
resnet50 80.1% 54.5% 8.0% 65.4% 8.3%
resnet152 81.6% 57.1% 8.0% 65.2% 7.7%
deberta 80.7% 59.4% 7.9% 70.8% 8.0%

OM模型加载流程

  • 加载阶段:读取文件 → 模型反序列化 → 解压属性 → 加载Task及Kernel二进制

  • 下沉阶段:初始化PlatformInfo → 申请Runtime资源 → 拷贝权重 → 注册二进制 → 解析TaskInfo → 组装args → 下沉Task

  • OM 模型各阶段性能拆解

load_perf_om.png

OM2模型加载流程

  • 加载阶段:加载模型SO → 读取权重 → 读取二进制

  • 下沉阶段:申请Runtime资源 → 拷贝权重 → 注册二进制 → 组装args → 下沉Task

  • OM2 模型各阶段性能拆解

load_perf_om2.png

OM2主要优化点
OM2在加载阶段移除了模型反序列化与属性解压过程,并将TaskInfo解析提前至编译阶段完成,从而简化了运行时加载路径。

likedislike
wangyuchenwangyuchen成员
3月4日 关联了pull request:【PR】: 【Feature】GE支持模型编译生成原生可执行代码一阶段
Wwangbin成员
3月10日 关联了pull request:【PR】: 简要描述 OM2模型IO地址刷新
Wwangbin成员
3月19日 关联了pull request:【PR】: 简要描述 静态图OM2模型,支持IO地址刷新
王王涛成员
3月23日 issue类型由 任务 改变为 需求
王王涛成员
3月23日 添加了label:requirement
王王涛成员
3月24日 取消置顶了issue
ClarkXie
ClarkXie成员
3月25日 评论:

OM2一阶段支持能力

atc支持编译生成om2

用户可通过 ATC(Ascend Tensor Compiler)工具进行模型转换,运行模式参数新增 --mode=7 参数值,用于指定模型转换格式为 OM2。执行如下命令可将 resnet50.onnx 模型转换为离线 resnet50_npu.om2 模型。相对于转换 OM 模型,仅需额外指定一个运行模式参数。
atc --mode=7 --model=./model/resnet50.onnx --framework=5 --output=./model/resnet50_npu --soc_version=<soc_version>

om2提供执行时能力

OM2 模型以 “模型即代码” 形式存在,OM2 模型的表达、资源的管理由编译阶段生成的 OM2 模型动态库承载。执行期不再将模型视为需解释的图结构或数据对象,而是通过 dlopen 动态加载模型动态库创建实例,并在执行过程中直接通过 aclrt 接口与 Runtime 交互下发算子。OM2 执行时仅负责模型装载、元数据解析及生命周期管理,模型卸载时通过销毁接口 释放资源。
下图是 OM2 执行时的组件依赖图,相较于现有 OM 执行路径,OM2 执行时不再依赖完整的 GE 图执行组件,其依赖集合被严格限制在 minizip、json、Runtime 执行时及 DFX 组件(当前DFX能力暂未支持,预计630完成完整支持)。

graph TD
    %% Entry Interfaces
    ACLIF[AscendCL API<br/>aclmdl* Interface]
    OM2IF[OM2 Interface]

    %% Model Artifact
    MODEL_SO[om2_model.so<br/>模型原生代码]

    %% ===== OM2 Executor Closure (Highlighted) =====
    subgraph OM2_EXEC_LAYER["OM2 Executor"]
        OM2EXEC[om2_executor<br/>OM2 执行时]
        ZIP[minizip / zlib<br/>归档解析]
        JSON[json<br/>元数据解析]
    end
    
        %% DFX
    DFX[OM DFX Capability<br/>Profiling / Dump]

    %% Runtime
    RT[runtime<br/>NPU Runtime]
    ACLRT[aclrt<br/>Runtime C API]

    %% Dependencies
    ACLIF --> OM2EXEC
    OM2IF --> MODEL_SO
		
		OM2EXEC -.-> OM2IF
    OM2EXEC --> ZIP
    OM2EXEC --> JSON
    OM2EXEC --> DFX

    MODEL_SO --> ACLRT
    MODEL_SO --> OM2EXEC
    ACLRT --> RT

ACL接口支持om2模型加载执行

OM2 模型须保持对现有 ACL Model 接口的向下兼容性,用户从 OM 模型切换至 OM2 模型无需修改脚本。当用户调用 aclmdlLoad 系列接口加载 OM2 格式文件时,ACL 框架层须识别 OM2 模型,实现逻辑分发。当前已完成支持的OM2的接口如下:

接口名 接口描述
aclmdlCreateDesc 创建aclmdlDesc模型描述信息
aclmdlDestroyDesc 销毁aclmdlDesc
aclmdlGetDesc 根据模型ID获取模型描述信息
aclmdlGetNumInputs 根据模型描述信息获取模型的输入个数
aclmdlGetNumOutputs 根据模型描述信息获取模型的输出个数
aclmdlGetInputSizeByIndex 根据模型描述信息获取指定输入的大小
aclmdlGetOutputSizeByIndex 根据模型描述信息获取指定输出的大小
aclmdlCreateDataset 创建aclmdlDataset类型的数据
aclmdlDestroyDataset 销毁通过aclmdlCreateDataset接口创建的aclmdlDataset类型的数据
aclmdlAddDatasetBuffer 向aclmdlDataset中增加aclDataBuffer
aclmdlGetDatasetNumBuffers 获取aclmdlDataset中aclDataBuffer的个数
aclmdlGetDatasetBuffer 获取aclmdlDataset中的第n个aclDataBuffer
aclmdlLoadFromFile 从文件加载离线模型
aclmdlLoadFromMem 从内存加载离线模型
aclmdlExecute 执行模型推理
aclmdlExecuteAsync 异步执行模型推理
aclmdlUnload 卸载模型
aclmdlGetInputNameByIndex 根据模型描述信息获取指定输入的输入名称
aclmdlGetOutputNameByIndex 根据模型描述信息获取指定输出的输出算子名称、算子输出边的下标、top名称或输出名称
aclmdlGetInputFormat 根据模型描述信息获取模型中指定输入的Format
aclmdlGetOutputFormat 根据模型描述信息获取模型中指定输出的Format
aclmdlGetInputDataType 根据模型描述信息获取模型中指定输入的数据类型
aclmdlGetOutputDataType 根据模型描述信息获取模型中指定输出的数据类型
aclmdlGetInputIndexByName 根据模型中的输入名称获取对应输入的索引编号
aclmdlGetOutputIndexByName 根据模型中的输出名称获取对应输出的索引编号

OM2 模型加载与执行流程

OM2 模型动态库须导出符合 C 风格调用约定的符号,确保用户和 OM2 执行器通过 dlsym 可完成符号绑定。

导出符号 技术逻辑与功能定义
Om2ModelCreate 资源自初始化:通过 bin_files 或内存数据 (bin_data) 加载模型,自动触发运行环境初始化,包括 Device 内存申请、权重加载及 Task 序列下沉。
Om2ModelRunAsync 非阻塞执行:基于 aclrtStream 调度推理任务。支持传入 gert::Tensor 数组实现输入输出张量的地址绑定。
Om2ModelRun 同步执行:在当前线程上下文中阻塞式下发执行逻辑,直至 NPU 返回结果。
Om2ModelDestroy 资源析构:显式释放模型实例占用的所有 Runtime 资源(如 FeatureMap、Weight 内存等)。

基于 ACL Model 接口加载、执行 OM2 模型的完整流程如下:

sequenceDiagram
    participant App as 用户APP
    participant ACL as ACL接口
    participant Exec as om2_executor
    participant So as 模型so
    participant Rt as aclrt/runtime

    App->>ACL: aclmdlLoadFromFile
    ACL->>Exec: LoadOm2ExecutorFromData
    Exec->>Exec: 解析归档与元数据
    Exec->>So: dlopen + dlsym
    Exec->>So: Om2ModelCreate
    So->>Rt: 初始化模型资源/注册二进制
    Rt-->>So: status
    So-->>Exec: status
    Exec-->>ACL: model_id

    App->>ACL: aclmdlExecute / ExecuteAsync
    ACL->>Exec: Run / RunAsync
    Exec->>So: Om2ModelRun / RunAsync
    So->>Rt: 执行模型
    Rt-->>So: status
    So-->>Exec: status
    Exec-->>ACL: status

    App->>ACL: aclmdlUnload
    ACL->>Exec: destroy executor
    Exec->>So: Om2ModelDestroy
    So->>Rt: 释放模型资源

OM2执行时支持能力

当前OM2执行时仅支持基础的Aicore以及Aicpu算子,已支持的task type如下所示:
MODEL_TASK_KERNEL
MODEL_TASK_ALL_KERNEL
MODEL_TASK_VECTOR_KERNEL
MODEL_TASK_VECTOR_ALL_KERNEL
MODEL_TASK_END_GRAPH
MODEL_TASK_FUSION_START
MODEL_TASK_FUSION_END
其余的执行时能力正在快速迭代中,整体静态shape离线场景的执行时能力预计于630完整支持。

likedislike
夏国正成员
5月25日 评论:

需求类issue,预计630完整支持静态shape离线场景

likedislike
Llining23666成员
8月20日 关联了pull request:fix(dflow): 修复 DeployContext transfer_queues_ 和 tansfer_routes_ 数据竞争
CANN-robotCANN-robot成员
8月20日 关闭了 issue
fary86
12 天前 评论:

OM2 自定义算子

1 背景

GE Runtime 存在两套执行框架:

  • GE 原有的运行时架构 V1,使用 CustomTaskInfo 执行自定义算子(Custom Op)
  • GE 新的离线推理执行框架 OM2,提供更轻量、更高效的离线模型执行能力

随着 OM2 离线推理场景的推进,需要支持包含 Custom Op 的模型在 OM2 框架上运行。

2. OM2 自定义算子的存储

自定义算子需要归档的内容包括算子二进制和自定义算子so文件两部分,在 OM2 的归档结构中分别增加了目录 data/custom_ops/binaries_npu_arch 和 data/custom_ops/shared_libs 用来保存这两类文件,具体结构如下面所示:

├── data
│   ├── custom_ops
│   │   ├── binaries_npu_arch   # 自定义算子二进制,文件名格式: kernelName_hashId_CustomKernel.bin
│   │   │   └── <kernel_name>_<file_hash>_CustomKernel.bin
│   │   └── shared_libs         # 自定义算子 so,文件名格式:文件size_文件Hash值_原so文件名
│   │       └── <file_size>_<file_name>_<original_file_name>.so
│   ├── kernels
│   │   └── te_cast_xxx.o
│   └── model_0
│       ├── constants_config.json
│       ├── debug
│       ├── model_meta.json
│       ├── op_attr.json
│       └── runtime
│           ├── csrc
│           └── libxxx_om2.so
└── manifest.json

3. OM2 自定义算子的加载

关键点:executor 先把自定义算子 so 经 memfd(内存文件,不落盘)dlopen 进进程,触发 so 内
REG_AUTO_MAPPING_OP 静态注册到全局 CustomOpFactory;随后 dlopen 模型 SO 并回调
GertModelLoad,模型 SO 在录制期(aclmdlRIBuildBegin/End 之间)通过 CustomOpFactory
找回算子实例、反序列化 kernel bin、并经 executor 的 launch_func 回调完成首次 Execute 与任务录制。

sequenceDiagram
    autonumber
    participant APP as 应用/ACL<br>(aclmdlLoadFromFile)
    participant EXE as Om2ModelExecutor<br>(libom2_executor.so)
    participant DESER as GertModelData反序列化
    participant CUSTSO as 自定义算子so<br>(libcust_opapi.so)
    participant FACT as CustomOpFactory<br>(全局注册表)
    participant MSO as 模型SO<br>(lib*_om2.so)
    participant KH as Om2KernelHandlesManager<br>(bin handle缓存)
    participant RT as ACL RT<br>(aclrt*/aclmdlRI*)
    participant CUSTOP as AddCustom算子实例
    participant DFX as dump/profiling<br>(libom2_dump.so)

    APP->>EXE: LoadOm2ExecutorFromData(model_data, load_arg)
    EXE->>DESER: DeserializeGertModelData(解压ZIP)
    DESER-->>EXE: custom_ops->binaries(*_CustomKernel.bin)<br>custom_ops->libraries(*.so)<br>kernels/runtime so_artifact

    Note over EXE: Impl::LoadFromOm2ModelData (om2_model_executor.cc:415)
    EXE->>EXE: BuildKernelBinInfoFromStruct<br>(普通kernels + custom_ops->binaries 合并为bin数组)

    loop 每个 custom_ops->libraries 中的 so (:438)
        EXE->>EXE: CreateSoMemFd: memfd_create + mmWrite<br>(so写入内存fd, 路径 /proc/pid/fd/N)
        EXE->>CUSTSO: mmDlopen(fd_path, RTLD_NOW)
        CUSTSO->>FACT: 静态构造 REG_AUTO_MAPPING_OP<br>RegisterCustomOpCreator("AddCustom", creator)
        EXE->>EXE: so_handle存入run_model_info_.cust_shared_libs
    end

    EXE->>EXE: LoadSoFromBuffer: 模型so写入memfd
    EXE->>MSO: LoadSharedObject: mmDlopen(模型so) (:553)
    EXE->>MSO: ResolveSymbols: mmDlsym<br>GertModelLoad/Run/RunAsync/Unload/RefreshFeatureMap (:567)
    EXE->>DFX: CreateDumpManager (:724)

    Note over EXE: CreateModelFromStruct (:660)
    EXE->>EXE: PrepareConstants/Variables/VarAddrs<br>(权重H2D、变量地址表)
    EXE->>EXE: CreateGertModelLoadCallbacks (:376)<br>launch_func=GertModelLaunchTask<br>+ 5个bin handle store回调

    EXE->>MSO: GertModelLoad(GertModelLoadConfig<br>{bin_files/bin_data/bin_size, constants,<br>instance_handle, callbacks}) (:700)

    Note over MSO: ===== 进入模型SO内部 (生成代码) =====
    MSO->>MSO: new Om2Model: bin_files→bin_info_map_
    MSO->>RT: InitResources: aclmdlRIBuildBegin(开始录制)<br>malloc workspace/创建并绑定stream/event
    loop RegisterKernels: 每个AICore/AICPU bin
        MSO->>KH: lock_bin_handle_store()
        MSO->>KH: query_bin_handle_from_store(bin_id)
        alt 缓存未命中
            MSO->>RT: aclrtBinaryLoadFromData(bin_data)
        end
        MSO->>KH: save_bin_handle_to_store(bin_id, handle)<br>(命中则引用计数+1)
        MSO->>RT: aclrtBinaryGetFunction → func_handle
        MSO->>KH: unlock_bin_handle_store()
    end

    Note over MSO: Om2Model::Load (:944)
    loop DeserializeCustKernelBinaries: 每个*_CustomKernel.bin (:247)
        MSO->>MSO: 从文件名提取kernel名<br>(AddCustom_xxx_CustomKernel.bin → AddCustom)
        MSO->>FACT: CreateOrGetCustomOp("AddCustom")
        FACT->>CUSTSO: 调creator new AddCustom<br>(依赖该so已在加载前期dlopen完成注册)
        CUSTSO-->>FACT: AddCustom实例
        FACT-->>MSO: BaseCustomOp* → dynamic_cast PortableOp*
        MSO->>CUSTOP: PortableOp::Deserialize(bin)<br>恢复JIT编译的device_elves_
    end

    MSO->>EXE: callbacks.report_model_base_info(rt_model_handle)
    EXE->>DFX: SetModelDumpInfo + RegisterModelToProfilingRuntime

    loop 录制期 DispatchOp: 遍历kOpDefs
        alt dispatch_type=21 自定义算子 (DispatchCustomKernel :388)
            MSO->>MSO: BuildTensor(输入/输出, 地址已解析到workspace)<br>BuildKernelContextHolder → EagerOpExecutionContext
            MSO->>FACT: CreateOrGetCustomOp(op_type)<br>→ dynamic_cast EagerExecuteOp*
            MSO->>EXE: launch_func=GertModelLaunchTask<br>(ACL_RT_LAUNCH_CUSTOM_KERNEL,<br>func_launch_custom_kernel=LaunchEagerExecuteOp,<br>eager_op, eager_op_context)
            Note over EXE: LaunchCustKernelDumpTask<br>(gert_model_executor_callbacks.cc:291)
            EXE->>DFX: ReportTaskPreprocess →<br>PreprocessOm2TaskInfo (task_type=CUSTOM_KERNEL)
            EXE->>CUSTOP: func_launch_custom_kernel →<br>EagerExecuteOp::Execute(ctx)
            CUSTOP->>CUSTOP: GetInputTensor/MallocOutputTensor<br>按shape key选device_elves_[key]
            CUSTOP->>RT: aclrtBinaryLoadFromData + aclrtBinaryGetFunction
            CUSTOP->>RT: aclrtLaunchKernelWithHostArgs(stream)<br>(录制期内被捕获进RT模型)
            EXE->>RT: ReportTaskPostprocess:<br>aclrtGetThreadLastTaskIdAndStreamId
            EXE->>DFX: PostprocessOm2TaskInfo(SaveTask)<br>+ SaveProfilingTaskInfo
        else 普通AICore/AICPU kernel (DispatchKernelAicore :548)
            MSO->>EXE: launch_func(ACL_RT_LAUNCH_KERNEL_V2,<br>func_handle, args, block_dim, stream)
            EXE->>DFX: ReportTaskPreprocess
            EXE->>RT: aclrtLaunchKernelV2(录制)
            EXE->>DFX: ReportTaskPostprocess
        end
    end

    MSO->>RT: aclmdlRIBuildEnd(结束录制, 得到可重放RT模型)
    MSO-->>EXE: GertModelLoad返回model_handle
    EXE->>DFX: DispatchDumpInfo → rtDatadumpInfoLoad (:765)
    EXE-->>APP: 加载完成(executor注册到model_id)

加载阶段要点

  1. 自定义 so 不落盘:CreateSoMemFd(om2_model_executor.cc:149)用 memfd_create + mmWrite 把 so 字节流写入匿名内存 fd,再以 /proc/pid/fd/N 路径 mmDlopen。
  2. dlopen 顺序有依赖:必须先 dlopen 自定义算子 so(完成 CustomOpFactory 注册),模型 SO 录制期 CreateOrGetCustomOp 才能命中注册表;模型 SO 中未定义的 CustomOpFactory 符号由全局符号表(executor 侧)解析,二者共享同一注册表实例。
  3. kernel bin 双通路:
    • 普通 AICore/AICPU bin:模型 SO RegisterKernels 经 GertModelLoadCallbacks 的 5 个 bin handle store 回调走 Om2KernelHandlesManager(om2_kernel_handles.cc:25,进程级单例 + bin_id 引用计数),多模型共享同一 bin 只加载一次;
    • 自定义算子 *_CustomKernel.bin:不走 bin handle 缓存,由 DeserializeCustKernelBinaries 交给算子自身的 PortableOp::Deserialize,device ELF 的实际加载(aclrtBinaryLoadFromData)延迟到算子 Execute 内部完成。
  4. 加载期即录制期:GertModelLoad 内 aclmdlRIBuildBegin/End 之间把所有任务(含自定义算子 kernel launch)录制为 RT 下沉模型;自定义算子的 host 逻辑 EagerExecuteOp::Execute 只在加载期执行一次。
  5. 自定义 so 不随模型卸载:Cleanup(om2_model_executor.cc:1221)仅 mmDlclose 模型 SO 并关闭其 memfd;cust_shared_libs 中的句柄/fd 未释放——注册表仍持有算子 creator,卸载会使全局注册失效,故按进程级常驻处理。

4. OM2 自定义算子的执行

执行阶段是录制-重放(sink)模式:GertModelRun/RunAsync 只刷新输入输出地址并整体重放 RT 模型;自定义算子的 kernel 作为已录制任务在 device 侧重放,不再回调 host 侧 EagerExecuteOp::Execute。

sequenceDiagram
    autonumber
    participant APP as 应用/ACL<br>(aclmdlExecute)
    participant EXE as Om2ModelExecutor
    participant PROF as ProfilingImpl<br>(executor本地)
    participant DFX as dump<br>(libom2_dump.so)
    participant MSO as 模型SO<br>(Om2Model::Run)
    participant RT as ACL RT<br>(aclmdlRI*)
    participant DEV as Device<br>(AI Core)

    APP->>EXE: Om2ModelExecuteCommon(modelId, input, output)<br>→ executor->Run/RunAsync(stream, inputs, outputs)
    Note over EXE: Impl::Run (:794) / RunAsync (:811)
    EXE->>EXE: BuildRunConfig: 组装GertModelRunConfig<br>(input/output Tensor数组, timeout,<br>profiling开启时挂run_callbacks)
    EXE->>DFX: UpdateStepId(step_id-1)<br>(step刷入dump设备内存, 异步模式在模型流上保序)

    EXE->>MSO: run_func/run_async_func =<br>GertModelRun(Async)(model_handle, config)

    Note over MSO: ===== 模型SO内部 (生成代码 :994/:1019) =====
    MSO->>MSO: 校验input_count/output_count
    MSO->>MSO: args_table_.UpdateHostArgs<br>(输入/输出Tensor新地址写入host args表)
    MSO->>EXE: run_callbacks.report_run_info_preprocess
    EXE->>PROF: ReportRunInfoPreprocess(model_id, step_id, stream)
    MSO->>RT: args_table_.CopyArgsToDevice<br>(args buffer H2D刷新)
    MSO->>RT: aclmdlRIExecute(model_handle, timeout)<br>/ aclmdlRIExecuteAsync(model_handle, stream)

    Note over RT,DEV: 重放加载期录制的任务序列
    RT->>DEV: MemcpyAsync/EventRecord/EventWait
    RT->>DEV: AICore kernel任务(aclrtLaunchKernelV2录制的)
    RT->>DEV: 自定义算子kernel任务<br>(录制期Execute里LaunchKernelWithHostArgs捕获的)<br>—— host侧EagerExecuteOp::Execute不再执行
    RT->>DEV: EndGraph任务
    DEV-->>RT: 同步等待完成(Run模式, 按stream_sync_timeout_ms)

    MSO->>EXE: run_callbacks.report_run_info_postprocess
    EXE->>PROF: ReportRunInfoPostprocess(model_id, step_id, stream)
    MSO-->>EXE: GertModelRun返回
    EXE->>EXE: ++step_id_
    EXE-->>APP: 执行完成(ACL侧回填输出TensorDesc,<br>GE侧host输出做D2H拷贝)

执行阶段要点

  1. 入口两条:ACL aclmdlExecute(Async) → Om2ModelExecuteCommon(model_om2.cpp:396,按 modelId 查 executor、dataset 转 gert::Tensor);GE ModelExecutor::RunOm2Graph(model_executor.cc:1127,host 输入先 H2D)→ Om2ModelManager::RunModel(stream 为空走同步 Run,否则 RunAsync)。
  2. 地址刷新机制:模型输入输出每次执行可换地址,靠 args_table_.UpdateHostArgs + CopyArgsToDevice 刷新录制模型引用的 args buffer;自定义算子的 IO 地址在录制期已固定指向 workspace 偏移,模型输入经录制好的 MemcpyAsync(io_refresh)任务搬运衔接。
  3. DFX 挂载点:执行期 profiling 走 GertModelRunCallbacks(executor 本地 ProfilingImpl,不依赖 libom2_dump.so);dump step 经 Om2DfxApi::UpdateStepId。任务级 dump/profiling 信息在加载录制期已由 ReportTaskPre/Postprocess 落库,重放期按 task_id 关联。
  4. GertModelLaunchTask 三种下发类型(om2_model_api.h:125,gert_model_executor_callbacks.cc:320):
    • ACL_RT_LAUNCH_KERNEL_V2:普通 kernel,executor 直接 aclrtLaunchKernelV2;
    • RT_STARS_TASK_LAUNCH_WITH_FLAG:DSA 任务,rtGeneralCtrl 下发;
    • ACL_RT_LAUNCH_CUSTOM_KERNEL:自定义算子,回调模型 SO 传入的 func_launch_custom_kernel(eager_op, eager_op_context),由 executor 包一层 dump 前后处理(LaunchCustKernelDumpTask,:291)。该回调仅发生在加载录制期。
likedislike
penghongwei
10 天前 评论:

OM2 离线静态图地址刷新实现原理

目录


1. 概述

1.1 为什么需要地址刷新

离线编译时,编译器无法预知运行期的真实设备地址:

  1. 模型输入/输出:用户每次执行传入的 Tensor 设备地址都可能不同(零拷贝场景直接用用户 buffer);
  2. Feature Memory(FM)基址:工作内存由 Executor 在加载期分配(或由外部传入),多档位切换、内存重映射时还会变化;
  3. 权重/变量/常量:地址在加载期才确定。

而每个算子任务的启动参数(args)中记录了大量上述地址。因此 OM2 必须在执行前把真实地址回填进 task args,这就是"地址刷新"。

1.2 设计哲学

OM2 地址刷新的核心思想可以浓缩为一个公式:

真实地址 = allocation 的当前活跃基址(运行期才知) + 段内偏移(编译期固化)
  • 编译期:用伪基址规划全部内存,把每个 task args 中的每个地址槽位归入一个 allocation(内存分配等价类),并固化 (allocation_id, 段内偏移, args 内槽位偏移) 三元组;
  • 运行期:allocation 基址变化时,按 allocation_id → 槽位列表 的静态映射表,批量把"新基址 + 偏移"写进 host args,再一次性 H2D 拷贝到 device args。

host args 是唯一事实源:所有地址刷新都写 host 侧 args 镜像,device args 每次执行前由 CopyArgsToDevice 整体重拷。这避免了运行期零散修改 device 内存,也使刷新逻辑可单测。


2. 总体架构

2.1 编译期与运行期分工

graph TD
    subgraph COMPILE["编译期(atc 进程内)"]
        GEMODEL["GeModel<br/>图 + TaskDef + 内存规划"] --> BUILDER["Om2CodegenModelBuilder::Build"]
        BUILDER --> RES["BuildRuntimeResource / BuildModelIo / ..."]
        BUILDER --> GENARGS["GenerateArgsData"]
        GENARGS --> MAM["om2::ModelArgsManager ★<br/>地址刷新规划核心"]
        MAM --> SEM["ArgsTableSemantic<br/>刷新元数据"]
        SEM --> PGEN["ProgramGenerator"]
        PGEN --> FILES["7 个生成文件<br/>含 Om2ArgsTable 刷新表"]
        FILES --> SO["lib&lt;model&gt;_om2.so"]
        SO --> ZIPSAVER["Om2ZipSaver 打包为 .om2(ZIP 格式)"]
    end

    subgraph RUNTIME["运行期(Executor 进程)"]
        EXEC["Om2ModelManager / Om2ModelExecutor"]
        EXEC --> UNZIP["解 ZIP → memfd dlopen 生成 so"]
        EXEC --> BASE["准备权重 / 变量 / 工作内存基址"]
        EXEC --> ABI["通过 C ABI 驱动 so"]
        ABI --> ABISYM["GertModelLoad / Run / RunAsync<br/>GertModelRefreshFeatureMap ★(FM 基址刷新)"]
        ABISYM --> ARGS["生成 so 内部:Om2ArgsTable<br/>执行真正的地址刷新"]
    end

    ZIPSAVER -->|"加载 .om2"| EXEC

2.2 地址刷新数据流总览

(1)编译期:静态固化在生成 so 中

graph TD
    ALLOC["logical_mem_allocations"] --> INFO["TaskArgsRefreshInfo{id, offset, args_offset, placement}"]
    INFO --> TBL["allocation_ids_to_model_args_refresh_infos[id] = [槽位列表]"]
    IOIDX["input_index / output_index"] --> IOID["allocation_id 映射"]
    FMIDX["refreshable_fm_index"] --> FMID["allocation_id 映射"]

(2)运行期:每次 Run 按编译期固化的静态表刷新

graph TD
    TBL["编译期固化的静态表<br/>allocation_id → 槽位列表"]
    USERADDR["用户 Tensor 设备地址"] -->|"UpdateHostArgs(type, io_index, addr)"| HARGS1["host_args_[args_offset] = addr + info.offset"]
    FMBASE["外部 FM 新基址"] -->|"RefreshFeatureMap(base)"| HARGS2["host_args_[args_offset] = base + info.offset"]
    TBL -.->|"查槽位与段内偏移"| HARGS1
    TBL -.->|"查槽位与段内偏移"| HARGS2
    HARGS1 --> COPY["Phase 2:CopyArgsToDevice<br/>host_args 经 H2D 拷贝至 dev_args"]
    HARGS2 --> COPY
    COPY --> KERNEL["下发 kernel"]

2.3 关键目录

目录 职责
base/common/om2/codegen/task_args_manager/ 地址刷新编译期规划核心(分类、布局、刷新表构建)
base/common/om2/codegen/ codegen 总入口、语义模型、AST/emitter
base/common/om2/codegen/file_code_generator/ 各生成文件的代码生成器(含 Om2ArgsTable 生成)
base/common/om2/codegen/emitter/stable_parts/ 稳定代码部件(ResolveOpAddr、C ABI 导出等)
base/common/om2/codegen/task_code_builder/ 各类 task(kernel/hccl/memcpy/...)的语义解析与代码生成
base/common/helper/om2/ 打包(zip saver)、rt_var 资源构建
runtime/om2/ 运行时执行器(加载、执行、FM 刷新、变量管理)
inc/framework/om2/model_api/om2_model_api.h SO ↔ Executor 的 C ABI 定义

3. 核心流程

3.1 OM2模型生成阶段时序图

%%{init: {"sequence": {"mirrorActors": false, "messageMargin": 5 }} }%%
sequenceDiagram
  autonumber
  participant CG as CodeGen
  participant OMAM as Om2ModelArgsManager
  participant TCB as TaskCodeBuilder
  participant MC as ModelCode

  CG->>CG: 初始化运行参数,InitRuntimeParams<br/>规划“假”的地址段,维护分配表<br/>初始化内存资源,InitWeightMem、InitFixedFeatureMap、InitFeatureMapAndP2PMem、PreProcessFileConstants<br/>初始化allocation表,GenMemAllocations
  CG->>OMAM: 初始化Om2ModelArgsManager::Init
  OMAM->>OMAM: 创建所有TaskCodeBuilder对象,ParseModelTaskDef
  OMAM->>TCB: 获取每个Task的iow地址信息及所需的args信息,ParseTaskRunParam
  TCB->>TCB: 获取iow和args信息
  TCB-->>OMAM: 返回
  OMAM->>OMAM: 对Task及其iow刷新类型分类,识别推导对端Task的Fixed地址,更新对应Task及其iow刷新类型,ClassifyMultiTasks<br/>对args进行排布,记录每个task在args排布中的位置,Plan
  OMAM->>OMAM: 分配“假”的args内存,分配段信息写入分配表,AllocModelArgs<br/>获取每个Task分配的args地址,生成UpdatePolicy策略与args拷贝分区关系,ConstructUpdateData<br/>对UpdatePolicy策略与args拷贝分区中的每个地址匹配分配表,数据结构处理为内存类型 + offset的形式<br/>分配“假”的Fixed内存,分配段信息写入分配表,分配给原本不支持刷新的地址及支持刷新但被推导为不能刷新的地址,AllocFixedAddrs
  OMAM->>OMAM: 更新收集上后需要改变的每个task的iow地址,ConstructTaskInitParams<br/>对每个task的iow地址和args地址去匹配规划的分配表,数据结构中增加内存类型 + offset的形式
  OMAM->>TCB: 传递每个task的iow和args初始化,Init
  TCB->>TCB: 初始化task参数生成offset表,Om2ArgsIoAddrsUpdater.Init
  TCB-->>OMAM: 返回
  OMAM->>TCB: 获取Task上报的args可刷新信息,GetTaskArgsRefreshInfos
  TCB->>TCB: 生成需要刷新的对象,Om2ArgsIoAddrsUpdater::GenArgsRefreshInfos
  TCB-->>OMAM: 返回
  OMAM->>OMAM: 生成allocation id与ModelArgsRefreshInfo的关系,GenModelArgsRefreshInfosForTask<br/>对allocation id与ModelArgsRefreshInfo中的每个地址匹配分配表,数据结构处理为内存类型 + offset的形式<br/>生成allocation id与UpdatePolicy的关系,InitForUpdate
  OMAM->>MC: allocation id与TaskArgsRefreshInfo的关系写入最终代码
  MC-->>OMAM: 返回
  OMAM->>MC: allocation id与UpdatePolicy的关系写入最终代码
  MC-->>OMAM: 返回
  OMAM->>MC: args内存相关变量的定义及初始化代码,FM/IO与allocation的映射关系等数据写入最终代码
  MC-->>OMAM: 返回
  OMAM-->>CG: 返回

3.2 OM2模型执行阶段时序图

%%{init: {"sequence": {"mirrorActors": false, "messageMargin": 5 }} }%%
sequenceDiagram
  autonumber
  participant OME as OM2Executor
  participant MCREATE as ModelCretate
  participant MCLOAD as ModelLoad
  participant MCEXECUTE as ModelExecute

  OME->>MCREATE: 创建模型对象,初始化args相关内存资源
  MCREATE-->>OME: 返回
  OME->>MCLOAD: 加载模型
  MCLOAD->>MCLOAD: 根据内存类型获取对应args基地址和iow基地址,组装下发参数
  MCLOAD->>MCLOAD: 把device侧args拷贝到对应的host侧args内存中
  MCLOAD->>MCLOAD: 调用接口下发
  MCLOAD-->>OME: 返回
  OME->>MCEXECUTE: 执行模型
  MCEXECUTE->>MCEXECUTE: 根据模型input/outputs的index顺序,查找inputs/outputs是否支持零拷贝
  MCEXECUTE->>MCEXECUTE: 不支持零拷贝的inputs进行强制拷贝
  MCEXECUTE->>MCEXECUTE: 支持零拷贝的input/outputs获取对用的allocation id
  MCEXECUTE->>MCEXECUTE: 根据allocation id获取ModelArgsRefreshInfo,更新对应的host args table
  MCEXECUTE->>MCEXECUTE: 根据allocation id获取UpdatePolicy,计算刷新策略
  MCEXECUTE->>MCEXECUTE: 执行刷新策略,进行H2D拷贝
  MCEXECUTE->>MCEXECUTE: 调用接口执行模型
  MCEXECUTE->>MCEXECUTE: 不支持零拷贝的outputs进行强制拷贝
  MCEXECUTE-->>OME: 返回
likedislike