aclnnDynamicMxQuantWithDualAxis
Applicable Products
| Product | Supported |
|---|---|
| √ | |
| × | |
| × | |
| × | |
| × | |
| × |
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 is quantized into
Simultaneously, group the input x along the -2 axis by 32 elements, where a group of 32 elements is quantized into
The -1-axis quantized forms the output y1Out according to the corresponding positions of , and mxscale1 forms the output mxscale1Out according to the corresponding groups along the -1 axis dimension. The -2-axis quantized forms the output y2Out according to the corresponding positions of , 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.
DataType emax FLOAT4_E2M1 2 FLOAT4_E1M2 0 FLOAT8_E4M3FN 8 FLOAT8_E5M2 15
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;
}