aclnnWeightQuantBatchMatmulNz

Applicable Products

ProductSupported
Ascend 950PR/Ascend 950DT√
Atlas A3 training products/Atlas A3 inference products×
Atlas A2 training products/Atlas A2 inference products×
Atlas 200I/500 A2 inference products×
Atlas inference products×
Atlas training products×

Function

  • Description: Performs a matrix multiplication computation where the input is in a pseudo-quantization scenario. This API only supports the right input matrix of the matrix multiplication in FRACTAL_NZ format.

  • Formula:

    • Basic formula:

      y=x@ANTIQUANT(weight)+biasy = x @ ANTIQUANT(weight) + bias

      Where weightweight is the input in the pseudo-quantization scenario, and its antiquantization formula ANTIQUANT(weight)ANTIQUANT(weight) is:

      ANTIQUANT(weight)=(weight+antiquantOffset)∗antiquantScaleANTIQUANT(weight) = (weight + antiquantOffset) * antiquantScale
    • Quantization formula when the output needs to be quantized:

      y=QUANT(x@ANTIQUANT(weight)+bias)=(x@ANTIQUANT(weight)+bias)∗quantScale+quantOffset\begin{aligned} y &= QUANT(x @ ANTIQUANT(weight) + bias) \\ &= (x @ ANTIQUANT(weight) + bias) * quantScale + quantOffset \\ \end{aligned}

Function Prototype

Each operator is divided into a two-phase API. You must first call the "aclnnWeightQuantBatchMatmulNzGetWorkspaceSize" API to obtain the workspace size required for computation and the executor that contains the operator computation process, and then call the "aclnnWeightQuantBatchMatmulNz" API to perform the computation.

