aclnnWeightQuantBatchMatmulV3

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

  • Operator Function: Performs matrix multiplication computation with pseudo-quantization input and supports quantization computation for the output. Compared with the aclnnWeightQuantBatchMatmulV2 API, this API has the following changes:

    The innerPrecise parameter is added to support the selection of high-precision or high-performance computation modes. In the A16W4 per_group scenario, when batchSize <= 16, it can be set to 1 to improve performance.

  • Formula:

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

    In the formula, weightweight is the input in the pseudo-quantization scenario, and its dequantization formula ANTIQUANT(weight)ANTIQUANT(weight) is

    ANTIQUANT(weight)=(weight+antiquantOffset)∗antiquantScaleANTIQUANT(weight) = (weight + antiquantOffset) * antiquantScale

    When the user configures the quantScaleOptional input, the output is quantized. The quantization formula is

    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}

    When the user configures the quantScaleOptional input as nullptr, the output is directly:

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

Function Prototype

Each operator is divided into a two-phase API. First call the "aclnnWeightQuantBatchMatmulV3GetWorkspaceSize" API to obtain the workspace size required for computation and the executor that contains the operator computation process, and then call the "aclnnWeightQuantBatchMatmulV3" API to perform the computation.

aclnnStatus aclnnWeightQuantBatchMatmulV3GetWorkspaceSize(
  const aclTensor *x,
  const aclTensor *weight,
  const aclTensor *antiquantScale,
  const aclTensor *antiquantOffsetOptional,
  const aclTensor *quantScaleOptional,
  const aclTensor *quantOffsetOptional,
  const aclTensor *biasOptional,
  int              antiquantGroupSize,
  int              innerPrecise,
  const aclTensor *y,
  uint64_t        *workspaceSize,
  aclOpExecutor   **executor)
aclnnStatus aclnnWeightQuantBatchMatmulV3(
  void          *workspace,
  uint64_t       workspaceSize,
  aclOpExecutor *executor,
  aclrtStream    stream)

