CANN ops-nn 量化矩阵乘接口 aclnnQuantMatmul 完全指南:INT8 量化 Matmul 的原理、调用与迁移
【免费下载链接】ops-nn本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。项目地址: https://gitcode.com/cann/ops-nn
本文以 CANN ops-nn 算子库中的aclnnQuantMatmul接口为对象,系统讲解其完成 INT8 量化矩阵乘的数学原理、两段式调用范式、全参数约束与错误码、完整可编译的调用示例,并结合仓库源码(matmul/quant_matmul/op_host/op_api/aclnn_quant_matmul.cpp)剖析其内部实现与校验流程,同时给出该接口向新版aclnnQuantMatmulV4的迁移路径。读者读完后,可以在 Atlas A2/A3 系列产品上独立完成 INT8 量化矩阵乘的算子 API 开发、调试与版本升级。
一、接口定位与产品支持情况
aclnnQuantMatmul是 CANN ops-nn 算子库(cann/ops-nn)中面向量化矩阵乘(Quantized Matmul)场景的 aclnn 单算子 API,其功能是完成带 bias 与反量化系数(deqScale)的 INT8 量化矩阵乘计算,最小支持 2 维输入,最大支持 3 维输入(第一维为 Batch 维度)。
须知:该接口后续版本会废弃,请使用最新aclnnQuantMatmulV5接口;在当前仓库中,官方给出的中间迁移目标是aclnnQuantMatmulV4(详见下文“迁移到 aclnnQuantMatmulV4”一节)。
各产品对aclnnQuantMatmul的支持情况如下:
| 产品 | 是否支持 |
|---|---|
| Ascend 950PR/Ascend 950DT | 不支持 |
| Atlas A3 训练系列产品 / Atlas A3 推理系列产品 | 支持 |
| Atlas A2 训练系列产品 / Atlas A2 推理系列产品 | 支持 |
| Atlas 200I/500 A2 推理产品 | 不支持 |
| Atlas 推理系列产品 | 不支持 |
| Atlas 训练系列产品 | 不支持 |
这与仓库源码中的 SoC 版本检查逻辑一致:aclnn_quant_matmul.cpp中的CheckSupportSocVersion()仅对ASCEND910B(对应 Atlas A2 训练/推理系列)与ASCEND910_93(对应 Atlas A3 训练/推理系列)返回ACLNN_SUCCESS,其余平台返回ACLNN_ERR_RUNTIME_ERROR(见 aclnn_quant_matmul.cpp)。
二、功能说明与计算公式
- 接口功能:完成量化的矩阵乘计算,最小支持维度为 2 维,最大支持输入维度为 3 维。
- 计算公式:
$$ out = (x1@x2 + bias) * deqScale $$
其中@表示矩阵乘(Matmul)运算。从量化视角看,该公式对应「INT8 低比特矩阵乘 + bias 累加 + 反量化(dequant)到 FLOAT16」的完整链路,deqScale即反量化系数,用于将 INT8 域计算结果还原到浮点域,属于典型的静态量化场景(量化参数预先确定,详见 量化介绍)。
相似接口:
- aclnnMm:仅支持 2 维 Tensor 作为输入的矩阵乘;
- aclnnBatchMatMul:仅支持 3 维的矩阵乘,其中第一维是 Batch 维度。
而aclnnQuantMatmul同时覆盖 2~3 维,并额外承担了量化参数的转换与反量化计算。
三、两段式接口架构与函数原型
每个 aclnn 算子分为两段式接口(参见 两段式接口):必须先调用aclnnQuantMatmulGetWorkspaceSize获取计算所需 workspace 大小以及包含了算子计算流程的执行器(executor),再调用aclnnQuantMatmul执行计算。
两段接口的函数原型如下:
aclnnStatus aclnnQuantMatmulGetWorkspaceSize( const aclTensor *x1, const aclTensor *x2, const aclTensor *bias, float deqScale, aclTensor *out, uint64_t *workspaceSize, aclOpExecutor **executor)aclnnStatus aclnnQuantMatmul( void *workspace, uint64_t workspaceSize, aclOpExecutor *executor, const aclrtStream stream)工作机制:
- 第一段接口完成入参校验与计算图构建,并返回
workspaceSize(算子在 NPU 上完成计算所需的临时内存大小)与executor(封装了算子计算流程的执行器); - 调用方按
workspaceSize在 Device 侧申请内存后,调用第二段接口执行计算; - 第二段接口不能重复调用,即
GetWorkspaceSize → 执行 → 执行的调用方式是异常的,每个 executor 只能执行一次。
从源码看,第二段接口aclnnQuantMatmul的实现非常简洁,直接委托给框架的统一运行入口CommonOpExecutorRun(workspace, workspaceSize, executor, stream)完成计算(见 aclnn_quant_matmul.cpp)。
四、第一段接口 aclnnQuantMatmulGetWorkspaceSize 参数说明
第一段接口的参数说明如下表:
| 参数名 | 输入/输出 | 描述 | 使用说明 | 数据类型 | 数据格式 | 维度(shape) | 非连续tensor |
|---|---|---|---|---|---|---|---|
| x1 | 输入 | 公式中的输入 x1 | 维度与 x2 一致,不支持 broadcast;数据类型需要与 x2 满足互推导关系 | INT8 | ND | 2-3 | - |
| x2 | 输入 | 公式中的输入 x2 | 维度与 x1 一致,不支持 broadcast;数据类型需要与 x1 满足互推导关系 | INT8 | ND | 2-3 | - |
| bias | 输入 | 公式中的输入 bias | shape 支持一维 (n, ),n 与 x2 的 n 一致。量化特殊处理过程:biasINT32 = round(round(biasFLOAT16/deqScale) - offsetX * wINT8) | INT32 | ND | 1 | - |
| deqScale | 输入 | 公式中的输入 deqScale,量化参数 | - | float | - | - | - |
| out | 输出 | 公式中的输出 out | - | FLOAT16 | ND | - | - |
| workspaceSize | 出参 | 返回需要在 Device 侧申请的 workspace 大小 | - | - | - | - | - |
| executor | 出参 | 返回 op 执行器,包含了算子计算流程 | - | - | - | - | - |
要点解读:
x1/x2 为 INT8、bias 为 INT32、out 为 FLOAT16、deqScale 为 float 标量。这是
aclnnQuantMatmul与 V2 版本的重要差异:V1 的 deqScale 是裸的float数值,而 V2 版本将其升级为 UINT64 的 aclTensor(见 aclnnQuantMatmulV2.md)。从源码看,第一段接口会调用TransDequantScaleToM1(deqScale)将 float 型反量化系数转成硬件 FixPipe 需要的 M1 定点表示(fixpipeDeqScale),再包装成 UINT64 标量 Tensor 参与计算(见 aclnn_quant_matmul.cpp)。互推导关系:x1 与 x2 的数据类型必须满足互推导关系表中可推导的组合;当两个输入均为 INT8 时,推导结果仍为 INT8,因此正常满足约束。
bias 的量化特殊处理:bias 在送入硬件前会被转换为 INT32 定点表示,转换公式为
biasINT32 = round(round(biasFLOAT16/deqScale) - offsetX * wINT8),其中offsetX为 x1 的量化零点偏移,wINT8为 x2 的 INT8 权重。这也是该接口在量化域完成「乘加 + bias」的关键一步。
五、返回值与错误码
两段接口均返回aclnnStatus状态码,具体取值参见 aclnn返回码。
第一段接口完成入参校验,出现以下场景时报错:
| 返回值 | 错误码 | 描述 |
|---|---|---|
| ACLNN_ERR_PARAM_NULLPTR | 161001 | 传入的 x1、x2 或 out 是空指针 |
| ACLNN_ERR_PARAM_INVALID | 161002 | x1、x2、bias 或 out 的数据类型/数据格式/维度不在支持的范围之内 |
| ACLNN_ERR_PARAM_INVALID | 161002 | x1 和 x2 的数据类型无法做数据类型推导 |
| ACLNN_ERR_PARAM_INVALID | 161002 | x1 和 x2 的输入 shape 不满足矩阵乘关系 |
| ACLNN_ERR_PARAM_INVALID | 161002 | x2 与 bias 的 shape 不一致 |
| ACLNN_ERR_PARAM_INVALID | 161002 | bias 存在且 m 和 n 均不为 0 但 k 为 0 的空 tensor |
源码级校验流程印证(见 aclnn_quant_matmul.cpp 的CheckParams):
- 空指针检查:x1、x2、deqScale、out 任一为空即返回空指针类错误;
- 数据类型检查:
CheckDtypeValid校验 x1/x2 必须为DT_INT8、bias 为DT_INT32、deqScale 为DT_UINT64、out 为DT_FLOAT16; - 数据格式检查:
CheckFormatVaild要求所有输入输出为FORMAT_ND,FORMAT_FRACTAL_NZ会被拒绝; - 维度检查:x1/x2 维度必须在 [2, 3] 区间,bias 必须为 1 维;
- Shape 关系检查:x1 与 x2 的维度数必须一致、Batch 维必须一致、K 维(reduce 轴)必须相等、bias 的第一维必须等于 x2 的 n、输出 shape 必须与推导结果完全匹配(含 Batch 广播语义);
- SoC 版本检查:仅支持 Atlas A2/A3 系列产品。
仓库测试用例 test_aclnn_quant_matmul.cpp 对这些异常路径做了全覆盖验证,例如 4 维 x1、1 维 x2、2 维 bias、deqScale 长度非 16 对齐、FRACTAL_NZ 格式、batch 不一致、K 不一致、输出 shape 与推导不一致等场景均断言返回ACLNN_ERR_PARAM_INVALID。
六、第二段接口 aclnnQuantMatmul 参数说明
| 参数名 | 输入/输出 | 描述 |
|---|---|---|
| workspace | 输入 | 在 Device 侧申请的 workspace 内存地址 |
| workspaceSize | 输入 | 在 Device 侧申请的 workspace 大小,由第一段接口 aclnnQuantMatmulGetWorkspaceSize 获取 |
| executor | 输入 | op 执行器,包含了算子计算流程 |
| stream | 输入 | 指定执行任务的 Stream |
返回值为aclnnStatus,具体参见 aclnn返回码。
七、约束说明与确定性
确定性说明:Atlas A3 训练系列产品/Atlas A3 推理系列产品、Atlas A2 训练系列产品/Atlas A2 推理系列产品上,
aclnnQuantMatmul为默认确定性实现,即相同输入多次运行结果可复现(相关背景可参考 确定性计算)。空 tensor 处理:当 x1 或 x2 为空 tensor 时,接口走空 tensor 快速路径——若 bias 存在且 k(reduce 轴)为 0,则直接报错;否则用
Fill算子生成全 0 输出,此时第一段接口返回的workspaceSize为 0(见 aclnn_quant_matmul.cpp)。测试用例ascend910B2_test_empty即覆盖了x1={16,0}、x2={0,16}的空 tensor 场景。非连续 tensor:接口内部通过
TensorContiguousProcess对 x1、x2、bias 做连续化处理(见 aclnn_quant_matmul.cpp)。测试用例ascend910B2_test_non_contiguous_x1、ascend910B2_test_non_contiguous_bias分别验证了转置步长{1, 16}的 x1 与步长为 2 的切片 bias 均可正常返回ACLNN_SUCCESS(非连续 tensor 相关概念可参考 非连续tensor)。
八、该接口迁移到 aclnnQuantMatmulV4 的方法
由于aclnnQuantMatmul即将废弃,官方在当前仓库中给出了迁移到aclnnQuantMatmulV4的明确步骤(V4 接口文档见 aclnnQuantMatmulV4.md):
- 输入 x1、x2、bias 可以直接转为
aclnnQuantMatmulV4接口中的 x1、x2、bias。 - 输入 deqScale 为 FLOAT 型,将该 FLOAT 数构造成 shape 为(1,)的 FLOAT 型 aclTensor(参考下文调用示例中的
CreateAclTensor),再利用aclnnTransQuantParamV2转为 shape 为(1,)的 uint64_t 的 aclTensor(参考 aclnnQuantMatmulV4 调用示例 中关于aclnnTransQuantParamV2的使用方式),记为scale,对标 V4 接口中的 scale 参数。 aclnnQuantMatmulV4接口中的可选输入offset/pertokenScaleOptional设置为nullptr,transposeX1和transposeX2均设置为false。- 最终调用形式为:
aclnnQuantMatmulV4GetWorkspaceSize(x1, x2, scale, nullptr, nullptr, bias, false, false, out, workspaceSize, executor)与 V2 版本迁移的差异:aclnnQuantMatmulV2的 deqScale 本身就是 UINT64 的 aclTensor,迁移到 V4 时仅需注意 V2 的 deqScale shape 为(t, )且t = align(n, 16),而 V4 的 scale shape 为(t, )且t = 1或n,因此直接使用原始 FLOAT 型量化参数经aclnnTransQuantParam转换即可(详见 aclnnQuantMatmulV2.md 的迁移章节)。
九、调用示例(完整可编译代码)
示例代码如下,仅供参考,具体编译和执行过程请参考编译与运行样例。仓库中的完整样例文件还有 test_aclnn_quant_matmul_v2.cpp。
#include <iostream> #include <vector> #include <memory> #include "acl/acl.h" #include "aclnnop/aclnn_quant_matmul.h" #define CHECK_RET(cond, return_expr) \ do { \ if (!(cond)) { \ return_expr; \ } \ } while (0) #define CHECK_FREE_RET(cond, return_expr) \ do { \ if (!(cond)) { \ Finalize(deviceId, stream);\ return_expr; \ } \ } while (0) #define LOG_PRINT(message, ...) \ do { \ printf(message, ##__VA_ARGS__); \ } while (0) int64_t GetShapeSize(const std::vector<int64_t>& shape) { int64_t shapeSize = 1; for (auto i : shape) { shapeSize *= i; } return shapeSize; } int Init(int32_t deviceId, aclrtStream* stream) { // 固定写法,资源初始化 auto ret = aclInit(nullptr); CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclInit failed. ERROR: %d\n", ret); return ret); ret = aclrtSetDevice(deviceId); CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclrtSetDevice failed. ERROR: %d\n", ret); return ret); ret = aclrtCreateStream(stream); CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclrtCreateStream failed. ERROR: %d\n", ret); return ret); return 0; } template <typename T> int CreateAclTensor(const std::vector<T>& hostData, const std::vector<int64_t>& shape, void** deviceAddr, aclDataType dataType, aclTensor** tensor) { auto size = GetShapeSize(shape) * sizeof(T); // 调用aclrtMalloc申请device侧内存 auto ret = aclrtMalloc(deviceAddr, size, ACL_MEM_MALLOC_HUGE_FIRST); CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclrtMalloc failed. ERROR: %d\n", ret); return ret); // 调用aclrtMemcpy将host侧数据拷贝到device侧内存上 ret = aclrtMemcpy(*deviceAddr, size, hostData.data(), size, ACL_MEMCPY_HOST_TO_DEVICE); CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclrtMemcpy failed. ERROR: %d\n", ret); return ret); // 计算连续tensor的strides std::vector<int64_t> strides(shape.size(), 1); for (int64_t i = shape.size() - 2; i >= 0; i--) { strides[i] = shape[i + 1] * strides[i + 1]; } // 调用aclCreateTensor接口创建aclTensor *tensor = aclCreateTensor(shape.data(), shape.size(), dataType, strides.data(), 0, aclFormat::ACL_FORMAT_ND, shape.data(), shape.size(), *deviceAddr); return 0; } void Finalize(int32_t deviceId, aclrtStream stream) { aclrtDestroyStream(stream); aclrtResetDevice(deviceId); aclFinalize(); } int aclnnQuantMatmulTest(int32_t deviceId, aclrtStream &stream) { auto ret = Init(deviceId, &stream); CHECK_FREE_RET(ret == ACL_SUCCESS, LOG_PRINT("Init acl failed. ERROR: %d\n", ret); return ret); // 2. 构造输入与输出,需要根据API的接口自定义构造 std::vector<int64_t> x1Shape = {2, 2}; std::vector<int64_t> x2Shape = {2, 2}; std::vector<int64_t> biasShape = {2}; std::vector<int64_t> outShape = {2, 2}; void* x1DeviceAddr = nullptr; void* x2DeviceAddr = nullptr; void* biasDeviceAddr = nullptr; void* outDeviceAddr = nullptr; aclTensor* x1 = nullptr; aclTensor* x2 = nullptr; aclTensor* bias = nullptr; aclTensor* out = nullptr; std::vector<int8_t> x1HostData{1, 1, 1, 1}; std::vector<int8_t> x2HostData{1, 1, 1, 1}; std::vector<int32_t> biasHostData{1, 1}; std::vector<uint16_t> outHostData{1, 1, 1, 1}; // 实际上是float16半精度方式 // 创建x1 aclTensor ret = CreateAclTensor(x1HostData, x1Shape, &x1DeviceAddr, aclDataType::ACL_INT8, &x1); std::unique_ptr<aclTensor, aclnnStatus (*)(const aclTensor *)> x1TensorPtr(x1, aclDestroyTensor); std::unique_ptr<void, aclError (*)(void *)> x1DeviceAddrPtr(x1DeviceAddr, aclrtFree); CHECK_FREE_RET(ret == ACL_SUCCESS, return ret); // 创建x2 aclTensor ret = CreateAclTensor(x2HostData, x2Shape, &x2DeviceAddr, aclDataType::ACL_INT8, &x2); std::unique_ptr<aclTensor, aclnnStatus (*)(const aclTensor *)> x2TensorPtr(x2, aclDestroyTensor); std::unique_ptr<void, aclError (*)(void *)> x2DeviceAddrPtr(x2DeviceAddr, aclrtFree); CHECK_FREE_RET(ret == ACL_SUCCESS, return ret); // 创建bias aclTensor ret = CreateAclTensor(biasHostData, biasShape, &biasDeviceAddr, aclDataType::ACL_INT32, &bias); std::unique_ptr<aclTensor, aclnnStatus (*)(const aclTensor *)> biasTensorPtr(bias, aclDestroyTensor); std::unique_ptr<void, aclError (*)(void *)> biasDeviceAddrPtr(biasDeviceAddr, aclrtFree); CHECK_FREE_RET(ret == ACL_SUCCESS, return ret); // 创建out aclTensor ret = CreateAclTensor(outHostData, outShape, &outDeviceAddr, aclDataType::ACL_FLOAT16, &out); std::unique_ptr<aclTensor, aclnnStatus (*)(const aclTensor *)> outTensorPtr(out, aclDestroyTensor); std::unique_ptr<void, aclError (*)(void *)> outDeviceAddrPtr(outDeviceAddr, aclrtFree); CHECK_FREE_RET(ret == ACL_SUCCESS, return ret); float deqScale = 1.0f; // 3. 调用CANN算子库API,需要修改为具体的API名称 uint64_t workspaceSize = 0; aclOpExecutor* executor; // 调用aclnnQuantMatmul第一段接口 ret = aclnnQuantMatmulGetWorkspaceSize(x1, x2, bias, deqScale, out, &workspaceSize, &executor); CHECK_FREE_RET(ret == ACL_SUCCESS, LOG_PRINT("aclnnQuantMatmulGetWorkspaceSize failed. ERROR: %d\n", ret); return ret); // 根据第一段接口计算出的workspaceSize申请device内存 void* workspaceAddr = nullptr; std::unique_ptr<void, aclError (*)(void *)> workspaceAddrPtr(nullptr, aclrtFree); if (workspaceSize > 0) { ret = aclrtMalloc(&workspaceAddr, workspaceSize, ACL_MEM_MALLOC_HUGE_FIRST); CHECK_FREE_RET(ret == ACL_SUCCESS, LOG_PRINT("allocate workspace failed. ERROR: %d\n", ret); return ret); workspaceAddrPtr.reset(workspaceAddr); } // 调用aclnnQuantMatmul第二段接口 ret = aclnnQuantMatmul(workspaceAddr, workspaceSize, executor, stream); CHECK_FREE_RET(ret == ACL_SUCCESS, LOG_PRINT("aclnnQuantMatmul failed. ERROR: %d\n", ret); return ret); // 4.(固定写法)同步等待任务执行结束 ret = aclrtSynchronizeStream(stream); CHECK_FREE_RET(ret == ACL_SUCCESS, LOG_PRINT("aclrtSynchronizeStream failed. ERROR: %d\n", ret); return ret); // 5. 获取输出的值,将device侧内存上的结果拷贝至host侧,需要根据具体API的接口定义修改 auto size = GetShapeSize(outShape); std::vector<float> resultData(size, 0); ret = aclrtMemcpy(resultData.data(), resultData.size() * sizeof(resultData[0]), outDeviceAddr, size * sizeof(resultData[0]), ACL_MEMCPY_DEVICE_TO_HOST); CHECK_FREE_RET(ret == ACL_SUCCESS, LOG_PRINT("copy result from device to host failed. ERROR: %d\n", ret); return ret); for (int64_t i = 0; i < size; i++) { LOG_PRINT("result[%ld] is: %f\n", i, resultData[i]); } return ACL_SUCCESS; } int main() { // 1.(固定写法)device/stream初始化,参考acl API手册 // 根据自己的实际device填写deviceId int32_t deviceId = 0; aclrtStream stream; auto ret = aclnnQuantMatmulTest(deviceId, stream); CHECK_FREE_RET(ret == ACL_SUCCESS, LOG_PRINT("aclnnQuantMatmulTest failed. ERROR: %d\n", ret); return ret); Finalize(deviceId, stream); return 0; }示例要点:
CreateAclTensor模板函数封装了「申请 Device 内存 → Host 到 Device 拷贝 → 按连续 strides 创建 ND 格式 aclTensor」的标准流程,可直接复用于 V2/V4 等其他接口的输入构造;- 由于
out是 FLOAT16,Host 侧用uint16_t缓冲区承载,实际取值需按 IEEE 754 半精度规则解析; - 通过
std::unique_ptr管理 aclTensor 与 Device 内存的 RAII 释放,保证异常路径不泄漏; - 若
workspaceSize == 0(例如空 tensor 场景),无需申请 workspace,直接以nullptr调用第二段接口。
十、编译与运行
按照 编译与运行样例 的通用流程:
- 环境准备:确保驱动、固件、CANN 软件包、ops 包等基础环境已搭建完成(开发和运行环境合设场景),并确保运行环境为支持列表内的 Atlas A2/A3 系列产品。
- 准备文件:将上述示例代码保存为
test_aclnn_quant_matmul.cpp,并准备 CMakeLists.txt(仓库样例中编译配置可参考 compile_and_run_sample.md 中给出的模板,链接libascendcl.so、libnnopbase.so、libopapi_math.so、libopapi_nn.so)。 - 配置环境变量:
source ${INSTALL_DIR}/set_env.sh其中${INSTALL_DIR}为 CANN 软件安装后的文件存储路径。
- 编译并运行:
mkdir -p build cd build cmake ../ -DCMAKE_CXX_COMPILER=g++ -DCMAKE_SKIP_RPATH=TRUE make cd bin ./opapi_test- 异常排查:若执行报错,可使用
aclGetRecentErrMsg接口获取具体错误信息,例如入参为空指针时会输出形如aclnnQuantMatmulGetWorkspaceSize failed. ERROR: 161001的错误码与参数错误详情。
十一、内部实现剖析:从 API 到 NPU 计算图
结合 aclnn_quant_matmul.cpp 与 quantBatchmatmul.cpp,aclnnQuantMatmul第一段接口的完整执行链路如下:
- 创建执行器:调用
CREATE_EXECUTOR()创建OpExecutor实例; - deqScale 定点化:
TransDequantScaleToM1(deqScale)将 float 型反量化系数转换为 FixPipe 的 M1 定点格式,并包装为 shape{1,1,1,1}、view 格式FORMAT_NHWC、存储格式FORMAT_NC1HWC0的 UINT64 aclTensor; - 入参校验:
CheckParams依次完成空指针、数据类型、数据格式(仅 ND)、维度(x1/x2 为 2~3 维、bias 为 1 维)、shape 关系(Batch 一致、K 一致、bias 的 n 一致、输出推导一致)以及 SoC 版本检查; - 连续化处理:
TensorContiguousProcess将非连续输入统一为连续布局,因此 V1 接口对外不承诺支持非连续 tensor(参数表中标注为-); - 构建计算图:
BuildQuantMatMulGraph调用 level0 算子l0op::QuantBatchMatmul(见 quantBatchmatmul.cpp),该算子通过INFER_SHAPE推导输出 shape、通过ADD_TO_LAUNCHER_LIST_AICORE挂载 AI Core 侧 kernel 启动器,随后对结果执行Reshape对齐到用户输出 shape,最后ViewCopy写回 out; - 返回 workspace:
executor->GetWorkspaceSize()汇总图中所有算子所需的临时内存大小,ReleaseTo(executor)将执行器所有权移交调用方。
这一链路也解释了为什么第一段接口会返回「包含算子计算流程的执行器」——它本质上是一张由 level0 算子(QuantBatchMatmul、Reshape、ViewCopy、必要时 Fill)构成的计算图,第二段接口只是把这张图提交到指定 stream 上执行。
十二、测试用例对行为边界的验证
仓库单测 test_aclnn_quant_matmul.cpp 系统性地覆盖了该接口的行为边界,可作为开发时的「约束清单」参考:
| 测试用例 | 输入构造 | 期望行为 |
|---|---|---|
ascend910B2_test_normal_input | x1{16,32}、x2{32,16}、bias{16}、deqScale=1.0 | GetWorkspaceSize 正常返回 |
ascend910B2_test_empty | x1{16,0}、x2{0,16} | 空 tensor 路径正常处理 |
ascend910B2_test_abnormal_input_x1 | x1 为 4 维 | ACLNN_ERR_PARAM_INVALID |
ascend910B2_test_abnormal_input_x2 | x2 为 1 维 | ACLNN_ERR_PARAM_INVALID |
ascend910B2_test_abnormal_input_bias | bias 为 2 维 | ACLNN_ERR_PARAM_INVALID |
ascend910B2_test_abnormal_input_deqScale | V2 的 deqScale 长度 15(非 16 对齐) | ACLNN_ERR_PARAM_INVALID |
ascend910B2_test_abnormal_input_format | FRACTAL_NZ 格式 | 格式不支持 |
ascend910B2_test_abnormal_input_sameDim | x1 2 维、x2 3 维 | 维度不一致报错 |
ascend910B2_test_abnormal_input_sameK | x1 的 k=32、x2 的 k=31 | K 不一致报错 |
ascend910B2_test_abnormal_outMDim_x1MDim | out 的 m 与 x1 的 m 不一致 | ACLNN_ERR_PARAM_INVALID |
ascend910B2_test_non_contiguous_x1 | 转置步长 {1,16} 的 x1 | ACLNN_SUCCESS |
这些用例同时印证了本文第五节错误码表中各「ACLNN_ERR_PARAM_INVALID」子场景的真实触发条件,开发者可将其作为自测用例的模板。
十三、总结
aclnnQuantMatmul是 CANN ops-nn 中面向 Atlas A2/A3 系列产品的 INT8 量化矩阵乘接口,通过out = (x1@x2 + bias) * deqScale一次完成低比特矩阵乘、bias 累加与反量化。其两段式调用范式(GetWorkspaceSize + 执行)、严格的 ND 格式/数据类型/shape 关系约束、确定性实现保证,以及完善的错误码体系,使其易于集成与调试。由于该接口即将废弃,新开发建议直接使用aclnnQuantMatmulV5,存量代码可按照本文第八节给出的步骤平滑迁移到aclnnQuantMatmulV4(将 float deqScale 经aclnnTransQuantParamV2转为 UINT64 scale tensor 即可)。
【免费下载链接】ops-nn本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。项目地址: https://gitcode.com/cann/ops-nn
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考