aclnnQuantConvolution

📄 View Source Code

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 per-channel quantized 2D and 3D convolution computation, where the convolution computation process is consistent with the aclnnConvolution API.

  • Formula:

    Assume the input shape is (N,Cin,D,H,W)(N, C_{\text{in}}, D, H, W), the weight shape is (Cout,Cin,Kd,Kh,Kw)(C_{\text{out}}, C_{\text{in}}, K_d, K_h, K_w), the scale shape is (Cout)(C_{\text{out}}), the bias shape is CoutC_{\text{out}}, and the output shape is (N,Cout,Dout,Hout,Wout)(N, C_{\text{out}}, D_{\text{out}}, H_{\text{out}}, W_{\text{out}}), where NN denotes the batch size, CC is the number of channels, DD, HH, and WW are the depth, height, and width of the sample respectively, and KdK_d, KhK_h, and KwK_w are the depth, height, and width of the convolution kernel respectively. The output is expressed as:

    output(Ni,Coutj,Dout,Hout,Wout)=[∑k=0Cin−1weight(Coutj,k)⋆input(Ni,k)]×scale(Coutj)+bias(Coutj)\text{output}(N_i, C_{\text{out}_j}, D_{\text{out}}, H_{\text{out}}, W_{\text{out}}) = \left[\sum_{k = 0}^{C_{\text{in}} - 1} \text{weight}(C_{\text{out}_j}, k) \star \text{input}(N_i, k)\right] \times \text{scale}(C_{\text{out}_j}) + \text{bias}(C_{\text{out}_j})

    Where ⋆\star denotes the convolution operation, which depends on the convolution input dimensions and the convolution type (dilated convolution, grouped convolution). NN represents the batch size, CC represents the number of channels, and DD, HH, and WW represent the depth, height, and width respectively. The formulas for the corresponding output dimensions are as follows:

    Dout=[(D+2×padding[0]−dilation[0]×(Kd−1)−1)/stride[0]]+1Hout=[(H+2×padding[1]−dilation[1]×(Kh−1)−1)/stride[1]]+1Wout=[(W+2×padding[2]−dilation[2]×(Kw−1)−1)/stride[2]]+1D_{\text{out}}=[(D + 2 \times padding[0] - dilation[0] \times (K_d - 1) - 1 ) / stride[0]] + 1 \\ H_{\text{out}}=[(H + 2 \times padding[1] - dilation[1] \times (K_h - 1) - 1 ) / stride[1]] + 1 \\ W_{\text{out}}=[(W + 2 \times padding[2] - dilation[2] \times (K_w - 1) - 1 ) / stride[2]] + 1

Function Prototype

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

aclnnStatus aclnnQuantConvolutionGetWorkspaceSize(
    const aclTensor       *input,
    const aclTensor       *weight,
    const aclTensor       *bias,
    const aclTensor       *scale,
    const aclTensor       *offset,
    const aclIntArray     *stride,
    const aclIntArray     *padding,
    const aclIntArray     *dilation,
    bool                   transposed,
    const aclIntArray     *outputPadding,
    int64_t                groups,
    int32_t                offsetx,
    const char            *roundMode,
    aclTensor             *output,
    uint64_t              *workspaceSize,
    aclOpExecutor         **executor)
aclnnStatus aclnnQuantConvolution(
    void            *workspace,
    const uint64_t   workspaceSize,
    aclOpExecutor   *executor,
    aclrtStream      stream)

