aclnnWeightQuantBatchMatmulV2
Applicable Products
| Product | Supported |
|---|---|
| √ | |
| √ | |
| √ | |
| × | |
| √ | |
| × |
Function
Description: Performs matrix multiplication with the input in a Fake Quantization scenario, and supports quantization computation on the output.
Formula:
In the formula, is the input in the Fake Quantization scenario, and its dequantization formula is:
- When no quantization operation is required on the output, the formula is:
- When the output needs to be quantized, the quantization formula is:
Function Prototype
Each operator is divided into a two-phase API. You must first call the "aclnnWeightQuantBatchMatmulV2GetWorkspaceSize" API to obtain the workspace size required for computation and the executor that contains the operator's computation process, and then call the "aclnnWeightQuantBatchMatmulV2" API to perform the computation.
aclnnStatus aclnnWeightQuantBatchMatmulV2GetWorkspaceSize(
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 aclnnWeightQuantBatchMatmulV2(
void *workspace,
uint64_t workspaceSize,
aclOpExecutor *executor,
aclrtStream stream)aclnnWeightQuantBatchMatmulV2GetWorkspaceSize
Parameters
Parameter Input/Output Description Instruction Data Type Data Format Dimension (Shape) Non-contiguous Tensor x Input Left input matrix for matrix multiplication, corresponding to input xin the formula.- FLOAT16, BFLOAT16 ND 2D, shape supports (m, k). Only the Transpose-Only Scenario is supported. weight Input The right input matrix of matrix multiplication, which is the input weightin the formula.- INT8, INT4, FLOAT8_E4M3FN2, HIFLOAT82, INT32, FLOAT2, FLOAT4_E2M12 ND, FRACTAL_NZ 2D, with shape (k, n). Supported only in the Transpose-Only Scenario. antiquantScale Input The dequantization scale parameter for input dequantization computation, which is the input antiquantScalein the dequantization formula.- FLOAT16, BFLOAT16, FLOAT8_E8M02, UINT641, INT641 ND 1-2 dimensions Only the Transpose-Only Scenario supports antiquantOffsetOptional Optional Input Dequantization offset parameter for input dequantization computation, corresponding to antiquantOffsetin the dequantization formula.Null pointer when not required. FLOAT16, BFLOAT16, INT321 ND Must be consistent with antiquantScale.Supported only in the Transpose-Only Scenario. quantScaleOptional Optional Input Quantization parameter for implementing output quantization computation. Obtained by converting the data of quantScaleandquantOffsetin the quantization formula through theaclnnTransQuantParamAPI. Set to a null pointer when not needed.UINT641 ND 1-2 dimensions Not supported. quantOffsetOptional Optional Input Quantization offset parameter for output quantization computation, corresponding to quantOffsetin the quantization formula.Null pointer when not needed. FLOAT1 ND Must be consistent with quantScaleOptional.- biasOptional Optional Input Bias input, which is biasin the formula.Null pointer when not required. FLOAT16, FLOAT, BFLOAT162 ND 1-2 dimensions - antiquantGroupSize Input In the pergroup and mx Quantization Mode of fake quantization, specifies the groupSize input for dequantizing the input weight, describing the size of the data to be dequantized corresponding to a group of dequantization parameters in the reduce direction.Pass 0 when the fake quantization algorithm is not pergroup or mx Quantization Mode.
When the fake quantization algorithm is pergroup Quantization Mode, the value range is [32, k-1] and the value must be a multiple of 32.
In mx Quantization Mode, only 32 is supported.- - - - y Output The yin the formula.- FLOAT16, BFLOAT16, INT81 ND 2D - workspaceSize Output Returns the workspace size to be allocated on the Device side. - - - - - executor Output Returns the operator executor, which contains the operator computation flow. - - - - - Ascend 950PR/Ascend 950DT :- The superscript "1" in the data type column of the table above indicates a data type not supported by this series.
Atlas A2 training products/Atlas A2 inference products ,Atlas A3 training products/Atlas A3 inference products :- The superscript "2" in the data type column of the table above indicates a data type not supported by this series.
Atlas inference products :- The superscript "3" in the data type column of the table above indicates a data type not supported by this series.
Return value
aclnnStatus: return code. For details, see aclnn Return Code.
The first-phase API performs input parameter validation and returns an error in the following scenarios:
Return Value Error Code Description ACLNN_ERR_PARAM_NULLPTR 161001 A required parameter is a null pointer. ACLNN_ERR_PARAM_INVALID 161002 The shape dimensions of x, weight, antiQuantScale, antiQuantOffsetOptional, quantScaleOptional, quantOffsetOptional, biasOptional, and y do not meet the requirements. The data types of x, weight, antiQuantScale, antiQuantOffsetOptional, quantScaleOptional, quantOffsetOptional, biasOptional, and y are not within the supported range. The reduction dimensions (k) of x and weight are not equal. When antiQuantOffsetOptional is present, its shape differs from that of antiQuantScale. When quantOffsetOptional is present, its shape differs from that of quantScale. The shape of biasOptional does not meet the requirements. The antiquantGroupSize value does not meet the requirements. When quantOffsetOptional exists, quantScaleOptional is a null pointer. The input k and n values are not within the range [1, 65535]. When the x matrix is non-transposed, m is not within the range [1, 2^31-1]; when transposed, m is not within the range [1, 65535]. Empty tensor scenarios are not supported. The continuity of x, weight, antiQuantScale, antiQuantOffsetOptional, quantScaleOptional, quantOffsetOptional, biasOptional, and y does not meet requirements. When x is bfloat16 and weight is float4_e2m1 or float32, bias only supports the bfloat16 data type. ACLNN_ERR_RUNTIME_ERROR 361001 The product model is not supported.
aclnnWeightQuantBatchMatmulV2
Parameters
Parameter Input/Output Description workspace Input Address of the workspace memory allocated on the Device side. workspaceSize Input Size of the workspace allocated on the Device side, obtained from the first-phase API aclnnWeightQuantBatchMatmulV2GetWorkspaceSize. 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
Atlas A2 training products/Atlas A2 inference products , Atlas A3 training products/Atlas A3 inference products
Determinism Description: Defaults to a non-deterministic implementation. Deterministic computation can be enabled by calling aclrtCtxSetSysParamOpt.
x(aclTensor *, Compute Input): When the matrix is non-transposed, the size of m is in the range [1, 2^31-1]; when transposed, the size of m is in the range [1, 65535].weight(aclTensor *, Compute Input): Supports 2 dimensions. The reduce dimension k must be equal in size to the reduce dimension k ofx. Supports INT8, INT4, and INT32 data types. When theweightdata format is FRACTAL_NZ and the data type is INT4 or INT32, or when theweightdata format is ND and the data type is INT32, it is only supported in the INT4Pack scenario and requires theaclnnConvertWeightToINT4PackAPI to complete the conversion from INT32 to INT4Pack and from ND to FRACTAL_NZ. For details, see the sample. If the data type is INT4, the inner axis ofweightmust be an even number. Non-contiguous tensor is only supported in the Transpose-Only Scenario. The shape supports (k, n), where k represents the size of the first matrix dimension and n represents the size of the second matrix dimension.For different Fake Quantization algorithm modes, the
weightdata format being FRACTAL_NZ is only supported in the following scenarios:perchannel Quantization Mode:
The data type of
weightis INT8, and the data type of y is non-INT8.The data type of
weightis INT4/INT32,weightis transposed, and the data type of y is non-INT8.
pergroup Quantization Mode: The data type of
weightis INT4/INT32,weightis non-transposed,xis non-transposed, antiquantGroupSize is 64 or 128, k is aligned to antiquantGroupSize, n is aligned to 64, and the data type of y is non-INT8.
antiquantScale(aclTensor *, Compute Input): Supported data types include FLOAT16, BFLOAT16, UINT64, and INT64. (When FLOAT16 or BFLOAT16 is used, the data type must be consistent with the inputx; when UINT64 or INT64 is used,xonly supports FLOAT16 and must not be transposed,weightonly supports INT8 with ND transpose, the mode only supports perchannel Quantization Mode, quantScaleOptional and quantOffsetOptional must be passed as null pointers, m only supports [1, 96], k and n must be aligned to 64, and the aclnnCast API must first be used to convert FLOAT16 to FLOAT32. For details, see Cast. Then, the aclnnTransQuantParamV2 API must be used to convert FLOAT32 to UINT64. For details, see TransQuantParamV2.) Non-contiguous Tensor is only supported in the transpose scenario.For different fake quantization algorithm modes, the supported shapes of
antiquantScaleare as follows:pertensor Quantization Mode: The input shape is (1,) or (1, 1).
perchannel Quantization Mode: The input shape is (1, n) or (n,).
pergroup Quantization Mode: The input shape is (⌈k/group_size⌉, n), where group_size indicates the size of each group into which k is to be grouped.
antiquantOffsetOptional(aclTensor *, Compute Input): The data type supports FLOAT16, BFLOAT16, and INT32. When the data type is FLOAT16 or BFLOAT16, the data type must be consistent with the data type of inputx. When the data type is INT32, the data range is limited to [-128, 127],xsupports only FLOAT16,weightsupports only INT8, andantiquantScalesupports only UINT64/INT64. Non-contiguous Tensor only supports the transpose scenario.quantScaleOptional(aclTensor *, Compute Input): The data type supports UINT64, and the data format supports ND. Non-contiguous Tensor is not supported. This is an optional input; when not needed, pass a null pointer. For different fake quantization algorithm modes, the supported shapes are as follows:pertensor Quantization Mode: The input shape is (1,) or (1, 1).
perchannel Quantization Mode: The input shape is (1, n) or (n,).
quantOffsetOptional(aclTensor *, Compute Input): The data type supports FLOAT, and the data format supports ND. Optional Input; when not needed, it is a null pointer. When present, the shape must be consistent withquantScaleOptional. Non-contiguous tensors are not supported.biasOptional(aclTensor *, Compute Input): The dimension supports 1D or 2D, and the shape supports (n,) or (1, n). The data type supports FLOAT16 and FLOAT. When the data type ofxis BFLOAT16, this parameter must be FLOAT. When the data type ofxis FLOAT16, this parameter must be FLOAT16.antiquantGroupSize(int, Compute Input): Represents the groupSize input for dequantizing the inputweightin the pergroup and mx Quantization Mode of Fake Quantization. It describes the size of the data to be dequantized corresponding to a group of dequantization parameters in the Reduce direction. When the Fake Quantization algorithm is not pergroup or mx Quantization Mode, pass 0. When the Fake Quantization algorithm is pergroup Quantization Mode, the value range is [32, k-1] and the value must be a multiple of 32. In mx Quantization Mode, only 32 is supported.y(aclTensor *, Compute Output): The dimension supports 2D, and the shape supports (m, n). The data type supports FLOAT16, BFLOAT16, and INT8. WhenquantScaleOptionalis present, the data type is INT8. WhenquantScaleOptionalis absent, the data type supports FLOAT16 and BFLOAT16, and must be consistent with the data type of the inputx.Performance optimization suggestions:
pertensor Quantization Mode: When the data format is ND, using the transposed
weightinput is recommended; when the data format is FRACTAL_NZ, using the non-transposedweightinput is recommended.pergroup Quantization Mode: Using the non-transposed weight input is recommended.
perchannel Quantization Mode: When the data format is ND, using the transposed
weightinput is recommended; when the data format is FRACTAL_NZ, using the non-transposedweightinput is recommended. When the m range is [65, 96], using the data type UINT64 or INT64 for antiQuantScale is recommended.
Atlas inference products
Determinism Description: Defaults to a non-deterministic implementation. Supports enabling determinism through aclrtCtxSetSysParamOpt.
x(aclTensor *, Compute Input): Supports FLOAT16 data type. Supports 2 to 6 dimensions for shape. The input shape must be (batch, m, k), where batch indicates the batch size of the matrix, supporting 0 to 4 dimensions, m indicates the size of the first dimension of a single batch matrix, and k indicates the size of the second dimension of a single batch matrix. The batch dimension must satisfy the broadcast relationship with the batch dimension ofweight. When the fake quantization algorithm mode is pertensor Quantization Mode,m*kcannot exceed 512000000.weight(aclTensor *, Compute Input): Supports 2 to 6 dimensions. The batch dimension must satisfy the broadcast relationship with the batch dimension ofx. Supports INT8 data type. Details are as follows:When the data format is ND, the input shape must be (batch, k, n), where batch indicates the batch size of the matrix, supporting 0 to 4 dimensions, k indicates the size of the first dimension of a single batch matrix, and n indicates the size of the second dimension of a single batch matrix.
When the data format is FRACTAL_NZ:
The input shape must be (batch, n, k), where batch indicates the batch size of the matrices, supporting 0 to 4 dimensions, k indicates the size of the first dimension of a single batch matrix, and n indicates the size of the second dimension of a single batch matrix.
Used together with aclnnCalculateMatmulWeightSizeV2 and aclnnTransMatmulWeight to convert the input format from ND to FRACTAL_NZ.
antiquantScale(aclTensor *, Compute Input): The data type supports FLOAT16, and the data type must keep consistent with the inputx.For different Fake Quantization algorithm modes, the supported shapes of
antiquantScaleare as follows:pertensor Quantization Mode: The input shape is (1,) or (1, 1).
perchannel Quantization Mode: The input shape is (n, 1) or (n,). Non-contiguous tensors are not supported.
pergroup Quantization Mode: The input shape is related to the data format of
weight, as follows:When the data format of
weightis ND, the input shape is (⌈k/group_size⌉, n), where group_size indicates the size of each group into which k is to be grouped.When the data format of
weightis FRACTAL_NZ, the input shape is (n, ⌈k/group_size⌉), where group_size indicates the size of each group into which k is to be grouped.
antiquantOffsetOptional(aclTensor *, Compute Input): Supports the FLOAT16 data type. The data type must be consistent with the inputx.quantScaleOptional(aclTensor *, Compute Input): Reserved parameter, temporarily unused. Pass a null pointer as a fixed input.quantOffsetOptional(aclTensor *, Compute Input): Reserved parameter, temporarily unused. Pass a null pointer as a fixed input.biasOptional(aclTensor *, Compute Input): Supports the FLOAT16 data type. Supports 1 to 6 dimensions. When a batch is present, the input shape must be (batch, 1, n), and the batch must be consistent with the batch after broadcasting the batch dimensions of x and weight. When no batch is present, the input shape must be (n,) or (1, n).antiquantGroupSize(int, Compute Input): Supports the FLOAT16 data type. Supports 2 to 6 dimensions. The shape supports (batch, m, n), where the batch may be absent. Broadcasting of the batch dimensions of x and weight is supported. The output batch is consistent with the batch after broadcasting, m is consistent with m of x, and n is consistent with n of weight.y(aclTensor*, Compute Output):
Ascend 950PR/Ascend 950DT
- Determinism Description: Defaults to a deterministic implementation.
General Constraints
The m, k, and n dimensions of the
xandweightmatrices are in the range [1, 2^31-1]. The reduce dimension k ofweightmust be equal to the reduce dimension k ofx.Supported quantization modes: pertensor Quantization Mode, perchannel Quantization Mode, pergroup Quantization Mode, and mx Quantization Mode.
xdoes not support transpose, and therefore does not support non-contiguous tensors.weightsupports non-contiguous tensors only in the transpose-only scenario.antiquantScaleandantiquantOffsetOptionalsupport non-contiguous tensors only in the transpose-only scenario, and their contiguity requirements must be consistent withweight.Shapes supported by
antiquantScalein different quantization modes:Pertensor Quantization Mode: (1,) or (1,1).
Perchannel Quantization Mode: Input shape is (1, n) or (n,).
Pergroup Quantization Mode: Input shape is (⌈k/group_size⌉, n), where group_size indicates the size of each group into which k is to be divided.
MX Quantization Mode: Input shape is (⌈k/group_size⌉, n), where group_size indicates the size of each group into which k is to be divided. Only 32 is supported.
quantScaleOptionalandquantOffsetOptionalare reserved parameters, temporarily unused. Pass a null pointer as a fixed input.
A16W8 Scenario Constraints
- Input and Output Data Type Combination Requirements
x weight Weight Format antiquantScale antiquantOffsetOptional quantScaleOptional quantOffsetOptional biasOptional antiquantGroupSize y Scenario Description FLOAT16/BFLOAT16 INT8 ND Same as x Same as x/null null null Same as x/FLOAT (only when x is BFLOAT16)/null pergroup: [32, k-1] and a multiple of 32
Other: 0Same as x T & C & G Quantization FLOAT16/BFLOAT16 HIFLOAT8/FLOAT8_E4M3FN ND Same as x null null null Same as x/null pergroup: [32, k-1] and a multiple of 32
Other: 0Same as x C Quantization A16W4 Scenario Constraints
- Input and Output Data Type Combination Requirements
x weight weight Format antiquantScale antiquantOffsetOptional quantScaleOptional quantOffsetOptional biasOptional antiquantGroupSize y Scenario Description FLOAT16/BFLOAT16 INT4/INT32 ND Same as x Same as x/null null null Same as x/FLOAT (only when x is BFLOAT16)/null 0 Same as x T Quantization FLOAT16/BFLOAT16 INT4/INT32 ND/FRACTAL_NZ Same as x Same as x/null null null Same as x/FLOAT (only when x is BFLOAT16)/null pergroup: [32, k-1] and a multiple of 32
others: 0Same as x C & G Quantization FLOAT16/BFLOAT16 FLOAT4_E2M1 FRACTAL_NZ Same as x null null null Same as x/null [32, k-1] and a multiple of 32 Same as x G Quantization FLOAT16/BFLOAT16 FLOAT FRACTAL_NZ Same as x null null null Same as x/null [32, k-1] and a multiple of 32 Same as x G Quantization FLOAT16/BFLOAT16 FLOAT4_E2M1 ND/FRACTAL_NZ FLOAT8_E8M0 null null null Same as x/null 32 Same as x MX Quantization FLOAT16/BFLOAT16 FLOAT ND/FRACTAL_NZ FLOAT8_E8M0 null null null Same as x/FLOAT (only when x is BFLOAT16)/null 32 Same as x MX Quantization Constraints
In addition to the General Constraints, the remaining constraints for the A16W4 scenario are as follows:
If the data type of
weightis INT4 or FLOAT4_E2M1, the last dimension of weight must be 2-aligned; if the data type ofweightis INT32 or FLOAT, the last dimension of weight must be 8-aligned.When the data type of
weightis INT32 or FLOAT, theaclnnConvertWeightToINT4PackAPI must be used to complete the conversion from INT32/FLOAT to densely packed INT4/FLOAT4_E2M1. For details, see the sample.The data format of
weightbeing FRACTAL_NZ is supported only in the following scenarios:Perchannel Quantization Mode: The data type of
weightis INT4/INT32,weightis non-transposed, andxis non-transposed.Pergroup Quantization Mode: The data type of
weightis INT4/INT32/FLOAT4_E2M1/FLOAT,weightis non-transposed, andxis non-transposed.mx Quantization Mode: The data type of
weightis FLOAT4_E2M1/FLOAT,weightis non-transposed, andxis non-transposed.
Performance Optimization
pertensor Quantization Mode: When the data format is ND, it is recommended to use the transposed
weightinput; when the data format is FRACTAL_NZ, it is recommended to use the non-transposedweightinput.perchannel Quantization Mode: When the data format is ND, it is recommended to use the transposed
weightinput; when the data format is FRACTAL_NZ, it is recommended to use the non-transposedweightinput.pergroup Quantization Mode and mx Quantization Mode: Non-transposed
weightinput is recommended.
Example
The following provides example code for reference only. For details about compilation and running, see Compile and Run Samples.
A16W8 example:
#include <iostream> #include <memory> #include <vector> #include "acl/acl.h" #include "aclnnop/aclnn_cast.h" #include "aclnnop/aclnn_weight_quant_batch_matmul_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) { // 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; } void Finalize(int32_t deviceId, aclrtStream stream) { aclrtDestroyStream(stream); aclrtResetDevice(deviceId); aclFinalize(); } 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 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; } } } int AclnnWeightQuantBatchMatmulV2Test(int32_t deviceId, aclrtStream stream) { int64_t m = 16; int64_t k = 32; int64_t n = 16; std::vector<int64_t> xShape = {m, k}; std::vector<int64_t> weightShape = {k, n}; std::vector<int64_t> antiquantScaleShape = {n}; std::vector<int64_t> yShape = {m, 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; // Fill with FP16 1.0. BF16 1.0 is 0b0011111110000000. std::vector<uint16_t> xHostData(GetShapeSize(xShape), 0b0011110000000000); // 1.0 in fp16 format. std::vector<int8_t> weightHostData(GetShapeSize(weightShape), 1); std::vector<uint16_t> antiquantScaleHostData(GetShapeSize(antiquantScaleShape), 0b0011110000000000); std::vector<float> yHostData(GetShapeSize(yShape), 0); // Create the x aclTensor. ACL_FLOAT16 or ACL_BFLOAT16 type is supported. auto 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. ACL_INT8, ACL_FLOAT8_E8M0, or ACL_HIFLOAT8 type is supported. ret = CreateAclTensor(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); // Create the y aclTensor for converting the output back to FP32. 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 yFp16 aclTensor, the actual computation output, with the same type as x. 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. uint64_t workspaceSize = 0; aclOpExecutor* executor; void* workspaceAddr = nullptr; // Call the first-phase API of aclnnWeightQuantBatchMatmulV2. ret = aclnnWeightQuantBatchMatmulV2GetWorkspaceSize( x, weight, antiquantScale, nullptr, nullptr, nullptr, nullptr, 0, yFp16, &workspaceSize, &executor); CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclnnWeightQuantBatchMatmulV2GetWorkspaceSize failed. ERROR: %d\n", ret); return ret); // Apply for device memory based on the workspaceSize computed by the first-phase API. 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 aclnnWeightQuantBatchMatmulV2. ret = aclnnWeightQuantBatchMatmulV2(workspaceAddr, workspaceSize, executor, stream); CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclnnWeightQuantBatchMatmulV2 failed. ERROR: %d\n", ret); return ret); // 4. (Boilerplate) Synchronously wait for task execution to complete. ret = aclrtSynchronizeStream(stream); CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclrtSynchronizeStream failed. ERROR: %d\n", ret); return ret); // Convert the output to FP32. 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("aclnnCast failed. ERROR: %d\n", ret); return ret); ret = aclrtSynchronizeStream(stream); CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclrtSynchronizeStream failed. ERROR: %d\n", ret); return ret); // 5. Obtain the output value and copy the result from the device memory to the host memory. Modify based on the specific API definition. auto size = GetShapeSize(yShape); std::vector<float> resultData(size, 0); ret = aclrtMemcpy( resultData.data(), resultData.size() * sizeof(resultData[0]), yDeviceAddr, size * sizeof(resultData[0]), ACL_MEMCPY_DEVICE_TO_HOST); CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("copy result from device to host failed. ERROR: %d\n", ret); return ret); PrintMat(resultData, yShape); return ACL_SUCCESS; } int main() { // 1. (Boilerplate) Initialize the device/stream. See the ACL API manual. // Fill in the deviceId based on your actual device. int32_t deviceId = 0; aclrtStream stream; auto ret = Init(deviceId, &stream); CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("Init acl failed. ERROR: %d\n", ret); return ret); // 2. Construct the inputs and outputs. Customize the construction based on the API interface. ret = AclnnWeightQuantBatchMatmulV2Test(deviceId, stream); CHECK_FREE_RET(ret == ACL_SUCCESS, LOG_PRINT("AclnnWeightQuantBatchMatmulV2Test failed. ERROR: %d\n", ret); return ret); Finalize(deviceId, stream); return 0; }A16W4 example: Call the
aclnnConvertWeightToINT4PackAPI to assist in completing the call:#include <iostream> #include <memory> #include <vector> #include "acl/acl.h" #include "aclnnop/aclnn_cast.h" #include "aclnnop/aclnn_npu_format_cast.h" #include "aclnnop/aclnn_weight_quant_batch_matmul_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) 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 allocate memory on the device side. auto ret = aclrtMalloc(deviceAddr, size, ACL_MEM_MALLOC_HUGE_FIRST); CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclrtMalloc failed. ERROR: %d\n", ret); return ret); // Copy host-side data to device-side memory using aclrtMemcpy. 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 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]; } // Create an aclTensor by calling the aclCreateTensor API. *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); // Allocate device-side memory using aclrtMalloc. 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); // Copy host-side data to device-side memory using aclrtMemcpy. 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 strides for 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 AclnnWeightQuantBatchMatmulV2Test(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. Custom construction is required 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}; // Fill with FP16 1.0. BF16 1.0 is 0b0011111110000000. std::vector<uint16_t> xHostData(GetSize(xShape), 0b0011110000000000); 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 fp16. // Create the x aclTensor. The type can be ACL_FLOAT16 or ACL_BFLOAT16. 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. The FLOAT4_E2M1 type is packed as FLOAT. 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 for converting the output back to FP32. 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, which is the actual computation output, with the same type as x. 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 aclnnWeightQuantBatchMatmulV2. workspaceSize = 0; executor = nullptr; ret = aclnnWeightQuantBatchMatmulV2GetWorkspaceSize( x, weightPacked, antiquantScale, nullptr, nullptr, nullptr, nullptr, groupSize, yFp16, &workspaceSize, &executor); CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclnnWeightQuantBatchMatmulV2GetWorkspaceSize 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 aclnnWeightQuantBatchMatmulV2. ret = aclnnWeightQuantBatchMatmulV2(workspaceAddr, workspaceSize, executor, stream); CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclnnWeightQuantBatchMatmulV2 failed. ERROR: %d\n", ret); return ret); // 4. (Boilerplate) Synchronously wait for task execution to complete. ret = aclrtSynchronizeStream(stream); CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclrtSynchronizeStream failed. ERROR: %d\n", ret); return ret); // Convert the output to FP32. workspaceSize = 0; executor = nullptr; ret = aclnnCastGetWorkspaceSize(yFp16, aclDataType::ACL_FLOAT, y, &workspaceSize, &executor); CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclnnCastGetWorkspaceSize failed. ERROR: %d\n", ret); return ret); // Apply for device memory based on the workspaceSize calculated by the first-phase API. void* workspaceCastAddr = nullptr; std::unique_ptr<void, aclError (*)(void*)> workspaceCastAddrPtr(nullptr, aclrtFree); if (workspaceSize > 0) { ret = aclrtMalloc(&workspaceCastAddr, workspaceSize, ACL_MEM_MALLOC_HUGE_FIRST); CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("allocate workspace failed. ERROR: %d\n", ret); return ret); workspaceCastAddrPtr.reset(workspaceCastAddr); } ret = aclnnCast(workspaceCastAddr, workspaceSize, executor, stream); CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclnnCast2 failed. ERROR: %d\n", ret); return ret); ret = aclrtSynchronizeStream(stream); CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclrtSynchronizeStream failed. ERROR: %d\n", ret); return ret); // 5. Obtain the output value and copy the result from the device memory to the host memory. Modify this based on the specific API definition. auto size = GetShapeSize(yShape); std::vector<float> resultData(size, 0); ret = aclrtMemcpy( resultData.data(), resultData.size() * sizeof(resultData[0]), yDeviceAddr, size * sizeof(resultData[0]), ACL_MEMCPY_DEVICE_TO_HOST); CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("copy result from device to host failed. ERROR: %d\n", ret); return ret); PrintMat(resultData, yShape); return ACL_SUCCESS; } int main() { // 1. (Boilerplate) Initialize the device/stream. See the ACL API manual. // Fill in the deviceId based on your actual device. int32_t deviceId = 0; aclrtStream stream; auto ret = AclnnWeightQuantBatchMatmulV2Test(deviceId, stream); CHECK_FREE_RET(ret == ACL_SUCCESS, LOG_PRINT("AclnnWeightQuantBatchMatmulV2Test failed. ERROR: %d\n", ret); return ret); Finalize(deviceId, stream); return 0; }