aclnnDynamicMxQuantWithDualAxis

📄 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 MX quantization with the target data type being FLOAT4 type or FLOAT8 type on both the -1 axis and the -2 axis simultaneously. On the given -1 axis and -2 axis, for every 32 numbers, computes the quantization scales mxscale1 and mxscale2 corresponding to these two groups of numbers as the corresponding parts of the outputs mxscale1Out and mxscale2Out, then divides all elements of the two groups by the corresponding mxscale1 or mxscale2 respectively, converts them to the corresponding dstType according to round_mode, and obtains the quantization results y1 and y2 as the corresponding parts of the outputs y1Out and y2Out respectively.

  • Formula:

    • Currently only supports scaleAlg=0, i.e., OCP implementation:

    • Group the input x along the -1 axis by 32 elements, where a group of 32 elements {{Vi}i=132}\{\{V_i\}_{i=1}^{32}\} is quantized into {mxscale1,{Pi}i=132}\{mxscale1, \{P_i\}_{i=1}^{32}\}

      shared_exp=floor(log2(maxi(∣Vi∣)))−emaxshared\_exp = floor(log_2(max_i(|V_i|))) - emax mxscale1=2shared_expmxscale1 = 2^{shared\_exp} Pi=cast_to_dst_type(Vi/mxscale1,round_mode), i from 1 to 32P_i = cast\_to\_dst\_type(V_i/mxscale1, round\_mode), \space i\space from\space 1\space to\space 32
    • Simultaneously, group the input x along the -2 axis by 32 elements, where a group of 32 elements {{Vj}j=132}\{\{V_j\}_{j=1}^{32}\} is quantized into {mxscale2,{Pj}j=132}\{mxscale2, \{P_j\}_{j=1}^{32}\}

      shared_exp=floor(log2(maxj(∣Vj∣)))−emaxshared\_exp = floor(log_2(max_j(|V_j|))) - emax mxscale2=2shared_expmxscale2 = 2^{shared\_exp} Pj=cast_to_dst_type(Vj/mxscale2,round_mode), j from 1 to 32P_j = cast\_to\_dst\_type(V_j/mxscale2, round\_mode), \space j\space from\space 1\space to\space 32
    • The -1-axis quantized PiP_{i} forms the output y1Out according to the corresponding positions of ViV_{i}, and mxscale1 forms the output mxscale1Out according to the corresponding groups along the -1 axis dimension. The -2-axis quantized PjP_{j} forms the output y2Out according to the corresponding positions of VjV_{j}, and mxscale2 forms the output mxscale2Out according to the corresponding groups along the -2 axis dimension.

    • emax: The exponent of the maximum normal number of the corresponding data type.

      DataTypeemax
      FLOAT4_E2M12
      FLOAT4_E1M20
      FLOAT8_E4M3FN8
      FLOAT8_E5M215

Function Prototype

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

aclnnStatus aclnnDynamicMxQuantWithDualAxisGetWorkspaceSize(
  const aclTensor *x,
  char            *roundModeOptional,
  int64_t          dstType,
  int64_t          scaleAlg,
  const aclTensor *y1Out,
  const aclTensor *mxscale1Out,
  const aclTensor *y2Out,
  const aclTensor *mxscale2Out,
  uint64_t        *workspaceSize,
  aclOpExecutor   **executor)
aclnnStatus aclnnDynamicMxQuantWithDualAxis(
  void          *workspace,
  uint64_t       workspaceSize,
  aclOpExecutor *executor,
  aclrtStream    stream)

