aclnnQuantMatmulDequant

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: Quantizes the input x, performs matrix multiplication, and then dequantizes the result.

  • Formula:

    1. If smoothScaleOptional is input, then
    x=x⋅scalesmoothx = x\cdot scale_{smooth}
    1. If xScaleOptional is not input, dynamic quantization is used, and the x quantization coefficient needs to be calculated.
    scalex=row_max(abs(x))/maxquantDataTypescale_{x}=row\_max(abs(x))/max_{quantDataType}
    1. Quantization
    xquantized=round(x/scalex)x_{quantized}=round(x/scale_{x})
    1. Matrix multiplication + dequantization
    • 4.1 If the data type of the input scaleweightscale_{weight} is FLOAT32:

      out=(xquantized@weightquantized+bias)∗scaleweight∗scalexout = (x_{quantized}@weight_{quantized} + bias) * scale_{weight} * scale_{x}
    • 4.2 If the data type of the input scaleweightscale_{weight} is INT64:

      scaleweight=torch.tensor(np.frombuffer(scaleweight.numpy().astype(np.int32).tobytes(),dtype=np.float32))out=(xquantized@weightquantized+bias)∗scaleweightscale_{weight} = torch.tensor(np.frombuffer(scale_{weight}.numpy().astype(np.int32).tobytes(), dtype=np.float32)) \\ out = (x_{quantized}@weight_{quantized} + bias) * scale_{weight}

      Special note: In scenario 4.2 above, scaleweightscale_{weight} has already undergone a matrix multiplication operation with scalexscale_{x} before being input, so the operator omits this step during internal computation. This requires that the quantization mode must be per-tensor static quantization. That is, before input, scaleweightscale_{weight} must be processed as follows to obtain data of the INT64 type:

      scaleweight=scaleweight∗scalexscaleweight=torch.tensor(np.frombuffer(scaleweight.numpy().astype(np.float32).tobytes(),dtype=np.int32).astype(np.int64))scale_{weight} = scale_{weight} * scale_{x} \\ scale_{weight} = torch.tensor(np.frombuffer(scale_{weight}.numpy().astype(np.float32). \\tobytes(), dtype=np.int32).astype(np.int64))

Function Prototype

Each operator is divided into a two-phase API. You must first call the "aclnnQuantMatmulDequantGetWorkspaceSize" API to obtain the input parameters and calculate the required workspace size based on the process, and then call the "aclnnQuantMatmulDequant" API to perform the computation.

aclnnStatus aclnnQuantMatmulDequantGetWorkspaceSize(
  const aclTensor *x,
  const aclTensor *weight,
  const aclTensor *weightScale,
  const aclTensor *biasOptional,
  const aclTensor *xScaleOptional,
  const aclTensor *xOffsetOptional,
  const aclTensor *smoothScaleOptional,
  char            *xQuantMode,
  bool             transposeWeight,
  const aclTensor *out,
  uint64_t        *workspaceSize,
  aclOpExecutor  **executor)
aclnnStatus aclnnQuantMatmulDequant(
  void          *workspace,
  uint64_t       workspaceSize,
  aclOpExecutor *executor,
  aclrtStream    stream)

