aclnnConvolutionBackward

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: Backward propagation of convolution. Computes the gradients of the input, weight, and bias based on the output mask settings. This function supports 1D, 2D, and 3D convolution.

  • Formula:

    The relationship between the input (N,Cin,Din,Hin,WinN,C_{in},D_{in},H_{in},W_{in}), output (N,Cout,Dout,Hout,WoutN,C_{out},D_{out},H_{out},W_{out}), convolution stride (stridestride), convolution kernel size (kernelSizekernelSize, kDkD, kHkH, kWkW), and dilation parameter (dilationdilation) is:

    Dout=⌊Din+2∗padding[0]−dilation[0]∗(kernelSize[0]−1)−1stride[0]+1⌋D_{out}=\lfloor \frac{D_{in}+2*padding[0]-dilation[0] * (kernelSize[0] - 1) - 1}{stride[0]}+1 \rfloor Hout=⌊Hin+2∗padding[1]−dilation[1]∗(kernelSize[1]−1)−1stride[1]+1⌋H_{out}=\lfloor \frac{H_{in}+2*padding[1]-dilation[1] * (kernelSize[1] - 1) - 1}{stride[1]}+1 \rfloor Wout=⌊Win+2∗padding[2]−dilation[2]∗(kernelSize[2]−1)−1stride[2]+1⌋W_{out}=\lfloor \frac{W_{in}+2*padding[2]-dilation[2] * (kernelSize[2] -1) -1}{stride[2]}+1 \rfloor

    Convolution backward propagation needs to compute the gradients with respect to the input tensor xx (corresponding to the input parameter in the function prototype), the convolution kernel weight tensor ww (corresponding to the weight parameter in the function prototype), and the bias bb.

    • For the gradient of xx, ∂L∂x\frac{\partial L}{\partial x} (corresponding to the gradInput parameter in the function prototype):

      ∂L∂xn,cin,i,j=∑cout=1Cout∑p=1kH∑q=1kW∂L∂yn,cout,i−p,j−q⋅wcout,cin,p,q\frac{\partial L}{\partial x_{n, c_{in}, i, j}} = \sum_{c_{out}=1}^{C_{out}} \sum_{p=1}^{k_H} \sum_{q=1}^{k_W} \frac{\partial L}{\partial y_{n, c_{out}, i-p, j-q}}\cdot w_{c_{out}, c_{in}, p, q}

      Where LL is the loss function, and ∂L∂y\frac{\partial L}{\partial y} is the gradient of the output tensor yy with respect to LL (corresponding to the gradOutput parameter in the function prototype).

    • For the gradient of ww, ∂L∂w\frac{\partial L}{\partial w} (corresponding to the gradWeight parameter in the function prototype):

      ∂L∂wcout,cin,p,q=∑n=1N∑i=1Hout∑j=1Woutxn,cin,i⋅sH+p,j⋅sW+q⋅∂L∂yn,cout,i,j\frac{\partial L}{\partial w_{c_{out}, c_{in}, p, q}} = \sum_{n=1}^{N} \sum_{i=1}^{H_{out}} \sum_{j=1}^{W_{out}} x_{n, c_{in}, i \cdot s_H + p, j \cdot s_W + q} \cdot \frac{\partial L}{\partial y_{n, c_{out}, i, j}}
    • For the gradient of bb, ∂L∂b\frac{\partial L}{\partial b} (corresponding to the gradBias parameter in the function prototype):

      ∂L∂bcout=∑n=1N∑i=1Hout∑j=1Wout∂L∂yn,cout,i,j\frac{\partial L}{\partial b_{c_{out}}} = \sum_{n=1}^{N} \sum_{i=1}^{H_{out}} \sum_{j=1}^{W_{out}} \frac{\partial L}{\partial y_{n, c_{out}, i, j}}

Function Prototype

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