aclnnWeightQuantBatchMatmulV3GetWorkspaceSize

  • Parameters

    Parameter Input/Output Description Instruction Data Type Data Format Dimension (Shape) Non-contiguous Tensor
    x Input Input `x` in the formula. - FLOAT16, BFLOAT16 ND Dimensions support two dimensions, and the shape supports (m, k), where the Reduce dimension k must be equal in size to the Reduce dimension k of `weight`. Non-contiguous tensors only support the transpose scenario.
    weight Input Input `weight` in the formula. - INT8, INT4, INT32, FLOAT8_E4M3FN2, HIFLOAT82, FLOAT2, FLOAT4_E2M12 ND, FRACTAL_NZ Supports (k, n). Non-contiguous tensors are supported only in the transpose scenario.
    antiquantScale Input The dequantization scale parameter for implementing input dequantization computation, which is the input `antiquantScale` in the dequantization formula. When the data type is FLOAT16 or BFLOAT16, it must be consistent with the data type of the input `x`. When the data type is UINT64 or INT64, `x` supports only FLOAT16 without transposition, `weight` supports only int8 with ND transposition, the mode supports only per_channel, `quantScaleOptional` and `quantOffsetOptional` must be empty, `m` supports only [1, 96], `k` and `n` must be 64-aligned. In this case, first use the aclnnCast API to convert FLOAT16 to FLOAT32, and then use the aclnnTransQuantParamV2 API to convert FLOAT32 to UINT64. See details. FLOAT16, BFLOAT16, UINT641, INT641, FLOAT8_E8M02 ND
    • per_tensor mode: input shape is (1,) or (1, 1).
    • per_channel mode: input shape is (1, n) or (n,).
    • per_group mode: input shape is (ceil(k, group_size), n).
    Non-contiguous tensors only support the transpose scenario.
    antiquantOffsetOptional Input Dequantization offset parameter for input dequantization computation, which is the input `antiquantOffset` in the dequantization formula. When the data type is FLOAT16 or BFLOAT16, it must be consistent with the data type of input `x`. When the data type is INT32, the data range is limited to [-128, 127], `x` supports only FLOAT16, `weight` supports only int8, and `antiquantScale` supports only UINT64/INT64. Optional input. A null pointer indicates that it is not required. FLOAT16, BFLOAT16, INT321 ND Must be consistent with `antiquantScale`. Non-contiguous tensors are only supported in transpose scenarios.
    quantScaleOptional Input Quantization parameter for output quantization computation, obtained by converting the quantScale and quantOffset data from the quantization formula through the aclnnTransQuantParam API. Obtained by converting the quantScale and quantOffset data from the quantization formula through the aclnnTransQuantParam API. UINT641 ND
    • per_tensor mode: Input shape is (1,) or (1, 1).
    • per_channel mode: Input shape is (1, n) or (n,).
    -
    quantOffsetOptional Input Quantization offset parameter for output quantization computation, corresponding to the input `quantOffset` in the quantization formula. Optional Input. Set to a null pointer when not required. FLOAT1 ND Consistent with `quantScaleOptional`. -
    biasOptional Input Bias input, corresponding to `bias` in the formula. When the data type of input `x` is BFLOAT16, the data type must be FLOAT; when the data type of input `x` is FLOAT16, the data type must be FLOAT16. Optional Input. When not required, set to a null pointer. FLOAT, FLOAT16, BFLOAT162 ND 1-2 -
    antiquantGroupSize Input Specifies the groupSize input for dequantizing the input `weight` in the pseudo-quantization per_group algorithm mode, describing the size of the data to be dequantized corresponding to a set of dequantization parameters in the reduce direction. Pass 0 when the pseudo-quantization algorithm mode is not per_group. When the pseudo-quantization algorithm mode is per_group, the value ranges from [32, k-1] and must be a multiple of 32. - - - - -
    innerPrecise Input Indicates whether pseudo-quantization uses the high-precision or high-performance computation mode (only 0 or 1 is supported). In the A16W4 per_group scenario, when batchSize <= 16, this can be set to 1 and the weight data format set to FRACTAL_NZ to improve performance. This is not recommended for other scenarios; passing 0 is recommended. - int - - -
    y Output Computation output, which is `y` in the formula. When `quantScaleOptional` exists, the data type is INT8. When `quantScaleOptional` does not exist, the data type supports FLOAT16 and BFLOAT16, and is consistent with the data type of the input `x`. INT81, FLOAT16, BFLOAT16 ND 2 -
    workspaceSize Output Returns the workspace size to be requested on the Device side.
    executor Output Returns the operator executor, which contains the operator computation flow.
    • Ascend 950PR/Ascend 950DT:

      • The superscript "1" in the data type column of the table above indicates a data type not supported by this series.
    • Atlas A2 training products/Atlas A2 inference products, Atlas A3 training products/Atlas A3 inference products:

      • The superscript "2" in the data type column of the table above indicates a data type not supported by this series.
  • 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 Error Code Description
    ACLNN_ERR_PARAM_NULLPTR 161001 A required parameter is a null pointer.
    ACLNN_ERR_PARAM_INVALID 161002 The shape dimensions of x, weight, antiquantScale, antiquantOffsetOptional, quantScaleOptional, quantOffsetOptional, biasOptional, or y do not meet the requirements.
    The data type of x, weight, antiquantScale, antiquantOffsetOptional, quantScaleOptional, quantOffsetOptional, biasOptional, or y is not within the supported range.
    The reduction dimensions (k) of x and weight are not equal.
    When antiquantOffsetOptional is provided, its shape is not the same as that of antiquantScale.
    When quantOffsetOptional is present, its shape differs from that of quantScale.
    The antiQuantGroupSize value does not meet the requirements.
    The innerPrecise value does not meet the requirements.
    When quantOffsetOptional is present, quantScaleOptional is a null pointer.
    The input k or n value is outside the range [1, 65535].
    When the x matrix is non-transposed, m is not in the range [1, 2^31-1]; when transposed, m is not in the range [1, 65535].
    Empty tensor scenarios are not supported.
    The contiguity of the passed x, weight, antiQuantScale, antiQuantOffsetOptional, quantScaleOptional, quantOffsetOptional, biasOptional, and y does not meet the requirements.
    ACLNN_ERR_RUNTIME_ERROR 361001 The product model is not supported.

