aclnnDynamicQuantV3
Applicable Products
| Applicable Products | Supported |
|---|---|
| √ | |
| × | |
| × | |
| × | |
| × | |
| × |
Function
Description: Performs dynamic quantization on the input tensor. In the MOE scenario, the smoothScalesOptional of each expert is different and is distinguished based on the input groupIndexOptional. Supports symmetric/asymmetric quantization. Supports pertoken/pertensor/perchannel quantization modes. Compared with aclnnDynamicQuantV2, pertensor/perchannel quantization modes are newly added, specified by the quantMode parameter.
Formula:
Symmetric quantization:
If smoothScalesOptional is not input:
If smoothScalesOptional is input:
Asymmetric quantization:
If smoothScalesOptional is not provided:
If smoothScalesOptional is provided:
where / represents the mode for computing the maximum/minimum value. If quantMode is "pertoken", then , indicating that the maximum/minimum value is computed for each token. If quantMode is "pertensor", then , indicating that the maximum/minimum value is computed for the entire tensor. If quantMode is "perchannel", then , indicating that the maximum/minimum value is computed for each channel. is the maximum value of the output type, and is the minimum value of the output type.
Function Prototype
Each operator is divided into a two-phase API. You must first call the "aclnnDynamicQuantV3GetWorkspaceSize" API to obtain the workspace size required for computation and the executor that encapsulates the operator computation flow, and then call the "aclnnDynamicQuantV3" API to perform the computation.
aclnnStatus aclnnDynamicQuantV3GetWorkspaceSize(
const aclTensor *x,
const aclTensor *smoothScalesOptional,
const aclTensor *groupIndexOptional,
int64_t dstType,
bool isSymmetrical,
const char *quantMode,
const aclTensor *yOut,
const aclTensor *scaleOut,
const aclTensor *offsetOut,
uint64_t *workspaceSize,
aclOpExecutor **executor)aclnnStatus aclnnDynamicQuantV3(
void *workspace,
uint64_t workspaceSize,
aclOpExecutor *executor,
aclrtStream stream)aclnnDynamicQuantV3GetWorkspaceSize
Parameters:
Parameter Input/Output Description Instruction Data Type Data Format Dimension (Shape) Non-contiguous Tensor x (aclTensor*) Input Operator input tensor. Corresponds to x in the formula. - When the data type of the corresponding yOut is INT4, the size of the last dimension of x must be divisible by 2.
FLOAT16, BFLOAT16 ND 2-8 √ smoothScalesOptional (aclTensor*) Input The smoothScales of the operator input. Corresponds to smoothScalesOptional in the formula description. - In the pertoken/pertensor scenario, when groupIndexOptional is absent, the shape dimension is the last dimension of x.
- When groupIndexOptional is present, the shape is two-dimensional, where the first dimension corresponds to the number of experts and its value cannot exceed 1024, and the second dimension equals the size of the last dimension of x.
- In the perchannel scenario, the shape size is the size of the second-to-last dimension of x.
- The data type must be consistent with x.
FLOAT16, BFLOAT16 ND 1, 2 √ groupIndexOptional (aclTensor*) Input The groupIndex of the operator input. Corresponds to groupIndexOptional in the corresponding formula. - The shape supports only one dimension, and the dimension size equals the first dimension of smoothScalesOptional.
- When groupIndexOptional is not nullptr, smoothScalesOptional must be non-nullptr.
INT32 ND 1 √ dstType (int64_t) Input Indicates the type of yOut after data conversion, corresponding to DType in the corresponding formula. - The input range is {2, 3, 29, 34, 35, 36}, corresponding to the output yOut data types: {2:INT8, 3:INT32, 29:INT4, 34:HIFLOAT8, 35:FLOAT8_E5M2, 36:FLOAT8_E4M3FN}.
- INT32 is actually eight INT4 values concatenated.
- - - - isSymmetrical (bool) Input Specifies whether to use symmetric quantization. - - - - - quantMode (char*) Input Specifies the quantization mode. - Currently supports "pertoken", "pertensor", and "perchannel".
- When quantMode is set to "pertensor" or "perchannel", groupIndexOptional must be nullptr.
- - - - yOut (aclTensor*) Output Quantized output tensor, whose type is specified by dstType, corresponding to yOut in the formula. - When the data type is INT4, the size of the last dimension must be divisible by 2.
- When the data type is INT32, the last dimension is 1/8 of the last dimension of x.
- For other data types, the shape is the same as x.
INT4, INT8, FLOAT8_E4M3FN, FLOAT8_E5M2, HIFLOAT8, INT32 ND 2-8 √ scaleOut (aclTensor*) Output Scale used for quantization. Corresponds to scaleOut in the formula. - When quantMode is pertoken, the shape is the shape of x excluding the last dimension.
- When quantMode is pertensor, the shape is (1,).
- When quantMode is perchannel, the shape is the shape of x excluding the second-to-last dimension.
FLOAT32 ND 1-7 √ offsetOut (aclTensor*) Output Offset used for asymmetric quantization. Corresponds to offsetOut in the corresponding formula. - Supported only when isSymmetrical is false. If isSymmetrical is true, offsetOut must be set to nullptr.
FLOAT32 ND 1-7 √ 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 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 The passed x or output parameter is a null pointer. ACLNN_ERR_PARAM_INVALID 161002 The data type, data format, or dimensions of the parameters are not within the supported range. ACLNN_ERR_INNER_CREATE_EXECUTOR 561001 Failed to create aclOpExecutor internally.
aclnnDynamicQuantV3
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 aclnnDynamicQuantV3GetWorkspaceSize. 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:
- aclnnDynamicQuantV3 defaults to a deterministic implementation.
When the data type of yOut is INT4, the last dimension of x and yOut must be divisible by 2. When the data type of yOut is INT32, the last dimension of x must be divisible by 8. When groupIndexOptional is present, the number of experts must not exceed the product of all dimensions of x excluding the last dimension. The values of groupIndexOptional must form a non-decreasing array with all values not less than zero, and the last value must equal the product of all dimensions of x excluding the last dimension. If this condition is not met, the result is meaningless.
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_dynamic_quant_v3.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 shapeSize = 1;
for (auto i : shape) {
shapeSize *= i;
}
return shapeSize;
}
void PrintOutResult(std::vector<int64_t> &shape, void** deviceAddr) {
auto size = GetShapeSize(shape);
std::vector<float> resultData(size, 0);
auto ret = aclrtMemcpy(resultData.data(), resultData.size() * sizeof(resultData[0]),
*deviceAddr, size * sizeof(resultData[0]), ACL_MEMCPY_DEVICE_TO_HOST);
CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("copy result from device to host failed. ERROR: %d\n", ret); return);
for (int64_t i = 0; i < size; i++) {
LOG_PRINT("mean result[%ld] is: %f\n", i, resultData[i]);
}
}
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 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_ND,
shape.data(), shape.size(), *deviceAddr);
return 0;
}
int main() {
// 1. (Boilerplate) Initialize the device/stream. See the ACL API manual.
// Fill in the deviceId based on your actual device.
int32_t deviceId = 0;
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. Custom construction is required based on the API interface.
int rowNum = 4;
int rowLen = 2;
int groupNum = 2;
std::vector<int64_t> xShape = {4, 2};
std::vector<int64_t> smoothShape = {groupNum, rowLen};
std::vector<int64_t> groupShape = {groupNum};
std::vector<int64_t> yShape = {4, 2};
std::vector<int64_t> scaleShape = {4};
std::vector<int64_t> offsetShape = {4};
void* xDeviceAddr = nullptr;
void* smoothDeviceAddr = nullptr;
void* groupDeviceAddr = nullptr;
void* yDeviceAddr = nullptr;
void* scaleDeviceAddr = nullptr;
void* offsetDeviceAddr = nullptr;
aclTensor* x = nullptr;
aclTensor* smooth = nullptr;
aclTensor* group = nullptr;
aclTensor* y = nullptr;
aclTensor* scale = nullptr;
aclTensor* offset = nullptr;
std::vector<aclFloat16> xHostData;
std::vector<aclFloat16> smoothHostData;
std::vector<int32_t> groupHostData = {2, rowNum};
std::vector<int8_t> yHostData;
std::vector<float> scaleHostData;
std::vector<float> offsetHostData;
for (int i = 0; i < rowNum; ++i) {
for (int j = 0; j < rowLen; ++j) {
float value1 = i * rowLen + j;
xHostData.push_back(aclFloatToFloat16(value1));
yHostData.push_back(0);
}
scaleHostData.push_back(0);
offsetHostData.push_back(0);
}
for (int m = 0; m < groupNum; ++m) {
for (int n = 0; n < rowLen; ++n) {
float value2 = m * rowLen + n;
smoothHostData.push_back(aclFloatToFloat16(value2));
}
}
// Create the x aclTensor.
ret = CreateAclTensor(xHostData, xShape, &xDeviceAddr, aclDataType::ACL_FLOAT16, &x);
CHECK_RET(ret == ACL_SUCCESS, return ret);
// Create the smooth aclTensor.
ret = CreateAclTensor(smoothHostData, smoothShape, &smoothDeviceAddr, aclDataType::ACL_FLOAT16, &smooth);
CHECK_RET(ret == ACL_SUCCESS, return ret);
// Create the group aclTensor.
ret = CreateAclTensor(groupHostData, groupShape, &groupDeviceAddr, aclDataType::ACL_INT32, &group);
CHECK_RET(ret == ACL_SUCCESS, return ret);
// Create the y aclTensor.
ret = CreateAclTensor(yHostData, yShape, &yDeviceAddr, aclDataType::ACL_INT8, &y);
CHECK_RET(ret == ACL_SUCCESS, return ret);
// Create the scale aclTensor.
ret = CreateAclTensor(scaleHostData, scaleShape, &scaleDeviceAddr, aclDataType::ACL_FLOAT, &scale);
CHECK_RET(ret == ACL_SUCCESS, return ret);
// Create the offset aclTensor.
ret = CreateAclTensor(offsetHostData, offsetShape, &offsetDeviceAddr, aclDataType::ACL_FLOAT, &offset);
CHECK_RET(ret == ACL_SUCCESS, return ret);
// 3. Call the CANN operator library API. Replace it with the specific API name.
uint64_t workspaceSize = 0;
aclOpExecutor* executor;
const char* quantMode = "pertoken";
// Call the first-phase API of aclnnDynamicQuantV3.
ret = aclnnDynamicQuantV3GetWorkspaceSize(x, smooth, group, 2, false, quantMode, y, scale, offset, &workspaceSize, &executor);
CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclnnDynamicQuantV3GetWorkspaceSize failed. ERROR: %d\n", ret); return ret);
// Apply for device memory based on the 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 aclnnDynamicQuantV3.
ret = aclnnDynamicQuantV3(workspaceAddr, workspaceSize, executor, stream);
CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclnnDynamicQuantV3 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 memory to the host side. Modify based on the specific API definition.
PrintOutResult(yShape, &yDeviceAddr);
// 6. Release aclTensor and aclScalar. Modify based on the specific API definition.
aclDestroyTensor(x);
aclDestroyTensor(smooth);
aclDestroyTensor(y);
aclDestroyTensor(scale);
aclDestroyTensor(offset);
// 7. Release device resources.
aclrtFree(xDeviceAddr);
aclrtFree(smoothDeviceAddr);
aclrtFree(yDeviceAddr);
aclrtFree(scaleDeviceAddr);
aclrtFree(offsetDeviceAddr);
if (workspaceSize > 0) {
aclrtFree(workspaceAddr);
}
aclrtDestroyStream(stream);
aclrtResetDevice(deviceId);
aclFinalize();
return 0;
}