CANN ops-nn权重量化矩阵乘
[📄 查看源码](https://link.gitcode.com/i/219d1a2be71ad3f5d81fe59462715d05)## 产品支持情况| 产品|是否支持|| :-----------------------------------------
aclnnWeightQuantBatchMatmulV2
【免费下载链接】ops-nn 本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。 项目地址: https://gitcode.com/cann/ops-nn
产品支持情况
| 产品 | 是否支持 |
|---|---|
| Ascend 950PR/Ascend 950DT | √ |
| Atlas A3 训练系列产品/Atlas A3 推理系列产品 | √ |
| Atlas A2 训练系列产品/Atlas A2 推理系列产品 | √ |
| Atlas 200I/500 A2 推理产品 | × |
| Atlas 推理系列产品 | √ |
| Atlas 训练系列产品 | × |
功能说明
-
接口功能:完成一个输入为伪量化场景的矩阵乘计算,并可以实现对于输出的量化计算。
-
计算公式:
$$ y = x @ ANTIQUANT(weight) + bias $$
公式中的$weight$为伪量化场景的输入,其反量化公式$ANTIQUANT(weight)$为
$$ ANTIQUANT(weight) = (weight + antiquantOffset) * antiquantScale $$
- 当不需要对输出进行量化操作时,其计算公式为
$$ y = x @ ANTIQUANT(weight) + bias $$
- 当需要对输出再进行量化处理时,其量化公式为
$$ \begin{aligned} y &= QUANT(x @ ANTIQUANT(weight) + bias) \ &= (x @ ANTIQUANT(weight) + bias) * quantScale + quantOffset \ \end{aligned} $$
函数原型
每个算子分为两段式接口,必须先调用“aclnnWeightQuantBatchMatmulV2GetWorkspaceSize”接口获取计算所需workspace大小以及包含了算子计算流程的执行器,再调用“aclnnWeightQuantBatchMatmulV2”接口执行计算。
aclnnStatus aclnnWeightQuantBatchMatmulV2GetWorkspaceSize(
const aclTensor *x,
const aclTensor *weight,
const aclTensor *antiquantScale,
const aclTensor *antiquantOffsetOptional,
const aclTensor *quantScaleOptional,
const aclTensor *quantOffsetOptional,
const aclTensor *biasOptional,
int antiquantGroupSize,
const aclTensor *y,
uint64_t *workspaceSize,
aclOpExecutor **executor)
aclnnStatus aclnnWeightQuantBatchMatmulV2(
void *workspace,
uint64_t workspaceSize,
aclOpExecutor *executor,
aclrtStream stream)
aclnnWeightQuantBatchMatmulV2GetWorkspaceSize
-
参数说明
参数名 输入/输出 描述 使用说明 数据类型 数据格式 维度(shape) 非连续Tensor x 输入 矩阵乘的左输入矩阵,公式中的输入 x。- FLOAT16、BFLOAT16 ND 2维,shape支持(m, k) 仅转置场景支持 weight 输入 矩阵乘的右输入矩阵,公式中的输入 weight。- INT8、INT4、FLOAT8_E4M3FN2、HIFLOAT82、INT32、FLOAT2、FLOAT4_E2M12 ND、FRACTAL_NZ 2维,shape支持(k, n) 仅转置场景支持 antiquantScale 输入 实现输入反量化计算的反量化scale参数,反量化公式中的输入 antiquantScale。- FLOAT16、BFLOAT16、FLOAT8_E8M02、UINT641、INT641 ND 1-2维 仅转置场景支持 antiquantOffsetOptional 可选输入 实现输入反量化计算的反量化offset参数,反量化公式中的 antiquantOffset。当不需要时为空指针。 FLOAT16、BFLOAT16、INT321 ND 要求与 antiquantScale一致。仅转置场景支持 quantScaleOptional 可选输入 实现输出量化计算的量化参数。 由量化公式中的 quantScale和quantOffset的数据通过aclnnTransQuantParam接口转化得到。不需要时为空指针。UINT641 ND 1-2维 不支持 quantOffsetOptional 可选输入 实现输出量化计算的量化offset参数,量化公式中的 quantOffset。不需要时为空指针。 FLOAT1 ND 要求与 quantScaleOptional一致- biasOptional 可选输入 偏置输入,公式中的 bias。当不需要时为空指针。 FLOAT16、FLOAT、BFLOAT162 ND 1-2维 - antiquantGroupSize 输入 表示在伪量化pergroup和mx量化模式下,对输入 weight进行反量化计算的groupSize输入,描述一组反量化参数对应的待反量化数据量在Reduce方向的大小。当伪量化算法不为pergroup和mx量化模式时传入0。
当伪量化算法为pergroup量化模式时传入值的范围为[32, k-1]且值要求是32的倍数。
在mx量化模式,仅支持32。- - - - y 输出 公式中的 y。- FLOAT16、BFLOAT16、INT81 ND 2维 - workspaceSize 输出 返回需要在Device侧申请的workspace大小。 - - - - - executor 输出 返回op执行器,包含了算子计算流程。 - - - - - -
Ascend 950PR/Ascend 950DT :
- 上表数据类型列中的角标“1”代表该系列不支持的数据类型;
-
Atlas A2 训练系列产品/Atlas A2 推理系列产品 、 Atlas A3 训练系列产品/Atlas A3 推理系列产品 :
- 上表数据类型列中的角标“2”代表该系列不支持的数据类型。
-
Atlas 推理系列产品 :
- 上表数据类型列中的角标“3”代表该系列不支持的数据类型。
-
-
返回值:
aclnnStatus:返回状态码,具体参见aclnn返回码。
第一段接口完成入参校验,出现以下场景时报错:
返回值 错误码 描述 ACLNN_ERR_PARAM_NULLPTR 161001 如果必选参数传入的是空指针。 ACLNN_ERR_PARAM_INVALID 161002 传入x、weight、antiquantScale、antiquantOffsetOptional、quantScaleOptional、quantOffsetOptional、biasOptional、y的shape维度不符合要求。 传入x、weight、antiquantScale、antiquantOffsetOptional、quantScaleOptional、quantOffsetOptional、biasOptional、y的数据类型不在支持的范围之内。 x、weight的reduce维度(k)不相等。 antiquantOffsetOptional存在输入时,shape与antiquantScale不相同。 quantOffsetOptional存在输入时,shape与quantScale不相同。 biasOptional的shape不符合要求。 antiquantGroupSize值不符合要求。 quantOffsetOptional存在时,quantScaleOptional是空指针。 输入的k、n值不在[1, 65535]范围内。 x矩阵为非转置时,m不在[1, 2^31-1]范围内;转置时,m不在[1, 65535]范围内。 不支持空tensor场景。 传入x、weight、antiquantScale、antiquantOffsetOptional、quantScaleOptional、quantOffsetOptional、biasOptional、y的连续性不符合要求。 x为bfloat16,weight为float4_e2m1或者float32时,bias数据类型只支持bfloat16。 ACLNN_ERR_RUNTIME_ERROR 361001 产品型号不支持。
aclnnWeightQuantBatchMatmulV2
-
参数说明
参数名 输入/输出 描述 workspace 输入 在Device侧申请的workspace内存地址。 workspaceSize 输入 在Device侧申请的workspace大小,由第一段接口aclnnWeightQuantBatchMatmulV2GetWorkspaceSize获取。 executor 输入 op执行器,包含了算子计算流程。 stream 输入 指定执行任务的Stream。 -
返回值:
aclnnStatus:返回状态码,具体参见aclnn返回码。
约束说明
Atlas A2 训练系列产品/Atlas A2 推理系列产品 、 Atlas A3 训练系列产品/Atlas A3 推理系列产品
-
确定性说明:默认非确定性实现,支持通过aclrtCtxSetSysParamOpt开启确定性。
-
x(aclTensor *, 计算输入):矩阵为非转置时,m大小在[1, 2^31-1]范围内;转置时,m大小在[1, 65535]范围内。 -
weight(aclTensor *, 计算输入):维度支持2维,Reduce维度k需要与x的Reduce维度k大小相等。数据类型支持INT8、INT4、INT32,当weight数据格式为FRACTAL_NZ且数据类型为INT4或INT32时,或者当weight数据格式为ND且数据类型为INT32时,仅在INT4Pack场景支持,需配合aclnnConvertWeightToINT4Pack接口完成从INT32到INT4Pack的转换,以及从ND到FRACTAL_NZ的转换,详情可参考样例,若数据类型为INT4,则weight的内轴应为偶数。非连续的Tensor仅支持转置场景。shape支持(k, n),其中k表示矩阵第1维的大小,n表示矩阵第2维的大小。 对于不同伪量化算法模式,weight的数据格式为FRACTAL_NZ仅在如下场景下支持: -
antiquantScale(aclTensor *, 计算输入):数据类型支持FLOAT16、BFLOAT16、UINT64、INT64(当FLOAT16、BFLOAT16时,数据类型要求和输入x保持一致;当为UINT64、INT64时,x仅支持FLOAT16,不转置,weight仅支持INT8,ND转置,模式仅支持perchannel量化模式,quantScaleOptional和quantOffsetOptional必须传入空指针,m仅支持[1, 96],k和n要求64对齐,需要首先配合aclnnCast接口完成FLOAT16到FLOAT32的转换,详情请参考Cast,再配合aclnnTransQuantParamV2接口完成FLOAT32到UINT64的转换,详情请参考TransQuantParamV2)。非连续的Tensor仅支持转置场景。 对于不同伪量化算法模式,antiquantScale支持的shape如下: -
antiquantOffsetOptional(aclTensor *, 计算输入):数据类型支持FLOAT16、BFLOAT16、INT32,数据类型为FLOAT16、BFLOAT16时,数据类型要求和输入x的数据类型保持一致;数据类型为INT32类型时,数据范围限制为[-128, 127],x仅支持FLOAT16,weight仅支持INT8,antiquantScale仅支持UINT64/INT64。非连续的Tensor仅支持转置场景。 -
quantScaleOptional(aclTensor *, 计算输入):数据类型支持UINT64,数据格式支持ND。不支持非连续的Tensor。可选输入,当不需要时为空指针;对于不同的伪量化算法模式,支持的shape如下: -
quantOffsetOptional(aclTensor *, 计算输入):数据类型支持FLOAT,数据格式支持ND。可选输入, 当不需要时为空指针;存在时shape要求与quantScaleOptional一致。不支持非连续的Tensor。 -
biasOptional(aclTensor *, 计算输入):维度支持1维或2维,shape支持(n,)或(1, n)。数据类型支持FLOAT16、FLOAT。当x的数据类型为BFLOAT16时,本参数要求为FLOAT;当x的数据类型为FLOAT16时,本参数要求为FLOAT16。 -
antiquantGroupSize(int, 计算输入):表示在伪量化pergroup和mx量化模式下,对输入weight进行反量化计算的groupSize输入,描述一组反量化参数对应的待反量化数据量在Reduce方向的大小。当伪量化算法不为pergroup和mx量化模式时传入0;当伪量化算法为pergroup量化模式时传入值的范围为[32, k-1]且值要求是32的倍数;在mx量化模式,仅支持32。 -
y(aclTensor *, 计算输出):维度支持2维,shape支持(m, n)。数据类型支持FLOAT16、BFLOAT16、INT8。当quantScaleOptional存在时,数据类型为INT8;当quantScaleOptional不存在时,数据类型支持FLOAT16、BFLOAT16,且与输入x的数据类型一致。 -
性能优化建议:
Atlas 推理系列产品
-
确定性说明:默认非确定性实现,支持通过aclrtCtxSetSysParamOpt开启确定性。
-
x(aclTensor *, 计算输入): 数据类型支持FLOAT16。shape支持2~6维,输入shape需要为(batch, m, k),其中batch表示矩阵的批次大小,支持0~4维,m表示单个batch矩阵第1维的大小,k表示单个batch矩阵的第2维的大小,batch维度需要与weight的batch维度满足broadcast关系。当伪量化算法模式为pertensor量化模式时,m*k不能超过512000000。 -
weight(aclTensor *, 计算输入):维度支持2~6维,batch维度需要与x的batch维度满足broadcast关系,数据类型支持INT8。具体如下: -
antiquantScale(aclTensor *, 计算输入):数据类型支持FLOAT16,数据类型要求和输入x保持一致。 对于不同伪量化算法模式,antiquantScale支持的shape如下:- pertensor量化模式:输入shape为(1,)或(1, 1)。
- perchannel量化模式:输入shape为(n, 1)或(n,),不支持非连续的Tensor。
- pergroup量化模式:输入shape与
weight的数据格式相关,如下:- 当
weight的数据格式为ND时,输入shape为(⌈k/group_size⌉, n),其中group_size表示k要分组的每组的大小。 - 当
weight的数据格式为FRACTAL_NZ时,输入shape为(n, ⌈k/group_size⌉),其中group_size表示k要分组的每组的大小。
- 当
-
antiquantOffsetOptional(aclTensor *, 计算输入):数据类型支持FLOAT16,数据类型要求和输入x保持一致。 -
quantScaleOptional(aclTensor *, 计算输入):预留参数,暂未使用,固定传入空指针。 -
quantOffsetOptional(aclTensor *, 计算输入):预留参数,暂未使用,固定传入空指针。 -
biasOptional(aclTensor *, 计算输入):数据类型支持FLOAT16。维度支持1~6维,带batch时,输入shape需要为(batch,1,n),batch要与x和weight的batch维度broadcast后的batch保持一致,不带batch时,输入shape需要为(n,)或(1, n)。 -
antiquantGroupSize(int, 计算输入):数据类型支持FLOAT16。维度支持2~6维,shape支持(batch, m, n),batch可不存在,支持x与weight的batch维度broadcast,输出batch与broadcast之后的batch一致,m与x的m一致,n与weight的n一致。 -
y(aclTensor *, 计算输出):
Ascend 950PR/Ascend 950DT
- 确定性说明:默认确定性实现。
-
公共约束
x和weight矩阵m、k、n大小在[1, 2^31-1]范围内。weightReduce维度k需要与x的Reduce维度k大小相等。- 支持的量化模式:pertensor量化模式、perchannel量化模式、pergroup量化模式和mx量化模式。
x不支持转置,因此不支持非连续Tensor,weight仅转置场景支持非连续的Tensor;antiquantScale、antiquantOffsetOptional非连续Tensor仅支持转置场景并且连续性要求和weight保持一致。antiquantScale不同量化模式支持的shape:quantScaleOptional和quantOffsetOptional为预留参数,暂未使用,固定传入空指针。
A16W8场景约束
- 输入和输出数据类型组合要求
x weight weight Format antiquantScale antiquantOffsetOptional quantScaleOptional quantOffsetOptional biasOptional antiquantGroupSize y 场景说明 FLOAT16/BFLOAT16 INT8 ND 与x一致 与x一致/null null null 与x一致/FLOAT(仅x为BFLOAT16)/null pergroup: [32, k-1]且为32倍数
其他: 0与x一致 T & C & G 量化 FLOAT16/BFLOAT16 HIFLOAT8/FLOAT8_E4M3FN ND 与x一致 null null null 与x一致/null pergroup: [32, k-1]且为32倍数
其他: 0与x一致 C 量化 A16W4场景约束
- 输入和输出数据类型组合要求
x weight weight Format antiquantScale antiquantOffsetOptional quantScaleOptional quantOffsetOptional biasOptional antiquantGroupSize y 场景说明 FLOAT16/BFLOAT16 INT4/INT32 ND 与x一致 与x一致/null null null 与x一致/FLOAT(仅x为BFLOAT16)/null 0 与x一致 T 量化 FLOAT16/BFLOAT16 INT4/INT32 ND 与x一致 与x一致/null null null 与x一致/FLOAT(仅x为BFLOAT16)/null pergroup: [32, k-1]且为32倍数
其他: 0与x一致 C & G 量化 FLOAT16/BFLOAT16 FLOAT4_E2M1 ND FLOAT8_E8M0 null null null 与x一致/null 32 与x一致 MX 量化 FLOAT16/BFLOAT16 FLOAT ND FLOAT8_E8M0 null null null 与x一致/null 32 与x一致 MX 量化
调用示例
示例代码如下,仅供参考,具体编译和执行过程请参考编译与运行样例。
-
Atlas A2 训练系列产品/Atlas A2 推理系列产品 、 Atlas A3 训练系列产品/Atlas A3 推理系列产品 、 Ascend 950PR/Ascend 950DT : A16W8调用示例:
#include <iostream> #include <memory> #include <vector> #include "acl/acl.h" #include "aclnnop/aclnn_cast.h" #include "aclnnop/aclnn_weight_quant_batch_matmul_v2.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; } void Finalize(int32_t deviceId, aclrtStream stream) { aclrtDestroyStream(stream); aclrtResetDevice(deviceId); aclFinalize(); } 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 PrintMat(std::vector<float> resultData, std::vector<int64_t> resultShape) { int64_t m = resultShape[0]; int64_t n = resultShape[1]; for (size_t i = 0; i < m; i++) { printf(i == 0 ? "[[" : " ["); for (size_t j = 0; j < n; j++) { printf(j == n - 1 ? "%.1f" : "%.1f, ", resultData[i * n + j]); if (j == 2 && j + 3 < n) { printf("..., "); j = n - 4; } } printf(i < m - 1 ? "],\n" : "]]\n"); if (i == 2 && i + 3 < m) { printf(" ... \n"); i = m - 4; } } } int AclnnWeightQuantBatchMatmulV2Test(int32_t deviceId, aclrtStream stream) { int64_t m = 16; int64_t k = 32; int64_t n = 16; std::vector<int64_t> xShape = {m, k}; std::vector<int64_t> weightShape = {k, n}; std::vector<int64_t> antiquantScaleShape = {n}; std::vector<int64_t> yShape = {m, n}; void* xDeviceAddr = nullptr; void* weightDeviceAddr = nullptr; void* antiquantScaleDeviceAddr = nullptr; void* yDeviceAddr = nullptr; aclTensor* x = nullptr; aclTensor* weight = nullptr; aclTensor* antiquantScale = nullptr; aclTensor* y = nullptr; // 填充FP16的1.0,BF16的1.0为0b0011111110000000 std::vector<uint16_t> xHostData(GetShapeSize(xShape), 0b0011110000000000); // fp16的1.0 std::vector<int8_t> weightHostData(GetShapeSize(weightShape), 1); std::vector<uint16_t> antiquantScaleHostData(GetShapeSize(antiquantScaleShape), 0b0011110000000000); std::vector<float> yHostData(GetShapeSize(yShape), 0); // 创建x aclTensor,可选ACL_FLOAT16/ACL_BFLOAT16类型 auto ret = CreateAclTensor(xHostData, xShape, &xDeviceAddr, aclDataType::ACL_FLOAT16, &x); std::unique_ptr<aclTensor, aclnnStatus (*)(const aclTensor*)> xTensorPtr(x, aclDestroyTensor); std::unique_ptr<void, aclError (*)(void*)> xDeviceAddrPtr(xDeviceAddr, aclrtFree); CHECK_RET(ret == ACL_SUCCESS, return ret); // 创建other aclTensor,可选ACL_INT8/ACL_FLOAT8_E8M0/ACL_HIFLOAT8类型 ret = CreateAclTensor(weightHostData, weightShape, &weightDeviceAddr, aclDataType::ACL_INT8, &weight); std::unique_ptr<aclTensor, aclnnStatus (*)(const aclTensor*)> weightTensorPtr(weight, aclDestroyTensor); std::unique_ptr<void, aclError (*)(void*)> weightDeviceAddrPtr(weightDeviceAddr, aclrtFree); CHECK_RET(ret == ACL_SUCCESS, return ret); // 创建y aclTensor,用于将输出转回FP32 ret = CreateAclTensor(yHostData, yShape, &yDeviceAddr, aclDataType::ACL_FLOAT, &y); std::unique_ptr<aclTensor, aclnnStatus (*)(const aclTensor*)> yTensorPtr(y, aclDestroyTensor); std::unique_ptr<void, aclError (*)(void*)> yDeviceAddrPtr(yDeviceAddr, aclrtFree); CHECK_RET(ret == ACL_SUCCESS, return ret); // 创建antiquantScale aclTensor ret = CreateAclTensor( antiquantScaleHostData, antiquantScaleShape, &antiquantScaleDeviceAddr, aclDataType::ACL_FLOAT16, &antiquantScale); std::unique_ptr<aclTensor, aclnnStatus (*)(const aclTensor*)> antiquantScaleTensorPtr( antiquantScale, aclDestroyTensor); std::unique_ptr<void, aclError (*)(void*)> antiquantScaleDeviceAddrPtr(antiquantScaleDeviceAddr, aclrtFree); CHECK_RET(ret == ACL_SUCCESS, return ret); // 创建yFp16 aclTensor,实际计算的输出,类型与x保持一致 void* yFp16DeviceAddr = nullptr; aclTensor* yFp16 = nullptr; ret = CreateAclTensor(yHostData, yShape, &yFp16DeviceAddr, aclDataType::ACL_FLOAT16, &yFp16); std::unique_ptr<aclTensor, aclnnStatus (*)(const aclTensor*)> yFp16TensorPtr(yFp16, aclDestroyTensor); std::unique_ptr<void, aclError (*)(void*)> yFp16deviceAddrPtr(yFp16DeviceAddr, aclrtFree); CHECK_RET(ret == ACL_SUCCESS, return ret); // 3. 调用CANN算子库API,需要修改为具体的Api名称 uint64_t workspaceSize = 0; aclOpExecutor* executor; void* workspaceAddr = nullptr; // 调用aclnnWeightQuantBatchMatmulV2第一段接口 ret = aclnnWeightQuantBatchMatmulV2GetWorkspaceSize( x, weight, antiquantScale, nullptr, nullptr, nullptr, nullptr, 0, yFp16, &workspaceSize, &executor); CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclnnWeightQuantBatchMatmulV2GetWorkspaceSize failed. ERROR: %d\n", ret); return ret); // 根据第一段接口计算出的workspaceSize申请device内存 std::unique_ptr<void, aclError (*)(void*)> workspaceAddrPtr(nullptr, aclrtFree); if (workspaceSize > 0) { ret = aclrtMalloc(&workspaceAddr, workspaceSize, ACL_MEM_MALLOC_HUGE_FIRST); CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("allocate workspace failed. ERROR: %d\n", ret); return ret); workspaceAddrPtr.reset(workspaceAddr); } // 调用aclnnWeightQuantBatchMatmulV2第二段接口 ret = aclnnWeightQuantBatchMatmulV2(workspaceAddr, workspaceSize, executor, stream); CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclnnWeightQuantBatchMatmulV2 failed. ERROR: %d\n", ret); return ret); // 4. (固定写法)同步等待任务执行结束 ret = aclrtSynchronizeStream(stream); CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclrtSynchronizeStream failed. ERROR: %d\n", ret); return ret); // 将输出转为FP32 workspaceSize = 0; executor = nullptr; ret = aclnnCastGetWorkspaceSize(yFp16, aclDataType::ACL_FLOAT, y, &workspaceSize, &executor); CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclnnCastGetWorkspaceSize failed. ERROR: %d\n", ret); return ret); // 根据第一段接口计算出的workspaceSize申请device内存 void* workspaceCastAddr = nullptr; std::unique_ptr<void, aclError (*)(void*)> workspaceCastAddrPtr(nullptr, aclrtFree); if (workspaceSize > 0) { ret = aclrtMalloc(&workspaceCastAddr, workspaceSize, ACL_MEM_MALLOC_HUGE_FIRST); CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("allocate workspace failed. ERROR: %d\n", ret); return ret); workspaceCastAddrPtr.reset(workspaceCastAddr); } ret = aclnnCast(workspaceCastAddr, workspaceSize, executor, stream); CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclnnCast failed. ERROR: %d\n", ret); return ret); ret = aclrtSynchronizeStream(stream); CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclrtSynchronizeStream failed. ERROR: %d\n", ret); return ret); // 5. 获取输出的值,将device侧内存上的结果拷贝至host侧,需要根据具体API的接口定义修改 auto size = GetShapeSize(yShape); std::vector<float> resultData(size, 0); ret = aclrtMemcpy( resultData.data(), resultData.size() * sizeof(resultData[0]), yDeviceAddr, size * sizeof(resultData[0]), ACL_MEMCPY_DEVICE_TO_HOST); CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("copy result from device to host failed. ERROR: %d\n", ret); return ret); PrintMat(resultData, yShape); return ACL_SUCCESS; } int main() { // 1. (固定写法)device/stream初始化,参考acl API手册 // 根据自己的实际device填写deviceId int32_t deviceId = 0; aclrtStream stream; auto ret = Init(deviceId, &stream); CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("Init acl failed. ERROR: %d\n", ret); return ret); // 2. 构造输入与输出,需要根据API的接口自定义构造 ret = AclnnWeightQuantBatchMatmulV2Test(deviceId, stream); CHECK_FREE_RET(ret == ACL_SUCCESS, LOG_PRINT("AclnnWeightQuantBatchMatmulV2Test failed. ERROR: %d\n", ret); return ret); Finalize(deviceId, stream); return 0; } -
Ascend 950PR/Ascend 950DT : A16MxFp4调用示例,需要调用
aclnnConvertWeightToINT4Pack接口辅助完成调用:#include <iostream> #include <memory> #include <vector> #include "acl/acl.h" #include "aclnnop/aclnn_cast.h" #include "aclnnop/aclnn_npu_format_cast.h" #include "aclnnop/aclnn_weight_quant_batch_matmul_v2.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) template <typename T1> inline T1 CeilDiv(T1 a, T1 b) { return b == 0 ? a : (a + b - 1) / b; }; template <typename T1> inline T1 CeilAlign(T1 a, T1 b) { return (a + b - 1) / b * b; }; int64_t GetShapeSize(const std::vector<int64_t>& shape) { int64_t shapeSize = 1; for (auto i : shape) { shapeSize *= i; } return shapeSize; } extern "C" aclnnStatus aclnnConvertWeightToINT4PackGetWorkspaceSize( const aclTensor* weight, const aclTensor* weightInt4Pack, uint64_t* workspaceSize, aclOpExecutor** executor); extern "C" aclnnStatus aclnnConvertWeightToINT4Pack( void* workspace, uint64_t workspaceSize, aclOpExecutor* executor, aclrtStream stream); 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; } template <typename T> int CreateAclTensorB4( const std::vector<T>& hostData, const std::vector<int64_t>& shape, void** deviceAddr, aclDataType dataType, aclTensor** tensor, aclFormat format) { auto size = hostData.size() * 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 PrintMat(std::vector<float> resultData, std::vector<int64_t> resultShape) { int64_t m = resultShape[0]; int64_t n = resultShape[1]; for (size_t i = 0; i < m; i++) { printf(i == 0 ? "[[" : " ["); for (size_t j = 0; j < n; j++) { printf(j == n - 1 ? "%.1f" : "%.1f, ", resultData[i * n + j]); if (j == 2 && j + 3 < n) { printf("..., "); j = n - 4; } } printf(i < m - 1 ? "],\n" : "]]\n"); if (i == 2 && i + 3 < m) { printf(" ... \n"); i = m - 4; } } } void Finalize(int32_t deviceId, aclrtStream stream) { aclrtDestroyStream(stream); aclrtResetDevice(deviceId); aclFinalize(); } int AclnnWeightQuantBatchMatmulV2Test(int32_t deviceId, aclrtStream& stream) { auto ret = Init(deviceId, &stream); aclDataType weightPackedDtype = aclDataType::ACL_FLOAT; // 可选:ACL_FLOAT类型 CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("Init acl failed. ERROR: %d\n", ret); return ret); // 2. 构造输入与输出,需要根据API的接口自定义构造 int64_t m = 16; int64_t k = 64; int64_t n = 64; int64_t groupSize = 32; int64_t weightDim0 = k; int64_t weightDim1 = n; bool isWeightTransposed = false; std::vector<int64_t> xShape = {m, k}; std::vector<int64_t> weightShape = {k, n}; std::vector<int64_t> antiquantScaleShape = {k / groupSize, n}; std::vector<int64_t> yShape = {m, n}; void* xDeviceAddr = nullptr; void* weightDeviceAddr = nullptr; void* weightB4PackDeviceAddr = nullptr; void* antiquantScaleDeviceAddr = nullptr; void* yDeviceAddr = nullptr; aclTensor* x = nullptr; aclTensor* weight = nullptr; aclTensor* y = nullptr; aclTensor* antiquantScale = nullptr; std::vector<int64_t> weightPackedShape; weightPackedShape = {weightDim0, weightDim1 / 8}; std::vector<uint16_t> xHostData(GetShapeSize(xShape), 0b0011110000000000); // fp16的1.0 xHostData[0] = 0; // fp16的0,验证结果是否符合要求 std::vector<float> weightHostData(GetShapeSize(weightShape), 1.0); // fp32的1.0,经过int4pack后转到fp4_e2m1的1.0 std::vector<float> yHostData(GetShapeSize(yShape), 0); std::vector<uint8_t> antiquantScaleHostData(GetShapeSize(antiquantScaleShape), 0b01111111); // fp8_e8m0的1.0 // 创建x aclTensor ret = CreateAclTensor(xHostData, xShape, &xDeviceAddr, aclDataType::ACL_FLOAT16, &x); std::unique_ptr<aclTensor, aclnnStatus (*)(const aclTensor*)> xTensorPtr(x, aclDestroyTensor); std::unique_ptr<void, aclError (*)(void*)> xDeviceAddrPtr(xDeviceAddr, aclrtFree); CHECK_RET(ret == ACL_SUCCESS, return ret); // 创建other aclTensor ret = CreateAclTensor(weightHostData, weightShape, &weightDeviceAddr, aclDataType::ACL_FLOAT, &weight); std::unique_ptr<aclTensor, aclnnStatus (*)(const aclTensor*)> weightTensorPtr(weight, aclDestroyTensor); std::unique_ptr<void, aclError (*)(void*)> weightDeviceAddrPtr(weightDeviceAddr, aclrtFree); CHECK_RET(ret == ACL_SUCCESS, return ret); // 创建y aclTensor ret = CreateAclTensor(yHostData, yShape, &yDeviceAddr, aclDataType::ACL_FLOAT, &y); std::unique_ptr<aclTensor, aclnnStatus (*)(const aclTensor*)> yTensorPtr(y, aclDestroyTensor); std::unique_ptr<void, aclError (*)(void*)> yDeviceAddrPtr(yDeviceAddr, aclrtFree); CHECK_RET(ret == ACL_SUCCESS, return ret); // 创建antiquantScale aclTensor ret = CreateAclTensor( antiquantScaleHostData, antiquantScaleShape, &antiquantScaleDeviceAddr, aclDataType::ACL_FLOAT8_E8M0, &antiquantScale); std::unique_ptr<aclTensor, aclnnStatus (*)(const aclTensor*)> antiquantScaleTensorPtr( antiquantScale, aclDestroyTensor); std::unique_ptr<void, aclError (*)(void*)> antiquantScaleDeviceAddrPtr(antiquantScaleDeviceAddr, aclrtFree); CHECK_RET(ret == ACL_SUCCESS, return ret); // 创建yFp16 aclTensor void* yFp16DeviceAddr = nullptr; aclTensor* yFp16 = nullptr; ret = CreateAclTensor(yHostData, yShape, &yFp16DeviceAddr, aclDataType::ACL_FLOAT16, &yFp16); std::unique_ptr<aclTensor, aclnnStatus (*)(const aclTensor*)> yFp16TensorPtr(yFp16, aclDestroyTensor); std::unique_ptr<void, aclError (*)(void*)> yFp16DeviceAddrPtr(yFp16DeviceAddr, aclrtFree); CHECK_RET(ret == ACL_SUCCESS, return ret); // 3. 调用CANN算子库API,需要修改为具体的Api名称 aclFormat weightFormat = aclFormat::ACL_FORMAT_ND; aclTensor* weightPacked = nullptr; std::vector<int8_t> weightB4PackHostData(n * k / 2, 0); // 一个B8数据存放2个B4数据,所以这里除以2 // 创建weightInt4Pack aclTensor ret = CreateAclTensorB4( weightB4PackHostData, weightPackedShape, &weightB4PackDeviceAddr, weightPackedDtype, &weightPacked, weightFormat); std::unique_ptr<aclTensor, aclnnStatus (*)(const aclTensor*)> weightPackedTensorPtr(weightPacked, aclDestroyTensor); std::unique_ptr<void, aclError (*)(void*)> weightPackedDeviceAddrPtr(weightB4PackDeviceAddr, aclrtFree); CHECK_RET(ret == ACL_SUCCESS, return ret); // 对weight做int32转int4pack uint64_t workspaceSize = 0; aclOpExecutor* executor; ret = aclnnConvertWeightToINT4PackGetWorkspaceSize(weight, weightPacked, &workspaceSize, &executor); CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclnnConvertWeightToINT4PackGetWorkspaceSize failed. ERROR: %d\n", ret); return ret); void* workspacePackAddr = nullptr; std::unique_ptr<void, aclError (*)(void*)> workspacePackAddrPtr(nullptr, aclrtFree); if (workspaceSize > 0) { ret = aclrtMalloc(&workspacePackAddr, workspaceSize, ACL_MEM_MALLOC_HUGE_FIRST); CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("allocate workspace failed. ERROR: %d\n", ret); return ret); workspacePackAddrPtr.reset(workspacePackAddr); } ret = aclnnConvertWeightToINT4Pack(workspacePackAddr, workspaceSize, executor, stream); CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclnnConvertWeightToINT4Pack failed. ERROR: %d\n", ret); return ret); // 调用aclnnWeightQuantBatchMatmulV2第一段接口 workspaceSize = 0; executor = nullptr; ret = aclnnWeightQuantBatchMatmulV2GetWorkspaceSize( x, weightPacked, antiquantScale, nullptr, nullptr, nullptr, nullptr, groupSize, yFp16, &workspaceSize, &executor); CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclnnWeightQuantBatchMatmulV2GetWorkspaceSize 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_RET(ret == ACL_SUCCESS, LOG_PRINT("allocate workspace failed. ERROR: %d\n", ret); return ret); workspaceAddrPtr.reset(workspaceAddr); } // 调用aclnnWeightQuantBatchMatmulV2第二段接口 ret = aclnnWeightQuantBatchMatmulV2(workspaceAddr, workspaceSize, executor, stream); CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclnnWeightQuantBatchMatmulV2 failed. ERROR: %d\n", ret); return ret); // 4. (固定写法)同步等待任务执行结束 ret = aclrtSynchronizeStream(stream); CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclrtSynchronizeStream failed. ERROR: %d\n", ret); return ret); // 将输出转为FP32 workspaceSize = 0; executor = nullptr; ret = aclnnCastGetWorkspaceSize(yFp16, aclDataType::ACL_FLOAT, y, &workspaceSize, &executor); CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclnnCastGetWorkspaceSize failed. ERROR: %d\n", ret); return ret); // 根据第一段接口计算出的workspaceSize申请device内存 void* workspaceCastAddr = nullptr; std::unique_ptr<void, aclError (*)(void*)> workspaceCastAddrPtr(nullptr, aclrtFree); if (workspaceSize > 0) { ret = aclrtMalloc(&workspaceCastAddr, workspaceSize, ACL_MEM_MALLOC_HUGE_FIRST); CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("allocate workspace failed. ERROR: %d\n", ret); return ret); workspaceCastAddrPtr.reset(workspaceCastAddr); } ret = aclnnCast(workspaceCastAddr, workspaceSize, executor, stream); CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclnnCast2 failed. ERROR: %d\n", ret); return ret); ret = aclrtSynchronizeStream(stream); CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclrtSynchronizeStream failed. ERROR: %d\n", ret); return ret); // 5. 获取输出的值,将device侧内存上的结果拷贝至host侧,需要根据具体API的接口定义修改 auto size = GetShapeSize(yShape); std::vector<float> resultData(size, 0); ret = aclrtMemcpy( resultData.data(), resultData.size() * sizeof(resultData[0]), yDeviceAddr, size * sizeof(resultData[0]), ACL_MEMCPY_DEVICE_TO_HOST); CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("copy result from device to host failed. ERROR: %d\n", ret); return ret); PrintMat(resultData, yShape); return ACL_SUCCESS; } int main() { // 1. (固定写法)device/stream初始化,参考acl API手册 // 根据自己的实际device填写deviceId int32_t deviceId = 0; aclrtStream stream; auto ret = AclnnWeightQuantBatchMatmulV2Test(deviceId, stream); CHECK_FREE_RET(ret == ACL_SUCCESS, LOG_PRINT("AclnnWeightQuantBatchMatmulV2Test failed. ERROR: %d\n", ret); return ret); Finalize(deviceId, stream); return 0; }
【免费下载链接】ops-nn 本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。 项目地址: https://gitcode.com/cann/ops-nn
更多推荐


所有评论(0)