aclnnFusedQuantMatmul
Applicable Products
| Product | Supported |
|---|---|
| × | |
| √ | |
| √ | |
| × | |
| × | |
| × |
Function
Description: Fused quantized matrix multiplication and Gelu computation.
Formula:
x1Scale, bias INT32 (no offset in this scenario):
x1Scale, bias BFLOAT16/FLOAT16/FLOAT32 (this scenario has no offset):
x1Scale without bias:
The OP type is defined by the fusedOpType input and supports the following:
gelu_tanh operation:
gelu_erf operation:
Function Prototype
Each operator is divided into a two-phase API. The "aclnnFusedQuantMatmulGetWorkspaceSize" API must be called first to obtain the workspace size required for computation and the executor that encapsulates the operator computation flow. Then, the "aclnnFusedQuantMatmul" API is called to perform the computation.
aclnnStatus aclnnFusedQuantMatmulGetWorkspaceSize(
const aclTensor *x1,
const aclTensor *x2,
const aclTensor *x1Scale,
const aclTensor *x2Scale,
const aclTensor *yScaleOptional,
const aclTensor *x1OffsetOptional,
const aclTensor *x2OffsetOptional,
const aclTensor *yOffsetOptional,
const aclTensor *biasOptional,
const aclTensor *x3Optional,
const char *fusedOpType,
int64_t groupSizeOptional,
aclTensor *out,
uint64_t *workspaceSize,
aclOpExecutor **executor)aclnnStatus aclnnFusedQuantMatmul(
void *workspace,
uint64_t workspaceSize,
aclOpExecutor *executor,
aclrtStream stream)aclnnFusedQuantMatmulGetWorkspaceSize
Parameters:
Parameter Input/Output Description Instruction Data Type Data Format Dimension (Shape) Non-Continuous Tensor x1 Input Input x1 in the formula. INT4, INT8, INT32 ND 2-6 √ x2 Input Input x2 in the formula. INT4, INT8, INT32 ND 2-6 √ x1Scale Input Represents the quantization parameter, x1Scale in the formula. - FLOAT32 ND 1 - x2Scale Input Represents the quantization parameter, which is the input x2Scale in the formula. - FLOAT32, BFLOAT16 ND 1 - yScaleOptional Input Quantization scale parameter for output y. Used in static quantization. Reserved parameter. Not supported in the current version. Pass nullptr. - - - - x1OffsetOptional Input Input x1Offset in the formula. Reserved parameter. Not supported in the current version. Pass nullptr. - - - - x2OffsetOptional Input In Formula, input x2Offset. Reserved Parameter. Not supported in the current version. Required input: nullptr. - - - - yOffsetOptional Input Input yOffset in the formula. Reserved Parameter. Not supported in the Current Version. nullptr is the Required Input. - - - - biasOptional Input Bias in the formula. Pass nullptr when there is no bias. INT32, FLOAT32, BFLOAT16, FLOAT16 ND 1, 3 - x3Optional Input Input of the fused binary operation. Currently, only fused unary operations are supported. This is a reserved parameter and is not supported in the current version. nullptr is required. - - - - fusedOpType Input In the formula, the input fusedOpType specifies the fusion mode supported by the QuantBatchMatmul operator. The fusion mode value must be one of "gelu_erf" or "gelu_tanh". STRING - - - groupSizeOptional Input Specifies the quantization group size in the m, n, and k directions. Reserved Parameter. Not supported in the current version. Required input nullptr. - - - - out Output Output out in the formula. - FLOAT16, BFLOAT16 ND 2-6 ✓ workspaceSize Output Returns the workspace size to be applied for on the Device side. - - - - - executor 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 Value Error Code Description ACLNN_ERR_PARAM_NULLPTR 161001 The input x1, x2, x1Scale, x2Scale, or out is a null pointer. ACLNN_ERR_PARAM_INVALID 161002 The shape of x1, x2, biasOptional, x1Scale, x2Scale, or out does not meet the validation conditions. The data type or data format of x1, x2, biasOptional, x1Scale, x2Scale, or out is not within the supported range. x1, x2, biasOptional, x1Scale, x2Scale, or out is an empty tensor. The input fusedOpType is neither "gelu_tanh" nor "gelu_erf". The input parameters yScaleOptional, x1OffsetOptional, x2OffsetOptional, yOffsetOptional, x3Optional, and groupSizeOptional are not supported in the current version.
aclnnFusedQuantMatmul
Parameters:
Parameter Input/Output Description workspace Input Memory address of the workspace applied on the Device side. workspaceSize Input Size of the workspace applied on the Device side, obtained from the first-phase API aclnnFusedQuantMatmulGetWorkspaceSize. executor Input Operator executor that contains the operator computation flow. stream Input Specifies the Stream on which the task is executed. Return value
aclnnStatus: return code. For details, see aclnn Return Code.
Constraints
Deterministic description:
Atlas A3 training products/Atlas A3 inference products ,Atlas A2 training products/Atlas A2 inference products : aclnnFusedQuantMatmul defaults to a deterministic implementation.
The input and output support the following data type combinations:
x1 x2 x1Scale x2Scale x2OffsetOptional yScaleOptional biasOptional yOffsetOptional out INT8 INT8 FLOAT32 FLOAT32/BFLOAT16 null null null/INT32/BFLOAT16/FLOAT32 null BFLOAT16 INT8 INT8 FLOAT32 FLOAT32 null null null/INT32/FLOAT16/FLOAT32 null FLOAT16 INT4/INT32 INT4/INT32 FLOAT32 FLOAT32/BFLOAT16 null null null/INT32/BFLOAT16/FLOAT32 null BFLOAT16 INT4/INT32 INT4/INT32 FLOAT32 FLOAT32 null null null/INT32/FLOAT16/FLOAT32 null FLOAT16 This API supports x1 pertoken quantization and x2 perchannel/pertensor quantization. The constraints on the input dtype combinations of x1, x2, x1Scale, and x2Scale supported by different quantization modes are as follows:
The x1 data type supports INT8, INT32, and INT4.
When the data type is INT32 or INT4, it is an INT4 quantization scenario:
Currently, only ND input is supported.
Currently, only non-transposed input is supported.
The inner axis of x1 must be an even number.
When the data type is INT32, each INT32 data stores eight INT4 data values, with the corresponding dimension representation: (batch, m, k // 8). k must be a multiple of 8.
x2 supports INT8, INT32, and INT4 data types.
When the data type is INT32 or INT4, it is an INT4 quantization scenario:
This API supports only the 2D ND format.
The inner axis of x2 must be an even number.
When the data type is INT32, each INT32 value stores eight INT4 values:
You can use the aclnnConvertWeightToINT4Pack API to convert the data format of x2 from INT32 (one int32 stores one int4 in bits 0-3) to INT32 (one int32 stores eight int4 values) or INT4 (one int4 represents one int4). See aclnnConvertWeightToINT4Pack.
x1Scale constraints are as follows:
- The shape supports 1D with a shape of (m,). The data type supports FLOAT32.
x2Scale constraints are as follows:
- The shape supports 1D with a shape of (n,) or (1,), where n is consistent with n of x2. The data type supports FLOAT32 and BFLOAT16.
Constraints for biasOptional are as follows:
The shape supports 1D and 3D. In the INT4 quantization scenario, only 1D biasOptional is supported, with the shape (n). For 3D, the biasOptional shape is (batch, 1, n).
The data type supports int32, float32, bfloat16, or float16.
Constraints for out are as follows:
- The shape supports 2D to 6D, (batch, m, n). The data type supports FLOAT16 and BFLOAT16.
Example
The following provides example code for reference only. For details about compilation and running, see Compile and Run Samples.
Atlas A3 training products/Atlas A3 inference products ,Atlas A2 training products/Atlas A2 inference products :
x1 is INT8, x2 is INT8, x1Scale is FLOAT32, and x2Scale is FLOAT32.
#include <iostream>
#include <memory>
#include <vector>
#include "acl/acl.h"
#include "aclnnop/aclnn_fused_quant_matmul.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 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;
}
void Finalize(int32_t deviceId, aclrtStream stream)
{
aclrtDestroyStream(stream);
aclrtResetDevice(deviceId);
aclFinalize();
}
int aclnnFusedQuantMatmulTest(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 inputs and outputs. Custom construction based on the API is required.
std::vector<int64_t> x1Shape = {2, 8192}; // (m,k)
std::vector<int64_t> x2Shape = {8192, 128}; // (k,n)
std::vector<int64_t> x1ScaleShape = {2}; // x1ScaleShape = [M]
std::vector<int64_t> x2ScaleShape = {128}; // x2ScaleShape = [N]
std::vector<int64_t> outShape = {2, 128};
void* x1DeviceAddr = nullptr;
void* x2DeviceAddr = nullptr;
void* x2ScaleDeviceAddr = nullptr;
void* x1ScaleDeviceAddr = nullptr;
void* outDeviceAddr = nullptr;
aclTensor* x1 = nullptr;
aclTensor* x2 = nullptr;
aclTensor* x2Scale = nullptr;
aclTensor* x1Scale = nullptr;
aclTensor* out = nullptr;
std::vector<int8_t> x1HostData(GetShapeSize(x1Shape), 1);
std::vector<int8_t> x2HostData(GetShapeSize(x2Shape), 1);
std::vector<float> x1ScaleHostData(GetShapeSize(x1ScaleShape), 1);
std::vector<float> x2ScaleHostData(GetShapeSize(x2ScaleShape), 1);
std::vector<uint16_t> outHostData(GetShapeSize(outShape), 1);
// Create the x1 aclTensor.
ret = CreateAclTensor(x1HostData, x1Shape, &x1DeviceAddr, aclDataType::ACL_INT8, &x1);
std::unique_ptr<aclTensor, aclnnStatus (*)(const aclTensor*)> x1TensorPtr(x1, aclDestroyTensor);
std::unique_ptr<void, aclError (*)(void*)> x1DeviceAddrPtr(x1DeviceAddr, aclrtFree);
CHECK_RET(ret == ACL_SUCCESS, return ret);
// Create the x2 aclTensor.
ret = CreateAclTensor(x2HostData, x2Shape, &x2DeviceAddr, aclDataType::ACL_INT8, &x2);
std::unique_ptr<aclTensor, aclnnStatus (*)(const aclTensor*)> x2TensorPtr(x2, aclDestroyTensor);
std::unique_ptr<void, aclError (*)(void*)> x2DeviceAddrPtr(x2DeviceAddr, aclrtFree);
CHECK_RET(ret == ACL_SUCCESS, return ret);
// Create the x1Scale aclTensor.
ret = CreateAclTensor(x1ScaleHostData, x1ScaleShape, &x1ScaleDeviceAddr, aclDataType::ACL_FLOAT, &x1Scale);
std::unique_ptr<aclTensor, aclnnStatus (*)(const aclTensor*)> x1ScaleTensorPtr(x1Scale, aclDestroyTensor);
std::unique_ptr<void, aclError (*)(void*)> x1ScaleDeviceAddrPtr(x1ScaleDeviceAddr, aclrtFree);
CHECK_RET(ret == ACL_SUCCESS, return ret);
// Create the x2Scale aclTensor.
ret = CreateAclTensor(x2ScaleHostData, x2ScaleShape, &x2ScaleDeviceAddr, aclDataType::ACL_FLOAT, &x2Scale);
std::unique_ptr<aclTensor, aclnnStatus (*)(const aclTensor*)> scaleTensorPtr(x2Scale, aclDestroyTensor);
std::unique_ptr<void, aclError (*)(void*)> x2ScaleDeviceAddrPtr(x2ScaleDeviceAddr, aclrtFree);
CHECK_RET(ret == ACL_SUCCESS, return ret);
// Create the out aclTensor.
ret = CreateAclTensor(outHostData, outShape, &outDeviceAddr, aclDataType::ACL_FLOAT16, &out);
std::unique_ptr<aclTensor, aclnnStatus (*)(const aclTensor*)> outTensorPtr(out, aclDestroyTensor);
std::unique_ptr<void, aclError (*)(void*)> outDeviceAddrPtr(outDeviceAddr, aclrtFree);
CHECK_RET(ret == ACL_SUCCESS, return ret);
int64_t groupSize = 0;
const char fusedOpType[] = "gelu_tanh";
// 3. Call the CANN operator library API. Replace with the specific API name.
uint64_t workspaceSize = 0;
aclOpExecutor* executor = nullptr;
ret = aclnnFusedQuantMatmulGetWorkspaceSize(
x1, x2, x1Scale, x2Scale, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, fusedOpType, groupSize, out,
&workspaceSize, &executor);
CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclnnFusedQuantMatmulGetWorkspaceSize 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 aclnnFusedQuantMatmul.
ret = aclnnFusedQuantMatmul(workspaceAddr, workspaceSize, executor, stream);
CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclnnFusedQuantMatmul 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);
// 5. Obtain the output value. Copy the result from the device-side memory to the host side. Modify this based on the specific API definition.
auto size = GetShapeSize(outShape);
std::vector<uint16_t> resultData(
size, 0); // In C, fp16 data cannot be printed directly. Read it as uint16 and manually convert it to fp16 via binary conversion.
ret = aclrtMemcpy(
resultData.data(), resultData.size() * sizeof(resultData[0]), outDeviceAddr, 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: %u\n", i, resultData[i]);
}
return ACL_SUCCESS;
}
int main()
{
// 1. (Boilerplate) Initialize the device/stream. See the ACL API manual.
// Set deviceId based on your actual device.
int32_t deviceId = 0;
aclrtStream stream;
auto ret = aclnnFusedQuantMatmulTest(deviceId, stream);
CHECK_FREE_RET(ret == ACL_SUCCESS, LOG_PRINT("aclnnFusedQuantMatmulTest failed. ERROR: %d\n", ret); return ret);
Finalize(deviceId, stream);
return 0;
}