aclnnQuantConvolutionGetWorkspaceSize

  • Parameters

    Parameter Input/Output Description Instruction Data Type Data Format Dimension (Shape) Non-Contiguous Tensor
    input (aclTensor*) Input input in the formula, which represents the convolution input.
    • Null tensor not supported.
    • The data types of input and weight must satisfy the data type deduction rules (see Deduction Relationship).
    • input, weight, and output must have the same dimension.
    • N≥0, C≥1, D≥0, H≥0, W≥0.
    INT8, FLOAT8_E4M3FN, HIFLOAT8 NCHW, NCDHW 4-5 √
    weight (aclTensor*) Input weight in the formula, represents the convolution weight.
    • Null tensor not supported.
    • The data type must satisfy the data type deduction rules with the data type of input (see Deduction Relationship).
    • The C dimension of its shape must be consistent with the C dimension of input.
    • All dimensions ≥ 1.
    INT8, FLOAT8_E4M3FN, HIFLOAT8 NCHW, NCDHW 4-5 √
    bias (aclTensor*) Input bias in the formula, represents the convolution bias. One-dimensional and equal to the first dimension of weight. BFLOAT16, FLOAT16, FLOAT, INT32 ND 1 √
    scale (aclTensor*) Input scale in the formula represents the quantization parameter. One-dimensional and equal to the first dimension of weight. FLOAT, INT64, UINT64 ND 1 √
    offset (aclTensor*) Input Reserved quantization parameter. Currently not supported. Pass a null pointer nullptr then. - - - -
    stride (aclIntArray*) Input Convolution stride.
    • Array length = 2 in the 2D scenario, and array length = 3 in the 3D scenario.
    • strideH and strideW should be within the range [1, 63].
    • strideD should be within the range [1, 1000000] in the conv3d scenario.
    INT32 - - -
    padding (aclIntArray*) Input Padding applied to input.
    • The value should be ≥ 0.
    • paddingH and paddingW should be within the range [0, 255].
    • In the conv3d scenario, paddingD should be within the range [0, 1000000].
    INT32 - - -
    dilation (aclIntArray*) Input Spacing between elements in the convolution kernel.
    • In the 2D scenario, array length = 2; in the 3D scenario, array length = 3.
    • Value should be > 0.
    • dilationH and dilationW should be within the range [1, 255].
    • In the conv3d scenario, dilationD should be within the range [1, 1000000].
    INT32 - - -
    transposed (bool) Input Reserved parameter. Indicates whether it is a transposed quantized convolution. Currently not supported. Pass in false. BOOL - - -
    outputPadding (aclIntArray*) Input Reserved parameter. Indicates the padding for all edges of the output in the case of transposed convolution. In the case of non-transposed convolution, this attribute configuration is ignored. Currently not supported; pass a null pointer nullptr then. INT32 - - -
    groups (int64_t) Input Indicates the number of block connections from input channels to output channels. The value must be ≥ 1, and must satisfy: C dimension of groups * weight = C dimension of input. INT64 - - -
    offsetx (int32_t) Input Represents the quantization factor. [-128, 127] or 0. INT32 - - -
    roundMode (char*) Input Represents the rounding mode. rint, round, or nullptr. CHAR* - - -
    output (aclTensor*) Output output in the formula represents the convolution output.
    • Null tensor output is not supported.
    • Its shape satisfies the convolution deduction rules.
    • The number of channels equals the first dimension of weight, and other dimensions are ≥ 0.
    BFLOAT16, FLOAT16, FLOAT, FLOAT8_E4M3FN, HIFLOAT8 NCHW, NCDHW 4-5 √
    workspaceSize (uint64_t*) Output Returns the workspace size to be applied on the Device side. - - - - -
    executor (aclOpExecutor**) Output Returns the op executor, which contains the operator computation flow. - - - - -
  • Return Value

    aclnnStatus: Return status 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 A pointer-type input parameter passed in is a null pointer.
    ACLNN_ERR_PARAM_INVALID 161002 The data type and data format of input, weight, bias, scale, offset, and output are not within the supported range.
    The input shape of stride, padding, or dilation is incorrect.
    The groups input is incorrect.
    The input shape of scale or bias is incorrect.
    The shape of output does not match the infershape result.
    A tensor with any dimension being zero does not meet the requirement.
    The spatial size of input after padding is smaller than the spatial size of weight (after dilation expansion, if dilation > 1 exists).
    The number of channels of weight and input does not meet the requirement.
    stride or dilation less than 0 does not meet the requirement.
    The current processor does not support convolution.
    ACLNN_ERR_INNER_NULLPTR 561103 Internal API validation error, typically caused by input data or attribute specifications not being within the supported range.
    ACLNN_ERR_RUNTIME_ERROR 361001 An exception occurs when the API calls the npu runtime interface, such as SocVersion not supported.