aclnnWeightQuantBatchMatmulV3

  • Parameters

    Parameter Input/Output Description
    workspace Input Memory address of the workspace applied for on the Device side.
    workspaceSize Input Size of the workspace applied for on the Device side, obtained from the first-phase API aclnnWeightQuantBatchMatmulV3GetWorkspaceSize.
    executor Input Operator executor, which contains the operator computation flow.
    stream Input Specifies the Stream for executing the task.
  • Return value

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

Constraints

Atlas A2 training products/Atlas A2 inference products, Atlas A3 training products/Atlas A3 inference products
  • Deterministic computation description: Defaults to a non-deterministic implementation. Supports enabling deterministic computation through aclrtCtxSetSysParamOpt.

  • Common Constraints

    • When the weight data format is FRACTAL_NZ and the data type is INT4/INT32, or when the weight data format is ND and the data type is INT32, it is supported only in the INT4Pack scenario. In this case, the aclnnConvertWeightToINT4Pack API must be used to complete the conversion from INT32 to INT4Pack and from ND to FRACTAL_NZ. See details. If the data type is INT4, the inner axis of weight must be an even number.

    • For different pseudo-quantization algorithm modes, the weight data format FRACTAL_NZ is supported only in the following scenarios:

      • per_channel mode:

        The weight data type is INT8 and the y data type is non-INT8. The weight data type is INT4/INT32, weight is transposed, and the y data type is non-INT8.

      • per_group mode: The weight data type is INT4/INT32, weight is non-transposed, x is non-transposed, antiquantGroupSize is 64 or 128, k is aligned to antiquantGroupSize, n is aligned to 64, and the y data type is non-INT8.

  • Performance Optimization

    • per_channel mode: To improve performance, it is recommended to use the transposed weight input. When the m range is [65, 96], it is recommended to use antiquantScale with the data type UINT64/INT64.

    • per_group mode: In the A16W4 scenario with batchSize <= 16, you can set the innerPrecise parameter to 1 and set the weight data format to FRACTAL_NZ to improve performance, but this may cause a certain degree of precision degradation.

