news 2026/9/20 11:15:04

CANN ops-nn WeightQuantBatchMatmulExperiment 算子 aclnn 单算子调用样例深度解析

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
CANN ops-nn WeightQuantBatchMatmulExperiment 算子 aclnn 单算子调用样例深度解析
  • 人工智能
  • 算子库
  • 深度学习
  • CANN
  • Ascend

【免费下载链接】ops-nn

本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。

项目地址:https://gitcode.com/cann/ops-nn
点击查看免费下载

本指南以 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.bininput_weight.bininput_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.cppop_runner.cppoperator_desc.cppcommon.cppCMakeLists.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,其核心思路是把浮点激活逐级分解为多个整数分量:

  1. 计算 (A_{max} = rowMax(|A_{group}|));
  2. 计算 (tmp_1 = \frac{7.49 \times A_{group}}{A_{max}});
  3. 计算 (A_1 = round(tmp_1));
  4. 计算 (tmp_2 = (tmp_1 - A_1) \times 14.98);
  5. 计算 (A_2 = round(tmp_2));
  6. 计算 (tmp_3 = (tmp_2 - A_2) \times 14.98);
  7. 计算 (A_3 = round(tmp_3));
  8. 构造矩阵 (A_{int} = [A_1; A_2; A_3]);
  9. 计算 (Y_{int} = A_{int} Weight_{group});
  10. 计算 (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});
  11. 计算 (Y^i = Y_{group} + Y^{i-1})(分组累加)。

样例中 7.49 / 14.98 两个常数以f1f2形式出现在 gen_data.py 中,与 README 中的公式严格对应。

算子规格如下:

内容
算子类型(OpType)WeightQuantBatchMatmul
输入 x1shape M×K,float16,ND
输入 x2(weight)shape K×N,int4,ND
输入 antiquant_scaleshape GroupNum×N,float16,ND
输出 yshape 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()负责aclrtResetDeviceaclFinalize清理。

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()

  1. 逐输入通过aclrtMemcpy(Host→Device,Device 模式为 Device→Device)将数据拷贝到 Device 侧;
  2. 创建 stream(aclrtCreateStream);
  3. 调用第一段接口获取 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);
  1. 若 workspaceSize 不为 0,用aclrtMalloc(..., ACL_MEM_MALLOC_HUGE_FIRST)申请 Device 侧 workspace;
  2. 调用第二段接口执行算子:
ret = aclnnWeightQuantBatchMatmulExperiment(workspace_, workspaceSize, handle, stream);
  1. aclrtSynchronizeStreamWithTimeout(stream, 5000)同步等待计算完成;
  2. 将输出结果从 Device 拷贝回 Host(aclrtMemcpy,Device 模式为 Device→Device)。

OpRunner::Init()中为每个输入输出分配 Device 内存与 Host 内存,并通过aclCreateTensor构建aclTensor描述;析构函数中依次释放 workspace、aclTensoraclDataBuffer以及 Device/Host 内存,资源管理较为完整。

4.4 编译配置(CMakeLists.txt)

examples/src/CMakeLists.txt 使用DDK_PATHNPU_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目录,链接库包括nnopbasecust_opapiascendclacl_op_compilerstdc++。其中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_result

5.3 run.sh 执行流程逐步拆解

run.sh 内部按六个步骤执行:

  1. 清理:删除$HOME/ascend/log/*./input/*.bin./output/*.bin遗留文件;
  2. 生成输入与真值数据python3 scripts/gen_data.py
  3. 编译 acl 可执行文件:进入build目录执行cmake ../src -DCMAKE_SKIP_RPATH=TRUEmake
  4. 运行可执行文件:设置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
  5. 结果校验python3 scripts/verify_result.py output/output_z.bin output/golden.bin
  6. 性能采集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_PATHNPU_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.isclosertol=1e-6, atol=1e-9equal_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_XDTYPE_WEIGHTMSD_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)
算子耗时396303

即在相同计算逻辑下,preload 流水模板通过流水优化获得了显著的性能提升。样例通过run.sh第 6 步使用 msprof 工具自动采集每个模板(执行用例分别编译对应模板)的性能数据,输出到examples/output/msprof_result供分析对比。

8. 更新说明

时间更新事项
2026/01/06新增本 readme

9. 小结与延伸阅读

通过本样例可以完整掌握一条 CANN 自定义算子的单算子验证链路:

  1. 算子定义与 kernel 实现:参考 op_host/weight_quant_batch_matmul_experiment_def.cpp 与 op_kernel/weight_quant_batch_matmul_experiment.cpp;
  2. 算子功能与流水设计:见 算子 README(支持的产品、算子规格、MSD 数学公式、两种流水模板与性能参考);
  3. aclnn 两段式 API 调用:见 examples/src/op_runner.cpp 与 examples/src/main.cpp;
  4. 一键编译运行与校验:见 examples/run.sh 及 gen_data.py、verify_result.py。

需要说明的是,本文所述编译运行步骤依赖本机已安装对应版本的 CANN 开发套件(toolkit 包 + ops 包),且--soc参数需与目标 NPU 型号匹配;若需深入了解单算子 API 调用的通用规范,可参考 CANN 官方文档中“单算子 API 调用”相关章节。

  • 人工智能
  • 算子库
  • 深度学习
  • CANN
  • Ascend

【免费下载链接】ops-nn

本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。

项目地址:https://gitcode.com/cann/ops-nn
点击查看免费下载
上一篇:5分钟快速上手libnfc:开源NFC开发库完整指南
下一篇:Web Components实战:构建企业级可复用UI组件的终极指南

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/20 11:14:04

多智能体协作框架实战:从选型到踩坑全指南

1. 先搞明白&#xff1a;多智能体协作到底在解决什么问题先说一个我经常跟朋友聊到的现象&#xff1a;很多人一听到“多智能体协作”&#xff0c;第一反应就是“把几个AI机器人拉到一个群里让它们互相聊天”&#xff0c;或者觉得“多开几个页面&#xff0c;分别问一遍再自己汇总…

作者头像 李华
网站建设 2026/9/20 11:12:34

SPI NOR Flash实战:GD25Q80E命令时序与STM32 QSPI配置详解

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/20 11:07:12

Windows Server 2019 U盘裸装实战:从镜像校验到分区避坑全指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/20 11:04:19

芯片互连协议深度对比:CHI七态与PBR路由在Scale-up场景中的工程实践

做芯片互连的朋友应该都有一种体验&#xff1a;平时聊协议头头是道&#xff0c;一旦落到 RTL 里写状态机&#xff0c;或者在上板后抓 Deadlock&#xff0c;才发现那些“资历很老”的互连协议每个都有自己的脾气。尤其 Scale-up 场景&#xff0c;核数翻倍、内存距离拉长、缓存一…

作者头像 李华