aclnnQuantConvolution

  • Parameters

    Parameter Input/Output Description
    workspace Input Workspace memory address applied on the Device side.
    workspaceSize Input Workspace size applied on the Device side, obtained by the first-phase API aclnnQuantConvolutionGetWorkspaceSize.
    executor Input Operator executor that contains the operator computation process.
    stream Input Specifies the stream for task execution.
  • Return Value

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

Constraints

  • Deterministic computation

    • aclnnQuantConvolution defaults to a deterministic implementation.
Constraint Type Atlas A2 training products/Atlas A2 inference products, Atlas A3 training products/Atlas A3 inference products
input, weight The data types of input and weight do not support FLOAT8_E4M3FN or HIFLOAT8, and the data format does not support NCHW.
bias The data type of bias does not support INT32 and will be converted to FLOAT for computation.
scale The scale data type does not support INT64 or UINT64.
padding The array length of padding must be equal to 3.
groups The groups value must be 1.
offsetx offsetx is currently not supported. Pass in 0 then.
roundMode roundMode is currently not supported. Pass in null pointer nullptr.
output The output data type supports BFLOAT16 and FLOAT16, and the data format only supports NCDHW.
Other Constraints
  • The operator is only supported for calling in inference scenarios.
  • Only forward 3D convolution is supported.
  • The size of each dimension of each tensor in input, weight, bias, and scale should be less than 1000000.

Example

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

Use different main functions for different product models.

#include <iostream>
#include <memory>
#include <vector>
#include "acl/acl.h"
#include "aclnnop/aclnn_quant_convolution.h"

#define CHECK_RET(cond, return_expr) \
  do {                               \
    if (!(cond)) {                   \
      return_expr;                   \
    }                                \
  } while (0)

#define CHECK_FREE_RET(cond, return_expr) \
  do {                                    \
    if (!(cond)) {                        \
      Finalize(deviceId, stream);         \
      return_expr;                        \
    }                                     \
  } while (0)

#define LOG_PRINT(message, ...)      \
  do {                               \
    printf(message, ##__VA_ARGS__);  \
  } while (0)

int64_t GetShapeSize(const std::vector<int64_t>& shape) {
  int64_t shape_size = 1;
  for (auto i: shape) {
    shape_size *= i;
  }
  return shape_size;
}

int Init(int32_t deviceId, aclrtStream* stream) {
  // Boilerplate: initialize resources.
  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 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_NCDHW,
                            shape.data(), shape.size(), *deviceAddr);
  return 0;
}

template <typename T>
int CreateAclTensorND(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 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);

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

void Finalize(int32_t deviceId, aclrtStream& stream)
{
  aclrtDestroyStream(stream);
  aclrtResetDevice(deviceId);
  aclFinalize();
}