Ascend 950PR/Ascend 950DT
  • Deterministic computation description: Defaults to a deterministic implementation.

  • Common Constraints

    • The m, k, and n dimensions of the x and weight matrices are in the range [1, 2^31-1]. The Reduce dimension k of weight must be equal to the Reduce dimension k of x.

    • Supported quantization modes: pertensor quantization mode, perchannel quantization mode, pergroup quantization mode, and mx quantization mode.

    • x does not support transposition, and therefore does not support non-contiguous tensors. weight supports non-contiguous tensors only in the transposed scenario. antiquantScale and antiquantOffsetOptional support non-contiguous tensors only in the transposed scenario, and their contiguity requirements must be consistent with weight.

    • Shapes supported by antiquantScale for different quantization modes:

      • pertensor quantization mode: (1,) or (1,1).

      • perchannel quantization mode: The input shape is (1, n) or (n,).

      • pergroup Quantization Mode: The input shape is (⌈k/group_size⌉, n), where group_size indicates the size of each group into which k is divided.

      • mx Quantization Mode: The input shape is (⌈k/group_size⌉, n), where group_size indicates the size of each group into which k is divided. Only 32 is supported.

    • quantScaleOptional and quantOffsetOptional are reserved parameters and not yet in use. Always pass a null pointer.

    A16W8 Scenario Constraints
    • Input and output data type combination requirements
    xweightweight FormatantiquantScaleantiquantOffsetOptionalquantScaleOptionalquantOffsetOptionalbiasOptionalantiquantGroupSizeyScenario Description
    FLOAT16/BFLOAT16INT8NDSame as xSame as x/nullnullnullSame as x/FLOAT (only when x is BFLOAT16)/nullpergroup: [32, k-1] and a multiple of 32
    Other: 0
    Same as xT & C & G quantization
    FLOAT16/BFLOAT16HIFLOAT8/FLOAT8_E4M3FNNDSame as xnullnullnullSame as x/nullpergroup: [32, k-1] and a multiple of 32
    Other: 0
    Same as xC quantization

    A16W4 Scenario Constraints
    • Input and Output Data Type Combination Requirements
    xweightweight FormatantiquantScaleantiquantOffsetOptionalquantScaleOptionalquantOffsetOptionalbiasOptionalantiquantGroupSizeyScenario Description
    FLOAT16/BFLOAT16INT4/INT32NDSame as xSame as x/nullnullnullSame as x/FLOAT (only when x is BFLOAT16)/null0Same as xT quantization
    FLOAT16/BFLOAT16INT4/INT32NDSame as xSame as x/nullnullnullSame as x/FLOAT (only when x is BFLOAT16)/nullpergroup: [32, k-1] and a multiple of 32
    Other: 0
    Same as xC & G quantization
    FLOAT16/BFLOAT16FLOAT4_E2M1NDFLOAT8_E8M0nullnullnullSame as x/null32Same as xMX quantization
    FLOAT16/BFLOAT16FLOATNDFLOAT8_E8M0nullnullnullSame as x/FLOAT (only when x is BFLOAT16)/null32Same as xMX quantization
    • Constraints

      In addition to the Common Constraints, the remaining constraints for the A16W4 scenario are as follows:

      • If the data type of weight is INT4 or FLOAT4_E2M1, the last dimension of weight must be 2-aligned. If the data type of weight is INT32 or FLOAT, the last dimension of weight must be 8-aligned.

      • If the data type of weight is INT32 or FLOAT, it must be used together with the aclnnConvertWeightToINT4Pack API to convert from INT32/FLOAT to densely packed INT4/FLOAT4_E2M1. For details, see the sample.

Example

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

  • Atlas A2 training products/Atlas A2 inference products, Atlas A3 training products/Atlas A3 inference products, Ascend 950PR/Ascend 950DT:

A16W8 example.

#include <iostream>
#include <vector>
#include "acl/acl.h"
#include "aclnnop/aclnn_cast.h"
#include "aclnnop/aclnn_weight_quant_batch_matmul_v3.h"

#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 allocate 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 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 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;
}