aclnnDynamicMxQuantWithDualAxisGetWorkspaceSize

  • Parameters:

    Parameter Input/Output Description Instruction Data Type Data Format Dimension (Shape) Non-contiguous Tensor
    x (aclTensor*) Input Indicates input x, corresponding to Vi in the formula. When the target type is FLOAT4_E2M1 or FLOAT4_E1M2, the last dimension of x must be an even number. Empty tensors are not supported. FLOAT16, BFLOAT16 ND 2-7 √
    roundModeOptional (char*) Input Indicates the data conversion mode, corresponding to round_mode in the formula.
    • When dstType is 40/41, corresponding to output y1Out and y2Out data types being FLOAT4_E2M1/FLOAT4_E1M2, {"rint", "floor", "round"} is supported.
    • When dstType is 35/36, corresponding to output y1Out and y2Out data types being FLOAT8_E5M2/FLOAT8_E4M3FN, only {"rint"} is supported.
    • When a null pointer is passed, the "rint" mode is used.
    STRING - - -
    dstType (int64_t) Input Indicates the data type of y1Out and y2Out after data conversion. The input range is {35, 36, 40, 41}, corresponding to the output data types of y1Out and y2Out as {35: FLOAT8_E5M2, 36: FLOAT8_E4M3FN, 40: FLOAT4_E2M1, 41: FLOAT4_E1M2}. INT64 - - -
    scaleAlg (int64_t) Input. Indicates the computation method for mxscale1Out and mxscale2Out. Currently only supports the value 0, which represents the OCP implementation. INT64 - - -
    y1Out (aclTensor*) Output Indicates the corresponding result after quantizing input x along the -1 axis, corresponding to Pi in the formula. The shape is consistent with input x. FLOAT4_E2M1, FLOAT4_E1M2, FLOAT8_E4M3FN, FLOAT8_E5M2 ND 2-7 √
    mxscale1Out (aclTensor*) Output Indicates the quantization scale corresponding to each group along the -1 axis, which corresponds to mxscale1 in the formula. The shape is the value of the -1 axis of x divided by 32 and rounded up, with even padding applied and the padding value set to 0. FLOAT8_E8M0 ND 2-8 √
    y2Out (aclTensor*) Output Indicates the corresponding result of input x after quantization along the -2 axis, corresponding to Pj in the formula. The shape is consistent with input x. FLOAT4_E2M1, FLOAT4_E1M2, FLOAT8_E4M3FN, FLOAT8_E5M2 ND 2-7 √
    mxscale2Out (aclTensor*) Output Indicates the quantization scale corresponding to each group on the -2 axis, corresponding to mxscale2 in the formula.
    • The shape is the value of the -2 axis of x divided by 32, rounded up, and padded to an even number with a padding value of 0.
    • The mxscale2Out output requires interleaving every two rows of data.
    FLOAT8_E8M0 ND 2-8 √
    workspaceSize Output Returns the workspace size to be applied on the Device side. - - - - -
    executor Output Returns the operator executor, which includes 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 x is a null pointer.
    ACLNN_ERR_PARAM_INVALID 161002 The data type or data format of x, roundModeOptional, dstType, scaleAlg, y1Out, mxscale1Out, y2Out, or mxscale2Out is not within the supported range.
    The shape of x, y1Out, y2Out, mxscale1Out, or mxscale2Out does not meet the validation conditions.
    roundModeOptional, dstType, or scaleAlg does not match the currently supported values.
    ACLNN_ERR_RUNTIME_ERROR 361001 The current platform is not within the supported platform range.

aclnnDynamicMxQuantWithDualAxis

  • Parameters:

    Parameter Input/Output Description
    workspace Input Device-side workspace memory address.
    workspaceSize Input Device-side workspace size, obtained from the first-phase API aclnnDynamicMxQuantWithDualAxisGetWorkspaceSize.
    executor Input Operator executor, which includes the computation process of the operator.
    stream Input Specifies the stream for task execution.
  • Return value

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

Constraints

  • The shape constraints for x, mxscale1Out, and mxscale2Out are as follows:

    • rank(mxscale1Out) = rank(x) + 1.

    • rank(mxscale2Out) = rank(x) + 1.

    • mxscale1Out.shape[-2] = (ceil(x.shape[-1] / 32) + 2 - 1) / 2.

    • mxscale2Out.shape[-3] = (ceil(x.shape[-2] / 32) + 2 - 1) / 2.

    • mxscale1Out.shape[-1] = 2.

    • mxscale2Out.shape[-1] = 2.

    • Other dimensions are consistent with input x.

    • Example: When the shape of input x is [B, M, N] and the target data type is FP8 type, the corresponding shapes of y1 and y2 are [B, M, N], the shape of mxscale1 is [B, M, (ceil(N/32)+2-1)/2, 2], and the shape of mxscale2 is [B, (ceil(M/32)+2-1)/2, N, 2].

  • Deterministic computation: aclnnDynamicMxQuantWithDualAxis 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 <memory>
