aclnnWeightQuantBatchMatmulV3
Applicable Products
| Product | Supported |
|---|---|
| √ | |
| √ | |
| √ | |
| × | |
| × | |
| × |
Function
Operator Function: Performs matrix multiplication computation with pseudo-quantization input and supports quantization computation for the output. Compared with the aclnnWeightQuantBatchMatmulV2 API, this API has the following changes:
The innerPrecise parameter is added to support the selection of high-precision or high-performance computation modes. In the A16W4 per_group scenario, when batchSize <= 16, it can be set to 1 to improve performance.
Formula:
In the formula, is the input in the pseudo-quantization scenario, and its dequantization formula is
When the user configures the quantScaleOptional input, the output is quantized. The quantization formula is
When the user configures the quantScaleOptional input as nullptr, the output is directly:
Function Prototype
Each operator is divided into a two-phase API. First call the "aclnnWeightQuantBatchMatmulV3GetWorkspaceSize" API to obtain the workspace size required for computation and the executor that contains the operator computation process, and then call the "aclnnWeightQuantBatchMatmulV3" API to perform the computation.
aclnnStatus aclnnWeightQuantBatchMatmulV3GetWorkspaceSize(
const aclTensor *x,
const aclTensor *weight,
const aclTensor *antiquantScale,
const aclTensor *antiquantOffsetOptional,
const aclTensor *quantScaleOptional,
const aclTensor *quantOffsetOptional,
const aclTensor *biasOptional,
int antiquantGroupSize,
int innerPrecise,
const aclTensor *y,
uint64_t *workspaceSize,
aclOpExecutor **executor)aclnnStatus aclnnWeightQuantBatchMatmulV3(
void *workspace,
uint64_t workspaceSize,
aclOpExecutor *executor,
aclrtStream stream)aclnnWeightQuantBatchMatmulV3GetWorkspaceSize
Parameters
Ascend 950PR/Ascend 950DT :- The superscript "1" in the data type column of the table above indicates a data type not supported by this series.
Atlas A2 training products/Atlas A2 inference products ,Atlas A3 training products/Atlas A3 inference products :- The superscript "2" in the data type column of the table above indicates a data type not supported by this series.
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 Error Code Description ACLNN_ERR_PARAM_NULLPTR 161001 A required parameter is a null pointer. ACLNN_ERR_PARAM_INVALID 161002 The shape dimensions of x, weight, antiquantScale, antiquantOffsetOptional, quantScaleOptional, quantOffsetOptional, biasOptional, or y do not meet the requirements. The data type of x, weight, antiquantScale, antiquantOffsetOptional, quantScaleOptional, quantOffsetOptional, biasOptional, or y is not within the supported range. The reduction dimensions (k) of x and weight are not equal. When antiquantOffsetOptional is provided, its shape is not the same as that of antiquantScale. When quantOffsetOptional is present, its shape differs from that of quantScale. The antiQuantGroupSize value does not meet the requirements. The innerPrecise value does not meet the requirements. When quantOffsetOptional is present, quantScaleOptional is a null pointer. The input k or n value is outside the range [1, 65535]. When the x matrix is non-transposed, m is not in the range [1, 2^31-1]; when transposed, m is not in the range [1, 65535]. Empty tensor scenarios are not supported. The contiguity of the passed x, weight, antiQuantScale, antiQuantOffsetOptional, quantScaleOptional, quantOffsetOptional, biasOptional, and y does not meet the requirements. ACLNN_ERR_RUNTIME_ERROR 361001 The product model is not supported.
aclnnWeightQuantBatchMatmulV3
Parameters
Parameter Input/Output Description workspace Input Memory address of the workspace applied for on the Device side. workspaceSize Input Size of the workspace applied for on the Device side, obtained from the first-phase API aclnnWeightQuantBatchMatmulV3GetWorkspaceSize. executor Input Operator executor, which contains the operator computation flow. stream Input Specifies the Stream for executing the task. Return value
aclnnStatus: return code. For details, see aclnn Return Code.
Constraints
Atlas A2 training products/Atlas A2 inference products , Atlas A3 training products/Atlas A3 inference products
Deterministic computation description: Defaults to a non-deterministic implementation. Supports enabling deterministic computation through aclrtCtxSetSysParamOpt.
Common Constraints
When the weight data format is FRACTAL_NZ and the data type is INT4/INT32, or when the weight data format is ND and the data type is INT32, it is supported only in the INT4Pack scenario. In this case, the aclnnConvertWeightToINT4Pack API must be used to complete the conversion from INT32 to INT4Pack and from ND to FRACTAL_NZ. See details. If the data type is INT4, the inner axis of weight must be an even number.
For different pseudo-quantization algorithm modes, the weight data format FRACTAL_NZ is supported only in the following scenarios:
per_channel mode:
The weight data type is INT8 and the y data type is non-INT8. The weight data type is INT4/INT32, weight is transposed, and the y data type is non-INT8.
per_group mode: The weight data type is INT4/INT32, weight is non-transposed, x is non-transposed, antiquantGroupSize is 64 or 128, k is aligned to antiquantGroupSize, n is aligned to 64, and the y data type is non-INT8.
Performance Optimization
per_channel mode: To improve performance, it is recommended to use the transposed weight input. When the m range is [65, 96], it is recommended to use antiquantScale with the data type UINT64/INT64.
per_group mode: In the A16W4 scenario with batchSize <= 16, you can set the innerPrecise parameter to 1 and set the weight data format to FRACTAL_NZ to improve performance, but this may cause a certain degree of precision degradation.
Ascend 950PR/Ascend 950DT
- Deterministic computation description: Defaults to a deterministic implementation.
Common Constraints
The m, k, and n dimensions of the
xandweightmatrices are in the range [1, 2^31-1]. The Reduce dimension k ofweightmust be equal to the Reduce dimension k ofx.Supported quantization modes: pertensor quantization mode, perchannel quantization mode, pergroup quantization mode, and mx quantization mode.
xdoes not support transposition, and therefore does not support non-contiguous tensors. weight supports non-contiguous tensors only in the transposed scenario. antiquantScale and antiquantOffsetOptional support non-contiguous tensors only in the transposed scenario, and their contiguity requirements must be consistent with weight.Shapes supported by
antiquantScalefor different quantization modes:pertensor quantization mode: (1,) or (1,1).
perchannel quantization mode: The input shape is (1, n) or (n,).
pergroup Quantization Mode: The input shape is (⌈k/group_size⌉, n), where group_size indicates the size of each group into which k is divided.
mx Quantization Mode: The input shape is (⌈k/group_size⌉, n), where group_size indicates the size of each group into which k is divided. Only 32 is supported.
quantScaleOptionalandquantOffsetOptionalare reserved parameters and not yet in use. Always pass a null pointer.
A16W8 Scenario Constraints
- Input and output data type combination requirements
x weight weight Format antiquantScale antiquantOffsetOptional quantScaleOptional quantOffsetOptional biasOptional antiquantGroupSize y Scenario Description FLOAT16/BFLOAT16 INT8 ND Same as x Same as x/null null null Same as x/FLOAT (only when x is BFLOAT16)/null pergroup: [32, k-1] and a multiple of 32
Other: 0Same as x T & C & G quantization FLOAT16/BFLOAT16 HIFLOAT8/FLOAT8_E4M3FN ND Same as x null null null Same as x/null pergroup: [32, k-1] and a multiple of 32
Other: 0Same as x C quantization A16W4 Scenario Constraints
- Input and Output Data Type Combination Requirements
x weight weight Format antiquantScale antiquantOffsetOptional quantScaleOptional quantOffsetOptional biasOptional antiquantGroupSize y Scenario Description FLOAT16/BFLOAT16 INT4/INT32 ND Same as x Same as x/null null null Same as x/FLOAT (only when x is BFLOAT16)/null 0 Same as x T quantization FLOAT16/BFLOAT16 INT4/INT32 ND Same as x Same as x/null null null Same as x/FLOAT (only when x is BFLOAT16)/null pergroup: [32, k-1] and a multiple of 32
Other: 0Same as x C & G quantization FLOAT16/BFLOAT16 FLOAT4_E2M1 ND FLOAT8_E8M0 null null null Same as x/null 32 Same as x MX quantization FLOAT16/BFLOAT16 FLOAT ND FLOAT8_E8M0 null null null Same as x/FLOAT (only when x is BFLOAT16)/null 32 Same as x MX quantization Constraints
In addition to the Common Constraints, the remaining constraints for the A16W4 scenario are as follows:
If the data type of
weightis INT4 or FLOAT4_E2M1, the last dimension of weight must be 2-aligned. If the data type ofweightis INT32 or FLOAT, the last dimension of weight must be 8-aligned.If the data type of
weightis INT32 or FLOAT, it must be used together with theaclnnConvertWeightToINT4PackAPI to convert from INT32/FLOAT to densely packed INT4/FLOAT4_E2M1. For details, see the sample.
Performance Optimization Suggestions
pertensor Quantization Mode: When the data format is ND, it is recommended to use the transposed
weightinput.perchannel Quantization Mode: When the data format is ND, it is recommended to use the transposed
weightinput.pergroup Quantization Mode and mx Quantization Mode: It is recommended to use the non-transposed
weightinput.
Example
The following provides example code for reference only. For details about compilation and running, see Compile and Run Samples.
Atlas A2 training products/Atlas A2 inference products ,Atlas A3 training products/Atlas A3 inference products ,Ascend 950PR/Ascend 950DT :
A16W8 example.
#include <iostream>
#include <vector>
#include "acl/acl.h"
#include "aclnnop/aclnn_cast.h"
#include "aclnnop/aclnn_weight_quant_batch_matmul_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;
}
int Init(int32_t deviceId, aclrtStream* stream) {
// Boilerplate for 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 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. Refer to 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 inputs and outputs. Custom construction is required based on the API interface.
std::vector<int64_t> xShape = {16, 32};
std::vector<int64_t> weightShape = {32, 16};
std::vector<int64_t> yShape = {16, 16};
void* xDeviceAddr = nullptr;
void* weightDeviceAddr = nullptr;
void* yDeviceAddr = nullptr;
aclTensor* x = nullptr;
aclTensor* weight = nullptr;
aclTensor* y = nullptr;
int32_t innerPrecise = 1;
std::vector<float> xHostData(512, 1);
std::vector<int8_t> weightHostData(512, 1);
std::vector<float> yHostData(256, 0);
std::vector<int64_t> antiquantScaleShape = {16};
void* antiquantScaleDeviceAddr = nullptr;
aclTensor* antiquantScale = nullptr;
std::vector<float> antiquantScaleHostData(16, 1);
// Create the x aclTensor.
ret = CreateAclTensor(xHostData, xShape, &xDeviceAddr, aclDataType::ACL_FLOAT, &x);
CHECK_RET(ret == ACL_SUCCESS, return ret);
// Create the other aclTensor.
ret = CreateAclTensor(weightHostData, weightShape, &weightDeviceAddr, aclDataType::ACL_INT8, &weight);
CHECK_RET(ret == ACL_SUCCESS, return ret);
// Create the y aclTensor.
ret = CreateAclTensor(yHostData, yShape, &yDeviceAddr, aclDataType::ACL_FLOAT, &y);
CHECK_RET(ret == ACL_SUCCESS, return ret);
// Create the antiquantScale aclTensor.
ret = CreateAclTensor(antiquantScaleHostData, antiquantScaleShape, &antiquantScaleDeviceAddr, aclDataType::ACL_FLOAT, &antiquantScale);
CHECK_RET(ret == ACL_SUCCESS, return ret);
// Create the xFp16 aclTensor.
void* xFp16DeviceAddr = nullptr;
aclTensor* xFp16 = nullptr;
ret = CreateAclTensor(xHostData, xShape, &xFp16DeviceAddr, aclDataType::ACL_FLOAT16, &xFp16);
CHECK_RET(ret == ACL_SUCCESS, return ret);
// Create the antiquantScale aclTensor.
void* antiquantScaleFp16DeviceAddr = nullptr;
aclTensor* antiquantScaleFp16 = nullptr;
ret = CreateAclTensor(antiquantScaleHostData, antiquantScaleShape, &antiquantScaleFp16DeviceAddr, aclDataType::ACL_FLOAT16, &antiquantScaleFp16);
CHECK_RET(ret == ACL_SUCCESS, return ret);
// Create the yFp16 aclTensor.
void* yFp16DeviceAddr = nullptr;
aclTensor* yFp16 = nullptr;
ret = CreateAclTensor(yHostData, yShape, &yFp16DeviceAddr, aclDataType::ACL_FLOAT16, &yFp16);
CHECK_RET(ret == ACL_SUCCESS, return ret);
// 3. Call the CANN operator library API. Replace with the specific API name.
uint64_t workspaceSize = 0;
aclOpExecutor* executor;
void* workspaceAddr = nullptr;
// Call cast to generate FP16 input.
ret = aclnnCastGetWorkspaceSize(x, aclDataType::ACL_FLOAT16, xFp16, &workspaceSize, &executor);
CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclnnCastGetWorkspaceSize0 failed. ERROR: %d\n", ret); return ret);
// Allocate device memory based on the workspaceSize calculated by the first-phase API.
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);
}
ret = aclnnCast(workspaceAddr, workspaceSize, executor, stream);
CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclnnCast0 failed. ERROR: %d\n", ret); return ret);
ret = aclrtSynchronizeStream(stream);
CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclrtSynchronizeStream failed. ERROR: %d\n", ret); return ret);
ret = aclnnCastGetWorkspaceSize(antiquantScale, aclDataType::ACL_FLOAT16, antiquantScaleFp16, &workspaceSize, &executor);
CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclnnCastGetWorkspaceSize1 failed. ERROR: %d\n", ret); return ret);
// Allocate device memory based on the workspaceSize calculated by the first-phase API.
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);
}
ret = aclnnCast(workspaceAddr, workspaceSize, executor, stream);
CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclnnCast1 failed. ERROR: %d\n", ret); return ret);
ret = aclrtSynchronizeStream(stream);
CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclrtSynchronizeStream failed. ERROR: %d\n", ret); return ret);
// Call the first-phase API of aclnnWeightQuantBatchMatmulV3.
ret = aclnnWeightQuantBatchMatmulV3GetWorkspaceSize(xFp16, weight, antiquantScaleFp16, nullptr, nullptr, nullptr, nullptr, 0, innerPrecise, yFp16, &workspaceSize, &executor);
CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclnnWeightQuantBatchMatmulV3GetWorkspaceSize failed. ERROR: %d\n", ret); return ret);
// Apply for device memory based on the workspaceSize calculated by the first-phase API.
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 aclnnWeightQuantBatchMatmulV3.
ret = aclnnWeightQuantBatchMatmulV3(workspaceAddr, workspaceSize, executor, stream);
CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclnnWeightQuantBatchMatmulV3 failed. ERROR: %d\n", ret); return ret);
// 4. (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);
// Convert the output to FP32.
ret = aclnnCastGetWorkspaceSize(yFp16, aclDataType::ACL_FLOAT, y, &workspaceSize, &executor);
CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclnnCastGetWorkspaceSize2 failed. ERROR: %d\n", ret); return ret);
// Apply for device memory based on the workspaceSize calculated by the first-phase API.
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);
}
ret = aclnnCast(workspaceAddr, workspaceSize, executor, stream);
CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclnnCast2 failed. ERROR: %d\n", ret); return ret);
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 memory. Modify based on the specific API definition.
auto size = GetShapeSize(yShape);
std::vector<float> resultData(size, 0);
ret = aclrtMemcpy(resultData.data(), resultData.size() * sizeof(resultData[0]), yDeviceAddr,
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 ret);
for (int64_t i = 0; i < size; i++) {
LOG_PRINT("result[%ld] is: %f\n", i, resultData[i]);
}
// 6. Release aclTensor and aclScalar. Modify based on the specific API definition.
aclDestroyTensor(x);
aclDestroyTensor(weight);
aclDestroyTensor(antiquantScale);
aclDestroyTensor(y);
aclDestroyTensor(xFp16);
aclDestroyTensor(antiquantScaleFp16);
aclDestroyTensor(yFp16);
// 7. Release device resources.
aclrtFree(xDeviceAddr);
aclrtFree(weightDeviceAddr);
aclrtFree(antiquantScaleDeviceAddr);
aclrtFree(yDeviceAddr);
aclrtFree(xFp16DeviceAddr);
aclrtFree(antiquantScaleFp16DeviceAddr);
aclrtFree(yFp16DeviceAddr);
if (workspaceSize > 0) {
aclrtFree(workspaceAddr);
}
aclrtDestroyStream(stream);
aclrtResetDevice(deviceId);
aclFinalize();
return 0;
}Ascend 950PR/Ascend 950DT :
A16MxFp4 example.
#include <iostream>
#include <memory>
#include <vector>
#include "acl/acl.h"
#include "aclnnop/aclnn_cast.h"
#include "aclnnop/aclnn_weight_quant_batch_matmul_v3.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)
template <typename T1>
inline T1 CeilDiv(T1 a, T1 b)
{
return b == 0 ? a : (a + b - 1) / b;
};
template <typename T1>
inline T1 CeilAlign(T1 a, T1 b)
{
return (a + b - 1) / b * b;
};
int64_t GetShapeSize(const std::vector<int64_t>& shape)
{
int64_t shapeSize = 1;
for (auto i : shape) {
shapeSize *= i;
}
return shapeSize;
}
extern "C" aclnnStatus aclnnConvertWeightToINT4PackGetWorkspaceSize(
const aclTensor* weight, const aclTensor* weightInt4Pack, uint64_t* workspaceSize, aclOpExecutor** executor);
extern "C" aclnnStatus aclnnConvertWeightToINT4Pack(
void* workspace, uint64_t workspaceSize, aclOpExecutor* executor, aclrtStream stream);
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 the 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 aclCreateTensor 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;
}
template <typename T>
int CreateAclTensorB4(
const std::vector<T>& hostData, const std::vector<int64_t>& shape, void** deviceAddr, aclDataType dataType,
aclTensor** tensor, aclFormat format)
{
auto size = hostData.size() * 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.
if (format == aclFormat::ACL_FORMAT_ND) {
*tensor = aclCreateTensor(
shape.data(), shape.size(), dataType, strides.data(), 0, aclFormat::ACL_FORMAT_ND, shape.data(),
shape.size(), *deviceAddr);
} else {
std::vector<int64_t> nzShape;
if (dataType == aclDataType::ACL_INT4 || dataType == aclDataType::ACL_FLOAT4_E2M1) {
nzShape = {CeilDiv(shape[1], (int64_t)16), CeilDiv(shape[0], (int64_t)16), 16, 16};
} else {
nzShape = {CeilDiv(shape[1], (int64_t)2), CeilDiv(shape[0], (int64_t)16), 16, 2};
}
*tensor = aclCreateTensor(
shape.data(), shape.size(), dataType, strides.data(), 0, aclFormat::ACL_FORMAT_FRACTAL_NZ, nzShape.data(),
nzShape.size(), *deviceAddr);
}
return 0;
}
void PrintMat(std::vector<float> resultData, std::vector<int64_t> resultShape)
{
int64_t m = resultShape[0];
int64_t n = resultShape[1];
for (size_t i = 0; i < m; i++) {
printf(i == 0 ? "[[" : " [");
for (size_t j = 0; j < n; j++) {
printf(j == n - 1 ? "%.1f" : "%.1f, ", resultData[i * n + j]);
if (j == 2 && j + 3 < n) {
printf("..., ");
j = n - 4;
}
}
printf(i < m - 1 ? "],\n" : "]]\n");
if (i == 2 && i + 3 < m) {
printf(" ... \n");
i = m - 4;
}
}
}
void Finalize(int32_t deviceId, aclrtStream stream)
{
aclrtDestroyStream(stream);
aclrtResetDevice(deviceId);
aclFinalize();
}
int aclnnWeightQuantBatchMatmulV3Test(int32_t deviceId, aclrtStream& stream)
{
auto ret = Init(deviceId, &stream);
aclDataType weightPackedDtype = aclDataType::ACL_FLOAT; // Optional: ACL_FLOAT type.
CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("Init acl failed. ERROR: %d\n", ret); return ret);
// 2. Construct the input and output. Customize them based on the API interface.
int64_t m = 16;
int64_t k = 64;
int64_t n = 64;
int64_t groupSize = 32;
int64_t weightDim0 = k;
int64_t weightDim1 = n;
bool isWeightTransposed = false;
std::vector<int64_t> xShape = {m, k};
std::vector<int64_t> weightShape = {k, n};
std::vector<int64_t> antiquantScaleShape = {k / groupSize, n};
std::vector<int64_t> yShape = {m, n};
void* xDeviceAddr = nullptr;
void* weightDeviceAddr = nullptr;
void* weightB4PackDeviceAddr = nullptr;
void* antiquantScaleDeviceAddr = nullptr;
void* yDeviceAddr = nullptr;
aclTensor* x = nullptr;
aclTensor* weight = nullptr;
aclTensor* y = nullptr;
aclTensor* antiquantScale = nullptr;
std::vector<int64_t> weightPackedShape;
weightPackedShape = {weightDim0, weightDim1 / 8};
std::vector<uint16_t> xHostData(GetShapeSize(xShape), 0b0011110000000000); // 1.0 in fp16
xHostData[0] = 0; // 0 in fp16, to verify whether the result meets the requirement.
std::vector<float> weightHostData(GetShapeSize(weightShape), 1.0); // 1.0 in fp32, converted to 1.0 in fp4_e2m1 after int4pack.
std::vector<float> yHostData(GetShapeSize(yShape), 0);
std::vector<uint8_t> antiquantScaleHostData(GetShapeSize(antiquantScaleShape), 0b01111111); // 1.0 in fp8_e8m0
// Create x aclTensor.
ret = CreateAclTensor(xHostData, xShape, &xDeviceAddr, aclDataType::ACL_FLOAT16, &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 other aclTensor.
ret = CreateAclTensor(weightHostData, 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_RET(ret == ACL_SUCCESS, return ret);
// Create y aclTensor.
ret = CreateAclTensor(yHostData, yShape, &yDeviceAddr, aclDataType::ACL_FLOAT, &y);
std::unique_ptr<aclTensor, aclnnStatus (*)(const aclTensor*)> yTensorPtr(y, aclDestroyTensor);
std::unique_ptr<void, aclError (*)(void*)> yDeviceAddrPtr(yDeviceAddr, aclrtFree);
CHECK_RET(ret == ACL_SUCCESS, return ret);
// Create antiQuantScale aclTensor.
ret = CreateAclTensor(
antiquantScaleHostData, antiquantScaleShape, &antiquantScaleDeviceAddr, aclDataType::ACL_FLOAT8_E8M0,
&antiquantScale);
std::unique_ptr<aclTensor, aclnnStatus (*)(const aclTensor*)> antiquantScaleTensorPtr(
antiquantScale, aclDestroyTensor);
std::unique_ptr<void, aclError (*)(void*)> antiquantScaleDeviceAddrPtr(antiquantScaleDeviceAddr, aclrtFree);
CHECK_RET(ret == ACL_SUCCESS, return ret);
// Create yFp16 aclTensor.
void* yFp16DeviceAddr = nullptr;
aclTensor* yFp16 = nullptr;
ret = CreateAclTensor(yHostData, yShape, &yFp16DeviceAddr, aclDataType::ACL_FLOAT16, &yFp16);
std::unique_ptr<aclTensor, aclnnStatus (*)(const aclTensor*)> yFp16TensorPtr(yFp16, aclDestroyTensor);
std::unique_ptr<void, aclError (*)(void*)> yFp16DeviceAddrPtr(yFp16DeviceAddr, aclrtFree);
CHECK_RET(ret == ACL_SUCCESS, return ret);
// 3. Call the CANN operator library API. Replace with the specific API name.
aclFormat weightFormat = aclFormat::ACL_FORMAT_ND; // Optional: ACL_FORMAT_FRACTAL_NZ
aclTensor* weightPacked = nullptr;
std::vector<int8_t> weightB4PackHostData(n * k / 2, 0); // One B8 data stores two B4 data, so divide by 2 here.
if (weightFormat == aclFormat::ACL_FORMAT_FRACTAL_NZ) {
weightB4PackHostData.resize(CeilAlign(weightDim1 / 2, (int64_t)8) * CeilAlign(weightDim0, (int64_t)16), 0);
}
// Create the weightInt4Pack aclTensor.
ret = CreateAclTensorB4(
weightB4PackHostData, weightPackedShape, &weightB4PackDeviceAddr, weightPackedDtype, &weightPacked,
weightFormat);
std::unique_ptr<aclTensor, aclnnStatus (*)(const aclTensor*)> weightPackedTensorPtr(weightPacked, aclDestroyTensor);
std::unique_ptr<void, aclError (*)(void*)> weightPackedDeviceAddrPtr(weightB4PackDeviceAddr, aclrtFree);
CHECK_RET(ret == ACL_SUCCESS, return ret);
// Convert weight from int32 to int4pack.
uint64_t workspaceSize = 0;
aclOpExecutor* executor;
ret = aclnnConvertWeightToINT4PackGetWorkspaceSize(weight, weightPacked, &workspaceSize, &executor);
CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclnnConvertWeightToINT4PackGetWorkspaceSize failed. ERROR: %d\n", ret);
return ret);
void* workspacePackAddr = nullptr;
std::unique_ptr<void, aclError (*)(void*)> workspacePackAddrPtr(nullptr, aclrtFree);
if (workspaceSize > 0) {
ret = aclrtMalloc(&workspacePackAddr, workspaceSize, ACL_MEM_MALLOC_HUGE_FIRST);
CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("allocate workspace failed. ERROR: %d\n", ret); return ret);
workspacePackAddrPtr.reset(workspacePackAddr);
}
ret = aclnnConvertWeightToINT4Pack(workspacePackAddr, workspaceSize, executor, stream);
CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclnnConvertWeightToINT4Pack failed. ERROR: %d\n", ret); return ret);
// Call the first-phase API of aclnnWeightQuantBatchMatmulV3.
workspaceSize = 0;
executor = nullptr;
ret = aclnnWeightQuantBatchMatmulV3GetWorkspaceSize(
x, weightPacked, antiquantScale, nullptr, nullptr, nullptr, nullptr, groupSize, 0, yFp16, &workspaceSize,
&executor);
CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclnnWeightQuantBatchMatmulV3GetWorkspaceSize 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 aclnnWeightQuantBatchMatmulV3.
ret = aclnnWeightQuantBatchMatmulV3(workspaceAddr, workspaceSize, executor, stream);
CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclnnWeightQuantBatchMatmulV3 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);
// Convert the output to FP32.
workspaceSize = 0;
executor = nullptr;
ret = aclnnCastGetWorkspaceSize(yFp16, aclDataType::ACL_FLOAT, y, &workspaceSize, &executor);
CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclnnCastGetWorkspaceSize failed. ERROR: %d\n", ret); return ret);
// Apply for device memory based on the workspaceSize calculated by the first-phase API.
void* workspaceCastAddr = nullptr;
std::unique_ptr<void, aclError (*)(void*)> workspaceCastAddrPtr(nullptr, aclrtFree);
if (workspaceSize > 0) {
ret = aclrtMalloc(&workspaceCastAddr, workspaceSize, ACL_MEM_MALLOC_HUGE_FIRST);
CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("allocate workspace failed. ERROR: %d\n", ret); return ret);
workspaceCastAddrPtr.reset(workspaceCastAddr);
}
ret = aclnnCast(workspaceCastAddr, workspaceSize, executor, stream);
CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclnnCast2 failed. ERROR: %d\n", ret); return ret);
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 memory. Modify this based on the specific API definition.
auto size = GetShapeSize(yShape);
std::vector<float> resultData(size, 0);
ret = aclrtMemcpy(
resultData.data(), resultData.size() * sizeof(resultData[0]), yDeviceAddr, 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 ret);
PrintMat(resultData, yShape);
return ACL_SUCCESS;
}
int main()
{
// 1. (Boilerplate) Initialize the device/stream. See the ACL API manual.
// Enter the deviceId based on your actual device.
int32_t deviceId = 0;
aclrtStream stream;
auto ret = aclnnWeightQuantBatchMatmulV3Test(deviceId, stream);
CHECK_FREE_RET(ret == ACL_SUCCESS, LOG_PRINT("aclnnWeightQuantBatchMatmulV3Test failed. ERROR: %d\n", ret);
return ret);
Finalize(deviceId, stream);
return 0;
}