aclnnQuantMatmulV3
Supported Products
| Product | Supported |
|---|---|
| √ | |
| √ | |
| × | |
| √ | |
| × |
Function
- Description: Performs quantized matrix multiplication, supporting at least two-dimensional input and at most six-dimensional input. Similar APIs include aclnnMm (only two-dimensional tensors can be used as the input of matrix multiplication) and aclnnBatchMatMul (only three-dimensional matrix multiplication is supported, whose first dimension is the batch dimension). It supports the T-C && T-T quantization mode.
- Formula:
No bias:
bias (INT32):
Atlas A2 training products/Atlas A2 inference products andAtlas A3 training series products/Atlas A3 inference series products : bias BFLOAT16/FLOAT32 is supported (no offset in this scenario).
Prototype
Each operator has two-phase API calls. First, aclnnQuantMatmulV3GetWorkspaceSize is called to obtain the workspace size required for computation and the executor that contains the operator computation process. Then, aclnnQuantMatmulV3 is called to perform computation.
aclnnStatus aclnnQuantMatmulV3GetWorkspaceSize(const aclTensor* x1, const aclTensor* x2, const aclTensor* scale, const aclTensor* offset, const aclTensor* bias, bool transposeX1, bool transposeX2, const aclTensor* out, uint64_t* workspaceSize, aclOpExecutor** executor)aclnnStatus aclnnQuantMatmulV3(void *workspace, uint64_t workspaceSize, aclOpExecutor *executor, aclrtStream stream)
aclnnQuantMatmulV3GetWorkspaceSize
Parameters:
x1 (aclTensor*, compute input): input x1 in the formula, aclTensor on the device. The data format can be ND. Non-contiguous tensors are supported only when the last two axes are transposed. The shape can be two- to six-dimensional. When transposeX1 is false, the shape is represented by (batch, m, k). When transposeX1 is true, the shape is represented by (batch, k, m), where batch is optional.
Atlas inference series products ,Atlas A2 training products/Atlas A2 inference products , andAtlas A3 training series products/Atlas A3 inference series products : The size of the last dimension of x1 cannot exceed 65535. The last dimension of x1 refers to m when transposeX1 is true or k when transposeX1 is false.Atlas inference series products : The data type can be INT8.Atlas A2 training products/Atlas A2 inference products andAtlas A3 training series products/Atlas A3 inference series products : The data type can be INT8, INT32, or INT4. When the data type is INT32 or INT4, the INT4 quantization scenario is used. Currently, transposeX1 can only be set to false. When the data type of x1 is INT4, the shape is represented by (batch, m, k), where k must be an even number. When the data type of x1 is INT32, each INT32 data entry stores eight INT4 data entries, with shape represented by (batch, m, k // 8), where k must be a multiple of 8.
x2 (aclTensor*, compute input): input x2 in the formula, aclTensor on the device. The data format can be ND format or AI processor affinity data layout format. In ND format, non-contiguous tensors are supported only when the last two axes are transposed. In other scenarios, non-contiguous tensors are not supported
Atlas inference series products ,Atlas A2 training products/Atlas A2 inference products , andAtlas A3 training series products/Atlas A3 inference series products : The size of the last dimension of x2 cannot exceed 65535. The last dimension of x2 refers to k when transposeX2 is true or n when transposeX2 is false.- In ND format, the shape can be two- to six-dimensional. When transposeX2 is false, the shape is represented by (batch, k, n). When transposeX2 is true, the shape is represented by (batch, n, k), where batch is optional and k is the same as that in the shape of x1.
- In AI processor affinity data layout format, the shape can be four- to eight-dimensional. When transposeX2 is true, the shape is represented by (batch, k1, n1, n0, k0), where batch is optional, k0 = 32, and n0 = 16. k in the shape of x1 and k1 in the shape of x2 must meet the following relationship: ceil (k/32) = k1. When transposeX2 is false, the shape is represented by (batch, n1, k1, k0, n0), where batch is optional, k0 = 16, and n0 = 32. k in the shape of x1 and k1 in the shape of x2 must meet the following relationship: ceil (k/16) = k1. aclnnCalculateMatmulWeightSizeV2 and aclnnTransMatmulWeight can be used to convert the input from ND format to AI processor affinity data layout format.
Atlas inference series products : The data type can be INT8. If the input x2 is in AI processor affinity data layout format, transposeX2 cannot be set to false.Atlas A2 training products/Atlas A2 inference products andAtlas A3 training series products/Atlas A3 inference series products : The data type can be INT8, INT32, or INT4. When the data type is INT32 or INT4, the INT4 quantization scenario is used. Currently, only the two-dimensional ND format is supported.- When the data type is INT4, if transposeX2 is true, the shape is represented by (n, k), where k must be an even number; if transposeX2 is false, the shape is represented by (k, n), where n must be an even number.
- When the data type is INT32, each INT32 data entry stores eight INT4 data entries. If transposeX2 is true, the shape is represented by (n, k // 8), where k must be a multiple of 8. If transposeX2 is false, the shape is represented by (k, n // 8), where n must be a multiple of 8.
- The aclnnConvertWeightToINT4Pack API can be used to convert x2 from INT32 (one int32 space stores one int4 data entry in bits 0–3) to INT32 (one int32 space stores eight int4 data entries) or INT4 (one int4 space stores one int4 data entry). For details, see aclnnConvertWeightToINT4Pack.
scale (aclTensor*, compute input): quantization parameter, scale in the formula, aclTensor on the device. The data format can be ND. The shape is one-dimensional (t,), and t = 1 or n, where n is the same as n of x2.
Atlas inference series products : The data type can be UINT64 or INT64.Atlas A2 training products/Atlas A2 inference products andAtlas A3 training series products/Atlas A3 inference series products : The data type can be UINT64, INT64, FLOAT32, or BFLOAT16.- If the original input type does not conform with the data type combinations described in Constraints, call the aclnn API of the TransQuantParamV2 operator to convert scale to the INT64 or UINT64 type in advance.
offset (aclTensor*, compute input): offset in the formula, aclTensor on the device. The data type can be FLOAT32. The data format can be ND. The shape is one-dimensional (t,), and t = 1 or n, where n is the same as n of x2. If the output data type is INT8, the offset can exist. For other input types, nullptr needs to be passed.
bias (aclTensor*, compute input): bias in the formula, aclTensor on the device. This is an optional parameter. The data format can be ND. The shape can be one-dimensional (n,) or three-dimensional (batch, 1, n), where n is the same as n of x2. When the shape of out is two-, four-, five-, or six-dimensional, the shape of bias can only be one-dimensional (n,).
Atlas inference series products : The data type can be INT32.Atlas A2 training products/Atlas A2 inference products andAtlas A3 training series products/Atlas A3 inference series products : The data type can be INT32, BFLOAT16 or FLOAT32. When x1 and x2 are INT32 or INT4, the shape of bias can only be one-dimensional (n,).
transposeX1 (bool, compute input): whether the input shape of x1 is transposed. When transposeX1 is false, the shape is represented by (batch, m, k). When transposeX1 is true, the shape is represented by (batch, k, m), where batch is optional.
Atlas A2 training products/Atlas A2 inference products andAtlas A3 training series products/Atlas A3 inference series products : If x1 and x2 are INT32 or INT4, transposeX1 can only be false.
transposeX2 (bool, compute input): whether the input shape of x2 is transposed.
- In ND format, if transposeX2 is false, the shape is represented by (batch, k, n); if transposeX2 is true, the shape is represented by (batch, n, k), where batch is optional and k is the same as that in the shape of x1.
- In AI processor affinity data layout format, if transposeX2 is true, the shape is represented by (batch, k1, n1, n0, k0), where batch is optional, k0 = 32, and n0 = 16. k in the shape of x1, and k1 in the shape of x2 must meet the following relationship: ceil (k/32) = k1. When transposeX2 is false, the shape is represented by (batch, n1, k1, k0, n0), where batch is optional, k0 = 16, and n0 = 32. k in the shape of x1 and k1 in the shape of x2 must meet the following relationship: ceil (k/16) = k1.
out (aclTensor*, compute output): out in the formula, aclTensor on the device. The data format can be ND. Non-contiguous tensors are supported. The shape can be two- to six-dimensional, and is represented by (batch, m, n), where batch is optional. The batch dimensions of x1 and x2 can be broadcast, the output batch is the same as the broadcast batch, m is the same as the m of x1, and n is the same as the n of x2.
Atlas inference series products : The data type can be FLOAT16 or INT8.Atlas A2 training products/Atlas A2 inference products andAtlas A3 training series products/Atlas A3 inference series products : The data type can be FLOAT16, INT8, BFLOAT16, or INT32.
workspaceSize (uint64_t*, output): size of the workspace required to be allocated on the device.
executor (aclOpExecutor**, output): operator executor, containing the operator computation process.
Returns:
aclnnStatus: status code. For details, see aclnn Return Codes.
The first-phase API implements input parameter verification. The following errors may be thrown: - 161001 (ACLNN_ERR_PARAM_NULLPTR): 1. The passed x1, x2, scale, or out is a null pointer. - 161002 (ACLNN_ERR_PARAM_INVALID): 1. The data type or format of x1, x2, bias, scale, offset, or out is not supported. 2. The shape of x1, x2, bias, scale, offset, or out does not meet the verification condition. 3. x1, x2, bias, scale, offset, or out is an empty tensor.
aclnnQuantMatmulV3
Parameters:
- workspace (void*, input): address of the workspace to be allocated on the device.
- workspaceSize (uint64_t, input): size of the workspace to be allocated on the device, which is obtained by the first-phase API aclnnQuantMatmulV3GetWorkspaceSize.
- executor (aclOpExecutor*, input): operator executor, containing the operator computation process.
- stream (aclrtStream, input): stream for executing the task.
Returns:
aclnnStatus: status code. For details, see aclnn Return Codes.
Constraints
Deterministic description:
Atlas training series products andAtlas inference series products : aclnnQuantMatmulV3 defaults to a deterministic implementation.
Atlas A2 training products/Atlas A2 inference products ,Atlas A3 training series products/Atlas A3 inference series products , andAtlas inference series products : Before calling this API, you can use aclnnTransMatmulWeight to process x2 in ND format to obtain x2 in AI processor affinity data layout format. The following data type combinations are supported for the input and output. The combinations support T-C && T-T quantization mode.Atlas inference series products :x1 x2 scale offset bias out INT8 INT8 UINT64/INT64 null null/INT32 FLOAT16 INT8 INT8 UINT64/INT64 null/FLOAT32 null/INT32 INT8 Atlas A2 training products/Atlas A2 inference products andAtlas A3 training series products/Atlas A3 inference series products :x1 x2 scale offset bias out INT8 INT8 UINT64/INT64 null null/INT32 FLOAT16 INT8 INT8 UINT64/INT64 null/FLOAT32 null/INT32 INT8 INT8 INT8 FLOAT32/BFLOAT16 null null/INT32/BFLOAT16/FLOAT32 BFLOAT16 INT4/INT32 INT4/INT32 UINT64/INT64 null null/INT32 FLOAT16 INT8 INT8 FLOAT32/BFLOAT16 null null/INT32 INT32
Example
Atlas A2 training products/Atlas A2 inference products andAtlas A3 training series products/Atlas A3 inference series products : The following example is for reference only. For details, see Compilation and Running Sample.#include <iostream> #include <memory> #include <vector> #include "acl/acl.h" #include "aclnnop/aclnn_quant_matmul_v3.h" #include "aclnnop/aclnn_trans_quant_param_v2.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) { // (Fixed writing) Initialize resources. 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. 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 the data on the host to the memory on the device. 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); // Compute the strides of the 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; } void Finalize(int32_t deviceId, aclrtStream stream) { aclrtDestroyStream(stream); aclrtResetDevice(deviceId); aclFinalize(); } int aclnnQuantMatmulV3Test(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 based on the API definition. std::vector<int64_t> x1Shape = {5, 2}; std::vector<int64_t> x2Shape = {2, 3}; std::vector<int64_t> biasShape = {3}; std::vector<int64_t> offsetShape = {3}; std::vector<int64_t> scaleShape = {3}; std::vector<int64_t> outShape = {5, 3}; void *x1DeviceAddr = nullptr; void *x2DeviceAddr = nullptr; void *scaleDeviceAddr = nullptr; void *quantParamDeviceAddr = nullptr; void *offsetDeviceAddr = nullptr; void *biasDeviceAddr = nullptr; void *outDeviceAddr = nullptr; aclTensor *x1 = nullptr; aclTensor *x2 = nullptr; aclTensor *bias = nullptr; aclTensor *scale = nullptr; aclTensor *quantParam = nullptr; aclTensor *offset = nullptr; aclTensor *out = nullptr; std::vector<int8_t> x1HostData = {1, 1, 1, 1, 1, 1, 1, 1, 1, 1}; std::vector<int8_t> x2HostData = {1, 1, 1, 1, 1, 1}; std::vector<int32_t> biasHostData = {1, 1, 1}; std::vector<float> scaleHostData = {1, 1, 1}; std::vector<float> offsetHostData = {1, 1, 1}; std::vector<uint16_t> outHostData = {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}; // Half-precision float16 // Create an 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 an 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 a scale aclTensor. ret = CreateAclTensor(scaleHostData, scaleShape, &scaleDeviceAddr, aclDataType::ACL_FLOAT, &scale); std::unique_ptr<aclTensor, aclnnStatus (*)(const aclTensor *)> scaleTensorPtr(scale, aclDestroyTensor); std::unique_ptr<void, aclError (*)(void *)> scaleDeviceAddrPtr(scaleDeviceAddr, aclrtFree); CHECK_RET(ret == ACL_SUCCESS, return ret); // Create a quantParam aclTensor. ret = CreateAclTensor(scaleHostData, scaleShape, &quantParamDeviceAddr, aclDataType::ACL_UINT64, &quantParam); std::unique_ptr<aclTensor, aclnnStatus (*)(const aclTensor *)> quantParamTensorPtr(quantParam, aclDestroyTensor); std::unique_ptr<void, aclError (*)(void *)> quantParamDeviceAddrPtr(quantParamDeviceAddr, aclrtFree); CHECK_RET(ret == ACL_SUCCESS, return ret); // Create an offset aclTensor. ret = CreateAclTensor(offsetHostData, offsetShape, &offsetDeviceAddr, aclDataType::ACL_FLOAT, &offset); std::unique_ptr<aclTensor, aclnnStatus (*)(const aclTensor *)> offsetTensorPtr(offset, aclDestroyTensor); std::unique_ptr<void, aclError (*)(void *)> offsetDeviceAddrPtr(offsetDeviceAddr, aclrtFree); CHECK_RET(ret == ACL_SUCCESS, return ret); // Create a bias aclTensor. ret = CreateAclTensor(biasHostData, biasShape, &biasDeviceAddr, aclDataType::ACL_INT32, &bias); std::unique_ptr<aclTensor, aclnnStatus (*)(const aclTensor *)> biasTensorPtr(bias, aclDestroyTensor); std::unique_ptr<void, aclError (*)(void *)> biasDeviceAddrPtr(biasDeviceAddr, aclrtFree); CHECK_RET(ret == ACL_SUCCESS, return ret); // Create an 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); bool transposeX1 = false; bool transposeX2 = false; // 3. Call the CANN operator library API, which needs to be replaced with the actual API. uint64_t workspaceSize = 0; aclOpExecutor *executor = nullptr; // Call the aclnn API of the TransQuantParamV2 operator in advance for scale of the FLOAT data type. // Call the first-phase API of aclnnTransQuantParamV2. ret = aclnnTransQuantParamV2GetWorkspaceSize(scale, offset, quantParam, &workspaceSize, &executor); CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclnnTransQuantParamV2GetWorkspaceSize failed. ERROR: %d\n", ret); return ret); // Allocate device memory based on workspaceSize computed by the first-phase API. void *workspaceAddr = nullptr; std::unique_ptr<void, aclError (*)(void *)> workspaceAddrPtrV2(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); workspaceAddrPtrV2.reset(workspaceAddr); } // Call the second-phase API of aclnnTransQuantParamV2. ret = aclnnTransQuantParamV2(workspaceAddr, workspaceSize, executor, stream); CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclnnTransQuantParamV2 failed. ERROR: %d\n", ret); return ret); // Call the first-phase API of aclnnQuantMatmulV3. ret = aclnnQuantMatmulV3GetWorkspaceSize(x1, x2, quantParam, nullptr, bias, transposeX1, transposeX2, out, &workspaceSize, &executor); CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclnnQuantMatmulV3GetWorkspaceSize failed. ERROR: %d\n", ret); return ret); // Allocate device memory based on workspaceSize computed by the first-phase API. workspaceAddr = nullptr; std::unique_ptr<void, aclError (*)(void *)> workspaceAddrPtrV3(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); workspaceAddrPtrV3.reset(workspaceAddr); } // Call the second-phase API of aclnnQuantMatmulV3. ret = aclnnQuantMatmulV3(workspaceAddr, workspaceSize, executor, stream); CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclnnQuantMatmulV3 failed. ERROR: %d\n", ret); return ret); // 4. (Fixed writing) Wait until the task execution is 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. Modify the configuration based on the API definition. auto size = GetShapeSize(outShape); std::vector<uint16_t> resultData( size, 0); // The fp16 data cannot be directly printed in the C language. The data needs to be read by using uint16 and converted into fp16 in binary mode. 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. (Fixed writing) Initialize the device and stream. For details, see the ACL API manual. // Set the device ID in use. int32_t deviceId = 0; aclrtStream stream; auto ret = aclnnQuantMatmulV3Test(deviceId, stream); CHECK_FREE_RET(ret == ACL_SUCCESS, LOG_PRINT("aclnnQuantMatmulV3Test failed. ERROR: %d\n", ret); return ret); Finalize(deviceId, stream); return 0; }Atlas A2 training products/Atlas A2 inference products andAtlas A3 training series products/Atlas A3 inference series products : The sample code (transposeX2 = false) when x2 is in AI processor affinity data layout format is as follows (for reference only). For details about the compilation and running process, see Compilation and Running Sample.#include <iostream> #include <memory> #include <vector> #include "acl/acl.h" #include "aclnnop/aclnn_permute.h" #include "aclnnop/aclnn_quant_matmul_v3.h" #include "aclnnop/aclnn_trans_matmul_weight.h" #include "aclnnop/aclnn_trans_quant_param_v2.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) { // (Fixed writing) Initialize resources. 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. 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 the data on the host to the memory on the device. 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); // Compute the strides of the 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; } void Finalize(int32_t deviceId, aclrtStream stream) { aclrtDestroyStream(stream); aclrtResetDevice(deviceId); aclFinalize(); } template <typename T> int CreateAclTensorX2(const std::vector<T> &hostData, const std::vector<int64_t> &shape, void **deviceAddr, aclDataType dataType, aclTensor **tensor) { auto size = static_cast<uint64_t>(GetShapeSize(shape)); const aclIntArray *mat2Size = aclCreateIntArray(shape.data(), shape.size()); auto ret = aclnnCalculateMatmulWeightSizeV2(mat2Size, dataType, &size); CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclnnCalculateMatmulWeightSizeV2 failed. ERROR: %d\n", ret); return ret); size *= sizeof(T); // Call aclrtMalloc to allocate memory on the device. 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 the data on the host to the memory on the device. 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); // Compute the strides of the 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]; } std::vector<int64_t> storageShape; storageShape.push_back(GetShapeSize(shape)); // Call aclCreateTensor to create an aclTensor. *tensor = aclCreateTensor(shape.data(), shape.size(), dataType, strides.data(), 0, aclFormat::ACL_FORMAT_ND, storageShape.data(), storageShape.size(), *deviceAddr); return 0; } int aclnnQuantMatmulV3Test(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 based on the API definition. std::vector<int64_t> x1Shape = {5, 2}; std::vector<int64_t> x2Shape = {2, 3}; std::vector<int64_t> biasShape = {3}; std::vector<int64_t> offsetShape = {3}; std::vector<int64_t> scaleShape = {3}; std::vector<int64_t> outShape = {5, 3}; void *x1DeviceAddr = nullptr; void *x2DeviceAddr = nullptr; void *scaleDeviceAddr = nullptr; void *quantParamDeviceAddr = nullptr; void *offsetDeviceAddr = nullptr; void *biasDeviceAddr = nullptr; void *outDeviceAddr = nullptr; aclTensor *x1 = nullptr; aclTensor *x2 = nullptr; aclTensor *bias = nullptr; aclTensor *scale = nullptr; aclTensor *quantParam = nullptr; aclTensor *offset = nullptr; aclTensor *out = nullptr; std::vector<int8_t> x1HostData = {1, 1, 1, 1, 1, 1, 1, 1, 1, 1}; std::vector<int8_t> x2HostData = {1, 1, 1, 1, 1, 1}; std::vector<int32_t> biasHostData = {1, 1, 1}; std::vector<float> scaleHostData = {1, 1, 1}; std::vector<float> offsetHostData = {1, 1, 1}; std::vector<uint16_t> outHostData = {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}; // Half-precision float16 // Create an 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 an x2 aclTensor in AI processor affinity data layout format. ret = CreateAclTensorX2(x2HostData, x2Shape, &x2DeviceAddr, aclDataType::ACL_INT8, &x2); std::unique_ptr<aclTensor, aclnnStatus (*)(const aclTensor *)> x2HPTensorPtr(x2, aclDestroyTensor); std::unique_ptr<void, aclError (*)(void *)> x2HPDeviceAddrPtr(x2DeviceAddr, aclrtFree); CHECK_RET(ret == ACL_SUCCESS, return ret); // Create a scale aclTensor. ret = CreateAclTensor(scaleHostData, scaleShape, &scaleDeviceAddr, aclDataType::ACL_FLOAT, &scale); std::unique_ptr<aclTensor, aclnnStatus (*)(const aclTensor *)> scaleTensorPtr(scale, aclDestroyTensor); std::unique_ptr<void, aclError (*)(void *)> scaleDeviceAddrPtr(scaleDeviceAddr, aclrtFree); CHECK_RET(ret == ACL_SUCCESS, return ret); // Create a quantParam aclTensor. ret = CreateAclTensor(scaleHostData, scaleShape, &quantParamDeviceAddr, aclDataType::ACL_UINT64, &quantParam); std::unique_ptr<aclTensor, aclnnStatus (*)(const aclTensor *)> quantParamTensorPtr(quantParam, aclDestroyTensor); std::unique_ptr<void, aclError (*)(void *)> quantParamDeviceAddrPtr(quantParamDeviceAddr, aclrtFree); CHECK_RET(ret == ACL_SUCCESS, return ret); // Create an offset aclTensor. ret = CreateAclTensor(offsetHostData, offsetShape, &offsetDeviceAddr, aclDataType::ACL_FLOAT, &offset); std::unique_ptr<aclTensor, aclnnStatus (*)(const aclTensor *)> offsetTensorPtr(offset, aclDestroyTensor); std::unique_ptr<void, aclError (*)(void *)> offsetDeviceAddrPtr(offsetDeviceAddr, aclrtFree); CHECK_RET(ret == ACL_SUCCESS, return ret); // Create a bias aclTensor. ret = CreateAclTensor(biasHostData, biasShape, &biasDeviceAddr, aclDataType::ACL_INT32, &bias); std::unique_ptr<aclTensor, aclnnStatus (*)(const aclTensor *)> biasTensorPtr(bias, aclDestroyTensor); std::unique_ptr<void, aclError (*)(void *)> biasDeviceAddrPtr(biasDeviceAddr, aclrtFree); CHECK_RET(ret == ACL_SUCCESS, return ret); // Create an 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); bool transposeX1 = false; bool transposeX2 = false; // 3. Call the CANN operator library API, which needs to be replaced with the actual API. uint64_t workspaceSize = 0; aclOpExecutor *executor = nullptr; void *workspaceAddr = nullptr; // Call the first-phase API of aclnnTransMatmulWeight. ret = aclnnTransMatmulWeightGetWorkspaceSize(x2, &workspaceSize, &executor); CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclnnTransMatmulWeightGetWorkspaceSize failed. ERROR: %d\n", ret); return ret); // Allocate device memory based on workspaceSize computed by the first-phase API. std::unique_ptr<void, aclError (*)(void *)> workspaceAddrPtrTrans(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); workspaceAddrPtrTrans.reset(workspaceAddr); } // Call the second-phase API of aclnnTransMatmulWeight. ret = aclnnTransMatmulWeight(workspaceAddr, workspaceSize, executor, stream); CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclnnTransMatmulWeight failed. ERROR: %d\n", ret); return ret); // Call the aclnn API of the TransQuantParamV2 operator in advance for scale of the FLOAT data type. // Call the first-phase API of aclnnTransQuantParamV2. ret = aclnnTransQuantParamV2GetWorkspaceSize(scale, offset, quantParam, &workspaceSize, &executor); CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclnnTransQuantParamV2GetWorkspaceSize failed. ERROR: %d\n", ret); return ret); // Allocate device memory based on workspaceSize computed by the first-phase API. workspaceAddr = nullptr; std::unique_ptr<void, aclError (*)(void *)> workspaceAddrPtrV2(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); workspaceAddrPtrV2.reset(workspaceAddr); } // Call the second-phase API of aclnnTransQuantParamV2. ret = aclnnTransQuantParamV2(workspaceAddr, workspaceSize, executor, stream); CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclnnTransQuantParamV2 failed. ERROR: %d\n", ret); return ret); // Call the first-phase API of aclnnQuantMatmulV3. workspaceSize = 0; ret = aclnnQuantMatmulV3GetWorkspaceSize(x1, x2, quantParam, nullptr, bias, transposeX1, transposeX2, out, &workspaceSize, &executor); CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclnnQuantMatmulV3GetWorkspaceSize failed. ERROR: %d\n", ret); return ret); // Allocate device memory based on workspaceSize computed by the first-phase API. std::unique_ptr<void, aclError (*)(void *)> workspaceAddrPtrV3(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); workspaceAddrPtrV3.reset(workspaceAddr); } // Call the second-phase API of aclnnQuantMatmulV3. ret = aclnnQuantMatmulV3(workspaceAddr, workspaceSize, executor, stream); CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclnnQuantMatmulV3 failed. ERROR: %d\n", ret); return ret); // 4. (Fixed writing) Wait until the task execution is 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. Modify the configuration based on the API definition. auto size = GetShapeSize(outShape); std::vector<uint16_t> resultData( size, 0); // The fp16 data cannot be directly printed in the C language. The data needs to be read by using uint16 and converted into fp16 in binary mode. 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. (Fixed writing) Initialize the device and stream. For details, see the ACL API manual. // Set the device ID in use. int32_t deviceId = 0; aclrtStream stream; auto ret = aclnnQuantMatmulV3Test(deviceId, stream); CHECK_FREE_RET(ret == ACL_SUCCESS, LOG_PRINT("aclnnQuantMatmulV3Test failed. ERROR: %d\n", ret); return ret); Finalize(deviceId, stream); return 0; }Atlas inference series products : The sample code (transposeX2 = true) when x2 is in AI processor affinity data layout format is as follows (for reference only). For details about the compilation and running process, see Compilation and Running Sample.#include <iostream> #include <memory> #include <vector> #include "acl/acl.h" #include "aclnnop/aclnn_permute.h" #include "aclnnop/aclnn_quant_matmul_v3.h" #include "aclnnop/aclnn_trans_matmul_weight.h" #include "aclnnop/aclnn_trans_quant_param_v2.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) { // (Fixed writing) Initialize resources. 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. 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 the data on the host to the memory on the device. 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); // Compute the strides of the 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; } void Finalize(int32_t deviceId, aclrtStream stream) { aclrtDestroyStream(stream); aclrtResetDevice(deviceId); aclFinalize(); } template <typename T> int CreateAclTensorX2(const std::vector<T> &hostData, const std::vector<int64_t> &shape, void **deviceAddr, aclDataType dataType, aclTensor **tensor) { auto size = static_cast<uint64_t>(GetShapeSize(shape)); const aclIntArray *mat2Size = aclCreateIntArray(shape.data(), shape.size()); auto ret = aclnnCalculateMatmulWeightSizeV2(mat2Size, dataType, &size); CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclnnCalculateMatmulWeightSizeV2 failed. ERROR: %d\n", ret); return ret); size *= sizeof(T); // Call aclrtMalloc to allocate memory on the device. 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 the data on the host to the memory on the device. 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); // Compute the strides of the 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]; } std::vector<int64_t> storageShape; storageShape.push_back(GetShapeSize(shape)); // Call aclCreateTensor to create an aclTensor. *tensor = aclCreateTensor(shape.data(), shape.size(), dataType, strides.data(), 0, aclFormat::ACL_FORMAT_ND, storageShape.data(), storageShape.size(), *deviceAddr); return 0; } int aclnnQuantMatmulV3Test(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 based on the API definition. std::vector<int64_t> x1Shape = {5, 2}; std::vector<int64_t> x2Shape = {2, 3}; std::vector<int64_t> x2TransposedShape = {3, 2}; std::vector<int64_t> biasShape = {3}; std::vector<int64_t> offsetShape = {3}; std::vector<int64_t> scaleShape = {3}; std::vector<int64_t> outShape = {5, 3}; void *x1DeviceAddr = nullptr; void *x2DeviceAddr = nullptr; void *x2TransposedDeviceAddr = nullptr; void *scaleDeviceAddr = nullptr; void *quantParamDeviceAddr = nullptr; void *offsetDeviceAddr = nullptr; void *biasDeviceAddr = nullptr; void *outDeviceAddr = nullptr; aclTensor *x1 = nullptr; aclTensor *x2 = nullptr; aclTensor *x2Transposed = nullptr; aclTensor *bias = nullptr; aclTensor *scale = nullptr; aclTensor *quantParam = nullptr; aclTensor *offset = nullptr; aclTensor *out = nullptr; std::vector<int8_t> x1HostData = {1, 1, 1, 1, 1, 1, 1, 1, 1, 1}; std::vector<int8_t> x2HostData = {1, 1, 1, 1, 1, 1}; std::vector<int8_t> x2TransposedHostData = {1, 1, 1, 1, 1, 1}; std::vector<int32_t> biasHostData = {1, 1, 1}; std::vector<float> scaleHostData = {1, 1, 1}; std::vector<float> offsetHostData = {1, 1, 1}; std::vector<uint16_t> outHostData = {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}; // Half-precision float16 // Create an 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 an x2 aclTensor in AI processor affinity data layout format. ret = CreateAclTensorX2(x2HostData, x2Shape, &x2DeviceAddr, aclDataType::ACL_INT8, &x2); std::unique_ptr<aclTensor, aclnnStatus (*)(const aclTensor *)> x2HPTensorPtr(x2, aclDestroyTensor); std::unique_ptr<void, aclError (*)(void *)> x2HPDeviceAddrPtr(x2DeviceAddr, aclrtFree); CHECK_RET(ret == ACL_SUCCESS, return ret); // Create an x2Transposed aclTensor in AI processor affinity data layout format. ret = CreateAclTensorX2(x2TransposedHostData, x2TransposedShape, &x2TransposedDeviceAddr, aclDataType::ACL_INT8, &x2Transposed); std::unique_ptr<aclTensor, aclnnStatus (*)(const aclTensor *)> x2TransposedHPTensorPtr(x2Transposed, aclDestroyTensor); std::unique_ptr<void, aclError (*)(void *)> x2TransposedHPDeviceAddrPtr(x2TransposedDeviceAddr, aclrtFree); CHECK_RET(ret == ACL_SUCCESS, return ret); // Create a scale aclTensor. ret = CreateAclTensor(scaleHostData, scaleShape, &scaleDeviceAddr, aclDataType::ACL_FLOAT, &scale); std::unique_ptr<aclTensor, aclnnStatus (*)(const aclTensor *)> scaleTensorPtr(scale, aclDestroyTensor); std::unique_ptr<void, aclError (*)(void *)> scaleDeviceAddrPtr(scaleDeviceAddr, aclrtFree); CHECK_RET(ret == ACL_SUCCESS, return ret); // Create a quantParam aclTensor. ret = CreateAclTensor(scaleHostData, scaleShape, &quantParamDeviceAddr, aclDataType::ACL_UINT64, &quantParam); std::unique_ptr<aclTensor, aclnnStatus (*)(const aclTensor *)> quantParamTensorPtr(quantParam, aclDestroyTensor); std::unique_ptr<void, aclError (*)(void *)> quantParamDeviceAddrPtr(quantParamDeviceAddr, aclrtFree); CHECK_RET(ret == ACL_SUCCESS, return ret); // Create an offset aclTensor. ret = CreateAclTensor(offsetHostData, offsetShape, &offsetDeviceAddr, aclDataType::ACL_FLOAT, &offset); std::unique_ptr<aclTensor, aclnnStatus (*)(const aclTensor *)> offsetTensorPtr(offset, aclDestroyTensor); std::unique_ptr<void, aclError (*)(void *)> offsetDeviceAddrPtr(offsetDeviceAddr, aclrtFree); CHECK_RET(ret == ACL_SUCCESS, return ret); // Create a bias aclTensor. ret = CreateAclTensor(biasHostData, biasShape, &biasDeviceAddr, aclDataType::ACL_INT32, &bias); std::unique_ptr<aclTensor, aclnnStatus (*)(const aclTensor *)> biasTensorPtr(bias, aclDestroyTensor); std::unique_ptr<void, aclError (*)(void *)> biasDeviceAddrPtr(biasDeviceAddr, aclrtFree); CHECK_RET(ret == ACL_SUCCESS, return ret); // Create an 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); bool transposeX1 = false; bool transposeX2 = true; // 3. Call the CANN operator library API, which needs to be replaced with the actual API. uint64_t workspaceSize = 0; aclOpExecutor *executor = nullptr; void *workspaceAddr = nullptr; // The shape of x2 needs to be transposed to the nk format before TransData. std::vector<int64_t> dimsData = {1, 0}; // Create a dims aclIntArray. aclIntArray *dims = aclCreateIntArray(dimsData.data(), dimsData.size()); // Call the first-phase API of aclnnPermute. ret = aclnnPermuteGetWorkspaceSize(x2, dims, x2Transposed, &workspaceSize, &executor); CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclnnPermuteGetWorkspaceSize failed. ERROR: %d\n", ret); return ret); // Allocate device memory based on workspaceSize computed by the first-phase API. std::unique_ptr<void, aclError (*)(void *)> workspaceAddrPtrPermute(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); workspaceAddrPtrPermute.reset(workspaceAddr); } // Call the second-phase API of aclnnPermute. ret = aclnnPermute(workspaceAddr, workspaceSize, executor, stream); CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclnnPermuteGetWorkspaceSize failed. ERROR: %d\n", ret); return ret); workspaceSize = 0; // Call the first-phase API of aclnnTransMatmulWeight. ret = aclnnTransMatmulWeightGetWorkspaceSize(x2Transposed, &workspaceSize, &executor); CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclnnTransMatmulWeightGetWorkspaceSize failed. ERROR: %d\n", ret); return ret); // Allocate device memory based on workspaceSize computed by the first-phase API. std::unique_ptr<void, aclError (*)(void *)> workspaceAddrPtrTrans(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); workspaceAddrPtrTrans.reset(workspaceAddr); } // Call the second-phase API of aclnnTransMatmulWeight. ret = aclnnTransMatmulWeight(workspaceAddr, workspaceSize, executor, stream); CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclnnTransMatmulWeight failed. ERROR: %d\n", ret); return ret); // Call the aclnn API of the TransQuantParamV2 operator in advance for scale of the FLOAT data type. // Call the first-phase API of aclnnTransQuantParamV2. ret = aclnnTransQuantParamV2GetWorkspaceSize(scale, offset, quantParam, &workspaceSize, &executor); CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclnnTransQuantParamV2GetWorkspaceSize failed. ERROR: %d\n", ret); return ret); // Allocate device memory based on workspaceSize computed by the first-phase API. workspaceAddr = nullptr; std::unique_ptr<void, aclError (*)(void *)> workspaceAddrPtrV2(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); workspaceAddrPtrV2.reset(workspaceAddr); } // Call the second-phase API of aclnnTransQuantParamV2. ret = aclnnTransQuantParamV2(workspaceAddr, workspaceSize, executor, stream); CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclnnTransQuantParamV2 failed. ERROR: %d\n", ret); return ret); // Call the first-phase API of aclnnQuantMatmulV3. workspaceSize = 0; ret = aclnnQuantMatmulV3GetWorkspaceSize(x1, x2Transposed, quantParam, nullptr, bias, transposeX1, transposeX2, out, &workspaceSize, &executor); CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclnnQuantMatmulV3GetWorkspaceSize failed. ERROR: %d\n", ret); return ret); // Allocate device memory based on workspaceSize computed by the first-phase API. std::unique_ptr<void, aclError (*)(void *)> workspaceAddrPtrV3(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); workspaceAddrPtrV3.reset(workspaceAddr); } // Call the second-phase API of aclnnQuantMatmulV3. ret = aclnnQuantMatmulV3(workspaceAddr, workspaceSize, executor, stream); CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclnnQuantMatmulV3 failed. ERROR: %d\n", ret); return ret); // 4. (Fixed writing) Wait until the task execution is 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. Modify the configuration based on the API definition. auto size = GetShapeSize(outShape); std::vector<uint16_t> resultData( size, 0); // The fp16 data cannot be directly printed in the C language. The data needs to be read by using uint16 and converted into fp16 in binary mode. 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. (Fixed writing) Initialize the device and stream. For details, see the ACL API manual. // Set the device ID in use. int32_t deviceId = 0; aclrtStream stream; auto ret = aclnnQuantMatmulV3Test(deviceId, stream); CHECK_FREE_RET(ret == ACL_SUCCESS, LOG_PRINT("aclnnQuantMatmulV3Test failed. ERROR: %d\n", ret); return ret); Finalize(deviceId, stream); return 0; }Atlas A2 training products/Atlas A2 inference products andAtlas A3 training series products/Atlas A3 inference series products : The sample code (x1 and x2 are int4, transposeX2 = false) in the INT4 quantization scenario is as follows (for reference only). For details about the compilation and running process, see Compilation and Running Sample.#include <iostream> #include <memory> #include <vector> #include "acl/acl.h" #include "aclnnop/aclnn_convert_weight_to_int4_pack.h" #include "aclnnop/aclnn_quant_matmul_v3.h" #include "aclnnop/aclnn_trans_quant_param_v2.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) { // (Fixed writing) Initialize resources. 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) { // Obtain the number of bytes of the allocated and copied memory through hostData. auto size = hostData.size() * sizeof(T); // Call aclrtMalloc to allocate memory on the device. 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 the data on the host to the memory on the device. 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); // Compute the strides of the 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; } void Finalize(int32_t deviceId, aclrtStream stream) { aclrtDestroyStream(stream); aclrtResetDevice(deviceId); aclFinalize(); } int aclnnQuantMatmulV3Test(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 based on the API definition. int64_t m = 16; int64_t k = 8; int64_t n = 32; aclDataType x1Dtype = aclDataType::ACL_INT4; aclDataType x2Int4PackDtype = aclDataType::ACL_INT4; std::vector<int64_t> x1Shape = {m, k}; std::vector<int64_t> x2Shape = {k, n}; std::vector<int64_t> x2Int4PackShape = {k, n}; std::vector<int64_t> biasShape = {n}; std::vector<int64_t> scaleShape = {n}; std::vector<int64_t> outShape = {m, n}; void *x1DeviceAddr = nullptr; void *x2DeviceAddr = nullptr; void *x2Int4PackDeviceAddr = nullptr; void *scaleDeviceAddr = nullptr; void *quantParamDeviceAddr = nullptr; void *offsetDeviceAddr = nullptr; void *biasDeviceAddr = nullptr; void *outDeviceAddr = nullptr; aclTensor *x1 = nullptr; aclTensor *x2 = nullptr; aclTensor *x2Int4Pack = nullptr; aclTensor *bias = nullptr; aclTensor *scale = nullptr; aclTensor *quantParam = nullptr; aclTensor *offset = nullptr; aclTensor *out = nullptr; std::vector<int8_t> x1HostData(m * k / 2, 17); // int8: 0001 0001 std::vector<int8_t> x2HostData(k * n, 1); std::vector<int8_t> x2Int4PackHostData(n * k / 2, 1); std::vector<int32_t> biasHostData(n, 1); std::vector<float> scaleHostData(n, 1); std::vector<uint16_t> outHostData(m * n, 1); // Create an x1 aclTensor. ret = CreateAclTensor(x1HostData, x1Shape, &x1DeviceAddr, x1Dtype, &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 an x2 aclTensor. ret = CreateAclTensor(x2HostData, x2Shape, &x2DeviceAddr, aclDataType::ACL_INT32, &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 an x2Int4Pack aclTensor. ret = CreateAclTensor(x2Int4PackHostData, x2Int4PackShape, &x2Int4PackDeviceAddr, x2Int4PackDtype, &x2Int4Pack); std::unique_ptr<aclTensor, aclnnStatus (*)(const aclTensor *)> x2Int4PackTensorPtr(x2Int4Pack, aclDestroyTensor); std::unique_ptr<void, aclError (*)(void *)> x2Int4PackDeviceAddrPtr(x2Int4PackDeviceAddr, aclrtFree); CHECK_RET(ret == ACL_SUCCESS, return ret); // Create a scale aclTensor. ret = CreateAclTensor(scaleHostData, scaleShape, &scaleDeviceAddr, aclDataType::ACL_FLOAT, &scale); std::unique_ptr<aclTensor, aclnnStatus (*)(const aclTensor *)> scaleTensorPtr(scale, aclDestroyTensor); std::unique_ptr<void, aclError (*)(void *)> scaleDeviceAddrPtr(scaleDeviceAddr, aclrtFree); CHECK_RET(ret == ACL_SUCCESS, return ret); // Create a quantParam aclTensor. ret = CreateAclTensor(scaleHostData, scaleShape, &quantParamDeviceAddr, aclDataType::ACL_UINT64, &quantParam); std::unique_ptr<aclTensor, aclnnStatus (*)(const aclTensor *)> quantParamTensorPtr(quantParam, aclDestroyTensor); std::unique_ptr<void, aclError (*)(void *)> quantParamDeviceAddrPtr(quantParamDeviceAddr, aclrtFree); CHECK_RET(ret == ACL_SUCCESS, return ret); // Create a bias aclTensor. ret = CreateAclTensor(biasHostData, biasShape, &biasDeviceAddr, aclDataType::ACL_INT32, &bias); std::unique_ptr<aclTensor, aclnnStatus (*)(const aclTensor *)> biasTensorPtr(bias, aclDestroyTensor); std::unique_ptr<void, aclError (*)(void *)> biasDeviceAddrPtr(biasDeviceAddr, aclrtFree); CHECK_RET(ret == ACL_SUCCESS, return ret); // Create an 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); bool transposeX1 = false; bool transposeX2 = false; // 3. Call the CANN operator library API, which needs to be replaced with the actual API. uint64_t workspaceSize = 0; aclOpExecutor *executor = nullptr; // Call aclnnConvertWeightToINT4Pack to construct the x2 input data. // Call the first-phase API of aclnnConvertWeightToINT4Pack. ret = aclnnConvertWeightToINT4PackGetWorkspaceSize(x2, x2Int4Pack, &workspaceSize, &executor); CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclnnConvertWeightToINT4PackGetWorkspaceSize failed. ERROR: %d\n", ret); return ret); // Allocate device memory based on workspaceSize computed by the first-phase API. void *workspaceAddr = nullptr; std::unique_ptr<void, aclError (*)(void *)> workspaceINT4PackAddrPtr(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); workspaceINT4PackAddrPtr.reset(workspaceAddr); } // Call the second-phase API of aclnnConvertWeightToINT4Pack. ret = aclnnConvertWeightToINT4Pack(workspaceAddr, workspaceSize, executor, stream); CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclnnConvertWeightToINT4Pack failed. ERROR: %d\n", ret); return ret); // Call the aclnn API of the TransQuantParamV2 operator in advance for scale of the FLOAT data type. // Call the first-phase API of aclnnTransQuantParamV2. ret = aclnnTransQuantParamV2GetWorkspaceSize(scale, offset, quantParam, &workspaceSize, &executor); CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclnnTransQuantParamV2GetWorkspaceSize failed. ERROR: %d\n", ret); return ret); // Allocate device memory based on workspaceSize computed by the first-phase API. workspaceAddr = nullptr; std::unique_ptr<void, aclError (*)(void *)> workspaceAddrPtrV2(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); workspaceAddrPtrV2.reset(workspaceAddr); } // Call the second-phase API of aclnnTransQuantParamV2. ret = aclnnTransQuantParamV2(workspaceAddr, workspaceSize, executor, stream); CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclnnTransQuantParamV2 failed. ERROR: %d\n", ret); return ret); // Call the first-phase API of aclnnQuantMatmulV3. ret = aclnnQuantMatmulV3GetWorkspaceSize(x1, x2Int4Pack, quantParam, nullptr, bias, transposeX1, transposeX2, out, &workspaceSize, &executor); CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclnnQuantMatmulV3GetWorkspaceSize failed. ERROR: %d\n", ret); return ret); // Allocate device memory based on workspaceSize computed by the first-phase API. workspaceAddr = nullptr; std::unique_ptr<void, aclError (*)(void *)> workspaceAddrPtrV3(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); workspaceAddrPtrV3.reset(workspaceAddr); } // Call the second-phase API of aclnnQuantMatmulV3. ret = aclnnQuantMatmulV3(workspaceAddr, workspaceSize, executor, stream); CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclnnQuantMatmulV3 failed. ERROR: %d\n", ret); return ret); // 4. (Fixed writing) Wait until the task execution is 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. Modify the configuration based on the API definition. auto size = GetShapeSize(outShape); std::vector<uint16_t> resultData( size, 0); // The fp16 data cannot be directly printed in the C language. The data needs to be read by using uint16 and converted into fp16 in binary mode. 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. (Fixed writing) Initialize the device and stream. For details, see the ACL API manual. // Set the device ID in use. int32_t deviceId = 0; aclrtStream stream; auto ret = aclnnQuantMatmulV3Test(deviceId, stream); CHECK_FREE_RET(ret == ACL_SUCCESS, LOG_PRINT("aclnnQuantMatmulV3Test failed. ERROR: %d\n", ret); return ret); Finalize(deviceId, stream); return 0; }