aclnnStatus aclnnWeightQuantBatchMatmulNzGetWorkspaceSize(
  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 aclnnWeightQuantBatchMatmulNz(
  void            *workspace,
  uint64_t         workspaceSize,
  aclOpExecutor   *executor,
  aclrtStream      stream)

aclnnWeightQuantBatchMatmulNzGetWorkspaceSize

  • Parameters

    Parameter Input/Output Description Instruction Data Type Data Format Dimension (shape) Non-contiguous Tensor
    x Input The left input matrix for matrix multiplication, corresponding to input x in the formula. Transposition is not supported. FLOAT16, BFLOAT16 ND 2 ×
    weight Input Right input matrix of the matrix multiplication, i.e., the input weight in the formula. Supports transposed and non-transposed modes. INT4, FLOAT4_E2M1, INT32, FLOAT, INT8 FRACTAL_NZ 4 √
    antiquantScale Input The scale parameter for input dequantization, corresponding to antiquantScale in the formula. The contiguity requirement is the same as that of weight. FLOAT16, BFLOAT16, FLOAT8_E8M0 ND 1, 2 √
    antiquantOffsetOptional Optional Input The offset parameter for input dequantization computation, i.e., antiquantOffset in the formula. The continuity requirement is the same as that of weight. FLOAT16, BFLOAT16 ND 1, 2 √
    quantScaleOptional Optional Input Reserved parameter, not currently used. Always pass a null pointer. - - - - -
    quantOffsetOptional Optional Input Reserved parameter, not used currently. Always pass a null pointer. - - - - -
    biasOptional Optional Input Bias input, which is bias in the formula. Pass a null pointer when not needed. FLOAT16, BFLOAT16, FLOAT ND 1, 2 ×
    antiquantGroupSize Input In pergroup and mx quantization modes, the groupSize input for dequantizing weight, describing the size of each group into which the Reduce axis is divided. In pergroup quantization mode, the value range is [32, k-1] and must be a multiple of 32.
    In mx quantization mode, the value must be fixed to 32.
    In perchannel quantization mode, pass 0.
    UINT64 - - -
    y Output Computes the output, which is y in the formula. - FLOAT16, BFLOAT16 ND 2 ×
    workspaceSize Output Returns the workspace size to be applied for on the Device side. - - - - -
    executor Output Returns the operator executor, which contains the operator computation flow. - - - - -
  • Return value

    aclnnStatus: return code. For details, see aclnn Return Code.

    The first-phase API performs input parameter validation and returns an error in the following scenarios:

    Return Value Return Code Description
    ACLNN_ERR_PARAM_NULLPTR 161001 Returns 161001 if the passed parameter is a mandatory input, output, or mandatory attribute and is a null pointer.
    ACLNN_ERR_PARAM_INVALID 161002 The shape, data format, or continuity of the input tensor does not meet the requirements.
    Empty tensor scenarios are not supported.
    ACLNN_ERR_RUNTIME_ERROR 361001 The product model is not supported.

aclnnWeightQuantBatchMatmulNz

  • Parameters

    Parameter Input/Output Description
    workspace Input Address of the workspace memory applied on the Device side.
    workspaceSize Input Size of the workspace applied on the Device side, obtained through the first-phase API aclnnWeightQuantBatchMatmulNzGetWorkspaceSize.
    executor Input Operator executor, which contains the operator computation flow.
    stream Input Specifies the stream for task execution.
  • Return value

    aclnnStatus: return code. For details, see aclnn Return Code.

Constraints

  • Deterministic computation description: aclnnWeightQuantBatchMatmulNz defaults to a deterministic implementation.

  • Supported quantization modes: perchannel Quantization Mode, pergroup Quantization Mode, and mx Quantization Mode.

  • The input and output support the following data type and shape combinations:

    • Ascend 950PR/Ascend 950DT:

      Quantization ModexweightantiquantScaleantiquantOffsetOptionalbiasOptionalyantiquantScale ShapeantiquantOffsetOptional Shape
      perchannelBFLOAT16INT32/INT4BFLOAT16null/BFLOAT16null/BFLOAT16/FLOAT32BFLOAT16(1, n)/(n,)null/(1, n)/(n,)
      perchannelFLOAT16INT32/INT4FLOAT16null/FLOAT16null/FLOAT16FLOAT16(1, n)/(n,)null/(1, n)/(n,)
      perchannelBFLOAT16INT8BFLOAT16null/BFLOAT16null/FLOAT32BFLOAT16(1, n)/(n,)null/(1, n)/(n,)
      perchannelFLOAT16INT8FLOAT16null/FLOAT16null/FLOAT16FLOAT16(1, n)/(n,)null/(1, n)/(n,)
      pergroupBFLOAT16INT32/INT4BFLOAT16null/BFLOAT16null/BFLOAT16/FLOAT32BFLOAT16(ceil(k/antiquantGroupSize), n)null/(ceil(k/antiquantGroupSize), n)
      pergroupFLOAT16INT32/INT4FLOAT16null/FLOAT16null/FLOAT16FLOAT16(ceil(k/antiquantGroupSize), n)null/(ceil(k/antiquantGroupSize), n)
      pergroupBFLOAT16FLOAT/FLOAT4_E2M1BFLOAT16nullnull/BFLOAT16BFLOAT16(ceil(k/antiquantGroupSize), n)null
      pergroupFLOAT16FLOAT/FLOAT4_E2M1FLOAT16nullnull/FLOAT16FLOAT16(ceil(k/antiquantGroupSize), n)null
      mxBFLOAT16FLOAT/FLOAT4_E2M1FLOAT8_E8M0nullnull/BFLOAT16BFLOAT16(ceil(k/32), n)null
      mxFLOAT16FLOAT/FLOAT4_E2M1FLOAT8_E8M0nullnull/FLOAT16FLOAT16(ceil(k/32), n)null
      • The shape of x is (m, k), the shape of y is (m, n), and the shape of biasOptional is null/(1, n)/(n,).

      • When the data type of weight is INT32 or FLOAT, it indicates tightly packed INT4 or FLOAT4_E2M1, and the following constraints must be met:

        • The last dimension of the original ND matrix is 8-aligned.

        • Before calling this API, you must use the aclnnConvertWeightToINT4Pack API to complete the conversion from sparsely arranged INT32/FLOAT to tightly packed INT4/FLOAT4_E2M1 and from ND to FRACTAL_NZ. For details, see the sample.

        • The shape of the FRACTAL_NZ matrix passed to this API is (ceil(n/16), ceil(k/16), 16, 2).

        • weight supports only non-transposed mode, and the shape of the original ND matrix is (k, n).

      • When the data type of weight is INT4 or FLOAT4_E2M1, the following constraints must be met:

        • The last dimension of the original ND matrix must be 2-aligned.

        • Before calling this API, you must use the aclnnConvertWeightToINT4Pack API to complete the conversion from ND to FRACTAL_NZ. For details, see the sample.

        • The shape of the FRACTAL_NZ matrix passed to this API is (ceil(n/16), ceil(k/16), 16, 16).

        • weight only supports non-transposed mode, and the shape of the original ND matrix is (k, n).

      • When the data type of weight is INT8, the following constraints must be met:

        • The shape of the FRACTAL_NZ matrix passed to this API is (ceil(n/32), ceil(k/16), 16, 32).

        • k is in the range [1, 65535], and n is in the range [2, 65535].

        • weight supports transposed and non-transposed formats, with the original ND matrix shape being (n, k) or (k, n).

      • m is in the range [1, 2^31-1].

Example

The following provides example code for reference only. For details about compilation and running, see Compile and Run Samples.

  • For the example where x is FLOAT16 and weight is FLOAT32, call the aclnnConvertWeightToINT4Pack API to assist with the invocation:

    #include <iostream>
    #include <memory>
    #include <vector>
    #include "acl/acl.h"
    #include "aclnnop/aclnn_cast.h"
    #include "aclnnop/aclnn_weight_quant_batch_matmul_nz.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)
    {
        // Boilerplate: resource initialization.
        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);
        // Call aclrtMalloc to apply for device memory.
        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);
        // Call aclrtMemcpy to copy data from the host to the 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);
    
        // Calculate the strides of a contiguous tensor.
        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];
        }
    
        // Call aclCreateTensor to create an aclTensor.
        *tensor = aclCreateTensor(
            shape.data(), shape.size(), dataType, strides.data(), 0, aclFormat::ACL_FORMAT_ND, shape.data(), shape.size(),
            *deviceAddr);
        return 0;
    }
    
    int GetSize(std::vector<int64_t>& shape)
    {
        int64_t size = 1;
        for (auto i : shape) {
            size *= i;
        }
        return size;
    }
    
    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);
        // Call aclrtMalloc to apply for memory on the device side.
        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);
        // Call aclrtMemcpy to copy data from the host to the 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);
    
        // Compute the strides of a contiguous tensor.
        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];
        }
    
        // Call the aclCreateTensor API to create an aclTensor.
        if (format == aclFormat::ACL_FORMAT_ND) {
            *tensor = aclCreateTensor(
                shape.data(), shape.size(), dataType, strides.data(), 0, aclFormat::ACL_FORMAT_ND, shape.data(),
                shape.size(), *deviceAddr);
        } else {
            std::vector<int64_t> nzShape;
            if (dataType == aclDataType::ACL_INT4 || dataType == aclDataType::ACL_FLOAT4_E2M1) {
                nzShape = {CeilDiv(shape[1], (int64_t)16), CeilDiv(shape[0], (int64_t)16), 16, 16};
            } else {
                nzShape = {CeilDiv(shape[1], (int64_t)2), CeilDiv(shape[0], (int64_t)16), 16, 2};
            }
            *tensor = aclCreateTensor(
                shape.data(), shape.size(), dataType, strides.data(), 0, aclFormat::ACL_FORMAT_FRACTAL_NZ, nzShape.data(),
                nzShape.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 AclnnWeightQuantBatchMatmulNzTest(int32_t deviceId, aclrtStream& stream)
    {
        auto ret = Init(deviceId, &stream);
        aclDataType weightPackedDtype = aclDataType::ACL_FLOAT; // Optional: ACL_FLOAT type.
        CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("Init acl failed. ERROR: %d\n", ret); return ret);
    
        // 2. Construct inputs and outputs. Customize the construction based on the 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(GetSize(xShape), 0b0011110000000000); // 1.0 in float16.
        std::vector<float> weightHostData(GetSize(weightShape), 1.0); // 1.0 in fp32, converted to 1.0 in fp4_e2m1 after int4pack.
        std::vector<float> yHostData(GetSize(yShape), 0);
    
        std::vector<uint16_t> antiquantScaleHostData(GetSize(antiquantScaleShape), 0b0011110000000000); // 1.0 in float16.
    
        // Create the 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);
        // Create the 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);
        // Create the 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);
        // Create the 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);
        // Create the 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. Call the CANN operator library API. Replace with the specific API name.
        aclFormat weightFormat = aclFormat::ACL_FORMAT_FRACTAL_NZ; // Optional: ACL_FORMAT_ND
        aclTensor* weightPacked = nullptr;
    
        std::vector<int8_t> weightB4PackHostData(n * k / 2, 0); // One B8 data stores two B4 data, so divide by 2 here.
        if (weightFormat == aclFormat::ACL_FORMAT_FRACTAL_NZ) {
            weightB4PackHostData.resize(CeilAlign(weightDim1 / 2, (int64_t)8) * CeilAlign(weightDim0, (int64_t)16), 0);
        }
        // Create the 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);
    
        // Convert weight from int32 to 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);
    
        // Call the first-phase API of aclnnWeightQuantBatchMatmulNz.
        workspaceSize = 0;
        executor = nullptr;
        ret = aclnnWeightQuantBatchMatmulNzGetWorkspaceSize(
            x, weightPacked, antiquantScale, nullptr, nullptr, nullptr, nullptr, groupSize, yFp16, &workspaceSize,
            &executor);
        CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclnnWeightQuantBatchMatmulNzGetWorkspaceSize failed. ERROR: %d\n", ret);
                  return ret);
        // Allocate device memory based on the workspaceSize computed by the first-phase API.
        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);
        }
        // Call the second-phase API of aclnnWeightQuantBatchMatmulNz.
        ret = aclnnWeightQuantBatchMatmulNz(workspaceAddr, workspaceSize, executor, stream);
        CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclnnWeightQuantBatchMatmulNz failed. ERROR: %d\n", ret); return ret);
    
        // 4. (Boilerplate) Synchronously wait for task execution to finish.
        ret = aclrtSynchronizeStream(stream);
        CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclrtSynchronizeStream failed. ERROR: %d\n", ret); return ret);
    
        // Convert the output to 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);
        // Apply for device memory based on the workspaceSize calculated by the first-phase API.
        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. Obtain the output value and copy the result from device memory to host memory. Modify based on the specific API definition.
        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. (Boilerplate) Initialize device/stream. Refer to the ACL API manual.
        // Fill in the deviceId based on your actual device.
        int32_t deviceId = 0;
        aclrtStream stream;
        auto ret = AclnnWeightQuantBatchMatmulNzTest(deviceId, stream);
        CHECK_FREE_RET(ret == ACL_SUCCESS, LOG_PRINT("AclnnWeightQuantBatchMatmulNzTest failed. ERROR: %d\n", ret);
                      return ret);
    
        Finalize(deviceId, stream);
        return 0;
    }
  • Example with x as FLOAT16 and weight as INT8, which requires calling the aclnnTransMatmulWeight API to assist in the call:

    #include <cmath>
    #include <iostream>
    #include <memory>
    #include <vector>
    #include "acl/acl.h"
    #include "aclnnop/aclnn_weight_quant_batch_matmul_nz.h"
    #include "aclnnop/aclnn_trans_matmul_weight.h"
    
    #define CHECK_FREE_RET(cond, return_expr) \
        do {                                  \
            if (!(cond)) {                    \
                Finalize(deviceId, stream);   \
                return_expr;                  \
            }                                 \
        } while (0)
    
    #define CHECK_RET(cond, return_expr) \
        do {                             \
            if (!(cond)) {               \
                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)
    {
        // Boilerplate for resource initialization.
        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);
        // Call aclrtMalloc to apply for device-side memory.
        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);
        // Call aclrtMemcpy to copy host-side data to the device-side memory.
        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);
    
        // Calculate the strides of a contiguous tensor.
        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];
        }
    
        // Call the aclCreateTensor API to create an 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 CreateAclTensorWeightNz(
        const std::vector<T>& hostData, const std::vector<int64_t>& shape, void** deviceAddr, aclDataType dataType,
        aclTensor** tensor)
    {
        auto size =static_cast<uint64_t>(GetShapeSize(shape) * sizeof(T));
        const aclIntArray* mat2Size = aclCreateIntArray(shape.data(), shape.size());
        auto ret = aclnnCalculateMatmulWeightSizeV2(mat2Size, dataType, &size);
        CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclnnCalculateMamtulWeightSizeV2 failed. ERROR: %d\n", ret); return ret);
    
        // Call aclrtMalloc to allocate memory on the device side.
        ret = aclrtMalloc(deviceAddr, size, ACL_MEM_MALLOC_HUGE_FIRST);
        CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclrtMalloc failed. ERROR: %d\n", ret); return ret);
        // Call aclrtMemcpy to copy data from the host side to the device memory.
        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);
    
        // Calculate the strides of a contiguous tensor.
        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];
        }
    
        // Call aclCreateTensor to create an aclTensor.
        *tensor = aclCreateTensor(
            shape.data(), shape.size(), dataType, strides.data(), 0, aclFormat::ACL_FORMAT_ND, shape.data(), shape.size(),
            *deviceAddr);
        return 0;
    }
    
    // Convert the uint16_t representation of FP16 to a float representation.
    float Fp16ToFloat(uint16_t h)
    {
        int s = (h >> 15) & 0x1;  // sign
        int e = (h >> 10) & 0x1F; // exponent
        int f = h & 0x3FF;        // fraction
        if (e == 0) {
            // Zero or Denormal
            if (f == 0) {
                return s ? -0.0f : 0.0f;
            }
            // Denormals
            float sig = f / 1024.0f;
            float result = sig * pow(2, -24);
            return s ? -result : result;
        } else if (e == 31) {
            // Infinity or NaN
            return f == 0 ? (s ? -INFINITY : INFINITY) : NAN;
        }
        // Normalized
        float result = (1.0f + f / 1024.0f) * pow(2, e - 15);
        return s ? -result : result;
    }
    
    void Finalize(int32_t deviceId, aclrtStream stream)
    {
        aclrtDestroyStream(stream);
        aclrtResetDevice(deviceId);
        aclFinalize();
    }
    
    int AclnnWeightQuantBatchMatmulNzA16W8Test(int32_t deviceId, aclrtStream& stream)
    {
        auto ret = Init(deviceId, &stream);
        CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("Init acl failed. ERROR: %d\n", ret); return ret);
    
        // 2. Construct inputs and outputs. Customize the construction based on the API.
        int64_t m = 2;
        int64_t k = 16;
        int64_t n = 8;
        std::vector<int64_t> xShape = {m, k};
        std::vector<int64_t> weightShape = {k, n};
        std::vector<int64_t> yShape = {m, n};
        std::vector<int64_t> antiquantScaleShape = {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;
        std::vector<uint16_t> xHostData(m * k, 0b0011110000000000); // 0b0011110000000000 is 1.0 in fp16.
        std::vector<int8_t> weightHostData(k * n, 1);
        std::vector<float> yHostData(m * n, 0);
    
        std::vector<uint16_t> antiquantScaleHostData(n, 0b0011110000000000); // 0b0011110000000000 is 1.0 in fp16.
    
        // Create the 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);
    
        // Create the weight aclTensor.
        uint64_t workspaceSize = 0;
        aclOpExecutor* executor;
        ret = CreateAclTensorWeightNz(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);
        // Call the first-phase API of aclnnTransMatmulWeight.
        ret = aclnnTransMatmulWeightGetWorkspaceSize(weight, &workspaceSize, &executor);
        CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclnnTransMatmulWeightGetWorkspaceSize failed. ERROR: %d\n", ret);
                  return ret);
        // Apply for device memory based on the workspaceSize calculated by the first-phase API.
        void* workspaceTransMatmulAddr = nullptr;
        std::unique_ptr<void, aclError (*)(void*)> workspaceTransMatmulAddrPtr(nullptr, aclrtFree);
        if (workspaceSize > 0) {
            ret = aclrtMalloc(&workspaceTransMatmulAddr, workspaceSize, ACL_MEM_MALLOC_HUGE_FIRST);
            CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("allocate workspace failed. ERROR: %d\n", ret); return ret);
            workspaceTransMatmulAddrPtr.reset(workspaceTransMatmulAddr);
        }
        // Call the second-phase API of aclnnTransMatmulWeight.
        ret = aclnnTransMatmulWeight(workspaceTransMatmulAddr, workspaceSize, executor, stream);
        CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclnnTransMatmulWeight failed. ERROR: %d\n", ret);
                  return ret);
    
        // Create the 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);
        // Create the y aclTensor.
        ret = CreateAclTensor(yHostData, yShape, &yDeviceAddr, aclDataType::ACL_FLOAT16, &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);
    
        // 3. Call the CANN operator library API. Replace with the specific API name.
        // Call the first-phase API of aclnnWeightQuantBatchMatmulNz.
        ret = aclnnWeightQuantBatchMatmulNzGetWorkspaceSize(
            x, weight, antiquantScale, nullptr, nullptr, nullptr, nullptr, 0, y, &workspaceSize, &executor);
        CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclnnWeightQuantBatchMatmulNzGetWorkspaceSize failed. ERROR: %d\n", ret);
                  return ret);
        // Apply for device memory based on the workspaceSize calculated by the first-phase API.
        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);
        }
        // Call the second-phase API of aclnnWeightQuantBatchMatmulNz.
        ret = aclnnWeightQuantBatchMatmulNz(workspaceAddr, workspaceSize, executor, stream);
        CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclnnWeightQuantBatchMatmulNz failed. ERROR: %d\n", ret); return ret);
    
        // 4. (Boilerplate) Synchronize and wait for task execution to complete.
        ret = aclrtSynchronizeStream(stream);
        CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclrtSynchronizeStream failed. ERROR: %d\n", ret); return ret);
    
        // 5. Obtain the output value and copy the result from the device memory to the host memory. Modify this based on the specific API definition.
        auto size = GetShapeSize(yShape);
        std::vector<uint16_t> 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);
        for (int64_t i = 0; i < size; i++) {
            LOG_PRINT("result[%ld] is: %f\n", i, Fp16ToFloat(resultData[i]));
        }
        return ACL_SUCCESS;
    }
    
    int main()
    {
        // 1. (Boilerplate) Initialize the device/stream. Refer to the ACL API manual.
        // Set deviceId based on your actual device.
        int32_t deviceId = 0;
        aclrtStream stream;
        auto ret = AclnnWeightQuantBatchMatmulNzA16W8Test(deviceId, stream);
        CHECK_FREE_RET(ret == ACL_SUCCESS, LOG_PRINT("AclnnWeightQuantBatchMatmulV2Test failed. ERROR: %d\n", ret);
                       return ret);
    
        Finalize(deviceId, stream);
        return 0;
    }