aclnnQuantMatmulDequantGetWorkspaceSize

  • Parameters

    Parameter Input/Output Description Instruction Data Type Data Format Dimension (Shape) Non-contiguous Tensor
    x (aclTensor*) Input Represents the left input matrix, i.e., x in the formula.
    • The shape supports 2 dimensions, where each dimension represents: (m, k).
    • Non-nullable tensor.
    FLOAT16 ND 2 √
    weight (aclTensor*) Input Represents the right input matrix. weight_{quantized} in the formula. Null tensors are not supported. INT FRACTAL_NZ, ND 2, 4 √
    weightScale (aclTensor*) Input Represents the quantization coefficient of weight, i.e., scale_{weight} in the formula.
    • The shape is 1D (n,), where n is consistent with the n of weight.
    • Nullable.
    • When the data type is INT64, the xScaleOptional data type must be FLOAT16 and the xQuantMode value must be pertensor.
    FLOAT32, INT64 ND 1 √
    biasOptional (aclTensor*) Optional input. Represents the offset in the computation, i.e., bias in the formula. Currently supports only null pointers. - - - -
    xScaleOptional (aclTensor*) Optional input Represents the quantization coefficient of x, i.e., scale_{x} in the formula.
    • When xQuantMode is pertensor, the shape is 1D (1,); when xQuantMode is pertoken, the shape is 1D (m,), where m is consistent with m of input x. If null, dynamic quantization is used.
    • Nullable.
    • When the data type is FLOAT16, the weightScale data type must be INT64, and the xQuantMode value is pertensor.
    FLOAT32, FLOAT16 ND - √
    xOffsetOptional (aclTensor*) Optional input Represents the offset of x. Currently only supports passing a null pointer. - - - -
    smoothScaleOptional (aclTensor*) Optional input. Represents the smoothing coefficient of x, the smoothing coefficient of x, i.e., scale_{smooth} in the formula.
    • The shape is 1D (k,), where k is consistent with k of x.
    • Nullable.
    FLOAT16 ND 1 √
    xQuantMode (char*) Input Specifies the quantization mode of input x. Supports pertoken or pertensor. Only pertoken is supported for dynamic quantization. pertoken indicates that each token (a row) has its own quantization parameter; pertensor indicates that the entire tensor uses a unified quantization parameter. STRING - - -
    transposeWeight (bool) Input Indicates whether to transpose the input weight. Currently, only true is supported. BOOL - - -
    out (aclTensor*) Output Computation result, i.e., out in the formula. Shape supports 2 dimensions, with each dimension representing: (m, n). Here, m is consistent with m of x, and n is consistent with n of weight. FLOAT16 ND 2 √
    workspaceSize(uint64_t*) Output Returns the workspace size to be applied for on the Device side. - - - - -
    executor (aclOpExecutor**) Output Returns the op executor, which contains the operator computation flow. - - - - -
    • When the weight parameter is in ND format, its shape supports 2 dimensions.

      • When transposeWeight is true, each dimension represents: (n, k).

      • When transposeWeight is false, each dimension represents: (k, n).

    • When the weight parameter is in FRACTAL_NZ format, its shape supports 4 dimensions.

      • When transposeWeight is true, each dimension represents: (k1, n1, n0, k0), where k0 = 32, n0 = 16, and k1 and k of x must satisfy the following relationship: ceilDiv(k, 32) = k1.

      • When transposeWeight is false, each dimension represents: (n1, k1, k0, n0), where k0 = 16, n0 = 32, and k1 and k of x must satisfy the following relationship: ceilDiv(k, 16) = k1.

      • The aclnnCalculateMatmulWeightSizeV2 API and aclnnTransMatmulWeight API can be used to convert the input format from ND to the FRACTAL_NZ format.

  • 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 Code Error Code Description
    ACLNN_ERR_PARAM_NULLPTR 161001 The input parameter is a mandatory input, output, or mandatory attribute and is a null pointer.
    ACLNN_ERR_PARAM_INVALID 161002
    • The input parameter type is aclTensor and its data type is not within the supported range.
    • n or k in the weight shape is not divisible by 16.
    ACLNN_ERR_INNER_TILING_ERROR 561002 The input parameter type is aclTensor and its shape does not match the parameter description above.

aclnnQuantMatmulDequant

  • Parameters

    Parameter Input/Output Description
    workspace Input Workspace memory address applied for on the Device side.
    workspaceSize Input Workspace size applied for on the Device side, obtained from the first-phase API aclnnQuantMatmulDequantGetWorkspaceSize.
    executor Input Operator executor, which contains the operator computation flow.
    stream Input Specifies the stream for executing tasks.
  • Return Value

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

Constraints

  • Deterministic description:

  • Atlas training products and Atlas inference products: aclnnQuantMatmulDequant default to a deterministic implementation.

  • Both n and k must be integer multiples of 16.

  • When the weightScale data type is INT64, the mandatory requirement is that the xScaleOptional data type is FLOAT16 and the xQuantMode value is pertensor; when the xScaleOptional data type is FLOAT16, the mandatory requirement is that the weightScale data type is INT64 and the xQuantMode value is pertensor.

Example

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

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

void PrintOutResult(std::vector<int64_t> &shape, void** deviceAddr) {
  auto size = GetShapeSize(shape);
  std::vector<float> resultData(size, 0);
  auto ret = aclrtMemcpy(resultData.data(), resultData.size() * sizeof(resultData[0]),
                         *deviceAddr, 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);
  for (int64_t i = 0; i < size; i++) {
    LOG_PRINT("mean result[%ld] is: %f\n", i, resultData[i]);
  }
}

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

