aclnnAddRmsNormDynamicQuantV2

📄 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: The RmsNorm operator is a commonly used normalization operation in large models. Compared with the LayerNorm operator, it removes the part of subtracting the mean. The DynamicQuant operator performs symmetric dynamic quantization on input tensors. The AddRmsNormDynamicQuant operator fuses the Add operator before RmsNorm and one or two DynamicQuant operators that receive the RmsNorm normalized output, reducing data movement operations. Compared with aclnnAddRmsNormDynamicQuant, aclnnAddRmsNormDynamicQuantV2 adds the bias term betaOptional parameter during the RmsNorm computation, i.e., the beta in the formula, and adds the output configuration item outputMaskOptional parameter, which configures whether to output the quantization result at the corresponding position.

  • Formula:

    x=x1+x2x=x_{1}+x_{2} y=RmsNorm⁡(x)=xRms⁡(x)⋅gamma+beta, where Rms⁡(x)=1n∑i=1nxi2+epsilony = \operatorname{RmsNorm}(x)=\frac{x}{\operatorname{Rms}(\mathbf{x})}\cdot gamma+beta, \quad \text { where } \operatorname{Rms}(\mathbf{x})=\sqrt{\frac{1}{n} \sum_{i=1}^n x_i^2+epsilon} input1={y⋅smoothScale1Optional  smoothScale1Optionaly! smoothScale1Optionalinput1 =\begin{cases} y\cdot smoothScale1Optional & \ \ smoothScale1Optional \\ y & !\ smoothScale1Optional \end{cases} input2={y⋅smoothScale2Optional  smoothScale2Optionaly! smoothScale2Optionalinput2 =\begin{cases} y\cdot smoothScale2Optional & \ \ smoothScale2Optional \\ y & !\ smoothScale2Optional \end{cases} scale1Out={row_max(abs(input1))/127(outputMask[0]=True ∣∣ !outputMask)&y1Out is INT8row_max(abs(input1))/7(outputMask[0]=True ∣∣ !outputMask)&y1Out is INT4invalid outputoutputMask[0]=Falsescale1Out=\begin{cases} row\_max(abs(input1))/127 & (outputMask[0]=True\ ||\ !outputMask) \& y1Out\ \text{is INT8} \\ row\_max(abs(input1))/7 & (outputMask[0]=True\ ||\ !outputMask) \& y1Out\ \text{is INT4} \\ \text{invalid output} & outputMask[0]=False \end{cases} y1Out={round(input1/scale1Out)outputMask[0]=True ∣∣ !outputMaskinvalid outputoutputMask[0]=Falsey1Out=\begin{cases} round(input1/scale1Out) & outputMask[0]=True\ ||\ !outputMask \\ \text{invalid output} & outputMask[0]=False \end{cases} scale2Out={row_max(abs(input2))/127(outputMask[1]=True ∣∣ (!outputMask & smoothScale1Optional & smoothScale2Optional))&y2Out is INT8row_max(abs(input2))/7(outputMask[1]=True ∣∣ (!outputMask & smoothScale1Optional & smoothScale2Optional))&y2Out is INT4invalidoutputoutputMask[1]=False ∣∣ (!outputMask & smoothScale1Optional & !smoothScale2Optional)scale2Out=\begin{cases} row\_max(abs(input2))/127 & (outputMask[1]=True\ ||\ (!outputMask\ \&\ smoothScale1Optional\ \&\ smoothScale2Optional)) \& y2Out\ is\ INT8 \\ row\_max(abs(input2))/7 & (outputMask[1]=True\ ||\ (!outputMask\ \&\ smoothScale1Optional\ \&\ smoothScale2Optional)) \& y2Out\ is\ INT4 \\ invalid output & outputMask[1]=False\ ||\ (!outputMask\ \&\ smoothScale1Optional\ \&\ !smoothScale2Optional) \end{cases} y2Out={round(input2/scale2Out)outputMask[1]=True ∣∣ (!outputMask & smoothScale1Optional & smoothScale2Optional)invalidoutputoutputMask[1]=False ∣∣ (!outputMask & smoothScale1Optional & !smoothScale2Optional)y2Out=\begin{cases} round(input2/scale2Out) & outputMask[1]=True\ ||\ (!outputMask\ \&\ smoothScale1Optional\ \&\ smoothScale2Optional)\\ invalid output & outputMask[1]=False\ ||\ (!outputMask\ \&\ smoothScale1Optional\ \&\ !smoothScale2Optional) \end{cases}

    In the formula, row_max represents the maximum value of each row.

Function Prototype

Each operator consists of a two-phase API. You must first call the aclnnAddRmsNormDynamicQuantV2GetWorkspaceSize API to obtain the input parameters and the workspace size required for the computation process, and then call the aclnnAddRmsNormDynamicQuantV2 API to perform the computation.

aclnnStatus aclnnAddRmsNormDynamicQuantV2GetWorkspaceSize(
  const aclTensor    *x1,
  const aclTensor    *x2,
  const aclTensor    *gamma,
  const aclTensor    *smoothScale1Optional,
  const aclTensor    *smoothScale2Optional,
  const aclTensor    *betaOptional,
  double              epsilon,
  const aclBoolArray *outputMaskOptional,
  aclTensor          *y1Out,
  aclTensor          *y2Out,
  aclTensor          *xOut,
  aclTensor          *scale1Out,
  aclTensor          *scale2Out,
  uint64_t           *workspaceSize,
  aclOpExecutor     **executor)
aclnnStatus aclnnAddRmsNormDynamicQuantV2(
  void          *workspace,
  uint64_t       workspaceSize,
  aclOpExecutor *executor,
  aclrtStream    stream)

aclnnAddRmsNormDynamicQuantV2GetWorkspaceSize

  • Parameters

    Parameter Input/Output Description Instruction Data Type Data Format Dimension (Shape) Non-contiguous Tensor
    x1 (aclTensor*) Input Source data tensor in the normalization process, corresponding to `x1` in the formula. Supports empty tensor. FLOAT16, BFLOAT16 ND 2-8 √
    x2 (aclTensor*) Input Represents the source data tensor in the normalization process. Corresponds to `x2` in the formula.
    • Supports empty tensors.
    • The shape and data type must be consistent with `x1`.
    FLOAT16, BFLOAT16 ND 2-8 √
    gamma (aclTensor*) Input Represents the weight tensor in the normalization process. Corresponds to `gamma` in the formula.
    • Supports empty tensor.
    • The data type must be consistent with `x1`.
    • The shape must be consistent with the last dimension of `x1`.
    FLOAT16, BFLOAT16 ND 1 √
    smoothScale1Optional (aclTensor*) Input SmoothScale tensor used to obtain `y1Out` during quantization. Corresponds to `smoothScale1Optional` in the formula.
    • Supports empty tensor.
    • Optional parameter. Supports passing a null pointer.
    • The shape and data type must be consistent with `gamma`.
    FLOAT16, BFLOAT16 ND 1 √
    smoothScale2Optional (aclTensor*) Input Indicates the smoothScale tensor used to obtain `y2Out` during quantization. Corresponds to `smoothScale2Optional` in the formula.
    • Supports empty tensor.
    • Optional parameter; supports passing a null pointer.
    • The shape and data type must be consistent with `gamma`.
    FLOAT16, BFLOAT16 ND 1 √
    betaOptional (aclTensor*) Input Represents the bias term in the normalization process. Corresponds to `beta` in the formula.
    • Supports empty tensors.
    • Optional parameter, supports passing a null pointer.
    • The shape and data type must be consistent with `gamma`.
    FLOAT16, BFLOAT16 ND 1 √
    epsilon (double) Input Represents the value used to prevent division-by-zero errors, corresponding to `epsilon` in the formula. A small positive number, such as 1e-6, is recommended. - - - -
    outputMaskOptional (aclBoolArray*) Input Output mask, corresponding to `outputMask` in the formula. Supports passing a null pointer or an array of length 2. - - - -
    y1Out (aclTensor*) Output Quantized output tensor, corresponding to `y1Out` in the formula.
    • Supports empty tensors.
    • The shape must be consistent with the input `x1`.
    INT8, INT4 ND 2-8 √
    y2Out (aclTensor*) Output Represents the quantized output tensor, corresponding to `y2Out` in the formula.
    • Supports empty tensors.
    • If `y2Out` is a valid output, its shape must be consistent with `y1Out`; if `y2Out` is an invalid output, its shape is [1].
    INT8, INT4 ND 2-8 √
    xOut (aclTensor*) Output Represents the sum of x1 and x2, corresponding to `x` in the formula.
    • Supports empty tensors.
    • The shape and data type must be consistent with the input `x1`/`x2`.
    FLOAT16, BFLOAT16 ND 2-8 √
    scale1Out (aclTensor*) Output Represents the output of the first quantization path, corresponding to `scale1Out` in the formula.
    • Supports empty tensors.
    • The shape must be consistent with the shape of input x1 excluding the last dimension, or consistent with the product of x1 excluding the last dimension.
    FLOAT32 ND 1-8 √
    scale2Out (aclTensor*) Output Represents the output of the second quantization, corresponding to `scale2Out` in the formula.
    • Supports empty tensors.
    • When smoothScale2Optional does not exist, this output is meaningless.
    • The shape must be consistent with `scale1Out`.
    FLOAT32 ND 1-8 √
    workspaceSize (uint64_t*) Output Size of the workspace to be applied for on the Device. - - - - -
    executor (aclOpExecutor**) Output Returns the op 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 Code Error Code Description
    ACLNN_ERR_PARAM_NULLPTR 161001 Returns 161001 if the input parameter is a required input, output, or required attribute and is a null pointer.
    ACLNN_ERR_PARAM_INVALID 161002 The data type of the input or output is not within the supported range.
    When outputMaskOptional is a null pointer, smoothScale2Optional is input but smoothScale1Optional is not.

aclnnAddRmsNormDynamicQuantV2

  • 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 aclnnAddRmsNormDynamicQuantV2GetWorkspaceSize.
    executor Input Operator executor that 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

  • Data format description: It is recommended that all input and output tensors use the ND format. Other data formats are converted to the ND format by the framework by default for processing.

  • When outputMaskOptional is not empty and smoothScale1Optional has a value, outputMaskOptional[0] must be True. When smoothScale2Optional has a value, outputMaskOptional[1] must be True.

  • When outputMaskOptional is not empty, outputMaskOptional[0] and outputMaskOptional[1] cannot both be False.

  • When outputMaskOptional is empty and smoothScale2Optional has a value, smoothScale1Optional must also have a value.

  • Supported data types for each product model:

    x1 Data Typex2 Data Typegamma Data TypesmoothScale1Optional Data TypesmoothScale2Optional Data TypebetaOptional Data Typey1Out Data Typey2Out Data Typescale1Out Data Typescale2Out Data Type
    FLOAT16FLOAT16FLOAT16FLOAT16FLOAT16FLOAT16INT8INT8FLOAT32FLOAT32
    BFLOAT16BFLOAT16BFLOAT16BFLOAT16BFLOAT16BFLOAT16INT8INT8FLOAT32FLOAT32
    FLOAT16FLOAT16FLOAT16FLOAT16FLOAT16FLOAT16INT4INT4FLOAT32FLOAT32
    BFLOAT16BFLOAT16BFLOAT16BFLOAT16BFLOAT16BFLOAT16INT4INT4FLOAT32FLOAT32
  • Deterministic computation:

    • aclnnAddRmsNormDynamicQuantV2 defaults to a deterministic implementation.

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_add_rms_norm_dynamic_quant_v2.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 shape_size = 1;
    for (auto i : shape) {
        shape_size *= i;
    }
    return shape_size;
}

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. Initialize device/stream (boilerplate). See the acl API manual.
    // Fill in deviceId based on the actual device.
    int32_t deviceId = 0;
    aclrtStream stream;
    auto ret = Init(deviceId, &stream);
    // Handle the check as needed.
    CHECK_RET(ret == 0, LOG_PRINT("Init acl failed. ERROR: %d\n", ret); return ret);
    // 2. Construct inputs and outputs. Custom construction is required based on the API.
    std::vector<int64_t> xShape = {2, 8};
    std::vector<int64_t> gammaShape = {8};
    std::vector<int64_t> betaShape = {8};
    std::vector<int64_t> reduceShape = {2, 1};

    void* x1DeviceAddr = nullptr;
    void* x2DeviceAddr = nullptr;
    void* gammaDeviceAddr = nullptr;
    void* betaDeviceAddr = nullptr;
    void* smooth1DeviceAddr = nullptr;
    void* smooth2DeviceAddr = nullptr;

    void* y1DeviceAddr = nullptr;
    void* y2DeviceAddr = nullptr;
    void* xDeviceAddr = nullptr;
    void* scale1DeviceAddr = nullptr;
    void* scale2DeviceAddr = nullptr;

    aclTensor* x1 = nullptr;
    aclTensor* x2 = nullptr;
    aclTensor* gamma = nullptr;
    aclTensor* beta = nullptr;
    aclTensor* smooth1 = nullptr;
    aclTensor* smooth2 = nullptr;
    aclTensor* y1 = nullptr;
    aclTensor* y2 = nullptr;
    aclTensor* x = nullptr;
    aclTensor* scale1 = nullptr;
    aclTensor* scale2 = nullptr;

    int64_t xShapeSize = GetShapeSize(xShape);
    int64_t gammaShapeSize = GetShapeSize(gammaShape);
    int64_t betaShapeSize = GetShapeSize(betaShape);
    int64_t reduceShapeSize = GetShapeSize(reduceShape);

    std::vector<short> x1HostData(xShapeSize, 0x3800);
    std::vector<short> x2HostData(xShapeSize, 0x3800);
    std::vector<short> gammaHostData(gammaShapeSize, 0x3e00);
    std::vector<short> betaHostData(betaShapeSize, 0x3e00);
    std::vector<short> smooth1HostData(gammaShapeSize, 0x3e00);
    std::vector<short> smooth2HostData(gammaShapeSize, 0x3e00);

    std::vector<short> y1HostData(xShapeSize, 0);
    std::vector<short> y2HostData(xShapeSize, 0);
    std::vector<short> xHostData(xShapeSize, 0);
    std::vector<short> scale1HostData(reduceShapeSize, 0);
    std::vector<short> scale2HostData(reduceShapeSize, 0);

    float epsilon = 1e-6;

    // Create x1 aclTensor.
    ret = CreateAclTensor(x1HostData, xShape, &x1DeviceAddr, aclDataType::ACL_FLOAT16, &x1);
    CHECK_RET(ret == ACL_SUCCESS, return ret);
    // Create the x2 aclTensor.
    ret = CreateAclTensor(x2HostData, xShape, &x2DeviceAddr, aclDataType::ACL_FLOAT16, &x2);
    CHECK_RET(ret == ACL_SUCCESS, return ret);
    // Create the gamma aclTensor.
    ret = CreateAclTensor(gammaHostData, gammaShape, &gammaDeviceAddr, aclDataType::ACL_FLOAT16, &gamma);
    CHECK_RET(ret == ACL_SUCCESS, return ret);
    // Create the beta aclTensor.
    ret = CreateAclTensor(betaHostData, betaShape, &betaDeviceAddr, aclDataType::ACL_FLOAT16, &beta);
    CHECK_RET(ret == ACL_SUCCESS, return ret);
    // Create the smooth1 aclTensor.
    ret = CreateAclTensor(smooth1HostData, gammaShape, &smooth1DeviceAddr, aclDataType::ACL_FLOAT16, &smooth1);
    CHECK_RET(ret == ACL_SUCCESS, return ret);
    // Create the smooth2 aclTensor.
    ret = CreateAclTensor(smooth2HostData, gammaShape, &smooth2DeviceAddr, aclDataType::ACL_FLOAT16, &smooth2);
    CHECK_RET(ret == ACL_SUCCESS, return ret);

    // Create y1 aclTensor.
    ret = CreateAclTensor(y1HostData, xShape, &y1DeviceAddr, aclDataType::ACL_INT8, &y1);
    CHECK_RET(ret == ACL_SUCCESS, return ret);
    // Create y2 aclTensor.
    ret = CreateAclTensor(y2HostData, xShape, &y2DeviceAddr, aclDataType::ACL_INT8, &y2);
    CHECK_RET(ret == ACL_SUCCESS, return ret);
    // Create x aclTensor.
    ret = CreateAclTensor(xHostData, xShape, &xDeviceAddr, aclDataType::ACL_FLOAT16, &x);
    CHECK_RET(ret == ACL_SUCCESS, return ret);
    // Create outScale1 aclTensor.
    ret = CreateAclTensor(scale1HostData, reduceShape, &scale1DeviceAddr, aclDataType::ACL_FLOAT, &scale1);
    CHECK_RET(ret == ACL_SUCCESS, return ret);
    // Create outScale1 aclTensor.
    ret = CreateAclTensor(scale2HostData, reduceShape, &scale2DeviceAddr, aclDataType::ACL_FLOAT, &scale2);
    CHECK_RET(ret == ACL_SUCCESS, return ret);

    // 3. Call the CANN operator library API. Replace with the specific API.
    uint64_t workspaceSize = 0;
    aclOpExecutor* executor;
    // Call the first-phase API of aclnnAddRmsNormDynamicQuantV2.
    ret = aclnnAddRmsNormDynamicQuantV2GetWorkspaceSize(
        x1, x2, gamma, smooth1, smooth2, beta, epsilon, nullptr, y1, y2, x, scale1, scale2, &workspaceSize, &executor);
    CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclnnAddRmsNormDynamicQuantV2GetWorkspaceSize failed. ERROR: %d\n", ret); return ret);
    // Apply for device memory based on 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 aclnnAddRmsNormDynamicQuantV2.
    ret = aclnnAddRmsNormDynamicQuantV2(workspaceAddr, workspaceSize, executor, stream);
    CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclnnAddRmsNormDynamicQuantV2 failed. ERROR: %d\n", ret); return ret);
    // 4. (Boilerplate) Synchronize and wait for task execution to complete.
    ret = aclrtSynchronizeStream(stream);
    CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclrtSynchronizeStream failed. ERROR: %d\n", ret); return ret);

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

    // 6. Release aclTensor and aclScalar. Modify based on the specific API definition.
    aclDestroyTensor(x1);
    aclDestroyTensor(x2);
    aclDestroyTensor(gamma);
    aclDestroyTensor(beta);
    aclDestroyTensor(smooth1);
    aclDestroyTensor(smooth2);
    aclDestroyTensor(y1);
    aclDestroyTensor(y2);
    aclDestroyTensor(x);
    aclDestroyTensor(scale1);
    aclDestroyTensor(scale2);

    // 7. Release device resources. Modify based on the specific API definition.
    aclrtFree(x1DeviceAddr);
    aclrtFree(x2DeviceAddr);
    aclrtFree(gammaDeviceAddr);
    aclrtFree(betaDeviceAddr);
    aclrtFree(smooth1DeviceAddr);
    aclrtFree(smooth2DeviceAddr);
    aclrtFree(y1DeviceAddr);
    aclrtFree(y2DeviceAddr);
    aclrtFree(xDeviceAddr);
    aclrtFree(scale1DeviceAddr);
    aclrtFree(scale2DeviceAddr);

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