考虑引入结构化打包方式,提高模型结构可解释性
当前 GE 采用的模型归档格式 OM
OM(Offline Model)是华为昇腾 AI 处理器支持的离线模型归档格式,由图编译器对原始模型进行算子融合、算子调度、内存优化等一系列编译优化后生成,用于昇腾 AI 在处理器上实现高性能推理。OM 模型可以通过 ATC 模型转换工具、IR 构图接口生成,部署于 NPU 设备上执行推理。此外,图编译缓存场景,GE 会将会将编译结果持久化为 OM 模型,当再次编译运行相同图时,直接加载缓存的 OM 以减少编译耗时。
随着 CANN 开源开放与生态易用性建设不断推进,现有OM(Offline Model)格式在支持更透明的调试流程、更灵活的开发模式方面,逐渐显现出其架构上的局限性。具体而言:
- OM 作为图编译产物,其“黑盒”特性导致模型可解释性不足
OM 模型归档格式内部为主要由模型首部、模型分区表和若干模型分区构成。模型首部长度是固定 256 字节,首部分区记录模型的版本、大小等元信息。模型分区表由多个分区表项构成,每个表项占用24字节,记录对应分区的类型、文件偏移量及长度。模型分区是一段遵循 protobuf 格式或无格式连续内存,记录了模型中算子信息、权重数据等,其解析方式由分区类型决定。
- 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 文件中。 |


OM2 模型加载性能与体积分析(穿刺方案)
OM2 模型的加载时间和空间性能不能回退。围绕该目标,选取 ResNet(CV 模型)和 debeta(NLP 模型)测试了加载性能与打包体积,并与当前版本的 OM 进行对比。
模型体积拆解
权重数据是模型存储空间的主体,使用ZIP压缩对权重数据的压缩率约为 8%,空间优化收益有限。整体对比表明(下图中左侧为OM,右侧为OM2),在不压缩处理的情况下,OM2 的整体模型体积并未超过OM。
- 模型各分区构成
- 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 模型各阶段性能拆解
OM2模型加载流程
-
加载阶段:加载模型SO → 读取权重 → 读取二进制
-
下沉阶段:申请Runtime资源 → 拷贝权重 → 注册二进制 → 组装args → 下沉Task
-
OM2 模型各阶段性能拆解
OM2主要优化点
OM2在加载阶段移除了模型反序列化与属性解压过程,并将TaskInfo解析提前至编译阶段完成,从而简化了运行时加载路径。


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完整支持。


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)
加载阶段要点
- 自定义 so 不落盘:
CreateSoMemFd(om2_model_executor.cc:149)用memfd_create+mmWrite把 so 字节流写入匿名内存 fd,再以/proc/pid/fd/N路径mmDlopen。 - dlopen 顺序有依赖:必须先 dlopen 自定义算子 so(完成
CustomOpFactory注册),模型 SO 录制期CreateOrGetCustomOp才能命中注册表;模型 SO 中未定义的CustomOpFactory符号由全局符号表(executor 侧)解析,二者共享同一注册表实例。 - 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内部完成。
- 普通 AICore/AICPU bin:模型 SO
- 加载期即录制期:
GertModelLoad内aclmdlRIBuildBegin/End之间把所有任务(含自定义算子 kernel launch)录制为 RT 下沉模型;自定义算子的 host 逻辑EagerExecuteOp::Execute只在加载期执行一次。 - 自定义 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拷贝)
执行阶段要点
- 入口两条:ACL
aclmdlExecute(Async)→Om2ModelExecuteCommon(model_om2.cpp:396,按 modelId 查 executor、dataset 转gert::Tensor);GEModelExecutor::RunOm2Graph(model_executor.cc:1127,host 输入先 H2D)→Om2ModelManager::RunModel(stream 为空走同步Run,否则RunAsync)。 - 地址刷新机制:模型输入输出每次执行可换地址,靠
args_table_.UpdateHostArgs+CopyArgsToDevice刷新录制模型引用的 args buffer;自定义算子的 IO 地址在录制期已固定指向 workspace 偏移,模型输入经录制好的MemcpyAsync(io_refresh)任务搬运衔接。 - DFX 挂载点:执行期 profiling 走
GertModelRunCallbacks(executor 本地ProfilingImpl,不依赖 libom2_dump.so);dump step 经Om2DfxApi::UpdateStepId。任务级 dump/profiling 信息在加载录制期已由ReportTaskPre/Postprocess落库,重放期按 task_id 关联。 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)。该回调仅发生在加载录制期。


OM2 离线静态图地址刷新实现原理
目录
1. 概述
1.1 为什么需要地址刷新
离线编译时,编译器无法预知运行期的真实设备地址:
- 模型输入/输出:用户每次执行传入的 Tensor 设备地址都可能不同(零拷贝场景直接用用户 buffer);
- Feature Memory(FM)基址:工作内存由 Executor 在加载期分配(或由外部传入),多档位切换、内存重映射时还会变化;
- 权重/变量/常量:地址在加载期才确定。
而每个算子任务的启动参数(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<model>_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: 返回


Thanks for sending an requirement! Please fill in the following template to help quickly solve your problem.
Backgroud(背景信息)
在基于图模式的深度学习计算中,模型计算流程被建模为一张计算图。GE 图引擎通过对整图进行全局的优化、编译与调度,有效提升模型执行效率。然而,当前 GE 图引擎对用户而言呈现较强的“黑盒”特性,其内部所执行的图优化策略不透明,导致用户在精度调试与性能调优阶段高度依赖华为官方工具链及技术支持,自主可控性较低。
Origin(信息来源)
图灵业务部
Benefit / Necessity (价值/作用)
Design(设计方案)
Om2在线场景流程图
Om2离线场景流程图