aclnnStatus aclnnConvolutionBackwardGetWorkspaceSize(
    const aclTensor     *gradOutput,
    const aclTensor     *input,
    const aclTensor     *weight,
    const aclIntArray   *biasSizes,
    const aclIntArray   *stride,
    const aclIntArray   *padding,
    const aclIntArray   *dilation,
    bool                 transposed,
    const aclIntArray   *outputPadding,
    int                  groups,
    const aclBoolArray  *outputMask,
    int8_t               cubeMathType,
    aclTensor           *gradInput,
    aclTensor           *gradWeight,
    aclTensor           *gradBias,
    uint64_t            *workspaceSize,
    aclOpExecutor      **executor)
aclnnStatus aclnnConvolutionBackward(
    void                *workspace,
    uint64_t             workspaceSize,
    aclOpExecutor       *executor,
    const aclrtStream    stream)

aclnnConvolutionBackwardGetWorkspaceSize

  • Parameters:

    Parameter Input/Output Description Instruction Data Type Data Format Dimension (Shape) Non-Contiguous Tensor
    gradOutput Input Gradient of the output tensor y with respect to L.
    • Supports empty tensors.
    • The data type must satisfy the data type deduction rules with input and weight (see Deduction Relationship and Constraints).
    • Broadcast is not supported for the shape. The shape must satisfy the convolution input-output shape deduction relationship with input and weight.
    • The data format must be consistent with input and gradInput.
    FLOAT, FLOAT16, BFLOAT16 NCL, NCHW, NCDHW See Constraints. √
    input Input x in the formula.
    • Supports empty tensors.
    • The data type must satisfy the data type deduction rules with gradOutput and weight (see Deduction Relationship and Constraints).
    • The shape does not support broadcast and must satisfy the convolution input/output shape deduction relationship with gradOutput and weight.
    • The data format must be consistent with gradOutput and gradInput.
    FLOAT, FLOAT16, BFLOAT16 NCL, NCHW, NCDHW See Constraints. √
    weight Input w in the formula.
    • Supports empty tensors.
    • The data type, along with gradOutput and input, must satisfy the data type deduction rules (see Deduction Relationship and Constraints).
    • The shape does not support broadcast and must satisfy the convolution input-output shape deduction relationship with gradOutput and input.
    • The data format must be consistent with gradWeight.
    FLOAT, FLOAT16, BFLOAT16 NCL, NCHW, NCDHW See Constraints. √
    biasSizes Input Shape of the bias in the forward convolution process.
    • The array length is 1.
    • In standard convolution, it equals [weight.shape[0]]; in transposed convolution, it equals [weight.shape[1] * groups].
    • In the empty tensor scenario, when outputMask specifies that the bias gradient needs to be computed, biasSizes cannot be nullptr.
    INT64 - - ×
    stride Input Specifies the step size at which the convolution kernel moves over the input during backward propagation.
    • For 1D convolution backward, the array length must be 1.
    • The array length equals the weight dimension minus 2, and the value must be greater than 0.
    INT64 - See Constraints. ×
    padding Input Padding for the input during backward propagation.
    • For 1D convolution backward, the array length must be 1.
    • The array length can be the weight dimension minus 2. In 2D scenarios, the array length can be 4.
    • The value must be greater than or equal to 0.
    INT64 - See Constraints. ×
    dilation Input Dilation parameter in the backward propagation process.
    • For 1D convolution backward, the array length must be 1.
    • The array length can be the weight dimension minus 2.
    • The value must be greater than 0.
    INT64 - See Constraints. ×
    transposed Input Transposed convolution enable flag. When set to True, enables transposed convolution. - - - - ×
    outputPadding Input Output padding during backward propagation.
    • The array length can be the weight dimension minus 2, and the value range of each dimension satisfies [0, stride value of the corresponding dimension).
    • When transposed is False, each element value must be 0.
    INT64 - - ×
    groups Input Number of groups for input channels during backward propagation. The condition groups * C dimension of weight = C dimension of input must be met. The value range of groups is [1, 65535]. INT32 - - ×
    outputMask Input
    • Output mask parameter, which specifies whether the output contains the gradients of input, weight, and bias.
    • During backward propagation, outputs the gradient at the position where the mask parameter is True.
    - - - - ×
    cubeMathType Input Determines which computation logic the Cube unit should use for the operation.
    • If the input data type has a deduction relationship, this parameter processes the data type after deduction by default.
    • The supported enumeration values are as follows:
      • 0: KEEP_DTYPE, keeps the input data type for computation.
      • 1: ALLOW_FP32_DOWN_PRECISION, allows reducing the precision of the input data for computation.
      • 2: USE_FP16, allows conversion to the FLOAT16 data type for computation. When the input data type is FLOAT, it is converted to FLOAT16 for computation.
      • 3: USE_HF32, allows conversion to the HFLOAT32 data type for computation. When the input is FLOAT16, FLOAT16 is still used for computation.
    - - - ×
    gradInput Output Gradient of the input tensor x with respect to L.
    • Supports empty tensors.
    • The data type must be consistent with input.
    • The data format must be consistent with input and gradOutput
    FLOAT, FLOAT16, BFLOAT16 NCL, NCHW, NCDHW - ×
    gradWeight Output Gradient of the convolution kernel weight tensor w with respect to L.
    • Supports empty tensors.
    • The data format must be consistent with weight.
    FLOAT, FLOAT16, BFLOAT16 NCL, NCHW, NCDHW - ×
    gradBias Output Gradient of bias b with respect to L.
    • Supports empty tensor.
    • The data type is the same as that of gradOutput
    FLOAT, FLOAT16, BFLOAT16 ND - ×
    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. - - - - ×
  • 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 The passed gradOutput, input, weight, biasSizes, stride, padding, dilation, outputPadding, outputMask, gradInput, and gradWeight are null pointers.
    When the output includes the gradient of bias, the passed gradBias is a null pointer.
    ACLNN_ERR_PARAM_INVALID 161002
    The data type of gradOutput, input, or weight is not within the supported range.
    The data formats of gradOutput, input, and weight are not within the supported range.
    The shapes of gradOutput, input, and weight do not meet the constraints.
    The shapes of biasSizes, stride, padding, dilation, and outputPadding do not meet the constraints.
    The condition groups * C dimension of weight = C dimension of input is not satisfied.
    The current processor does not support Convolution Backward.
    ACLNN_ERR_INNER_NULLPTR 561103 API internal verification error, typically caused by input data or attributes falling outside the supported specifications.

