SO in OM Feature Description
1. Feature Overview
In the Ascend AI processor ecosystem, operators are implemented as dynamic link libraries (.so files). In the traditional deployment mode, users need to:
- Install the complete OPP (Operator Primitive Package) on the target machine, which contains hundreds of .so files
- Ensure the operator package versions match exactly between the compilation and runtime environments
- Manage complex environment variable paths (
ASCEND_OPP_PATH) that point to the operator library location
The above approach has the following limitations:
- High deployment complexity: Each inference node requires installing a large operator package, resulting in huge container images during containerized deployment
- Difficult version matching: Inconsistent operator versions between compilation and runtime leads to inference failures, and errors are difficult to locate
- Runtime compilation dependency in dynamic shape scenarios: Models with unknown shapes require dynamically generating tiling parameters at runtime, which depends on the operator implementation .so from the compilation phase
1.2 Feature Introduction
The SO in OM feature packs the operator .so files that the model depends on directly into the .om (Offline Model) file. This makes the model file self-contained with all operator code needed at runtime, allowing it to load and execute without external operator packages.
This feature uses an on-demand packaging strategy. Through dependency analysis during compilation, only the operators actually used by the model are packaged, avoiding unnecessary size overhead.
2. Overall Architecture
graph TB
subgraph "Compilation Phase (ATC/GeGenerator)"
A[User Graph] --> B[Graph Compilation Pipeline]
B --> C[Operator Compilation]
C --> D[Generate TaskDef]
D --> E{Check Dependent SO}
E -->|SpaceRegistry| F[Collect tiling/infer shape so]
E -->|OpMasterDevice| G[Collect op_master_device so]
E -->|Autofuse| H[Collect autofuse so]
E -->|CustomOp| H2[Collect custom op so]
F --> I[OpSoStore Packaging]
G --> I
H --> I
H2 --> I
I --> J[Write to OM file SO_BINS partition]
end
subgraph "OM File Structure"
J --> K[ModelFileHeader]
K --> L[MODEL_DEF partition]
K --> M[WEIGHTS_DATA partition]
K --> N[TBE_KERNELS partition]
K --> O[SO_BINS partition]
K --> P[TILING_DATA partition]
K --> P2[CUSTOM_OPS partition]
end
subgraph "Runtime (ModelManager)"
O --> Q[ModelHelper::LoadOpSoBin]
Q --> R[OpSoStore::Load parsing]
R --> S{Classify by SoBinType}
S -->|SpaceRegistry| T[Register to OpImplSpaceRegistry]
S -->|OpMasterDevice| U[Load to built_in/cust_op_master<br/>_so_names_to_bin_]
S -->|Autofuse| V[guard_check.so restored to _guard_check_so_data<br/>others written to bin_file_buffer ext attribute]
S -->|CustomOp| V2[Load to CustomOpSoLoader]
T --> W[Dynamic invocation during model execution]
U --> W
V --> W
V2 --> W
end
2.1 OM File Partition Structure
The SO in OM feature adds a new partition type SO_BINS to the OM file format:
| Partition Type | Purpose | Relationship with SO in OM |
|---|---|---|
MODEL_DEF |
Model definition (graph structure, operator attributes) | Contains so_in_om_flag marker |
WEIGHTS_DATA |
Model weight data | Independent |
TBE_KERNELS |
TBE operator binary | Complementary to SO_BINS |
SO_BINS |
Operator .so file collection | Core carrier of SO in OM |
TILING_DATA |
Precomputed tiling parameters | Used together with SpaceRegistry SO |
CUSTOM_OPS |
Custom operator instance serialized data | Used together with kCustomOp type SO in SO_BINS |
2.2 Four SO Types
GE categorizes the SO files to be packaged into four types, each corresponding to different use cases and lifecycle:
graph LR
subgraph "SoBinType Enumeration"
A[kSpaceRegistry = 0] -->|bit 15| B[0x8000]
C[kOpMasterDevice = 1] -->|bit 14| D[0x4000]
E[kAutofuse = 2] -->|bit 13| F[0x2000]
E2[kCustomOp = 3] -->|bit 12| F2[0x1000]
end
subgraph "Purpose"
B --> G[RT2 dynamic shape<br/>infer shape / tiling so]
D --> H[Device-side tiling so<br/>op_master_device]
F --> I[Autofuse fusion operator so<br/>offline save and load]
F2 --> I2[PortableOp custom operator so<br/>offline save and load]
end
subgraph "Trigger Condition"
G --> J[Dynamic shape model<br/>or _static_to_dynamic_softsync_op]
H --> K[TaskDef contains<br/>PREPROCESS_KERNEL type task]
I --> L[Graph node contains<br/>bin_file_path attribute<br/>or _guard_check_so_data is non-empty]
I2 --> L2[Graph contains PortableOp custom operators<br/>recognizable by CustomOpRegistry]
end
so_in_om_flag is a uint16_t, where each bit indicates whether a SO type is enabled. Multiple SO types can be combined (for example, 0xC000 indicates both SpaceRegistry and OpMasterDevice are included).
3. Compilation Phase: SO Packaging Process
3.1 Trigger Point
SO packaging occurs at the end of the GeGenerator offline model generation process. Key entry point:
File Path: compiler/api/generator/ge_generator.cc
GenerateOfflineModel()
└── GenerateModel()
└── impl_->SaveRootModel()
└── ModelHelper::SaveToOmRootModel() (model_helper.cc)
└── SaveRootModelPartitions() (model_helper.cc)
├── SaveSoStoreModelPartitionInfo() ← SO packaging entry (model_helper.cc)
└── SaveCustomOpsPartition() ← Custom operator partition (model_helper.cc)
3.2 Detection Phase: CheckAndSetNeedSoInOM
Before packaging, the system needs to determine whether the model requires SO packaging and which types of SO need to be packaged.
File Path: base/common/model/ge_root_model.cc
The detection logic consists of four independent check functions:
3.2.1 CheckAndSetSpaceRegistry
Trigger Conditions:
- Model contains dynamic shape (
ATTR_NAME_DYNAMIC_SHAPE_PARTITIONEDis true orGetGraphUnknownFlag()is true) - Model contains
_static_to_dynamic_softsync_optype operators
Description: Dynamic shape models require dynamically computing tensor memory layout and tiling parameters at runtime. These computation logic is provided by .so files in SpaceRegistry. After packaging, the runtime does not need to load from external OPP paths.
3.2.2 CheckAndSetOpMasterDevice
Trigger Conditions: Traverse all TaskDefs. If a MODEL_TASK_PREPROCESS_KERNEL type task is found and its kernel().so_name() is non-empty.
Description: PREPROCESS_KERNEL is the preprocessing logic executed on the device side before the operator executes (such as tiling calculation), which requires corresponding .so files to provide implementation. These .so files are typically located in /op_impl/ai_core/tbe/op_master_device/lib/ path.
3.2.3 CheckAndSetAutofuseSo
Trigger Conditions:
- Graph node contains
bin_file_pathattribute - Root graph
_guard_check_so_dataattribute is non-empty (ge_root_model.cc)
Description: Autofuse is GE's operator automatic fusion optimization feature. Fused operators generate independent .so files that need to be distributed with the model. In addition to checking bin_file_path, CheckAndSetAutofuseSo() also checks the root graph's _guard_check_so_data attribute — when this attribute is non-empty, the kAutofuse flag is also set, ensuring guard check data is saved and loaded with the model.
3.2.4 CheckAndSetCustomOpSo
Trigger Conditions: The graph contains PortableOp custom operators recognizable by the CustomOpRegistry held by the current GeRootModel.
Description: GraphManager::PreRun() explicitly binds the process-level global CustomOpRegistry to the current GeRootModel after BuildModel() returns. Subsequent custom operator SO collection and CUSTOM_OPS partition serialization both access custom operators through ge_root_model->GetCustomOpRegistry(), and the save process no longer directly accesses CustomOpFactory. When repackaging an existing OM, if the model does not carry a custom op registry, only the custom op partition processing is skipped, without falling back to the process-level global registry.
Cross-compilation scenario (ge_root_model.cc): CheckAndSetCustomOpSo() uses IsCrossCompileTarget() to determine whether the target environment differs from the compilation environment (comparing OS and CPU architecture). In non-cross-compilation scenarios, dladdr is used to resolve the actual SO path from the PortableOp vtable, and CheckSoArchMatchesTarget() performs ELF architecture validation; in cross-compilation scenarios, local SO collection is skipped, and CollectCustomOpSoFromCustomOppPath() is called to collect SOs from the target environment operator package directory pointed to by the ASCEND_CUSTOM_OPP_PATH environment variable, also validated by CheckSoArchMatchesTarget() to ensure the ELF architecture matches the target CPU.
3.3 Collection Phase: LoadAndStoreOppSo
After determining the SO types to be packaged, ModelHelper calls LoadAndStoreOppSo() to load .so files from disk into the OpSoStore object in memory.
File Path: base/common/helper/model_helper.cc
SaveSpaceRegistrySoBin()
└── GetSoBinData(cpu, os) ← Get corresponding so based on compilation host environment
└── LoadAndStoreOppSo()
SaveOpMasterDeviceSoBin()
└── LoadAndStoreOppSo(ge_root_model->GetOpMasterDeviceSoSet())
SaveAutofuseSoBin() (model_helper.cc)
├── Process _guard_check_so_data attribute → generate guard_check.so OpSoBin
├── Process bin_file_buffer ext attribute → sync existing OpSoBin
└── LoadAndStoreOppSo(ge_root_model->GetAutofuseSoSet())
SaveCustomOpSoBin() (model_helper.cc)
└── LoadAndStoreOppSo(ge_root_model->GetCustomOpSoSet(), SoBinType::kCustomOp)
SpaceRegistry SO file names embed the compilation host's OS and CPU information (such as _linux_x86_64 suffix), because tiling/infer shape logic executes on the host side and needs to match the compilation environment.
In addition to the regular LoadAndStoreOppSo() loading, SaveAutofuseSoBin() has two extra steps: first, it wraps the root graph's _guard_check_so_data attribute content as an OpSoBin named guard_check.so and adds it to OpSoStore; then it checks the root graph's bin_file_buffer ext attribute (present when repackaging an existing OM), and if non-empty, directly syncs the OpSoBins within it to OpSoStore without repeatedly loading from disk.
3.4 Serialization Phase: OpSoStore::Build
File Path: base/common/op_so_store/op_so_store.cc
OpSoStore serializes multiple .so files into a contiguous memory block and writes it to the SO_BINS partition of the OM file. The binary format is as follows:
┌─────────────────────────────────────────┐
│ SoStoreHead (4 bytes) │
│ so_num: uint32 │ ← Total SO file count
├─────────────────────────────────────────┤
│ SoStoreItemHead (16 bytes) │ ← Header of 1st SO
│ magic: 0x5D776EFD │
│ so_name_len: uint16 │
│ so_bin_type: uint16 │ ← SpaceRegistry/OpMasterDevice/Autofuse/CustomOp
│ vendor_name_len: uint32 │
│ bin_len: uint32 │
├─────────────────────────────────────────┤
│ so_name (so_name_len bytes) │
├─────────────────────────────────────────┤
│ vendor_name (vendor_name_len bytes) │
├─────────────────────────────────────────┤
│ so binary data (bin_len bytes) │
├─────────────────────────────────────────┤
│ SoStoreItemHead (16 bytes) │ ← Header of 2nd SO
│ ... │
└─────────────────────────────────────────┘
Format description:
- Magic number validation: Each item contains a magic number (
0x5D776EFD), used to validate data integrity during loading - Variable-length strings: so_name and vendor_name use length-prefixed variable-length encoding, avoiding space waste from fixed-length fields
- Type marker: Each item independently records
so_bin_type, and during loading, items are distributed to different caches by type
3.5 Environment Information Recording
File Path: base/common/helper/model_helper.cc
While packaging SO, the system records compilation environment information to the SoInOmInfo structure, including compilation host CPU architecture, operating system, OPP operator package version, and compiler version. This information is used for compatibility validation during runtime loading, ensuring the SO in the OM file is compatible with the current runtime environment.
4. Runtime: SO Loading and Execution
4.1 Loading Entry
File Path: base/common/helper/model_custom_kernels_helper.cc
During model loading, ModelHelper processes the SO_BINS partition in the following order. Note: LoadModel() (model_helper.cc) does not process SO_BINS; the SO loading entry is in the LoadRootModel() (model_helper.cc) flow:
ModelHelper::LoadRootModel() (model_helper.cc)
└── GenerateGeRootModel() (model_helper.cc)
└── LoadCustomOpRegistry() (model_custom_kernels_helper.cc)
└── LoadOpSoBin() (model_custom_kernels_helper.cc)
└── GeRootModel::LoadSoBinData() (ge_root_model.cc)
└── OpSoStore::Load(data, len) ← Deserialize SO_BINS partition
└── Parse SoStoreHead and each SoStoreItemHead
└── Create OpSoBin objects and add to kernels_ list
4.2 Loading by Type Distribution
File Path: base/common/helper/model_custom_kernels_helper.cc, runtime/v1/graph/load/model_manager/model_manager.cc
After LoadOpSoBin() completes, SO files are distributed to different processing paths by type:
4.2.1 SpaceRegistry SO Loading
SpaceRegistry SO is registered to OpImplSpaceRegistryV2Array, which is the core data structure used by RT2 (Runtime V2) executor to manage dynamic shape operator implementations. During inference, the executor finds and loads corresponding tiling/infer shape functions through the registry.
4.2.2 OpMasterDevice SO Loading
File Path: runtime/v1/graph/load/model_manager/model_manager.cc
OpMasterDevice SO loading (InitOpMasterDeviceSo, model_manager.cc) uses two deduplication strategies, stored in different maps:
- Built-in SO: Stored in
built_in_op_master_so_names_to_bin_(model_manager.cc), deduplicated by SO name (type + version number ensures uniqueness). Only one copy of SO with the same name is retained - Custom SO: Stored in
cust_op_master_so_names_to_bin_(model_manager.cc), deduplicated by binary content — the complete SO data is used as the key to establish mapping incust_op_master_so_datas_to_name_(model_manager.cc). When multiple models reference custom operators with the same content but different file names, the system can identify and reuse existing SO, avoiding repeated loading
4.2.3 Autofuse SO Loading
File Path: base/common/helper/model_custom_kernels_helper.cc
When LoadOpSoBin() iterates over all OpSoBins, kAutofuse type SOs are not simply cached but processed by content separately (model_custom_kernels_helper.cc):
guard_check.so: Its binary content is restored as the root graph's_guard_check_so_datastring attribute, for runtime guard check logic to use- Other Autofuse SOs: Stored in the
bin_file_buffermapping withvendor_name/so_nameas the key, and set as the root graph's ext attribute for on-demand loading at runtime
4.2.4 CUSTOM_OPS Partition Loading
File Path: base/common/helper/model_custom_kernels_helper.cc
The CUSTOM_OPS partition in the offline OM carries custom operator instance serialized data. When loading the root model offline, even if the OM does not carry custom operator SOs or a non-empty CUSTOM_OPS partition, a model-level empty CustomOpRegistry is created and injected into the GeRootModel, to identify the model's custom operator lookup scope. When loading a non-empty CUSTOM_OPS partition, it must be written to the CustomOpRegistry held by the current model, and must not fall back to the process-level global CustomOpFactory, to prevent multi-model private custom operator state from polluting each other. RT2 ModelConverter::ConvertGeModelToExecuteGraph() only consumes the registry already injected into GeRootModel; if the registry is empty, it is treated as an upstream construction anomaly, and the global registry is not used as fallback during the Convert phase.
4.2.5 Compatibility Validation
File Path: base/common/helper/model_helper.cc
During loading, the system validates whether the OPP version and compiler version recorded in the OM file are compatible with the current runtime environment, detecting incompatibility issues early.
4.3 Execution Invocation
After SO is loaded into memory, it is invoked during model execution through the following path:
Model execution request
└── StreamExecutor::Execute()
└── HybridModelExecutor::Execute()
└── NodeExecutor::Execute()
└── OpImplSpaceRegistry::GetFunction() ← Find loaded SO function
└── dlsym() to get function pointer
└── Call tiling/infer shape function
For Single Op scenarios (runtime/v1/single_op/), the execution flow is SingleOpModel::BuildOp() → BuildTaskList() → BuildTEKernelAndTask(), using the kernel implementations in the loaded SO.
5. Single Op Scenario
5.1 Single Op Compilation Process
File Path: api/acl/acl_op_compiler/single_op/compile/local_compiler.cpp
Single Op compilation is an important application scenario of the SO in OM feature. Users compile individual operators to OM files through ACL API:
aclopCompileOp()
└── OpCompiler::CompileOp()
└── LocalCompiler::DoCompile()
└── OnlineCompileAndDump()
└── GeGenerator::BuildSingleOpModel()
└── BuildSingleOp()
└── Compile operator → Generate OM → Package SO
5.2 Single Op Execution
File Path: runtime/v1/single_op/single_op_model.cc
After the Single Op OM file is loaded, it is parsed and executed through the SingleOpModel class, completing input/output tensor description parsing, device memory allocation, address mapping setup, TaskDef list parsing, and execution task chain construction.
The SingleOpModelParam structure contains the space_registries_ field, used to pass SpaceRegistry SO registration information, ensuring Single Op execution can also access tiling functions needed for dynamic shape.
6. Data Structures
6.1 SoInOmFlag Bit Flags
File Path: base/common/op_so_store/op_so_store_utils.h
Bit flags implement type judgment and setting through bit shift operations. From high bit to low bit: SpaceRegistry(15), OpMasterDevice(14), Autofuse(13), CustomOp(12).
Bit Flag Values:
kSpaceRegistry(0):0x8000kOpMasterDevice(1):0x4000kAutofuse(2):0x2000kCustomOp(3):0x1000
Combination example: 0xC000 = SpaceRegistry + OpMasterDevice
6.2 OpSoBin Object
File Path: inc/graph_metadef/graph/op_so_bin.h
OpSoBin encapsulates the metadata and binary content of a single SO file, containing SO file name, vendor name (built-in / vendors/xxx), binary data, data size, and SO type.
6.3 SoStoreHead and SoStoreItemHead
File Path: base/common/op_so_store/op_so_store.h
SoStoreHead records the total SO file count. SoStoreItemHead contains magic number (0x5D776EFD), SO name length, SO type enumeration value, vendor name length, and binary data length.
7. Key File Index
| File Path | Responsibility |
|---|---|
inc/graph_metadef/graph/op_so_bin.h |
OpSoBin, SoBinType, SoInOmInfo definitions |
base/common/op_so_store/op_so_store.h |
OpSoStore class definition, SO serialization container |
base/common/op_so_store/op_so_store.cc |
OpSoStore::Build/Load implementation |
base/common/op_so_store/op_so_store_utils.h |
OpSoStoreUtils bit flag operation utilities |
base/common/model/ge_root_model.cc |
CheckAndSetNeedSoInOM detection logic |
base/common/helper/model_helper.cc |
Core process of SO packaging and loading |
base/common/helper/model_custom_kernels_helper.cc |
LoadOpSoBin, LoadCustomOpRegistry, SaveCustomOpsPartition implementation |
compiler/api/generator/ge_generator.cc |
BuildSingleOpModel compilation entry |
runtime/v1/graph/load/model_manager/model_manager.cc |
InitOpMasterDeviceSo runtime loading |
runtime/v1/single_op/single_op_model.cc |
Single Op model parsing and execution |
api/acl/acl_op_compiler/single_op/compile/local_compiler.cpp |
ACL Single Op compilation implementation |
tests/ge/st/testcase/fast_runtime_v2/so_in_om_system_test.cc |
SO in OM system test cases |