Convert ONNX Model to a Model Usable by MaixCAM2 MaixPy (MUD)
For MaixCAM / MaixCAM-Pro model conversion, please refer to the MaixCAM Model Conversion Documentation
Introduction
Models trained on a computer cannot be directly used on MaixCAM2 due to its limited hardware capabilities. Typically, the model needs to be quantized to INT8 to reduce computation, and converted to a format supported by MaixCAM2.
This article describes how to convert an ONNX model into a model usable by MaixCAM2 (MUD model).
Supported Model File Format for MaixCAM2
MUD (Model Universal Description) is a model description file supported by MaixPy that unifies models across different platforms, making MaixPy code cross-platform. It is a text file in ini format and can be edited with a text editor.
A MUD file is usually accompanied by one or more actual model files. For MaixCAM2, the actual model file is in .axmodel format, and the MUD file provides meta information about it.
For example, for a YOLOv8 model, the files are yolov8n.mud, yolo11n_640x480_vnpu.axmodel, and yolo11n_640x480_npu.axmodel. The .mud file contains:
[basic]
type = axmodel
model_npu = yolo11n_640x480_npu.axmodel
model_vnpu = yolo11n_640x480_vnpu.axmodel
[extra]
model_type = yolo11
type=detector
input_type = rgb
labels = person, bicycle, car, motorcycle, airplane, bus, train, truck, boat, traffic light, fire hydrant, stop sign, parking meter, bench, bird, cat, dog, horse, sheep, cow, elephant, bear, zebra, giraffe, backpack, umbrella, handbag, tie, suitcase, frisbee, skis, snowboard, sports ball, kite, baseball bat, baseball glove, skateboard, surfboard, tennis racket, bottle, wine glass, cup, fork, knife, spoon, bowl, banana, apple, sandwich, orange, broccoli, carrot, hot dog, pizza, donut, cake, chair, couch, potted plant, bed, dining table, toilet, tv, laptop, mouse, remote, keyboard, cell phone, microwave, oven, toaster, sink, refrigerator, book, clock, vase, scissors, teddy bear, hair drier, toothbrush
input_cache = true
output_cache = true
input_cache_flush = false
output_cache_inval = true
mean = 0,0,0
scale = 0.00392156862745098, 0.00392156862745098, 0.00392156862745098
As you can see, the model type is axmodel, and the paths to the model files are relative to the .mud file.
Also included are important attributes:
labels: 80 categories of detection targets.input_cache/output_cache: Whether input/output uses cached memory. Enables faster reading when data is accessed repeatedly (e.g., in post-processing).input_cache_flush: Whether to flush cache to DDR before model execution. If the first layer is an NPU operator, this should betrue. For YOLO11, preprocessing is embedded (CPU first layer), so set tofalse. If unsure, usetrue.output_cache_inval: Whether to invalidate output buffer after model execution to ensure DDR is accessed. If the last layer is NPU, set totrue. For CPU,falseis okay. If unsure, usetrue.mean/scale: Although preprocessing is embedded during conversion, these are shown for reference and must match preprocessing used during training.
Just place all three files in the same directory for use.
Prepare the ONNX Model
The goal of this section is to obtain a .onnx file that Pulsar2 can read. ONNX usually comes from one of these sources:
- Exported after training: for example, after training YOLO11 / YOLOv8 and obtaining a
.ptfile, follow the ONNX export section in Offline Training YOLO Models. For MaixCAM2, use a fixed input size such as640x480or320x240; do not use dynamic input shapes. - Exported from another framework: for example, PyTorch or TensorFlow. Make sure the exported ONNX has a fixed input shape and can be opened in Netron.
- Provided by a third party: you can start from the ONNX file directly, but you need to confirm that the license allows usage and that the model structure is suitable for MaixCAM2.
If .mud and .axmodel files are already available, the model has already been converted for MaixCAM2 and can be deployed directly. This conversion flow is not required.
Place the ONNX file in a separate working directory and name it model.onnx. Then open it with Netron and record the following information:
| Item | Verification method | Subsequent use |
|---|---|---|
| Input node name | Check the input node area in Netron; a common name is images |
Input parameter for the ONNX extraction command and tensor_name in config.json |
| Input shape | Check the input node shape in Netron, for example 1x3x480x640 |
Used to confirm that export size and MaixPy runtime input size match |
| Candidate output nodes | Check the final output nodes before post-processing | Output parameters for the ONNX extraction command and output_processors in config.json |
Also confirm that the model operators are supported by Pulsar2. MaixCAM2 corresponds to the AX620E platform in Pulsar2. If conversion reports an unsupported operator, adjust the model structure during training/export, or use a model structure already supported by MaixPy.
Find Appropriate Quantization Output Nodes
The goal of this section is to generate export.onnx, which is used by the conversion command later.
Many detection models contain post-processing nodes at the end of the ONNX graph. These nodes are usually better handled by CPU code. Quantizing the full ONNX may increase quantization error or cause conversion failure, so first choose suitable output nodes and extract a smaller ONNX.
Use the following table to select output nodes for common model types. For the basis of the YOLO node selection, see Offline Training YOLO Models - Output Node Selection. For classification model trimming principles, see ONNX Node Trimming Tutorial.
| Model type | Recommended output node choice | Next step |
|---|---|---|
| YOLO11 / YOLOv8 detection | For MaixCAM2, use the /model.xx/Concat... output nodes and leave decoding / NMS to MaixPy post-processing |
Use the common node table below |
| YOLO11 / YOLOv8 pose / seg / obb | These models have more output nodes | Use the MaixCAM2 scheme in Offline Training YOLO Models |
| Classification model | Use the final classification output; if the graph ends with softmax, use the output before softmax |
Record that node name |
Common MaixCAM2 output nodes for YOLO detection:
| Model | Recommended output nodes |
|---|---|
| YOLOv8 detection | /model.22/Concat_1_output_0/model.22/Concat_2_output_0/model.22/Concat_3_output_0 |
| YOLO11 detection | /model.23/Concat_output_0/model.23/Concat_1_output_0/model.23/Concat_2_output_0 |
If your node names are not exactly the same, use Netron to find nodes at the same position and with the same meaning instead of copying the names mechanically. After confirming the input node name and output node names, install the tools needed for extraction and simplification:
pip install onnx onnxsim
Then run:
python -c "import onnx,sys; onnx.utils.extract_model(sys.argv[1], sys.argv[2], [s.strip() for s in sys.argv[3].split(',')], [s.strip() for s in sys.argv[4].split(',')])" model.onnx tmp_extract.onnx "images" "/model.23/Concat_output_0,/model.23/Concat_1_output_0,/model.23/Concat_2_output_0"
onnxsim tmp_extract.onnx export.onnx
Replace:
model.onnxwith your original ONNX file name."images"with the input node name shown in Netron."/model.23/Concat_output_0,...with your selected output node names, separated by English commas.
After the command succeeds, export.onnx is generated in the current directory. All later --input ./export.onnx commands refer to this extracted and simplified ONNX file.
Install the Model Conversion Environment
Follow the Pulsar2 Toolchain Documentation to install it. Use Docker to avoid host environment conflicts. If you're new to Docker, think of it as a lightweight virtual machine.
Install Docker
Follow Docker's official documentation. After installation, run the following command. If it prints a version number, Docker is ready:
docker --version
Example:
# Install Docker dependencies
sudo apt-get update
sudo apt-get install apt-transport-https ca-certificates curl gnupg-agent software-properties-common
# Add Docker repo
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -
sudo add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable"
# Install Docker
sudo apt-get update
sudo apt-get install docker-ce docker-ce-cli containerd.io
Load the Docker Image
Follow the instructions in the Pulsar2 Documentation, or download the image from Hugging Face / ModelScope.
After downloading pulsar2_vxx.tar.gz, load it with:
docker load -i pulsar2_vxx.tar.gz
Then check the actual image name:
docker images | grep pulsar2
Run the Container
Enter your model conversion work directory and run the container. Replace pulsar2:6.0 with the image name shown by docker images if yours is different:
mkdir -p ~/maixcam2_convert
cd ~/maixcam2_convert
docker run -it --net host --rm -v "$PWD":/data -w /data pulsar2:6.0
Inside the container, the current directory is /data. Put export.onnx, datasets/train.tar, and config/*.json in this directory for the following steps.
Convert the Model
Before running pulsar2 build, prepare these files in the working directory:
| File | Purpose |
|---|---|
export.onnx |
The extracted and simplified ONNX from the previous step |
datasets/train.tar |
Calibration image package for INT8 quantization |
config/yolo11n.npu.json |
Configuration for the full NPU model |
config/yolo11n.vnpu.json |
Configuration for the virtual NPU model |
To create the calibration package, put 20 to 100 representative real-scene images into datasets/train_images, then run:
mkdir -p datasets/train_images
# Put 20 to 100 representative images into datasets/train_images first.
tar -cf datasets/train.tar -C datasets/train_images .
The main Pulsar2 command is:
pulsar2 build --target_hardware AX620E --input onnx_path --output_dir out_dir --config config_path
The parameters should be filled as follows:
| Parameter | Value |
|---|---|
--target_hardware AX620E |
Fixed for MaixCAM2 |
--input onnx_path |
The extracted ONNX, for example ./export.onnx |
--output_dir out_dir |
Temporary output directory, for example ./tmp |
--config config_path |
Conversion config file, for example ./config/yolo11n.npu.json |
For YOLO11 detection, create config/yolo11n.npu.json first. The important fields are:
{
"model_type": "ONNX",
"npu_mode": "NPU2",
"quant": {
"input_configs": [
{
"tensor_name": "images",
"calibration_dataset": "datasets/train.tar",
"calibration_size": 64,
"calibration_mean": [0, 0, 0],
"calibration_std": [255, 255, 255]
}
],
"calibration_method": "MinMax",
"precision_analysis": true
},
"input_processors": [
{
"tensor_name": "images",
"tensor_format": "RGB",
"tensor_layout": "NCHW",
"src_format": "RGB",
"src_dtype": "U8",
"src_layout": "NHWC",
"csc_mode": "NoCSC"
}
],
"output_processors": [
{
"tensor_name": "/model.23/Concat_output_0",
"dst_perm": [0, 2, 3, 1]
},
{
"tensor_name": "/model.23/Concat_1_output_0",
"dst_perm": [0, 2, 3, 1]
},
{
"tensor_name": "/model.23/Concat_2_output_0",
"dst_perm": [0, 2, 3, 1]
}
],
"compiler": {
"check": 3,
"check_mode": "CheckOutput",
"check_cosine_simularity": 0.9
}
}
Then copy it to config/yolo11n.vnpu.json and change only:
cp config/yolo11n.npu.json config/yolo11n.vnpu.json
"npu_mode": "NPU1"
NPU2 uses the full NPU and should be written to model_npu in the .mud file. NPU1 leaves resources for AI-ISP / virtual NPU usage and should be written to model_vnpu.
Run the conversion commands:
pulsar2 build --target_hardware AX620E --input ./export.onnx --output_dir ./tmp --config ./config/yolo11n.npu.json
mkdir -p out
cp tmp/compiled.axmodel out/yolo11n_npu.axmodel
rm -rf tmp
pulsar2 build --target_hardware AX620E --input ./export.onnx --output_dir ./tmp --config ./config/yolo11n.vnpu.json
cp tmp/compiled.axmodel out/yolo11n_vnpu.axmodel
After successful execution, out/ contains yolo11n_npu.axmodel and yolo11n_vnpu.axmodel.
Write the mud File
Create out/yolo11n.mud in the same directory as the two .axmodel files:
[basic]
type = axmodel
model_npu = yolo11n_npu.axmodel
model_vnpu = yolo11n_vnpu.axmodel
[extra]
model_type = yolo11
type=detector
input_type = rgb
labels = your_label_0,your_label_1,your_label_2
input_cache = true
output_cache = true
input_cache_flush = false
output_cache_inval = true
mean = 0,0,0
scale = 0.00392156862745098, 0.00392156862745098, 0.00392156862745098
Replace labels with the class names used during training, and keep their order exactly the same as the training dataset. The [basic] section is required; once it is correct, MaixPy can load the model through the .mud file.
Deploy to the Device and Verify Quickly
MaixPy usually loads the .mud file. The .mud file then points to the actual .axmodel files. The simplest approach is to put them in the same directory, for example:
/root/models/my_model.mud
/root/models/my_model_npu.axmodel
/root/models/my_model_vnpu.axmodel
Then first confirm that the model can be loaded:
from maix import nn
model = nn.NN("/root/models/my_model.mud")
print(model)
If the model type is already supported by MaixPy, prefer the wrapped API. For example, for YOLO, see the YOLO object detection documentation:
from maix import nn
detector = nn.YOLO11(model="/root/models/yolo11n.mud", dual_buff=True)
Debug Checklist
If the model cannot be loaded or the result is wrong, check these items first:
- Whether the
.mudand.axmodelfiles are in the same directory, and whethermodel_npuandmodel_vnpuin the.mudfile use the correct file names. - Whether the path used on the device really exists, such as
/root/models/xxx.mud. - Whether
labelsexactly matches the class count and class order used during training. - Whether
model_typeis supported by MaixPy, such asyolo11,yolov8, orclassifier. - Whether input resolution,
mean,scale, and RGB/BGR order match training, export, and conversion settings. - Whether the ONNX output nodes exactly match
output_processorsinconfig.json. - If you are not sure whether the problem is the model or your code, test with a MaixHub model or a built-in model first. After an official model runs correctly, debug your own model.
After these basic checks pass, continue with the specific model documentation or conversion workflow. If this is a model type not yet wrapped by MaixPy, continue with the next section.
Write Post-processing Code
If the model type is already supported by MaixPy, such as YOLO or a classifier, you usually do not need to write post-processing manually. Use the corresponding MaixPy API directly.
If MaixPy does not yet wrap your model type, implement post-processing according to the model outputs and choose one of these paths:
- Quick verification: use
maix.nn.NNto load the.mudfile, callforwardorforward_image, and write post-processing in Python. See Porting a New Model for the full workflow. - Formal integration: add a model decoding class in
MaixCDKso bothMaixCDKandMaixPycan use it with better performance. You can refer to the YOLOv5 source code, add the correspondinghppfile, complete the@maixpyannotations, and rebuild MaixPy.
After adding support for a new model, you can submit a Pull Request to the main MaixPy repository or share your model on MaixHub.