- 人工智能
- 算子库
- 深度学习
- CANN
- Ascend
【免费下载链接】ops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
本指南以 experimental/matmul/weight_quant_batch_matmul_experiment/examples/README.md 为骨架,系统讲解 WeightQuantBatchMatmulExperiment 算子(A16W4 PerGroup 伪量化 MatMul)在 CANN ops-nn 仓库中的目录结构、两段式 aclnn 单算子 API 调用原理、编译运行全流程、数据生成与真值校验逻辑,并结合 op_host / op_kernel 源码与 run.sh、gen_data.py、verify_result.py 等脚本进行源码级佐证,帮助你快速上手自定义算子的单算子 API 验证与性能采集。
1. 样例工程整体目录结构
WeightQuantBatchMatmulExperiment 算子样例工程位于experimental/matmul/weight_quant_batch_matmul_experiment,整体结构如下:
├── examples // 通过aclnn调用的方式调用WeightQuantBatchMatmulExperiment算子 │ ├── inc // 头文件目录 │ ├── input // 存放脚本生成的输入数据目录 │ ├── output // 存放算子运行输出数据和真值数据的目录 │ ├── scripts // 存放样例工程依赖脚本的目录 │ ├── src // 存放样例工程的源代码目录 │ └── run.sh // 执行命令脚本其中 examples 各子目录与文件的职责:
| 路径 | 职责 |
|---|---|
examples/inc/ | 头文件目录,包含common.h(日志与公共工具宏)、op_runner.h(算子执行封装类声明)、operator_desc.h(算子输入输出描述类声明) |
examples/input/ | 存放脚本生成的输入数据(input_a.bin、input_weight.bin、input_antiquant_scale_fp16.bin),由run.sh清理并重新生成 |
examples/output/ | 存放算子运行输出数据(output_z.bin)与真值数据(golden.bin),以及 msprof 性能采集结果目录msprof_result |
examples/scripts/ | 依赖脚本,gen_data.py负责生成输入数据与真值数据,verify_result.py负责输出结果比对 |
examples/src/ | 样例源代码,包含main.cpp、op_runner.cpp、operator_desc.cpp、common.cpp及CMakeLists.txt |
examples/run.sh | 一键执行脚本:生成数据 → cmake/make 编译 → 运行 aclnn 样例 → 校验结果 → msprof 采集性能 |
2. 算子背景:A16W4 PerGroup 伪量化 MatMul 与 MSD 算法
在阅读样例代码之前,先理解算子本身。WeightQuantBatchMatmulExperiment 实现的是 MatMul 伪量化 A16W4 PerGroup 场景:激活(x1)为 float16(A16),权重(x2)为 int4(W4),并按 PerGroup 分组完成伪量化计算,整个伪量化过程使用 MSD(Multi-Stage Decomposition,多级分解)算法完成。算子详细功能与数学表达式见 算子 README,其核心思路是把浮点激活逐级分解为多个整数分量:
- 计算 (A_{max} = rowMax(|A_{group}|));
- 计算 (tmp_1 = \frac{7.49 \times A_{group}}{A_{max}});
- 计算 (A_1 = round(tmp_1));
- 计算 (tmp_2 = (tmp_1 - A_1) \times 14.98);
- 计算 (A_2 = round(tmp_2));
- 计算 (tmp_3 = (tmp_2 - A_2) \times 14.98);
- 计算 (A_3 = round(tmp_3));
- 构造矩阵 (A_{int} = [A_1; A_2; A_3]);
- 计算 (Y_{int} = A_{int} Weight_{group});
- 计算 (Y_{group} = [(\frac{Y_1}{7.49}+\frac{Y_2}{7.49 \times 14.98}+\frac{Y_3}{7.49 \times 14.98 \times 14.98}) \times A_{max}] \times scale_{group});
- 计算 (Y^i = Y_{group} + Y^{i-1})(分组累加)。
样例中 7.49 / 14.98 两个常数以f1、f2形式出现在 gen_data.py 中,与 README 中的公式严格对应。
算子规格如下:
| 项 | 内容 |
|---|---|
| 算子类型(OpType) | WeightQuantBatchMatmul |
| 输入 x1 | shape M×K,float16,ND |
| 输入 x2(weight) | shape K×N,int4,ND |
| 输入 antiquant_scale | shape GroupNum×N,float16,ND |
| 输出 y | shape M×N,float16,ND |
| 核函数名 | WeightQuantBatchMatmulExperiment |
算子定义在 op_host/weight_quant_batch_matmul_experiment_def.cpp 中通过注册宏OP_ADD(WeightQuantBatchMatmulExperiment)声明:输入x(float16)、weight(int4)、antiquant_scale(float16)均为 REQUIRED,输出y为 float16,且该实现为 AICore 算子(this->AICore()),配置ascend910b。从 算子 README 可知,该算子支持 Atlas A3 训练/推理系列产品与 Atlas A2 训练/推理系列产品。
3. 两段式 aclnn 单算子 API 调用原理
完成自定义算子的开发部署后,可以通过单算子调用的方式验证单算子功能。src/main.cpp代码即为单算子 API 执行方式:单算子 API 执行是基于 C 语言的 API 执行算子,无需提供单算子描述文件进行离线模型的转换,直接调用单算子 API 接口即可。自定义算子编译部署后,会自动生成单算子 API,可以在应用程序中直接调用。
算子 API 的形式一般定义为“两段式接口”,形如:
// 获取算子使用的workspace空间大小 aclnnStatus aclnnWeightQuantBatchMatmulExperimentGetWorkspaceSize(const aclTensor *a, const aclTensor *b, const aclTensor *bias, bool transposeX1, bool transposeX2, const aclTensor *out, uint64_t *workspaceSize, aclOpExecutor **executor); // 执行算子 aclnnStatus aclnnWeightQuantBatchMatmulExperiment(void *workspace, uint64_t workspaceSize, aclOpExecutor *executor, aclrtStream stream);- 第一段接口
aclnnWeightQuantBatchMatmulExperimentGetWorkspaceSize:主要用于计算本次 API 调用计算过程中需要多少 workspace 内存,同时创建并返回aclOpExecutor句柄; - 获取到 workspaceSize 大小后,按照该大小申请 Device 侧内存,然后调用第二段接口
aclnnWeightQuantBatchMatmulExperiment执行计算。
4. 样例代码实现解析
4.1 算子描述构建(CreateOpDesc)
在 main.cpp 中,通过CreateOpDesc()定义算子的输入输出描述,示例取 M=1、N=12288、K=8192、groupSize=128:
OperatorDesc CreateOpDesc() { int64_t m = 1; int64_t n = 12288; int64_t k = 8192; int64_t groupSize = 128; // define operator std::vector<int64_t> shapeA{m, k}; std::vector<int64_t> shapeWeight{k, n}; std::vector<int64_t> shapeAntiquantScale{k / groupSize, n}; std::vector<int64_t> shapeC{m, n}; aclDataType dataTypeA = ACL_FLOAT16; aclDataType dataTypeWeight = ACL_INT4; aclDataType dataTypeAntiquantScale = ACL_FLOAT16; aclDataType dataTypeC = ACL_FLOAT16; aclFormat format = ACL_FORMAT_ND; OperatorDesc opDesc; opDesc.AddInputTensorDesc(dataTypeA, shapeA.size(), shapeA.data(), format); opDesc.AddInputTensorDesc(dataTypeWeight, shapeWeight.size(), shapeWeight.data(), format); opDesc.AddInputTensorDesc(dataTypeAntiquantScale, shapeAntiquantScale.size(), shapeAntiquantScale.data(), format); opDesc.AddOutputTensorDesc(dataTypeC, shapeC.size(), shapeC.data(), format); return opDesc; }关键点:
- 三个输入依次为激活
x(M×K)、权重weight(K×N,int4)、反量化 scale(GroupNum×N,其中 GroupNum = K / groupSize = 8192 / 128 = 64); - 数据类型与算子定义文件中的
DataType({ge::DT_FLOAT16})、DataType({ge::DT_INT4})完全一致; - 所有张量均使用 ND 格式。
4.2 资源初始化与数据搬入搬出
main.cpp的主流程为:InitResource()→RunOp()→DestroyResource()。其中InitResource()完成 acl 初始化(aclInit)、设置设备(aclrtSetDevice)、获取运行模式(aclrtGetRunMode,用于区分 Host/Device 模式);DestroyResource()负责aclrtResetDevice与aclFinalize清理。
SetInputData()从../input/目录读取三个 bin 文件到 Host 侧输入缓冲区,ProcessOutputData()将输出写回../output/output_z.bin:
bool SetInputData(OpRunner& runner) { size_t fileSize = 0; ReadFile("../input/input_a.bin", fileSize, runner.GetInputBuffer<void>(0), runner.GetInputSize(0)); ReadFile("../input/input_weight.bin", fileSize, runner.GetInputBuffer<void>(1), runner.GetInputSize(1)); ReadFile("../input/input_antiquant_scale_fp16.bin", fileSize, runner.GetInputBuffer<void>(2), runner.GetInputSize(2)); return true; } bool ProcessOutputData(OpRunner& runner) { WriteFile("../output/output_z.bin", runner.GetOutputBuffer<void>(0), runner.GetOutputSize(0)); return true; }4.3 aclnn 两段式调用核心逻辑(OpRunner::RunOp)
真正体现“两段式接口”调用的是 op_runner.cpp 中的RunOp():
- 逐输入通过
aclrtMemcpy(Host→Device,Device 模式为 Device→Device)将数据拷贝到 Device 侧; - 创建 stream(
aclrtCreateStream); - 调用第一段接口获取 workspace 大小与执行句柄:
size_t workspaceSize = 0; bool transposeX1 = false; bool transposeX2 = false; aclOpExecutor* handle = nullptr; auto ret = aclnnWeightQuantBatchMatmulExperimentGetWorkspaceSize(inputTensor_[0], inputTensor_[1], inputTensor_[2], outputTensor_[0], &workspaceSize, &handle);- 若 workspaceSize 不为 0,用
aclrtMalloc(..., ACL_MEM_MALLOC_HUGE_FIRST)申请 Device 侧 workspace; - 调用第二段接口执行算子:
ret = aclnnWeightQuantBatchMatmulExperiment(workspace_, workspaceSize, handle, stream);aclrtSynchronizeStreamWithTimeout(stream, 5000)同步等待计算完成;- 将输出结果从 Device 拷贝回 Host(
aclrtMemcpy,Device 模式为 Device→Device)。
OpRunner::Init()中为每个输入输出分配 Device 内存与 Host 内存,并通过aclCreateTensor构建aclTensor描述;析构函数中依次释放 workspace、aclTensor、aclDataBuffer以及 Device/Host 内存,资源管理较为完整。
4.4 编译配置(CMakeLists.txt)
examples/src/CMakeLists.txt 使用DDK_PATH与NPU_HOST_LIB环境变量定位 CANN 开发套件与 Host 侧链接库(默认/usr/local/Ascend/cann及${arch}-${os}/devlib),同时引入自定义算子包头文件与库目录${INC_PATH}/opp/vendors/custom_nn/op_api。生成的可执行文件名为execute_weight_quant_batch_matmul_experiment_op,输出到../output目录,链接库包括nnopbase、cust_opapi、ascendcl、acl_op_compiler、stdc++。其中cust_opapi正是自定义算子自动生成的 aclnn 单算子 API 动态库。
5. 运行样例算子
5.1 前置准备(编译安装自定义算子包)
运行样例前,需要先完成自定义算子的编译部署。根据 算子 README,步骤如下:
(1)配置环境变量,根据 CANN 开发套件包(toolkit 包 + ops 包)的安装方式选择其一:
# 默认路径,root用户安装CANN软件包 export ASCEND_INSTALL_PATH=/usr/local/Ascend/cann # 默认路径,非root用户安装CANN软件包 export ASCEND_INSTALL_PATH=$HOME/Ascend/cann # 指定路径install_path,安装CANN软件包 export ASCEND_INSTALL_PATH=${install_path}/cann(2)编译并安装自定义算子 run 包:
# 切换到工程根目录 cd ${git_clone_path} # 编译样例算子run包 bash build.sh --pkg --soc=ascend910b --vendor_name=custom --ops=weight_quant_batch_matmul_experiment --experimental # 安装自定义算子run包 ./build_out/cann-ops-nn-${vendor_name}-${arch}_linux.run注意:--soc=ascend910b与 op_def 中AICore().AddConfig("ascend910b")保持一致;--experimental表明该算子位于 experimental 目录。
5.2 编译+执行 aclnn 接口样例并采集性能
# 切换weight_quant_batch_matmul_experiment aclnn执行用例目录 cd ${git_clone_path}/experimental/matmul/weight_quant_batch_matmul_experiment/examples # 编译+执行aclnn接口+采集性能数据 bash run.sh # 切换aclnn用例性能数据目录 cd ${git_clone_path}/experimental/matmul/weight_quant_batch_matmul_experiment/examples/output/msprof_result5.3 run.sh 执行流程逐步拆解
run.sh 内部按六个步骤执行:
- 清理:删除
$HOME/ascend/log/*与./input/*.bin、./output/*.bin遗留文件; - 生成输入与真值数据:
python3 scripts/gen_data.py; - 编译 acl 可执行文件:进入
build目录执行cmake ../src -DCMAKE_SKIP_RPATH=TRUE与make; - 运行可执行文件:设置
LD_LIBRARY_PATH指向自定义算子包$_ASCEND_INSTALL_PATH/opp/vendors/custom_nn/op_api/lib,在output目录运行./execute_weight_quant_batch_matmul_experiment_op,输出日志同时通过tee保存到output_msg.txt; - 结果校验:
python3 scripts/verify_result.py output/output_z.bin output/golden.bin; - 性能采集:
msprof --output=./msprof_result ./execute_weight_quant_batch_matmul_experiment_op,性能数据输出到output/msprof_result。
脚本开头会自动从ASCEND_INSTALL_PATH/ASCEND_HOME_PATH环境变量(缺省/usr/local/Ascend/cann)中确定 CANN 安装路径,并source set_env.sh,同时导出DDK_PATH与NPU_HOST_LIB供 CMake 使用。
6. 数据生成与真值校验机制
6.1 输入数据与真值生成(gen_data.py)
gen_data.py 使用 numpy 复现 README 中的 MSD 伪量化公式,生成输入与真值:
- 激活 (A):
np.random.uniform(low=-3, high=3, size=(M, K))转为 float16; - 反量化 scale:
np.random.uniform(low=-3, high=3, size=(K//GROUP_SIZE, N))转为 float16; - 权重 (W):
np.random.uniform(low=-7, high=7, size=(K, N))转为 int8(取值范围 [-7, 7] 与 int4 对称量化一致),并执行int4 打包:(int4.astype(np.uint8) << 4)[:, 1:N:2] + (int4.astype(np.uint8) & 0x0F)[:, 0:N:2],把两个 int4 元素打包进一个 uint8 字节,shape 变为 (K, N//2),落盘为input/input_weight.bin;未打包的原始权重另存为./input_input_weight_real.bin便于调试对照; - 真值计算:逐 group(groupSize=128)执行公式 1~11,将每个分组的 (c_{tmp} \times scale \times a_{max}) 累加,得到 float32 的 golden 后转为 float16 落盘为
output/golden.bin。
生成的文件对应关系:input_a.bin(激活)、input_weight.bin(打包后 int4 权重)、input_antiquant_scale_fp16.bin(反量化 scale)、golden.bin(真值)。
6.2 输出校验(verify_result.py)
verify_result.py 将算子输出与真值逐元素比较:先转 float32 再使用np.isclose(rtol=1e-6, atol=1e-9,equal_nan=True)找出不一致元素;对不一致元素计算相对误差rdiff = |output - golden| / golden,超过error_tol=1e-3的元素计入误差数量;最终误差率error_ratio <= error_tol即视为校验通过,否则以[ERROR] result error退出码 1 结束,run.sh 随之报错。此外,该校验脚本最多打印前 101 个不一致元素的索引、期望值、实际值与相对误差,便于定位问题。
7. 算子 kernel 与流水设计
7.1 kernel 入口
kernel 入口位于 op_kernel/weight_quant_batch_matmul_experiment.cpp,核函数签名为weight_quant_batch_matmul_experiment(xGM, weightGM, antiquantScaleGM, yGM, workspaceGM, tiling),通过GET_TILING_DATA_WITH_STRUCT获取 tiling 数据后实例化WqbmmExpMsdController模板类并调用op.Init(...)与op.Process()。模板参数DTYPE_X、DTYPE_WEIGHT、MSD_MODE由编译期确定,支持基础模板与计算解耦流水模板两套实现(见op_kernel/msd/目录下的*_msd_controller.h、*_cube_compute.h、*_vec_compute.h、*_tool.h)。
7.2 基础流水模板
基础模板流水如图:
在前处理模块中,多个核产生后处理模块依赖的 (A_{max}) 和矩阵计算模块依赖的 (A_{int});矩阵计算模块产生后处理模块依赖的 (Y_{int})。因此这些模块执行结束后都需要执行一次全核同步,保证下个模块处理时上一个模块已完全使用完数据,避免数据踩踏。
7.3 Preload 流水模板
Preload 流水模板如图:
前处理计算单元可以连续执行两轮,实现“当前轮的矩阵计算模板处理”与“上一轮的后处理模块、下一轮的前处理模块”并行计算的效果,从而隐藏跨模块同步等待。
7.4 性能分析参考
算子 README 给出了基于 Atlas A2 的示例测试数据(仅为该样例工程自测结果,不代表全部场景):
| 模板类型 | 基础流水模板(us) | preload 流水模板(us) |
|---|---|---|
| 算子耗时 | 396 | 303 |
即在相同计算逻辑下,preload 流水模板通过流水优化获得了显著的性能提升。样例通过run.sh第 6 步使用 msprof 工具自动采集每个模板(执行用例分别编译对应模板)的性能数据,输出到examples/output/msprof_result供分析对比。
8. 更新说明
| 时间 | 更新事项 |
|---|---|
| 2026/01/06 | 新增本 readme |
9. 小结与延伸阅读
通过本样例可以完整掌握一条 CANN 自定义算子的单算子验证链路:
- 算子定义与 kernel 实现:参考 op_host/weight_quant_batch_matmul_experiment_def.cpp 与 op_kernel/weight_quant_batch_matmul_experiment.cpp;
- 算子功能与流水设计:见 算子 README(支持的产品、算子规格、MSD 数学公式、两种流水模板与性能参考);
- aclnn 两段式 API 调用:见 examples/src/op_runner.cpp 与 examples/src/main.cpp;
- 一键编译运行与校验:见 examples/run.sh 及 gen_data.py、verify_result.py。
需要说明的是,本文所述编译运行步骤依赖本机已安装对应版本的 CANN 开发套件(toolkit 包 + ops 包),且--soc参数需与目标 NPU 型号匹配;若需深入了解单算子 API 调用的通用规范,可参考 CANN 官方文档中“单算子 API 调用”相关章节。
- 人工智能
- 算子库
- 深度学习
- CANN
- Ascend
【免费下载链接】ops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
相关推荐
CANN ops-nn 中 MatmulFp32 算子 aclnn 单算子调用样例全解析
CANN ops nn 中 MatmulFp32 算子 aclnn 单算子调用样例全解析 导读 本文基于 CANN ops nn 开源仓库中 experimen
人工智能算子库深度学习CANNAscendCANN ops-nn L2Loss 算子深度解析:原理、aclnn 调用与 NPU 实现
CANN ops nn L2Loss 算子深度解析:原理、aclnn 调用与 NPU 实现 本篇技术指南以 CANN 神经网络算子库 ops nn 中 expe
人工智能算子库深度学习CANNAscendCANN ops-nn 算子深度解析:ReluGradV3 多核 ReLU 反向梯度算子实现与 aclnn 调用实战
CANN ops nn 算子深度解析:ReluGradV3 多核 ReLU 反向梯度算子实现与 aclnn 调用实战 导读 ReluGradV3 是 CANN
人工智能算子库深度学习CANNAscend
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考