#include <vector>
#include "acl/acl.h"
#include "aclnnop/aclnn_dynamic_mx_quant_with_dual_axis.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 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 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 aclnnDynamicMxQuantWithDualAxisTest(int32_t deviceId, 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 input and output. Customize the construction based on the API.
    std::vector<int64_t> xShape = {1, 4};
    std::vector<int64_t> y1OutShape = {1, 4};
    std::vector<int64_t> y2OutShape = {1, 4};
    std::vector<int64_t> mxscale1OutShape = {1, 1, 2};
    std::vector<int64_t> mxscale2OutShape = {1, 4, 2};
    void* xDeviceAddr = nullptr;
    void* y1OutDeviceAddr = nullptr;
    void* mxscale1OutDeviceAddr = nullptr;
    void* y2OutDeviceAddr = nullptr;
    void* mxscale2OutDeviceAddr = nullptr;
    aclTensor* x = nullptr;
    aclTensor* y1Out = nullptr;
    aclTensor* mxscale1Out = nullptr;
    aclTensor* y2Out = nullptr;
    aclTensor* mxscale2Out = nullptr;
    std::vector<uint16_t> xHostData = {0, 16640, 17024, 17408};
    std::vector<uint8_t> y1OutHostData = {0, 72, 96, 120};
    std::vector<uint8_t> y2OutHostData = {0, 0, 0, 0};
    std::vector<uint8_t> mxscale1OutHostData = {128, 0};
    std::vector<uint8_t> mxscale2OutHostData = {0, 0, 122, 0, 125, 0, 128, 0};
    char* roundModeOptional = const_cast<char*>("rint");
    int64_t dstType = 36;
    int64_t scaleAlg = 0;
    // Create the x aclTensor.
    ret = CreateAclTensor(xHostData, xShape, &xDeviceAddr, aclDataType::ACL_BF16, &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 the y1Out aclTensor.
    ret = CreateAclTensor(y1OutHostData, y1OutShape, &y1OutDeviceAddr, aclDataType::ACL_FLOAT8_E4M3FN, &y1Out);
    std::unique_ptr<aclTensor, aclnnStatus (*)(const aclTensor*)> y1OutTensorPtr(y1Out, aclDestroyTensor);
    std::unique_ptr<void, aclError (*)(void*)> y1OutDeviceAddrPtr(y1OutDeviceAddr, aclrtFree);
    CHECK_RET(ret == ACL_SUCCESS, return ret);
    // Create the mxscale1Out aclTensor.
    ret = CreateAclTensor(mxscale1OutHostData, mxscale1OutShape, &mxscale1OutDeviceAddr, aclDataType::ACL_FLOAT8_E8M0, &mxscale1Out);
    std::unique_ptr<aclTensor, aclnnStatus (*)(const aclTensor*)> mxscale1OutTensorPtr(mxscale1Out, aclDestroyTensor);
    std::unique_ptr<void, aclError (*)(void*)> mxscale1OutDeviceAddrPtr(mxscale1OutDeviceAddr, aclrtFree);
    CHECK_RET(ret == ACL_SUCCESS, return ret);
    // Create y2Out aclTensor.
    ret = CreateAclTensor(y2OutHostData, y2OutShape, &y2OutDeviceAddr, aclDataType::ACL_FLOAT8_E4M3FN, &y2Out);
    std::unique_ptr<aclTensor, aclnnStatus (*)(const aclTensor*)> y2OutTensorPtr(y2Out, aclDestroyTensor);
    std::unique_ptr<void, aclError (*)(void*)> y2OutDeviceAddrPtr(y2OutDeviceAddr, aclrtFree);
    CHECK_RET(ret == ACL_SUCCESS, return ret);
    // Create mxscale2Out aclTensor.
    ret = CreateAclTensor(mxscale2OutHostData, mxscale2OutShape, &mxscale2OutDeviceAddr, aclDataType::ACL_FLOAT8_E8M0, &mxscale2Out);
    std::unique_ptr<aclTensor, aclnnStatus (*)(const aclTensor*)> mxscale2OutTensorPtr(mxscale2Out, aclDestroyTensor);
    std::unique_ptr<void, aclError (*)(void*)> mxscale2OutDeviceAddrPtr(mxscale2OutDeviceAddr, aclrtFree);
    CHECK_RET(ret == ACL_SUCCESS, return ret);

    // Call the CANN operator library API. Replace with the actual API name.
    uint64_t workspaceSize = 0;
    aclOpExecutor* executor;

    // Call the first-phase API of aclnnDynamicMxQuantWithDualAxis.
    ret = aclnnDynamicMxQuantWithDualAxisGetWorkspaceSize(x, roundModeOptional, dstType, scaleAlg, y1Out, mxscale1Out, y2Out, mxscale2Out, &workspaceSize, &executor);
    CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclnnDynamicMxQuantWithDualAxisGetWorkspaceSize 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 aclnnDynamicMxQuantWithDualAxis.
    ret = aclnnDynamicMxQuantWithDualAxis(workspaceAddr, workspaceSize, executor, stream);
    CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclnnDynamicMxQuantWithDualAxis failed. ERROR: %d\n", ret); return ret);

    //(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);

    // Obtain the output value and copy the result from the device memory to the host memory. Modify this according to the specific API definition.
    auto size1 = GetShapeSize(y1OutShape);
    auto size2 = GetShapeSize(y2OutShape);
    std::vector<uint8_t> y1OutData(
        size1, 0);  // In C, fp4 data cannot be printed directly. Read it as uint8 and manually convert it to fp4 via binary.
    std::vector<uint8_t> y2OutData(
        size2, 0);  // In C, fp4 data cannot be printed directly. Read it as uint8 and manually convert it to fp4 via binary.
    ret = aclrtMemcpy(y1OutData.data(), y1OutData.size() * sizeof(y1OutData[0]), y1OutDeviceAddr,
                      size1 * sizeof(y1OutData[0]), ACL_MEMCPY_DEVICE_TO_HOST) &&
                      aclrtMemcpy(y2OutData.data(), y2OutData.size() * sizeof(y2OutData[0]), y2OutDeviceAddr,
                      size2 * sizeof(y2OutData[0]), ACL_MEMCPY_DEVICE_TO_HOST);
    CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("copy y1Out and y2Out from device to host failed. ERROR: %d\n", ret);
              return ret);
    for (int64_t i = 0; i < size1; i++) {
        LOG_PRINT("y1Out[%ld] is: %d\n", i, y1OutData[i]);
    }
    for (int64_t i = 0; i < size2; i++) {
        LOG_PRINT("y2Out[%ld] is: %d\n", i, y2OutData[i]);
    }
    size1 = GetShapeSize(mxscale1OutShape);
    size2 = GetShapeSize(mxscale2OutShape);
    std::vector<uint8_t> mxscale1OutData(
        size1, 0);  // fp8 data cannot be directly printed in C. Read it as uint8 and convert it to fp8 through binary conversion.
    std::vector<uint8_t> mxscale2OutData(
        size2, 0);  // fp8 data cannot be directly printed in C. Read it as uint8 and convert it to fp8 through binary conversion.
    ret = aclrtMemcpy(mxscale1OutData.data(), mxscale1OutData.size() * sizeof(mxscale1OutData[0]), mxscale1OutDeviceAddr,
                      size1 * sizeof(mxscale1OutData[0]), ACL_MEMCPY_DEVICE_TO_HOST);
    CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("copy mxscale1Out from device to host failed. ERROR: %d\n", ret);
              return ret);
    ret = aclrtMemcpy(mxscale2OutData.data(), mxscale2OutData.size() * sizeof(mxscale2OutData[0]), mxscale2OutDeviceAddr,
                      size2 * sizeof(mxscale2OutData[0]), ACL_MEMCPY_DEVICE_TO_HOST);
    CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("copy mxscale2Out from device to host failed. ERROR: %d\n", ret);
              return ret);
    for (int64_t i = 0; i < size1; i++) {
        LOG_PRINT("mxscale1Out[%ld] is: %d\n", i, mxscale1OutData[i]);
    }
    for (int64_t i = 0; i < size2; i++) {
        LOG_PRINT("mxscale2Out[%ld] is: %d\n", i, mxscale2OutData[i]);
    }
    return ACL_SUCCESS;
}

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

    Finalize(deviceId, stream);
    return 0;
}