aclnnWeightQuantBatchMatmulNz
Applicable Products
| Product | Supported |
|---|---|
| √ | |
| × | |
| × | |
| × | |
| × | |
| × |
Function
Description: Performs a matrix multiplication computation where the input is in a pseudo-quantization scenario. This API only supports the right input matrix of the matrix multiplication in FRACTAL_NZ format.
Formula:
Basic formula:
Where is the input in the pseudo-quantization scenario, and its antiquantization formula is:
Quantization formula when the output needs to be quantized:
Function Prototype
Each operator is divided into a two-phase API. You must first call the "aclnnWeightQuantBatchMatmulNzGetWorkspaceSize" API to obtain the workspace size required for computation and the executor that contains the operator computation process, and then call the "aclnnWeightQuantBatchMatmulNz" API to perform the computation.
aclnnStatus aclnnWeightQuantBatchMatmulNzGetWorkspaceSize(
const aclTensor *x,
const aclTensor *weight,
const aclTensor *antiquantScale,
const aclTensor *antiquantOffsetOptional,
const aclTensor *quantScaleOptional,
const aclTensor *quantOffsetOptional,
const aclTensor *biasOptional,
int antiquantGroupSize,
const aclTensor *y,
uint64_t *workspaceSize,
aclOpExecutor **executor)aclnnStatus aclnnWeightQuantBatchMatmulNz(
void *workspace,
uint64_t workspaceSize,
aclOpExecutor *executor,
aclrtStream stream)aclnnWeightQuantBatchMatmulNzGetWorkspaceSize
Parameters
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 Return Code Description ACLNN_ERR_PARAM_NULLPTR 161001 Returns 161001 if the passed parameter is a mandatory input, output, or mandatory attribute and is a null pointer. ACLNN_ERR_PARAM_INVALID 161002 The shape, data format, or continuity of the input tensor does not meet the requirements. Empty tensor scenarios are not supported. ACLNN_ERR_RUNTIME_ERROR 361001 The product model is not supported.
aclnnWeightQuantBatchMatmulNz
Parameters
Parameter Input/Output Description workspace Input Address of the workspace memory applied on the Device side. workspaceSize Input Size of the workspace applied on the Device side, obtained through the first-phase API aclnnWeightQuantBatchMatmulNzGetWorkspaceSize. executor Input Operator executor, which contains the operator computation flow. stream Input Specifies the stream for task execution. Return value
aclnnStatus: return code. For details, see aclnn Return Code.
Constraints
Deterministic computation description: aclnnWeightQuantBatchMatmulNz defaults to a deterministic implementation.
Supported quantization modes: perchannel Quantization Mode, pergroup Quantization Mode, and mx Quantization Mode.
The input and output support the following data type and shape combinations:
Ascend 950PR/Ascend 950DT :Quantization Mode x weight antiquantScale antiquantOffsetOptional biasOptional y antiquantScale Shape antiquantOffsetOptional Shape perchannel BFLOAT16 INT32/INT4 BFLOAT16 null/BFLOAT16 null/BFLOAT16/FLOAT32 BFLOAT16 (1, n)/(n,) null/(1, n)/(n,) perchannel FLOAT16 INT32/INT4 FLOAT16 null/FLOAT16 null/FLOAT16 FLOAT16 (1, n)/(n,) null/(1, n)/(n,) perchannel BFLOAT16 INT8 BFLOAT16 null/BFLOAT16 null/FLOAT32 BFLOAT16 (1, n)/(n,) null/(1, n)/(n,) perchannel FLOAT16 INT8 FLOAT16 null/FLOAT16 null/FLOAT16 FLOAT16 (1, n)/(n,) null/(1, n)/(n,) pergroup BFLOAT16 INT32/INT4 BFLOAT16 null/BFLOAT16 null/BFLOAT16/FLOAT32 BFLOAT16 (ceil(k/antiquantGroupSize), n) null/(ceil(k/antiquantGroupSize), n) pergroup FLOAT16 INT32/INT4 FLOAT16 null/FLOAT16 null/FLOAT16 FLOAT16 (ceil(k/antiquantGroupSize), n) null/(ceil(k/antiquantGroupSize), n) pergroup BFLOAT16 FLOAT/FLOAT4_E2M1 BFLOAT16 null null/BFLOAT16 BFLOAT16 (ceil(k/antiquantGroupSize), n) null pergroup FLOAT16 FLOAT/FLOAT4_E2M1 FLOAT16 null null/FLOAT16 FLOAT16 (ceil(k/antiquantGroupSize), n) null mx BFLOAT16 FLOAT/FLOAT4_E2M1 FLOAT8_E8M0 null null/BFLOAT16 BFLOAT16 (ceil(k/32), n) null mx FLOAT16 FLOAT/FLOAT4_E2M1 FLOAT8_E8M0 null null/FLOAT16 FLOAT16 (ceil(k/32), n) null The shape of x is (m, k), the shape of y is (m, n), and the shape of biasOptional is null/(1, n)/(n,).
When the data type of weight is INT32 or FLOAT, it indicates tightly packed INT4 or FLOAT4_E2M1, and the following constraints must be met:
The last dimension of the original ND matrix is 8-aligned.
Before calling this API, you must use the
aclnnConvertWeightToINT4PackAPI to complete the conversion from sparsely arranged INT32/FLOAT to tightly packed INT4/FLOAT4_E2M1 and from ND to FRACTAL_NZ. For details, see the sample.The shape of the FRACTAL_NZ matrix passed to this API is (ceil(n/16), ceil(k/16), 16, 2).
weight supports only non-transposed mode, and the shape of the original ND matrix is (k, n).
When the data type of weight is INT4 or FLOAT4_E2M1, the following constraints must be met:
The last dimension of the original ND matrix must be 2-aligned.
Before calling this API, you must use the
aclnnConvertWeightToINT4PackAPI to complete the conversion from ND to FRACTAL_NZ. For details, see the sample.The shape of the FRACTAL_NZ matrix passed to this API is (ceil(n/16), ceil(k/16), 16, 16).
weight only supports non-transposed mode, and the shape of the original ND matrix is (k, n).
When the data type of weight is INT8, the following constraints must be met:
The shape of the FRACTAL_NZ matrix passed to this API is (ceil(n/32), ceil(k/16), 16, 32).
k is in the range [1, 65535], and n is in the range [2, 65535].
weight supports transposed and non-transposed formats, with the original ND matrix shape being (n, k) or (k, n).
m is in the range [1, 2^31-1].
Example
The following provides example code for reference only. For details about compilation and running, see Compile and Run Samples.
For the example where x is FLOAT16 and weight is FLOAT32, call the
aclnnConvertWeightToINT4PackAPI to assist with the invocation:#include <iostream> #include <memory> #include <vector> #include "acl/acl.h" #include "aclnnop/aclnn_cast.h" #include "aclnnop/aclnn_weight_quant_batch_matmul_nz.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 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 data from the host to 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); // 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; } int GetSize(std::vector<int64_t>& shape) { int64_t size = 1; for (auto i : shape) { size *= i; } return size; } 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 to 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 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 AclnnWeightQuantBatchMatmulNzTest(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 inputs and outputs. Customize the construction based on the API. 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(GetSize(xShape), 0b0011110000000000); // 1.0 in float16. std::vector<float> weightHostData(GetSize(weightShape), 1.0); // 1.0 in fp32, converted to 1.0 in fp4_e2m1 after int4pack. std::vector<float> yHostData(GetSize(yShape), 0); std::vector<uint16_t> antiquantScaleHostData(GetSize(antiquantScaleShape), 0b0011110000000000); // 1.0 in float16. // Create the 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 the 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 the 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 the antiquantScale aclTensor. ret = CreateAclTensor( antiquantScaleHostData, antiquantScaleShape, &antiquantScaleDeviceAddr, aclDataType::ACL_FLOAT16, &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 the 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_FRACTAL_NZ; // Optional: ACL_FORMAT_ND 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 aclnnWeightQuantBatchMatmulNz. workspaceSize = 0; executor = nullptr; ret = aclnnWeightQuantBatchMatmulNzGetWorkspaceSize( x, weightPacked, antiquantScale, nullptr, nullptr, nullptr, nullptr, groupSize, yFp16, &workspaceSize, &executor); CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclnnWeightQuantBatchMatmulNzGetWorkspaceSize failed. ERROR: %d\n", ret); return ret); // Allocate device memory based on the workspaceSize computed 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 aclnnWeightQuantBatchMatmulNz. ret = aclnnWeightQuantBatchMatmulNz(workspaceAddr, workspaceSize, executor, stream); CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclnnWeightQuantBatchMatmulNz failed. ERROR: %d\n", ret); return ret); // 4. (Boilerplate) Synchronously wait for task execution to finish. 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 device memory to 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); PrintMat(resultData, yShape); return ACL_SUCCESS; } int main() { // 1. (Boilerplate) Initialize 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 = AclnnWeightQuantBatchMatmulNzTest(deviceId, stream); CHECK_FREE_RET(ret == ACL_SUCCESS, LOG_PRINT("AclnnWeightQuantBatchMatmulNzTest failed. ERROR: %d\n", ret); return ret); Finalize(deviceId, stream); return 0; }Example with x as FLOAT16 and weight as INT8, which requires calling the
aclnnTransMatmulWeightAPI to assist in the call:#include <cmath> #include <iostream> #include <memory> #include <vector> #include "acl/acl.h" #include "aclnnop/aclnn_weight_quant_batch_matmul_nz.h" #include "aclnnop/aclnn_trans_matmul_weight.h" #define CHECK_FREE_RET(cond, return_expr) \ do { \ if (!(cond)) { \ Finalize(deviceId, stream); \ return_expr; \ } \ } while (0) #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 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 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; } template <typename T> int CreateAclTensorWeightNz( 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) * sizeof(T)); const aclIntArray* mat2Size = aclCreateIntArray(shape.data(), shape.size()); auto ret = aclnnCalculateMatmulWeightSizeV2(mat2Size, dataType, &size); CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclnnCalculateMamtulWeightSizeV2 failed. ERROR: %d\n", ret); return ret); // Call aclrtMalloc to allocate memory on the device side. 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 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; } // Convert the uint16_t representation of FP16 to a float representation. float Fp16ToFloat(uint16_t h) { int s = (h >> 15) & 0x1; // sign int e = (h >> 10) & 0x1F; // exponent int f = h & 0x3FF; // fraction if (e == 0) { // Zero or Denormal if (f == 0) { return s ? -0.0f : 0.0f; } // Denormals float sig = f / 1024.0f; float result = sig * pow(2, -24); return s ? -result : result; } else if (e == 31) { // Infinity or NaN return f == 0 ? (s ? -INFINITY : INFINITY) : NAN; } // Normalized float result = (1.0f + f / 1024.0f) * pow(2, e - 15); return s ? -result : result; } void Finalize(int32_t deviceId, aclrtStream stream) { aclrtDestroyStream(stream); aclrtResetDevice(deviceId); aclFinalize(); } int AclnnWeightQuantBatchMatmulNzA16W8Test(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 inputs and outputs. Customize the construction based on the API. int64_t m = 2; int64_t k = 16; int64_t n = 8; std::vector<int64_t> xShape = {m, k}; std::vector<int64_t> weightShape = {k, n}; std::vector<int64_t> yShape = {m, n}; std::vector<int64_t> antiquantScaleShape = {n}; void* xDeviceAddr = nullptr; void* weightDeviceAddr = nullptr; void* antiquantScaleDeviceAddr = nullptr; void* yDeviceAddr = nullptr; aclTensor* x = nullptr; aclTensor* weight = nullptr; aclTensor* antiquantScale = nullptr; aclTensor* y = nullptr; std::vector<uint16_t> xHostData(m * k, 0b0011110000000000); // 0b0011110000000000 is 1.0 in fp16. std::vector<int8_t> weightHostData(k * n, 1); std::vector<float> yHostData(m * n, 0); std::vector<uint16_t> antiquantScaleHostData(n, 0b0011110000000000); // 0b0011110000000000 is 1.0 in fp16. // Create the 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 the weight aclTensor. uint64_t workspaceSize = 0; aclOpExecutor* executor; ret = CreateAclTensorWeightNz(weightHostData, weightShape, &weightDeviceAddr, aclDataType::ACL_INT8, &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); // Call the first-phase API of aclnnTransMatmulWeight. ret = aclnnTransMatmulWeightGetWorkspaceSize(weight, &workspaceSize, &executor); CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclnnTransMatmulWeightGetWorkspaceSize failed. ERROR: %d\n", ret); return ret); // Apply for device memory based on the workspaceSize calculated by the first-phase API. void* workspaceTransMatmulAddr = nullptr; std::unique_ptr<void, aclError (*)(void*)> workspaceTransMatmulAddrPtr(nullptr, aclrtFree); if (workspaceSize > 0) { ret = aclrtMalloc(&workspaceTransMatmulAddr, workspaceSize, ACL_MEM_MALLOC_HUGE_FIRST); CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("allocate workspace failed. ERROR: %d\n", ret); return ret); workspaceTransMatmulAddrPtr.reset(workspaceTransMatmulAddr); } // Call the second-phase API of aclnnTransMatmulWeight. ret = aclnnTransMatmulWeight(workspaceTransMatmulAddr, workspaceSize, executor, stream); CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclnnTransMatmulWeight failed. ERROR: %d\n", ret); return ret); // Create the antiquantScale aclTensor. ret = CreateAclTensor( antiquantScaleHostData, antiquantScaleShape, &antiquantScaleDeviceAddr, aclDataType::ACL_FLOAT16, &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 the y aclTensor. ret = CreateAclTensor(yHostData, yShape, &yDeviceAddr, aclDataType::ACL_FLOAT16, &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); // 3. Call the CANN operator library API. Replace with the specific API name. // Call the first-phase API of aclnnWeightQuantBatchMatmulNz. ret = aclnnWeightQuantBatchMatmulNzGetWorkspaceSize( x, weight, antiquantScale, nullptr, nullptr, nullptr, nullptr, 0, y, &workspaceSize, &executor); CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclnnWeightQuantBatchMatmulNzGetWorkspaceSize 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 aclnnWeightQuantBatchMatmulNz. ret = aclnnWeightQuantBatchMatmulNz(workspaceAddr, workspaceSize, executor, stream); CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclnnWeightQuantBatchMatmulNz failed. ERROR: %d\n", ret); return ret); // 4. (Boilerplate) Synchronize and wait for task execution to complete. ret = aclrtSynchronizeStream(stream); CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclrtSynchronizeStream failed. ERROR: %d\n", ret); return ret); // 5. Obtain the output value and copy the result from the device memory to the host memory. Modify this based on the specific API definition. auto size = GetShapeSize(yShape); std::vector<uint16_t> 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, Fp16ToFloat(resultData[i])); } return ACL_SUCCESS; } int main() { // 1. (Boilerplate) Initialize the device/stream. Refer to the ACL API manual. // Set deviceId based on your actual device. int32_t deviceId = 0; aclrtStream stream; auto ret = AclnnWeightQuantBatchMatmulNzA16W8Test(deviceId, stream); CHECK_FREE_RET(ret == ACL_SUCCESS, LOG_PRINT("AclnnWeightQuantBatchMatmulV2Test failed. ERROR: %d\n", ret); return ret); Finalize(deviceId, stream); return 0; }