aclnnConvolutionBackward

  • Parameters:

    ParameterInput/OutputDescription
    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 aclnnConvolutionBackwardGetWorkspaceSize.
    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

    • aclnnConvolutionBackward defaults to a non-deterministic implementation. You can enable deterministic computation by calling aclrtCtxSetSysParamOpt.
Constraint Type Ascend 950PR/Ascend 950DT Atlas A2 training products/Atlas A2 inference products, Atlas A3 training products/Atlas A3 inference products Atlas inference products, Atlas training products
Empty Tensor Constraints
  • The dimension of input must be at least 2. The dimensions of stride, padding, dilation, and outputPadding must equal the input dimension minus 2. For 2D convolution, padding can be 4-dimensional.
  • When transposed is false, the D, H, and W dimension constraints satisfy Formula 1 (see the description below the table).
  • When transposed is true:
    • The N axis of weight must be greater than 0.
    • The N axis of weight must equal the C axis of input.
-
gradOutput Constraints - In 1d, 2d, and 3d transposed=false scenarios, the size of each dimension must be greater than or equal to 1. When input is an empty tensor, the N, C, D, H, and W dimensions can be 0. Empty tensors are not supported.
input Constraints
  • transposed=true: Supports empty tensors with N dimension equal to 0. When the N dimension is 0 and the empty tensor constraints are met, DHW dimensions equal to 0 are also supported.
  • transposed=false: Supports empty tensors with N and C dimensions equal to 0 (when the C dimension is 0, the C dimension of weight must also be 0). When the N or C dimension is 0 and the empty tensor constraints are met, DHW dimensions equal to 0 are also supported.