int main() {
  // 1. (Boilerplate) Initialize the 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 = Init(deviceId, &stream);
  CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("Init acl failed. ERROR: %d\n", ret); return ret);

  // 2. Construct the inputs and outputs. Custom construction is required based on the API interface.
  std::vector<int64_t> xShape = {16, 32};
  std::vector<int64_t> weightShape = {32, 16};
  std::vector<int64_t> yShape = {16, 16};
  void* xDeviceAddr = nullptr;
  void* weightDeviceAddr = nullptr;
  void* yDeviceAddr = nullptr;
  aclTensor* x = nullptr;
  aclTensor* weight = nullptr;
  aclTensor* y = nullptr;
  int32_t innerPrecise = 1;
  std::vector<float> xHostData(512, 1);
  std::vector<int8_t> weightHostData(512, 1);
  std::vector<float> yHostData(256, 0);

  std::vector<int64_t> antiquantScaleShape = {16};
  void* antiquantScaleDeviceAddr = nullptr;
  aclTensor* antiquantScale = nullptr;
  std::vector<float> antiquantScaleHostData(16, 1);


  // Create the x aclTensor.
  ret = CreateAclTensor(xHostData, xShape, &xDeviceAddr, aclDataType::ACL_FLOAT, &x);
  CHECK_RET(ret == ACL_SUCCESS, return ret);
  // Create the other aclTensor.
  ret = CreateAclTensor(weightHostData, weightShape, &weightDeviceAddr, aclDataType::ACL_INT8, &weight);
  CHECK_RET(ret == ACL_SUCCESS, return ret);
  // Create the y aclTensor.
  ret = CreateAclTensor(yHostData, yShape, &yDeviceAddr, aclDataType::ACL_FLOAT, &y);
  CHECK_RET(ret == ACL_SUCCESS, return ret);
  // Create the antiquantScale aclTensor.
  ret = CreateAclTensor(antiquantScaleHostData, antiquantScaleShape, &antiquantScaleDeviceAddr, aclDataType::ACL_FLOAT, &antiquantScale);
  CHECK_RET(ret == ACL_SUCCESS, return ret);

  // Create the xFp16 aclTensor.
  void* xFp16DeviceAddr = nullptr;
  aclTensor* xFp16 = nullptr;
  ret = CreateAclTensor(xHostData, xShape, &xFp16DeviceAddr, aclDataType::ACL_FLOAT16, &xFp16);
  CHECK_RET(ret == ACL_SUCCESS, return ret);
  // Create the antiquantScale aclTensor.
  void* antiquantScaleFp16DeviceAddr = nullptr;
  aclTensor* antiquantScaleFp16 = nullptr;
  ret = CreateAclTensor(antiquantScaleHostData, antiquantScaleShape, &antiquantScaleFp16DeviceAddr, aclDataType::ACL_FLOAT16, &antiquantScaleFp16);
  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);
  CHECK_RET(ret == ACL_SUCCESS, return ret);

  // 3. Call the CANN operator library API. Replace with the specific API name.
  uint64_t workspaceSize = 0;
  aclOpExecutor* executor;
  void* workspaceAddr = nullptr;

  // Call cast to generate FP16 input.
  ret = aclnnCastGetWorkspaceSize(x, aclDataType::ACL_FLOAT16, xFp16, &workspaceSize, &executor);
  CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclnnCastGetWorkspaceSize0 failed. ERROR: %d\n", ret); return ret);
  // Allocate device memory based on the workspaceSize calculated by the first-phase API.

  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);
  }
  ret = aclnnCast(workspaceAddr, workspaceSize, executor, stream);
  CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclnnCast0 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);

  ret = aclnnCastGetWorkspaceSize(antiquantScale, aclDataType::ACL_FLOAT16, antiquantScaleFp16, &workspaceSize, &executor);
  CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclnnCastGetWorkspaceSize1 failed. ERROR: %d\n", ret); return ret);
  // Allocate device memory based on the workspaceSize calculated by the first-phase API.

  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);
  }
  ret = aclnnCast(workspaceAddr, workspaceSize, executor, stream);
  CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclnnCast1 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);

  // Call the first-phase API of aclnnWeightQuantBatchMatmulV3.
  ret = aclnnWeightQuantBatchMatmulV3GetWorkspaceSize(xFp16, weight, antiquantScaleFp16, nullptr, nullptr, nullptr, nullptr, 0, innerPrecise, yFp16, &workspaceSize, &executor);
  CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclnnWeightQuantBatchMatmulV3GetWorkspaceSize failed. ERROR: %d\n", ret); return ret);
  // Apply for device memory based on the workspaceSize calculated by the first-phase API.

  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);
  }
  // Call the second-phase API of aclnnWeightQuantBatchMatmulV3.
  ret = aclnnWeightQuantBatchMatmulV3(workspaceAddr, workspaceSize, executor, stream);
  CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclnnWeightQuantBatchMatmulV3 failed. ERROR: %d\n", ret); return ret);

  // 4. (Boilerplate) Synchronously wait for task execution to complete.
  ret = aclrtSynchronizeStream(stream);
  CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclrtSynchronizeStream failed. ERROR: %d\n", ret); return ret);

