02 .xmm 模型加载
.xmm 是 GK7206 NPU 的专有神经网络模型格式,它是一个二进制文件,主要包含以下内容:
| 内容 | 说明 |
|---|---|
| 模型权重 (Weights) | 量化后的网络参数 |
| 计算图结构 | 算子编排、层间连接关系 |
| 私有数据 (Private Data) | 输入/输出 tensor 描述、量化参数等 |
| 文件头 | 以 "ZZZZ" 魔数开头 |
模型来源:通常由训练框架(如 PyTorch/ONNX)导出后,经过 Goke 提供的XMTVM模型转换工具量化并编译生成 .xmm 文件。
说明
.bin 格式的模型文件是更高层 SVP 接口封装使用的模型格式(内部同样调用 NPU 加载 .xmm)
GK7206 提供了两套 API,适用场景不同:
| 方式 | API 层级 | 头文件 | 适用场景 |
|---|---|---|---|
| 底层 CL 接口 | xmedia_cl_* | xmedia_cl.h | 直接加载 .xmm,完全控制推理流程 |
| 高层 SVP 接口 | xmedia_svp_* | xmedia_svp.h | 使用 .bin 模型,内置前后处理(NMS 等),开箱即用 |
提示
高层SVP接口请参考SVP视频处理
底层 CL 接口:加载 .xmm 模型的完整步骤
| 阶段 | 关键操作 | 说明 |
|---|---|---|
| 阶段 1 | 系统初始化 | 初始化 XMedia 系统模块,并初始化 Compute Library(NPU 运行时) |
| 阶段 2 | 获取 NPU 设备并创建上下文 | 先获取设备数量,再获取设备 ID,最后创建上下文作为资源管理基础 |
| 阶段 3 | 查询模型所需内存 | 通过 xmedia_cl_graph_querysize_from_file() 获取 workspace 和 weight 的大小 |
| 阶段 4 | 分配 NPU 专用内存(MMZ) | 为 workspace 和 weight 分配物理连续内存,不能使用普通 malloc() |
| 阶段 5 | 加载模型 | 使用 xmedia_cl_graph_loadmodel_from_file_withmem() 或简化版接口加载 .xmm 模型 |
| 阶段 6 | 获取输入/输出 Tensor 信息 | 查询输入输出数量并获取完整的 tensor 描述信息 |
| 阶段 7 | 准备数据并执行推理 | 分配输入输出缓冲区,设置 tensor 地址,刷新 cache 后执行推理 |
| 阶段 8 | 释放资源 | 卸载模型、释放上下文、释放设备、反初始化并释放 MMZ 内存 |
阶段 1:系统初始化
#include "xmedia_cl.h"
#include "xmedia_mmz.h"
#include "xmedia_sys.h"
// 初始化 XMedia 系统模块
ret = xmedia_sys_init(XMEDIA_NULL);
// 初始化 Compute Library(NPU 运行时)
ret = xmedia_cl_init();阶段 2:获取 NPU 设备 & 创建上下文
xmedia_cl_u32 num_devices = 0;
xmedia_cl_device_id *devices = NULL;
xmedia_cl_context context = NULL;
// 第一次调用:获取设备数量
ret = xmedia_cl_get_device_ids(XMEDIA_CL_DEVICE_NPU, NULL, &num_devices);
// 分配设备数组
devices = calloc(num_devices, sizeof(xmedia_cl_device_id));
// 第二次调用:获取设备 ID
ret = xmedia_cl_get_device_ids(XMEDIA_CL_DEVICE_NPU, devices, &num_devices);
// 创建上下文(所有资源管理的基础)
xmedia_cl_s32 err_code = 0;
context = xmedia_cl_create_context(num_devices, devices, &err_code);阶段 3:查询模型所需内存
xmedia_cl_u32 worksize, weightsize;
// 查询模型需要的 workspace 和 weight 大小
ret = xmedia_cl_graph_querysize_from_file("data/neuron_network.xmm",
&worksize, &weightsize);worksize:推理中间结果的临时缓冲区(workspace)weightsize:模型权重占用的空间
阶段 4:分配 NPU 专用内存(MMZ)
xmedia_u64 phy_addr[4] = {0};
void *virt_addr[4] = {0};
// 分配 workspace
if (worksize) {
XMEDIA_API_SYS_MmzAlloc_Cached(&phy_addr[0], &virt_addr[0],
"npu_workspace", NULL, worksize);
}
// 分配 weight
if (weightsize) {
XMEDIA_API_SYS_MmzAlloc_Cached(&phy_addr[1], &virt_addr[1],
"npu_weight", NULL, weightsize);
}注意
NPU 使用的是物理连续内存(MMZ),不能用普通 malloc(),必须用 xmedia_mmz_alloc() / xmedia_mmz_map()。
阶段 5:加载模型
xmedia_cl_graph graph = NULL;
// 从文件加载,使用用户提供的内存
ret = xmedia_cl_graph_loadmodel_from_file_withmem(
&context,
"data/neuron_network.xmm", // .xmm 文件路径
virt_addr[0], // workspace 地址
worksize, // workspace 大小
virt_addr[1], // weight 地址
weightsize, // weight 大小
&graph // 输出: graph 句柄
);也有简化版本(SDK 自动分配内存):
ret = xmedia_cl_graph_loadmodel_from_file(&context, "data/neuron_network.xmm", &graph);阶段 6:获取输入/输出 Tensor 信息
xmedia_cl_tensor_info_inout input = {0}, output = {0};
xmedia_cl_u32 input_num = 0, output_num = 0;
// 第一次:获取输入数量
ret = xmedia_cl_graph_get_input(graph, input_num, &input);
input_num = input.num;
// 分配 tensor 数组内存
malloc_inout_tensor_mem(&input);
// 第二次:获取完整的输入 tensor 描述(shape, size, type, quant)
ret = xmedia_cl_graph_get_input(graph, input_num, &input);
// 同样方式获取输出
ret = xmedia_cl_graph_get_output(graph, output_num, &output);
output_num = output.num;
malloc_inout_tensor_mem(&output);
ret = xmedia_cl_graph_get_output(graph, output_num, &output);每个 tensor 包含:
typedef struct {
xmedia_cl_u32 tensor_id;
void *addr; // 数据地址(用户需要设置)
xmedia_cl_tensor_shape shape; // 维度信息 (N,C,H,W)
xmedia_cl_tensor_quant quant; // 量化参数 (scale, zero_point)
xmedia_cl_u32 size; // 数据字节大小
xmedia_cl_s8 *name; // tensor 名称
} xmedia_cl_tensor;阶段 7:准备数据 & 执行推理
// 分配输入/输出缓冲区
XMEDIA_API_SYS_MmzAlloc_Cached(&phy_addr[2], &virt_addr[2],
"npu_input", NULL, inputsize);
XMEDIA_API_SYS_MmzAlloc_Cached(&phy_addr[3], &virt_addr[3],
"npu_output", NULL, outputsize);
// 设置输入数据地址(将预处理后的数据写入)
input.tensor[0].addr = virt_addr[2];
// ... 拷贝实际图像数据到 input.tensor[i].addr ...
// 设置输出数据地址
output.tensor[0].addr = virt_addr[3];
// 刷新 cache(确保 NPU 看到最新数据)
XMEDIA_API_SYS_MmzFlushCache(phy_addr[2], virt_addr[2], inputsize);
// 绑定输入输出到 graph
ret = xmedia_cl_graph_set_inout(graph, &input, &output);
// 执行推理(同步)
ret = xmedia_cl_graph_process(graph);推理完成后,output.tensor[i].addr 中就是推理结果。
阶段 8:释放资源
// 卸载模型
xmedia_cl_graph_unload(graph);
// 释放上下文
xmedia_cl_release_context(context);
// 释放设备
xmedia_cl_release_device_ids(devices, &num_devices);
free(devices);
// 反初始化
xmedia_cl_uninit();
xmedia_sys_exit();
// 释放 MMZ 内存
for (i = 0; i < 4; i++) {
if (virt_addr[i]) XMEDIA_API_SYS_MmzFree(phy_addr[i], virt_addr[i]);
}提示
- 在使用 NPU 之前,需要先加载内核驱动:
- 加载 ko 驱动(在 out/xm7206xxx/ko 目录下):
./load xm7206v11a -i - 或者手动加载 NPU 模块:
insmod xm_npu.ko
- 加载 ko 驱动(在 out/xm7206xxx/ko 目录下):
- 详细.xmm模型使用可参考sample/npu/xmm