In 1D, 2D, and 3D transposed=false scenarios, the size of each dimension must be greater than or equal to 1.
  • transposed=true: Supports empty tensors with N dimension equal to 0. When the N dimension is 0 and the empty tensor constraints are met, DHW dimensions equal to 0 are also supported.
  • transposed=false: Supports empty tensors with N and C dimensions equal to 0 (when the C dimension is 0, the C dimension of weight must also be 0). When the N or C dimension is 0 and the empty tensor constraints are met, DHW dimensions equal to 0 are also supported.
-
weight Constraints
  • transposed=true: The sizes of H and W must be in the range [1, 255], and the sizes of other dimensions must be greater than or equal to 1. When input is an empty tensor and the empty tensor constraints are met, the C, D, H, and W axes equal to 0 are supported.
  • transposed=false: Supports the C dimension equal to 0 (when C is 0, the C dimension of input must also be 0), and the sizes of all dimensions must be greater than or equal to 1. When input is an empty tensor and the empty tensor constraints are met, the D, H, and W axes equal to 0 are supported.
For 2D and 3D transposed=false scenarios, the sizes of H and W must be in the range [1, 255], and the sizes of other dimensions must be greater than or equal to 1. For 1D transposed=false scenarios, the size of L must be in the range [1, 255], and the sizes of other dimensions must be greater than or equal to 1.
  • transposed=true: When input is an empty tensor and satisfies the empty tensor constraints, the C, D, H, and W axes can be 0.
  • transposed=false: The C dimension can be 0 (when C is 0, the input C dimension must also be 0). When input is an empty tensor and satisfies the empty tensor constraints, the D, H, and W axes can be 0.
Empty tensors are not supported.
Stride Constraints For 1D, 2D, and 3D transposed=false scenarios, each value must be greater than or equal to 1. For 1D, 2D, and 3D transposed=true scenarios, strideD must be in the range [1, 1000000], and strideH and strideW must be in the range [1, 2147483646]. For 3D transposed=false scenarios, strideD must be greater than or equal to 1, and strideH and strideW must be in the range [1, 63]. For 1D and 2D transposed=false scenarios, each value must be greater than or equal to 1. -
Padding Constraints In 1D, 2D, and 3D transposed=false scenarios, each value must be greater than or equal to 0. In 1D, 2D, and 3D transposed=true scenarios, paddingD must be in the range [0, 1000000], and paddingH and paddingW must be in the range [0, 2147483646]. In 3D transposed=false scenarios, paddingD must be greater than or equal to 0, and paddingH and paddingW must be in the range [0, 255]. In 1D and 2D transposed=false scenarios, each value must be in the range [0, 255]. -
dilation Constraints In 1D, 2D, and 3D transposed=false scenarios, each value must be greater than or equal to 1. In 1D, 2D, and 3D transposed=true scenarios, dilationD must be in the range [1, 1000000], and dilationH and dilationW must be in the range [1, 2147483646]. In 1D, 2D, and 3D transposed=false scenarios, each value must be in the range [1, 255]. -
dtype Constraints The data type supports HIFLOAT8 and FLOAT8_E4M3FN only when transposed=true and output_mask[0]=true. HIFLOAT8 and FLOAT8_E4M3FN are not supported. BFLOAT16, HIFLOAT8, and FLOAT8_E4M3FN are not supported.
cubeMathType Description
  • Enumeration value 0: No constraints.
  • Enumeration value 1: When the input is FLOAT, it is converted to HFLOAT32 for computation. When the input is of other data types, no processing is performed.
  • Enumeration value 2: This option is not supported when the input is BFLOAT16. When the input is of other data types, no processing is performed.
  • Enumeration value 3: When the input is FLOAT, it is converted to HFLOAT32 for computation. When the input is of other data types, no processing is performed.
  • Enumeration Value 0: When the input is FLOAT, the Cube compute unit does not support it, and an error is reported when 0 is selected.
  • Enumeration Value 1: When the input is FLOAT, it is converted to FLOAT16 for computation. No processing is performed for other data types.
  • Enumeration Value 2: No constraints currently.
  • Enumeration Value 3: When the input is FLOAT, the Cube compute unit does not support it.