// Convert the output to FP32.
  ret = aclnnCastGetWorkspaceSize(yFp16, aclDataType::ACL_FLOAT, y, &workspaceSize, &executor);
  CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclnnCastGetWorkspaceSize2 failed. ERROR: %d\n", ret); return ret);
  // Apply for device memory based on the workspaceSize calculated by the first-phase API.

  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);
  }
  ret = aclnnCast(workspaceAddr, 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 the device memory to the 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);
  for (int64_t i = 0; i < size; i++) {
    LOG_PRINT("result[%ld] is: %f\n", i, resultData[i]);
  }

  // 6. Release aclTensor and aclScalar. Modify based on the specific API definition.
  aclDestroyTensor(x);
  aclDestroyTensor(weight);
  aclDestroyTensor(antiquantScale);
  aclDestroyTensor(y);
  aclDestroyTensor(xFp16);
  aclDestroyTensor(antiquantScaleFp16);
  aclDestroyTensor(yFp16);

  // 7. Release device resources.
  aclrtFree(xDeviceAddr);
  aclrtFree(weightDeviceAddr);
  aclrtFree(antiquantScaleDeviceAddr);
  aclrtFree(yDeviceAddr);
  aclrtFree(xFp16DeviceAddr);
  aclrtFree(antiquantScaleFp16DeviceAddr);
  aclrtFree(yFp16DeviceAddr);

  if (workspaceSize > 0) {
    aclrtFree(workspaceAddr);
  }
  aclrtDestroyStream(stream);
  aclrtResetDevice(deviceId);
  aclFinalize();

  return 0;
}
  • Ascend 950PR/Ascend 950DT:

A16MxFp4 example.

#include <iostream>
#include <memory>
#include <vector>
#include "acl/acl.h"
#include "aclnnop/aclnn_cast.h"
#include "aclnnop/aclnn_weight_quant_batch_matmul_v3.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-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 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;
}

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 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 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 aclnnWeightQuantBatchMatmulV3Test(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 the input and output. Customize them based on the API interface.
    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); // 1.0 in fp16
    xHostData[0] = 0;                                                  // 0 in fp16, to verify whether the result meets the requirement.
    std::vector<float> weightHostData(GetShapeSize(weightShape), 1.0); // 1.0 in fp32, converted to 1.0 in fp4_e2m1 after int4pack.
    std::vector<float> yHostData(GetShapeSize(yShape), 0);

    std::vector<uint8_t> antiquantScaleHostData(GetShapeSize(antiquantScaleShape), 0b01111111); // 1.0 in fp8_e8m0

    // Create 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 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 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 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);
    // Create 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_ND; // Optional: ACL_FORMAT_FRACTAL_NZ
    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 aclnnWeightQuantBatchMatmulV3.
    workspaceSize = 0;
    executor = nullptr;
    ret = aclnnWeightQuantBatchMatmulV3GetWorkspaceSize(
        x, weightPacked, antiquantScale, nullptr, nullptr, nullptr, nullptr, groupSize, 0, yFp16, &workspaceSize,
        &executor);
    CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclnnWeightQuantBatchMatmulV3GetWorkspaceSize 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 aclnnWeightQuantBatchMatmulV3.
    ret = aclnnWeightQuantBatchMatmulV3(workspaceAddr, workspaceSize, executor, stream);
    CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclnnWeightQuantBatchMatmulV3 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);

    // 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 the device memory to the host memory. Modify this 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 the device/stream. See the ACL API manual.
    // Enter the deviceId based on your actual device.
    int32_t deviceId = 0;
    aclrtStream stream;
    auto ret = aclnnWeightQuantBatchMatmulV3Test(deviceId, stream);
    CHECK_FREE_RET(ret == ACL_SUCCESS, LOG_PRINT("aclnnWeightQuantBatchMatmulV3Test failed. ERROR: %d\n", ret);
                  return ret);

    Finalize(deviceId, stream);
    return 0;
}