aclnnWeightQuantBatchMatmulV2

【免费下载链接】ops-nn 本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。 【免费下载链接】ops-nn 项目地址: 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 可选输入 实现输出量化计算的量化参数。 由量化公式中的quantScalequantOffset的数据通过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仅在如下场景下支持:

    • perchannel量化模式
      • weight的数据类型为INT8,y的数据类型为非INT8。
      • weight的数据类型为INT4/INT32,weight转置,y的数据类型为非INT8。
    • pergroup量化模式weight的数据类型为INT4/INT32,weight非转置,x非转置,antiquantGroupSize为64或128,k为antiquantGroupSize对齐,n为64对齐,y的数据类型为非INT8。
  • 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如下:

    • pertensor量化模式:输入shape为(1,)或(1, 1)。
    • perchannel量化模式:输入shape为(1, n)或(n,)。
    • pergroup量化模式:输入shape为(⌈k/group_size⌉, n),其中group_size表示k要分组的每组的大小。
  • 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的数据类型一致。

  • 性能优化建议:

    • pertensor量化模式:当数据格式为ND时,推荐使用转置后的weight输入;当数据格式为FRACTAL_NZ时,推荐使用非转置的weight输入。
    • pergroup量化模式:推荐使用非转置的weight输入。
    • perchannel量化模式:当数据格式为ND时,推荐使用转置后的weight输入;当数据格式为FRACTAL_NZ时,推荐使用非转置的weight输入。m范围为[65, 96]时,推荐使用数据类型为UINT64或INT64的antiquantScale。

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。具体如下:

    • 数据格式为ND时,输入shape需要为(batch, k, n),其中batch表示矩阵的批次大小,支持0~4维,k表示单个batch矩阵第1维的大小,n表示单个batch矩阵的第2维的大小。
    • 数据格式为FRACTAL_NZ时:
      • 输入shape需要为(batch, n, k),其中batch表示矩阵的批次大小,支持0~4维,k表示单个batch矩阵第1维的大小,n表示单个batch矩阵的第2维的大小。
      • 配合aclnnCalculateMatmulWeightSizeV2以及aclnnTransMatmulWeight完成输入Format从ND到FRACTAL_NZ的转换。
  • 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
  • 确定性说明:默认确定性实现。

  • 公共约束

    • xweight矩阵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:
      • pertensor量化模式:(1,)或(1,1)。
      • perchannel量化模式:输入shape为(1, n)或(n,)。
      • pergroup量化模式:输入shape为(⌈k/group_size⌉, n),其中group_size表示k要分组的每组的大小。
      • mx量化模式:输入shape为(⌈k/group_size⌉, n),其中group_size表示k要分组的每组的大小,仅支持32。
    • quantScaleOptionalquantOffsetOptional为预留参数,暂未使用,固定传入空指针。

    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 量化
    • 约束说明

      公共约束外,A16W4场景其余约束如下:

      • weight数据类型为INT4或FLOAT4_E2M1,则weight的最后一维应为2对齐;若weight数据类型为INT32或FLOAT,则weight的最后一维应为8对齐。
      • weight数据类型为INT32/FLOAT时,必须配合aclnnConvertWeightToINT4Pack接口完成从INT32/FLOAT到紧密排布的INT4/FLOAT4_E2M1的转换,详情可参考样例

调用示例

示例代码如下,仅供参考,具体编译和执行过程请参考编译与运行样例

  • 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上加速计算。 【免费下载链接】ops-nn 项目地址: https://gitcode.com/cann/ops-nn

Logo

脑启社区是一个专注类脑智能领域的开发者社区。欢迎加入社区,共建类脑智能生态。社区为开发者提供了丰富的开源类脑工具软件、类脑算法模型及数据集、类脑知识库、类脑技术培训课程以及类脑应用案例等资源。

更多推荐