Other Constraints The computation behavior of gradients in the padding region depends on the input shape. Depending on the operator optimization strategy, gradients in the padding region may be directly set to 0. - Currently, only backward propagation for 1D and 2D convolution is supported. Backward propagation for 3D convolution is not supported yet.
  • Formula 1:(inputdim+paddim×2)≥((weightdim−1)×dilationdim+1)(input_{dim} + pad_{dim} \times 2) \ge ((weight_{dim} - 1) \times dilation_{dim} + 1)

Due to hardware resource limitations, the operator may fail to execute under certain combinations of parameter values. Analyze and troubleshoot the issue based on the log information. If the issue persists, click Link to obtain technical support.

Example

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

#include <iostream>
#include <memory>
#include <vector>

#include "acl/acl.h"
#include "aclnnop/aclnn_convolution_backward.h"

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

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

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

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

int Init(int32_t deviceId, aclrtStream *stream)
{
    // 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 allocate 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);
    // Copy data from the host side to the device memory using aclrtMemcpy.
    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 (shape.size() == 4) {
        *tensor = aclCreateTensor(shape.data(), shape.size(), dataType, strides.data(), 0, aclFormat::ACL_FORMAT_NCHW,
                                  shape.data(), shape.size(), *DeviceAddr);
    } else {
        *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 aclnnConvolutionBackwardTest(int32_t deviceId, aclrtStream &stream)
{
    // 1. Initialization.
    auto ret = Init(deviceId, &stream);
    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.
    std::vector<int64_t> gradOutputShape = {2, 2, 7, 7};
    std::vector<int64_t> inputShape = {2, 2, 7, 7};
    std::vector<int64_t> weightShape = {2, 2, 1, 1};
    std::vector<int64_t> biasSize = {2};
    std::vector<int64_t> stride = {1, 1};
    std::vector<int64_t> padding = {0, 0};
    std::vector<int64_t> dilation = {1, 1};
    bool transposed = false;
    std::vector<int64_t> outputPadding = {0, 0};
    int groups = 1;
    bool outputMask[3] = {true, true, true};
    int8_t cubeMathType = 1;

    std::vector<int64_t> gradInputShape = {2, 2, 7, 7};
    std::vector<int64_t> gradWeightShape = {2, 2, 1, 1};
    std::vector<int64_t> gradBiasShape = {2};

    // Create gradOutput aclTensor.
    std::vector<float> gradOutputData(GetShapeSize(gradOutputShape), 1);
    aclTensor *gradOutput = nullptr;
    void *gradOutputDeviceAddr = nullptr;
    ret = CreateAclTensor(gradOutputData, gradOutputShape, &gradOutputDeviceAddr, aclDataType::ACL_FLOAT, &gradOutput);
    std::unique_ptr<aclTensor, aclnnStatus (*)(const aclTensor *)> gradOutputTensorPtr(gradOutput, aclDestroyTensor);
    std::unique_ptr<void, aclError (*)(void *)> gradOutputDeviceAddrPtr(gradOutputDeviceAddr, aclrtFree);
    CHECK_FREE_RET(ret == ACL_SUCCESS, return ret);

    // Create input aclTensor.
    std::vector<float> inputData(GetShapeSize(inputShape), 1);
    aclTensor *input = nullptr;
    void *inputDeviceAddr = nullptr;
    ret = CreateAclTensor(inputData, inputShape, &inputDeviceAddr, aclDataType::ACL_FLOAT, &input);
    std::unique_ptr<aclTensor, aclnnStatus (*)(const aclTensor *)> inputTensorPtr(input, aclDestroyTensor);
    std::unique_ptr<void, aclError (*)(void *)> inputDeviceAddrPtr(inputDeviceAddr, aclrtFree);
    CHECK_FREE_RET(ret == ACL_SUCCESS, return ret);

    // Create weight aclTensor.
    std::vector<float> weightData(GetShapeSize(weightShape), 1);
    aclTensor *weight = nullptr;
    void *weightDeviceAddr = nullptr;
    ret = CreateAclTensor(weightData, 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_FREE_RET(ret == ACL_SUCCESS, return ret);

    // Create gradInput aclTensor.
    std::vector<float> gradInputData(GetShapeSize(inputShape), 1);
    aclTensor *gradInput = nullptr;
    void *gradInputDeviceAddr = nullptr;
    ret = CreateAclTensor(gradInputData, inputShape, &gradInputDeviceAddr, aclDataType::ACL_FLOAT, &gradInput);
    std::unique_ptr<aclTensor, aclnnStatus (*)(const aclTensor *)> gradInputTensorPtr(gradInput, aclDestroyTensor);
    std::unique_ptr<void, aclError (*)(void *)> gradInputDeviceAddrPtr(gradInputDeviceAddr, aclrtFree);
    CHECK_FREE_RET(ret == ACL_SUCCESS, return ret);

    // Create gradWeight aclTensor.
    std::vector<float> gradWeightData(GetShapeSize(weightShape), 1);
    aclTensor *gradWeight = nullptr;
    void *gradWeightDeviceAddr = nullptr;
    ret = CreateAclTensor(gradWeightData, weightShape, &gradWeightDeviceAddr, aclDataType::ACL_FLOAT, &gradWeight);
    std::unique_ptr<aclTensor, aclnnStatus (*)(const aclTensor *)> gradWeightTensorPtr(gradWeight, aclDestroyTensor);
    std::unique_ptr<void, aclError (*)(void *)> gradWeightDeviceAddrPtr(gradWeightDeviceAddr, aclrtFree);
    CHECK_FREE_RET(ret == ACL_SUCCESS, return ret);

    // Create the gradBias aclTensor.
    std::vector<float> gradBiasData(GetShapeSize(biasSize), 1);
    aclTensor *gradBias = nullptr;
    void *gradBiasDeviceAddr = nullptr;
    ret = CreateAclTensor(gradBiasData, biasSize, &gradBiasDeviceAddr, aclDataType::ACL_FLOAT, &gradBias);
    std::unique_ptr<aclTensor, aclnnStatus (*)(const aclTensor *)> gradBiasTensorPtr(gradBias, aclDestroyTensor);
    std::unique_ptr<void, aclError (*)(void *)> gradBiasDeviceAddrPtr(gradBiasDeviceAddr, aclrtFree);
    CHECK_FREE_RET(ret == ACL_SUCCESS, return ret);

    // Create the biasSizes aclIntArray.
    aclIntArray *biasSizes = aclCreateIntArray(biasSize.data(), 1);
    std::unique_ptr<aclIntArray, aclnnStatus (*)(const aclIntArray *)> biasSizesPtr(biasSizes, aclDestroyIntArray);
    CHECK_FREE_RET(biasSizes != nullptr, return ACL_ERROR_INTERNAL_ERROR);

    // Create the strides aclIntArray.
    aclIntArray *strides = aclCreateIntArray(stride.data(), 2);
    std::unique_ptr<aclIntArray, aclnnStatus (*)(const aclIntArray *)> stridesPtr(strides, aclDestroyIntArray);
    CHECK_FREE_RET(strides != nullptr, return ACL_ERROR_INTERNAL_ERROR);

    // Create the pads aclIntArray.
    aclIntArray *pads = aclCreateIntArray(padding.data(), 2);
    std::unique_ptr<aclIntArray, aclnnStatus (*)(const aclIntArray *)> padsPtr(pads, aclDestroyIntArray);
    CHECK_FREE_RET(pads != nullptr, return ACL_ERROR_INTERNAL_ERROR);

    // Create the dilations aclIntArray.
    aclIntArray *dilations = aclCreateIntArray(dilation.data(), 2);
    std::unique_ptr<aclIntArray, aclnnStatus (*)(const aclIntArray *)> dilationsPtr(dilations, aclDestroyIntArray);
    CHECK_FREE_RET(dilations != nullptr, return ACL_ERROR_INTERNAL_ERROR);

    // Create outputPads aclIntArray.
    aclIntArray *outputPads = aclCreateIntArray(outputPadding.data(), 2);
    std::unique_ptr<aclIntArray, aclnnStatus (*)(const aclIntArray *)> outputPadsPtr(outputPads, aclDestroyIntArray);
    CHECK_FREE_RET(outputPads != nullptr, return ACL_ERROR_INTERNAL_ERROR);

    // Create outMask aclBoolArray.
    aclBoolArray *outMask = aclCreateBoolArray(outputMask, 3);
    std::unique_ptr<aclBoolArray, aclnnStatus (*)(const aclBoolArray *)> outMaskPtr(outMask, aclDestroyBoolArray);
    CHECK_FREE_RET(outMask != nullptr, return ACL_ERROR_INTERNAL_ERROR);

    // 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 aclnnConvolutionBackwardGetWorkspaceSize.
    ret = aclnnConvolutionBackwardGetWorkspaceSize(gradOutput, input, weight, biasSizes, strides, pads, dilations,
                                                   transposed, outputPads, groups, outMask, cubeMathType, gradInput,
                                                   gradWeight, gradBias, &workspaceSize, &executor);
    CHECK_FREE_RET(ret == ACL_SUCCESS, LOG_PRINT("aclnnConvolutionBackwardGetWorkspaceSize 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 aclnnConvolutionBackward.
    ret = aclnnConvolutionBackward(workspaceAddr, workspaceSize, executor, stream);
    CHECK_FREE_RET(ret == ACL_SUCCESS, LOG_PRINT("aclnnConvolutionBackward 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 and copy the result from the device memory to the host memory. Modify this step based on the specific API definition.
    auto size = GetShapeSize(gradInputShape);
    std::vector<float> gradInputResult(size, 0);
    ret = aclrtMemcpy(gradInputResult.data(), gradInputResult.size() * sizeof(gradInputResult[0]), gradInputDeviceAddr,
                      size * sizeof(gradInputResult[0]), 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("gradInputResult[%ld] is: %f\n", i, gradInputResult[i]);
    }

    size = GetShapeSize(gradWeightShape);
    std::vector<float> gradWeightResult(size, 0);
    ret = aclrtMemcpy(gradWeightResult.data(), gradWeightResult.size() * sizeof(gradWeightResult[0]), gradWeightDeviceAddr,
                      size * sizeof(gradWeightResult[0]), 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("gradWeightResult[%ld] is: %f\n", i, gradWeightResult[i]);
    }

    size = GetShapeSize(gradBiasShape);
    std::vector<float> gradBiasResult(size, 0);
    ret = aclrtMemcpy(gradBiasResult.data(), gradBiasResult.size() * sizeof(gradBiasResult[0]), gradBiasDeviceAddr,
                      size * sizeof(gradBiasResult[0]), 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("gradBiasResult[%ld] is: %f\n", i, gradBiasResult[i]);
    }
    return ACL_SUCCESS;
}

int main()
{
    // 1. (Boilerplate) Initialize the device/stream.
    // Set deviceId based on the actual device.
    int32_t deviceId = 0;
    aclrtStream stream;
    auto ret = aclnnConvolutionBackwardTest(deviceId, stream);
    CHECK_FREE_RET(ret == ACL_SUCCESS, LOG_PRINT("aclnnConvolutionBackwardTest failed. ERROR: %d\n", ret); return ret);

    Finalize(deviceId, stream);
    return 0;
}