int aclnnQuantConvolutionTest(int32_t deviceId, aclrtStream& stream, std::vector<aclDataType> dtypesInfo)
{
  auto ret = Init(deviceId, &stream);
  // Handle the check result as needed.
  CHECK_FREE_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 interface.
  std::vector<int64_t> shapeInput = {2, 2, 32, 32, 32};
  std::vector<int64_t> shapeWeight = {2, 2, 3, 3, 3};
  std::vector<int64_t> shapeScale = {2};
  std::vector<int64_t> shapeBias = {2};
  std::vector<int64_t> shapeResult = {2, 2, 32, 32, 32};
  std::vector<int64_t> convStrides;
  std::vector<int64_t> convPads;
  std::vector<int64_t> convOutPads;
  std::vector<int64_t> convDilations;

  void* deviceDataA = nullptr;
  void* deviceDataB = nullptr;
  void* deviceDataScale = nullptr;
  void* deviceDataBias = nullptr;
  void* deviceDataResult = nullptr;

  aclTensor* input = nullptr;
  aclTensor* weight = nullptr;
  aclTensor* scale= nullptr;
  aclTensor* bias= nullptr;
  aclTensor* result = nullptr;
  std::vector<int8_t> inputData(GetShapeSize(shapeInput), 1);
  std::vector<int8_t> weightData(GetShapeSize(shapeWeight), 1);
  std::vector<float> biasData(GetShapeSize(shapeBias), 1);
  std::vector<float> scaleData(GetShapeSize(shapeScale), 1);
  std::vector<uint16_t> outputData(GetShapeSize(shapeResult), 1);
  convStrides = {1, 1, 1};
  convPads = {1, 1, 1};
  convOutPads = {1, 1, 1};
  convDilations = {1, 1, 1};
  aclDataType inputDtype = dtypesInfo[0];
  aclDataType weightDtype = dtypesInfo[1];
  aclDataType biasDtype = dtypesInfo[2];
  aclDataType scaleDtype = dtypesInfo[3];
  aclDataType outputDtype = dtypesInfo[4];
  // Create the input aclTensor.
  ret = CreateAclTensor(inputData, shapeInput, &deviceDataA, inputDtype, &input);
  std::unique_ptr<aclTensor, aclnnStatus (*)(const aclTensor *)> inputTensorPtr(input, aclDestroyTensor);
  std::unique_ptr<void, aclError (*)(void *)> deviceDataAPtr(deviceDataA, aclrtFree);
  CHECK_FREE_RET(ret == ACL_SUCCESS, return ret);

  // Create the weight aclTensor.
  ret = CreateAclTensor(weightData, shapeWeight, &deviceDataB, weightDtype, &weight);
  std::unique_ptr<aclTensor, aclnnStatus (*)(const aclTensor *)> weightTensorPtr(weight, aclDestroyTensor);
  std::unique_ptr<void, aclError (*)(void *)> deviceDataBPtr(deviceDataB, aclrtFree);
  CHECK_FREE_RET(ret == ACL_SUCCESS, return ret);

    // Create the scale.
  ret = CreateAclTensorND(scaleData, shapeScale, &deviceDataScale, scaleDtype, &scale);
  std::unique_ptr<aclTensor, aclnnStatus (*)(const aclTensor *)> scaleTensorPtr(scale, aclDestroyTensor);
  std::unique_ptr<void, aclError (*)(void *)> deviceDataScalePtr(deviceDataScale, aclrtFree);
  CHECK_FREE_RET(ret == ACL_SUCCESS, return ret);

    // Create the bias.
  ret = CreateAclTensorND(biasData, shapeBias, &deviceDataBias, biasDtype, &bias);
  std::unique_ptr<aclTensor, aclnnStatus (*)(const aclTensor *)> biasTensorPtr(bias, aclDestroyTensor);
  std::unique_ptr<void, aclError (*)(void *)> deviceDataBiasPtr(deviceDataBias, aclrtFree);
  CHECK_FREE_RET(ret == ACL_SUCCESS, return ret);

  // Create the output aclTensor.
  ret = CreateAclTensor(outputData, shapeResult, &deviceDataResult, outputDtype, &result);
  std::unique_ptr<aclTensor, aclnnStatus (*)(const aclTensor *)> outputTensorPtr(result, aclDestroyTensor);
  std::unique_ptr<void, aclError (*)(void *)> deviceDataResultPtr(deviceDataResult, aclrtFree);
  CHECK_FREE_RET(ret == ACL_SUCCESS, return ret);

  aclIntArray *strides = aclCreateIntArray(convStrides.data(), 3);
  std::unique_ptr<aclIntArray, aclnnStatus (*)(const aclIntArray *)> stridesPtr(strides, aclDestroyIntArray);
  CHECK_FREE_RET(strides != nullptr, return ACL_ERROR_INTERNAL_ERROR);
  aclIntArray *pads = aclCreateIntArray(convPads.data(), 3);
  std::unique_ptr<aclIntArray, aclnnStatus (*)(const aclIntArray *)> padsPtr(pads, aclDestroyIntArray);
  CHECK_FREE_RET(pads != nullptr, return ACL_ERROR_INTERNAL_ERROR);
  aclIntArray *outPads = aclCreateIntArray(convOutPads.data(), 3);
  std::unique_ptr<aclIntArray, aclnnStatus (*)(const aclIntArray *)> outPadsPtr(outPads, aclDestroyIntArray);
  CHECK_FREE_RET(outPads != nullptr, return ACL_ERROR_INTERNAL_ERROR);
  aclIntArray *dilations = aclCreateIntArray(convDilations.data(), 3);
  std::unique_ptr<aclIntArray, aclnnStatus (*)(const aclIntArray *)> dilationsPtr(dilations, aclDestroyIntArray);
  CHECK_FREE_RET(dilations != nullptr, return ACL_ERROR_INTERNAL_ERROR);

  // 3. Call the CANN operator library API. Replace with the specific API.
  uint64_t workspaceSize = 0;
  aclOpExecutor* executor;
  // Call the first-phase API of aclnnConvolution.
  ret = aclnnQuantConvolutionGetWorkspaceSize(input, weight, bias, scale, nullptr, strides, pads, dilations,
                                              false, outPads, 1, 0, nullptr, result, &workspaceSize, &executor);
  CHECK_FREE_RET(ret == ACL_SUCCESS, LOG_PRINT("aclnnQuantConvolutionGetWorkspaceSize 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_FREE_RET(ret == ACL_SUCCESS, LOG_PRINT("allocate workspace failed. ERROR: %d\n", ret); return ret);
    workspaceAddrPtr.reset(workspaceAddr);
  }
  // Call the second-phase API of aclnnConvolution.
  ret = aclnnQuantConvolution(workspaceAddr, workspaceSize, executor, stream);
  CHECK_FREE_RET(ret == ACL_SUCCESS, LOG_PRINT("aclnnQuantConvolution failed. ERROR: %d\n", ret); return ret);

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

  // 5. Obtain the output value, copy the result from the device memory to the host, and modify based on the specific API definition.
  auto size = GetShapeSize(shapeResult);
  std::vector<uint16_t> resultData(size, 0);
  ret = aclrtMemcpy(resultData.data(), resultData.size() * sizeof(resultData[0]), deviceDataResult,
                    size * sizeof(uint16_t), ACL_MEMCPY_DEVICE_TO_HOST);
  CHECK_FREE_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: %d\n", i, resultData[i]);
  }

  return ACL_SUCCESS;
}

int main() {
  // 1. (Boilerplate) Initialize the device/stream. Refer to the ACL API manual.
  // Fill in deviceId based on your actual device.
  int32_t deviceId = 0;
  aclrtStream stream;
  std::vector<aclDataType> dtypesInfo = {aclDataType::ACL_INT8, aclDataType::ACL_INT8, aclDataType::ACL_FLOAT,
    aclDataType::ACL_FLOAT, aclDataType::ACL_BF16}; // datatypes of input/weight/bias/scale/output respectively.
  auto ret = aclnnQuantConvolutionTest(deviceId, stream, dtypesInfo);
  CHECK_FREE_RET(ret == ACL_SUCCESS, LOG_PRINT("aclnnQuantConvolutionTest failed. ERROR: %d\n", ret); return ret);

  Finalize(deviceId, stream);
  return 0;
}