int main() {
  // 1. (Boilerplate) Initialize the device/stream. See 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. Customize the construction based on the API interface.
  int M = 64;
  int K = 256;
  int N = 512;

  char quantMode[16] = "pertoken";
  bool transposeWeight = true;

  std::vector<int64_t> xShape = {M,K};
  std::vector<int64_t> weightShape = {N,K};
  std::vector<int64_t> weightScaleShape = {N};
  std::vector<int64_t> xScaleShape = {M};
  std::vector<int64_t> smoothScaleShape = {K};
  std::vector<int64_t> outShape = {M,N};

  void* xDeviceAddr = nullptr;
  void* weightDeviceAddr = nullptr;
  void* weightScaleDeviceAddr = nullptr;
  void* xScaleDeviceAddr = nullptr;
  void* smoothScaleDeviceAddr = nullptr;
  void* outDeviceAddr = nullptr;

  aclTensor* x = nullptr;
  aclTensor* weight = nullptr;
  aclTensor* weightScale = nullptr;
  aclTensor* bias = nullptr;
  aclTensor* xScale = nullptr;
  aclTensor* xOffset = nullptr;
  aclTensor* smoothScale = nullptr;
  aclTensor* out = nullptr;

  std::vector<uint16_t> xHostData(GetShapeSize(xShape));
  std::vector<uint8_t> weightHostData(GetShapeSize(weightShape));
  std::vector<uint16_t> weightScaleHostData(GetShapeSize(weightScaleShape));
  std::vector<uint16_t> xScaleHostData(GetShapeSize(xScaleShape));
  std::vector<uint16_t> smoothScaleHostData(GetShapeSize(smoothScaleShape));
  std::vector<uint16_t> outHostData(GetShapeSize(outShape));

  ret = CreateAclTensor(xHostData, xShape, &xDeviceAddr, aclDataType::ACL_FLOAT16, &x);
  CHECK_RET(ret == ACL_SUCCESS, return ret);
  ret = CreateAclTensor(weightHostData, weightShape, &weightDeviceAddr, aclDataType::ACL_INT8, &weight);
  CHECK_RET(ret == ACL_SUCCESS, return ret);
  ret = CreateAclTensor(weightScaleHostData, weightScaleShape, &weightScaleDeviceAddr, aclDataType::ACL_FLOAT, &weightScale);
  CHECK_RET(ret == ACL_SUCCESS, return ret);
  ret = CreateAclTensor(xScaleHostData, xScaleShape, &xScaleDeviceAddr, aclDataType::ACL_FLOAT, &xScale);
  CHECK_RET(ret == ACL_SUCCESS, return ret);
  ret = CreateAclTensor(smoothScaleHostData, smoothScaleShape, &smoothScaleDeviceAddr, aclDataType::ACL_FLOAT16, &smoothScale);
  CHECK_RET(ret == ACL_SUCCESS, return ret);
  ret = CreateAclTensor(outHostData, outShape, &outDeviceAddr, aclDataType::ACL_FLOAT16, &out);
  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;

  // Call the first-phase API of aclnnQuantMatmulDequant.
  ret = aclnnQuantMatmulDequantGetWorkspaceSize(x, weight, weightScale,
                                                bias, xScale, xOffset, smoothScale,
                                                quantMode, transposeWeight, out,
                                                &workspaceSize, &executor);
  CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclnnQuantMatmulDequantGetWorkspaceSize failed. ERROR: %d\n", ret); return ret);

  // Apply for device memory based on the workspaceSize calculated by the first-phase API.
  void* workspaceAddr = nullptr;
  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 aclnnQuantMatmulDequant.
  ret = aclnnQuantMatmulDequant(workspaceAddr, workspaceSize, executor, stream);
  CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclnnQuantMatmulDequant 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);

  // 5. Obtain the output value and copy the result from the device side memory to the host side. Modify based on the specific API definition.
  PrintOutResult(outShape, &outDeviceAddr);

  // 6. Release aclTensor and aclScalar. Modify based on the specific API definition.
  aclDestroyTensor(x);
  aclDestroyTensor(weight);
  aclDestroyTensor(weightScale);
  aclDestroyTensor(xScale);
  aclDestroyTensor(smoothScale);
  aclDestroyTensor(out);

  // 7. Release device resources.
  aclrtFree(xDeviceAddr);
  aclrtFree(weightDeviceAddr);
  aclrtFree(weightScaleDeviceAddr);
  aclrtFree(xScaleDeviceAddr);
  aclrtFree(smoothScaleDeviceAddr);
  aclrtFree(outDeviceAddr);

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

  return 0;
}