aclnnWeightQuantPreprocess
产品支持情况
- Ascend 950PR/Ascend 950DT:支持
- Atlas A3 训练系列产品/Atlas A3 推理系列产品:不支持
- Atlas A2 训练系列产品/Atlas A2 推理系列产品:不支持
- Atlas 200I/500 A2 推理产品:不支持
- Atlas 推理系列产品:不支持
- Atlas 训练系列产品:不支持
功能说明
完成伪量化Matmul(包括QuantBatchMatmulV5、GroupedMatmul-伪量化、WeightQuantBatchMatmulV2)的参数预处理:主要将weight从ND格式转换为FRACTAL_NZ格式(MM_A16S4数据流的pertensor及perchannel/pergroup转置场景为ND直拷透传),并在需要时对weightScale、weightOffsetOptional、biasOptional进行同步处理。
函数原型
每个算子分为两段式接口:
- 调用
aclnnWeightQuantPreprocessGetWorkspaceSize获取workspace大小及执行器; - 调用
aclnnWeightQuantPreprocess执行计算。
注意:用户需自行构造输出张量,参考约束说明中的shape计算公式。
aclnnStatus aclnnWeightQuantPreprocessGetWorkspaceSize(
const aclTensor *weight,
const aclTensor *weightScale,
const aclTensor *weightOffsetOptional,
const aclTensor *biasOptional,
const aclDataType xDtype,
const aclDataType xScaleDtype,
const int64_t kGroupSize,
aclTensor *outWeight,
aclTensor *outWeightScale,
aclTensor *outWeightOffsetOptional,
aclTensor *outBiasOptional,
uint64_t *workspaceSize,
aclOpExecutor **executor)
aclnnStatus aclnnWeightQuantPreprocess(
void *workspace,
uint64_t workspaceSize,
aclOpExecutor *executor,
aclrtStream stream)
aclnnWeightQuantPreprocessGetWorkspaceSize
-
参数说明
参数名 输入/输出 描述 使用说明 数据类型 数据格式 维度(shape) 非连续Tensor weight(aclTensor *) 输入 Matmul的权重矩阵 不支持空tensor float4_e2m1/int4 ND 2-3 仅转置场景支持 weightScale(aclTensor *) 输入 权重的反量化scale参数 不支持空tensor float8_e8m0/float16/bfloat16 ND/NCL/NCHW 1-4 仅转置场景支持 weightOffsetOptional(aclTensor *) 可选输入 权重的反量化offset参数 MM_A16S4数据流支持透传(要求与weightScale同viewShape、同数据类型);其余数据流不支持,必须为nullptr float16/bfloat16 ND 1-2 仅转置场景支持 biasOptional(aclTensor *) 可选输入 Matmul的偏置矩阵 不支持空tensor,必须contiguous float16/bfloat16 ND 1-2 不支持 xDtype(aclDataType) 输入 Matmul的激活矩阵的数据类型 - aclDataType - - - xScaleDtype(aclDataType) 输入 激活的量化scale参数的数据类型 - aclDataType - - - kGroupSize(int64_t) 输入 权重在pergroup量化时K维度的group的大小 - int64 - - - outWeight(aclTensor *) 输出 预处理后的weight - int8/int4/fp8_e4m3/hif8/fp4_e2m1 ND/NZ 2-5 仅转置场景支持 outWeightScale(aclTensor *) 输出 预处理后的weightScale - float16/bfloat16/fp8_e8m0 ND/NCL/NCHW 1-4 仅转置场景支持 outWeightOffsetOptional(aclTensor *) 输出 预处理后的weightOffset weightOffsetOptional非空时不可为nullptr,且要求与weightOffsetOptional同viewShape、同storageShape、同数据类型;其余场景必须为nullptr float16/bfloat16 ND 1-2 仅转置场景支持 outBiasOptional(aclTensor *) 输出 预处理后的bias 必须contiguous float16/bfloat16 ND 1-2 不支持 workspaceSize(uint64_t *) 输出 计算所需的workspace大小 - uint64* - - - executor(aclOpExecutor **) 输出 包含算子计算流程的执行器 - aclOpExecutor** - - - -
返回值
aclnnStatus:返回状态码,具体参见aclnn返回码。
第一段接口完成入参校验,出现以下场景时报错:
返回值 错误码 描述 ACLNN_ERR_PARAM_NULLPTR 161001 weight、weightScale、outWeight或outWeightScale是空指针;或biasOptional非空但outBiasOptional是空指针。 ACLNN_ERR_PARAM_INVALID 161002 输入的数据类型组合不支持,无法匹配当前支持的任何数据流(MM_MX_A8W4/GMM_MX_A8W4/MM_A16S4)。 weight、weightScale、outWeight或outWeightScale是空tensor;或biasOptional/outBiasOptional在提供时为空tensor。 weight、weightScale、biasOptional、outWeight、outWeightScale或outBiasOptional的数据类型和数据格式不在支持的范围之内。 weight、weightScale、biasOptional、outWeight、outWeightScale或outBiasOptional的shape或storage shape不满足校验条件。 weight或weightScale的stride不满足转置要求,或biasOptional/outBiasOptional在提供时不连续。 weightOffsetOptional或outWeightOffsetOptional非空(MM_MX_A8W4/GMM_MX_A8W4数据流;MM_A16S4数据流支持透传,要求与weightScale同viewShape、同数据类型),或kGroupSize不等于32(MM_MX_A8W4/GMM_MX_A8W4数据流);MM_A16S4数据流还要求kGroupSize与weightScale的量化粒度匹配。 ACLNN_ERR_RUNTIME_ERROR 361001 产品型号不支持。 ACLNN_ERR_INNER_CREATE_EXECUTOR 561101 内部错误,执行器创建失败。 ACLNN_ERR_INNER_NULLPTR 561103 workspaceSize或executor是空指针,或API内部构图接口返回空指针。
aclnnWeightQuantPreprocess
-
参数说明
参数名 输入/输出 描述 workspace 输入 在Device侧申请的workspace内存地址。 workspaceSize 输入 在Device侧申请的workspace大小,由第一段接口aclnnWeightQuantPreprocessGetWorkspaceSize获取。 executor 输入 op执行器,包含了算子计算流程。 stream 输入 指定执行任务的Stream。 -
返回值
aclnnStatus:返回状态码,具体参见aclnn返回码。
约束说明
- 确定性说明:aclnnWeightQuantPreprocess默认确定性实现。
MM_MX_A8W4数据流(MM表示Matmul;MX_A8W4表示x的数据类型为FLOAT8_E4M3FN,weight的数据类型为FLOAT4_E2M1,Mx量化模式)
-
weight
- 数据类型:FLOAT4_E2M1
- 格式:ND
- K % kGroupSize == 0
- view shape:2-D
{K, N} - storage shape:
{N, K}(transposed) - stride:
[1, K](最后两维transposed) - 不支持空tensor
-
weightScale
- 数据类型:FLOAT8_E8M0
- 格式:ND/NCL
- view shape:3-D
{ceildiv(K, 64), N, 2} - storage shape:
{N, ceildiv(K, 64), 2}(transposed) - stride:
[2, 2*ceildiv(K,64), 1](维度0和1交换) - 不支持空tensor
-
weightOffsetOptional
- 当前不支持,必须为nullptr
- outWeightOffsetOptional也必须为nullptr
-
biasOptional
- 数据类型:float16/bfloat16
- 格式:ND
- 必须为contiguous
- 不支持空tensor(若提供)
-
kGroupSize
- 必须等于 32
-
xDtype
- FLOAT8_E4M3FN
-
xScaleDtype
- FLOAT8_E8M0
-
outWeight
- 数据类型:与weight相同
- 格式:FRACTAL_NZ_C0_32
- view shape:与weight view shape相同
{K, N} - storage shape:4-D
{ceildiv(K, 32), ceildiv(N, 16), 16, 32}
-
outWeightScale
- 数据类型:与weightScale相同
- 格式:ND
- view shape:与weightScale view shape相同
- storage shape:与weightScale storage shape相同
-
outBiasOptional
- 数据类型:与biasOptional相同
- 格式:ND
- 必须为contiguous
- view shape:与biasOptional相同
- storage shape:与biasOptional相同
GMM_MX_A8W4数据流(GMM表示GroupedMatmul;MX_A8W4表示x的数据类型为FLOAT8_E4M3FN,weight的数据类型为FLOAT4_E2M1,Mx量化模式)
-
weight
- 数据类型:FLOAT4_E2M1
- 格式:ND
- K % kGroupSize == 0
- view shape:3-D
{G, K, N} - storage shape:
{G, N, K}(transposed,最后两维交换) - stride:
[K*N, 1, K](维度1和2 transposed) - 不支持空tensor
-
weightScale
- 数据类型:FLOAT8_E8M0
- 格式:ND/NCL/NCHW
- view shape:4-D
{G, ceildiv(K, 64), N, 2} - storage shape:
{G, N, ceildiv(K, 64), 2}(transposed,维度2和3交换) - stride:
[2*ceildiv(K,64)*N, 2, 2*ceildiv(K,64), 1](维度2和3交换) - 不支持空tensor
-
weightOffsetOptional
- 当前不支持,必须为nullptr
- outWeightOffsetOptional也必须为nullptr
-
biasOptional
- 数据类型:float16/bfloat16
- 格式:ND
- 必须为contiguous
- 不支持空tensor(若提供)
-
kGroupSize
- 必须等于 32
-
xDtype
- FLOAT8_E4M3FN
-
xScaleDtype
- FLOAT8_E8M0
-
outWeight
- 数据类型:与weight相同
- 格式:FRACTAL_NZ_C0_32
- view shape:与weight view shape相同
{G, K, N} - storage shape:5-D
{G, ceildiv(K, 32), ceildiv(N, 16), 16, 32}
-
outWeightScale
- 数据类型:与weightScale相同
- 格式:ND
- view shape:与weightScale view shape相同
- storage shape:与weightScale storage shape相同
-
outBiasOptional
- 数据类型:与biasOptional相同
- 格式:ND
- 必须为contiguous
- view shape:与biasOptional相同
- storage shape:与biasOptional相同
MM_A16S4数据流(MM表示Matmul;A16S4表示x的数据类型为FLOAT16/BFLOAT16,weight的数据类型为INT4(紧凑4-bit,每字节打包2个值),支持pertensor/perchannel/pergroup量化模式)
-
weight
- 数据类型:INT4
- 格式:ND
- view shape:2-D
{K, N}(K为reduction维,N为输出维) - 支持连续或末两维严格转置(stride
[1, K]) - 紧凑4-bit打包维(连续维)必须为偶数:非转置时N为偶数,转置时K为偶数
- 不支持空tensor
-
weightScale
- 数据类型:FLOAT16/BFLOAT16
- 格式:ND
- view shape按量化粒度区分:
- pertensor:单元素,
{1}或{1, 1} - perchannel:
{N}或{1, N} - pergroup:2-D
{ceildiv(K, kGroupSize), N},且分组数大于1;weight转置输入时,weightScale需与weight布局一致(同为转置视图)
- pertensor:单元素,
- 不支持空tensor
-
weightOffsetOptional
- 可选;若提供,数据类型与view shape必须与weightScale相同,格式为ND,不支持空tensor
- outWeightOffsetOptional同步要求:与weightOffsetOptional同view shape、同storage shape、同数据类型
-
biasOptional
- 格式:ND
- view shape:
{N}或{1, N} - 必须为contiguous
- 不支持空tensor(若提供)
-
kGroupSize
- pertensor/perchannel:必须等于 0
- pergroup:必须大于 0,且weightScale第0维等于ceildiv(K, kGroupSize)
-
xDtype
- FLOAT16/BFLOAT16
-
xScaleDtype
- 必须为ACL_DT_UNDEFINED
-
outWeight
- 数据类型:与weight相同
- 格式:pertensor(转置/非转置)与perchannel/pergroup转置场景为ND直拷(物理透传);perchannel/pergroup非转置场景为FRACTAL_NZ_C0_16(ND→NZ分形转换)
- view shape:与weight view shape相同
{K, N} - storage shape:FRACTAL_NZ_C0_16时为4-D
{ceildiv(N, 16), ceildiv(K, 16), 16, 16}(N块在前,区别于A8W4 NZ_C0_32的K块在前);ND直拷场景与输入weight保持相同的view shape/strides(属纯透传)
-
outWeightScale
- 数据类型:与weightScale相同
- 格式:ND
- view shape:与weightScale view shape相同
- storage shape:与weightScale storage shape相同
-
outBiasOptional
- 数据类型:与biasOptional相同
- 格式:ND
- 必须为contiguous
- view shape:与biasOptional相同
- storage shape:与biasOptional相同
- 除上述数据流外的数据类型与shape组合为预留接口,当前调用将返回ACLNN_ERR_PARAM_INVALID
调用示例
示例代码如下,仅供参考,具体编译和执行过程请参考编译与运行样例。
注意:用户需自行计算并构造输出张量shape,参考约束说明中的公式。以下示例为MM_MX_A8W4数据流:
- outWeight viewShape:与weight viewShape相同
- outWeight storageShape:
{CeilDiv(K, 32), CeilDiv(N, 16), 16, 32} - outWeight format:
ACL_FORMAT_FRACTAL_NZ_C0_32
#include <iostream>
#include <memory>
#include <vector>
#include "acl/acl.h"
#include "aclnnop/aclnn_weight_quant_preprocess.h"
#define CHECK_RET(cond, return_expr) \
do { \
if (!(cond)) { \
return_expr; \
} \
} while (0)
#define CEIL_DIV(x, y) (((x) + (y) - 1) / (y))
int64_t GetShapeSize(const std::vector<int64_t>& shape)
{
int64_t size = 1;
for (auto d : shape)
size *= d;
return size;
}
class AclRuntimeGuard {
public:
explicit AclRuntimeGuard(int32_t deviceId) : deviceId_(deviceId) {}
~AclRuntimeGuard()
{
if (stream_ != nullptr) {
aclrtDestroyStream(stream_);
stream_ = nullptr;
}
if (deviceSet_) {
aclrtResetDevice(deviceId_);
deviceSet_ = false;
}
if (aclInited_) {
aclFinalize();
aclInited_ = false;
}
}
int Init(aclrtStream* stream)
{
auto ret = aclInit(nullptr);
CHECK_RET(ret == ACL_SUCCESS, return ret);
aclInited_ = true;
ret = aclrtSetDevice(deviceId_);
CHECK_RET(ret == ACL_SUCCESS, return ret);
deviceSet_ = true;
ret = aclrtCreateStream(stream);
CHECK_RET(ret == ACL_SUCCESS, return ret);
stream_ = *stream;
return ACL_SUCCESS;
}
private:
int32_t deviceId_;
aclrtStream stream_ = nullptr;
bool aclInited_ = false;
bool deviceSet_ = false;
};
int main()
{
int32_t deviceId = 0;
aclrtStream stream = nullptr;
AclRuntimeGuard aclGuard(deviceId);
auto ret = aclGuard.Init(&stream);
CHECK_RET(ret == ACL_SUCCESS, std::cout << "Init failed" << std::endl; return ret);
// weight: FLOAT4_E2M1, transposed (MM_MX_A8W4)
int64_t k = 64;
int64_t n = 128;
int64_t C0 = 32; // FLOAT4_E2M1 对应 C0=32
std::vector<int64_t> weightViewShape = {k, n};
std::vector<int64_t> weightStorageShape = {n, k};
std::vector<int64_t> weightStrides = {1, k};
int64_t weightStorageSize = GetShapeSize(weightStorageShape);
int64_t weightBytes = weightStorageSize / 2; // FP4: 4 bits = 0.5 bytes per element
std::vector<int8_t> weightHostData(weightBytes, 0);
void* weightDeviceAddr = nullptr;
ret = aclrtMalloc(&weightDeviceAddr, weightBytes, ACL_MEM_MALLOC_HUGE_FIRST);
CHECK_RET(ret == ACL_SUCCESS, std::cout << "Malloc weight failed" << std::endl; return ret);
std::unique_ptr<void, aclError (*)(void*)> weightDeviceAddrPtr(weightDeviceAddr, aclrtFree);
ret = aclrtMemcpy(weightDeviceAddr, weightBytes, weightHostData.data(), weightBytes, ACL_MEMCPY_HOST_TO_DEVICE);
CHECK_RET(ret == ACL_SUCCESS, std::cout << "Memcpy weight failed" << std::endl; return ret);
aclTensor* weight = aclCreateTensor(weightViewShape.data(), weightViewShape.size(), ACL_FLOAT4_E2M1,
weightStrides.data(), 0, ACL_FORMAT_ND, weightStorageShape.data(),
weightStorageShape.size(), weightDeviceAddr);
std::unique_ptr<aclTensor, aclnnStatus (*)(const aclTensor*)> weightPtr(weight, aclDestroyTensor);
CHECK_RET(weight != nullptr, std::cout << "Create weight tensor failed" << std::endl; return ACL_ERROR_FAILURE);
// weightScale: FLOAT8_E8M0, 3-D transposed (MM_MX_A8W4)
// viewShape: {ceildiv(K,64), N, 2} = {1, 128, 2}
// storageShape: {N, ceildiv(K,64), 2} = {128, 1, 2}
// transposed stride: {2, 2, 1} (dim0 <-> dim1)
std::vector<int64_t> scaleViewShape = {k / 64, n, 2};
std::vector<int64_t> scaleStorageShape = {n, k / 64, 2};
std::vector<int64_t> scaleStrides = {2, 2, 1};
int64_t scaleStorageSize = GetShapeSize(scaleStorageShape);
int64_t scaleBytes = scaleStorageSize; // FP8: 1 byte per element
std::vector<int8_t> scaleHostData(scaleBytes, 0);
void* scaleDeviceAddr = nullptr;
ret = aclrtMalloc(&scaleDeviceAddr, scaleBytes, ACL_MEM_MALLOC_HUGE_FIRST);
CHECK_RET(ret == ACL_SUCCESS, std::cout << "Malloc weightScale failed" << std::endl; return ret);
std::unique_ptr<void, aclError (*)(void*)> scaleDeviceAddrPtr(scaleDeviceAddr, aclrtFree);
ret = aclrtMemcpy(scaleDeviceAddr, scaleBytes, scaleHostData.data(), scaleBytes, ACL_MEMCPY_HOST_TO_DEVICE);
CHECK_RET(ret == ACL_SUCCESS, std::cout << "Memcpy weightScale failed" << std::endl; return ret);
aclTensor* weightScale = aclCreateTensor(scaleViewShape.data(), scaleViewShape.size(), ACL_FLOAT8_E8M0,
scaleStrides.data(), 0, ACL_FORMAT_ND, scaleStorageShape.data(),
scaleStorageShape.size(), scaleDeviceAddr);
std::unique_ptr<aclTensor, aclnnStatus (*)(const aclTensor*)> weightScalePtr(weightScale, aclDestroyTensor);
CHECK_RET(weightScale != nullptr, std::cout << "Create weightScale tensor failed" << std::endl;
return ACL_ERROR_FAILURE);
// 用户自行构造 outWeight (FRACTAL_NZ_C0_32)
// viewShape 与 weight viewShape 相同,storageShape 按公式计算
std::vector<int64_t> outWeightViewShape = {k, n};
std::vector<int64_t> outWeightStorageShape = {CEIL_DIV(k, C0), CEIL_DIV(n, 16), 16, C0};
int64_t outWeightStorageSize = GetShapeSize(outWeightStorageShape);
int64_t outWeightBytes = outWeightStorageSize / 2; // FP4
void* outWeightDeviceAddr = nullptr;
ret = aclrtMalloc(&outWeightDeviceAddr, outWeightBytes, ACL_MEM_MALLOC_HUGE_FIRST);
CHECK_RET(ret == ACL_SUCCESS, std::cout << "Malloc outWeight failed" << std::endl; return ret);
std::unique_ptr<void, aclError (*)(void*)> outWeightDeviceAddrPtr(outWeightDeviceAddr, aclrtFree);
aclTensor* outWeight = aclCreateTensor(outWeightViewShape.data(), outWeightViewShape.size(), ACL_FLOAT4_E2M1,
nullptr, 0, ACL_FORMAT_FRACTAL_NZ_C0_32, outWeightStorageShape.data(),
outWeightStorageShape.size(), outWeightDeviceAddr);
std::unique_ptr<aclTensor, aclnnStatus (*)(const aclTensor*)> outWeightPtr(outWeight, aclDestroyTensor);
CHECK_RET(outWeight != nullptr, std::cout << "Create outWeight tensor failed" << std::endl;
return ACL_ERROR_FAILURE);
// 构造 outWeightScale (viewShape 和 storageShape 都与 weightScale 相同)
// 根据实现要求:outWeightScale 的 viewShape 和 storageShape 必须都与 weightScale 相同
void* outScaleDeviceAddr = nullptr;
ret = aclrtMalloc(&outScaleDeviceAddr, scaleBytes, ACL_MEM_MALLOC_HUGE_FIRST);
CHECK_RET(ret == ACL_SUCCESS, std::cout << "Malloc outWeightScale failed" << std::endl; return ret);
std::unique_ptr<void, aclError (*)(void*)> outScaleDeviceAddrPtr(outScaleDeviceAddr, aclrtFree);
aclTensor* outWeightScale = aclCreateTensor(scaleViewShape.data(), scaleViewShape.size(), ACL_FLOAT8_E8M0,
scaleStrides.data(), 0, ACL_FORMAT_ND, scaleStorageShape.data(),
scaleStorageShape.size(), outScaleDeviceAddr);
std::unique_ptr<aclTensor, aclnnStatus (*)(const aclTensor*)> outWeightScalePtr(outWeightScale, aclDestroyTensor);
CHECK_RET(outWeightScale != nullptr, std::cout << "Create outWeightScale tensor failed" << std::endl;
return ACL_ERROR_FAILURE);
aclDataType xDtype = ACL_FLOAT8_E4M3FN;
aclDataType xScaleDtype = ACL_FLOAT8_E8M0;
int64_t kGroupSize = 32;
// 1. 获取 workspace 与执行器
uint64_t workspaceSize = 0;
aclOpExecutor* executor = nullptr;
ret = aclnnWeightQuantPreprocessGetWorkspaceSize(
weight, weightScale, nullptr, nullptr, // weightOffsetOptional, biasOptional
xDtype, xScaleDtype, kGroupSize, outWeight, outWeightScale, nullptr, nullptr, &workspaceSize, &executor);
CHECK_RET(ret == ACL_SUCCESS, std::cout << "GetWorkspaceSize failed" << std::endl; return ret);
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, std::cout << "Malloc workspace failed" << std::endl; return ret);
workspaceAddrPtr.reset(workspaceAddr);
}
// 2. 执行计算
ret = aclnnWeightQuantPreprocess(workspaceAddr, workspaceSize, executor, stream);
CHECK_RET(ret == ACL_SUCCESS, std::cout << "Preprocess failed" << std::endl; return ret);
ret = aclrtSynchronizeStream(stream);
CHECK_RET(ret == ACL_SUCCESS, std::cout << "Synchronize failed" << std::endl; return ret);
// 3. 释放资源
workspaceAddrPtr.reset();
outWeightScalePtr.reset();
outWeightPtr.reset();
weightScalePtr.reset();
weightPtr.reset();
outScaleDeviceAddrPtr.reset();
outWeightDeviceAddrPtr.reset();
scaleDeviceAddrPtr.reset();
weightDeviceAddrPtr.reset();
return 0;
}
MM_A16S4数据流调用示例如下(x为FLOAT16,weight为INT4紧凑排布;包含perchannel非转置NZ_C0_16输出与pertensor ND直拷两个场景):
- outWeight viewShape:与weight viewShape相同
- outWeight storageShape:FRACTAL_NZ_C0_16输出场景为
{CeilDiv(N, 16), CeilDiv(K, 16), 16, 16}(N块在前,区别于MM_MX_A8W4的{CeilDiv(K, 32), CeilDiv(N, 16), 16, 32});ND直拷场景与输入weight保持相同的view shape/strides - outWeight format:perchannel/pergroup非转置场景为
ACL_FORMAT_FRACTAL_NZ_C0_16,pertensor及perchannel/pergroup转置场景为ACL_FORMAT_ND
#include <iostream>
#include <memory>
#include <vector>
#include "acl/acl.h"
#include "aclnnop/aclnn_weight_quant_preprocess.h"
#define CHECK_RET(cond, return_expr) \
do { \
if (!(cond)) { \
return_expr; \
} \
} while (0)
#define CEIL_DIV(x, y) (((x) + (y) - 1) / (y))
int64_t GetShapeSize(const std::vector<int64_t>& shape)
{
int64_t size = 1;
for (auto d : shape)
size *= d;
return size;
}
class AclRuntimeGuard {
public:
explicit AclRuntimeGuard(int32_t deviceId) : deviceId_(deviceId) {}
~AclRuntimeGuard()
{
if (stream_ != nullptr) {
aclrtDestroyStream(stream_);
stream_ = nullptr;
}
if (deviceSet_) {
aclrtResetDevice(deviceId_);
deviceSet_ = false;
}
if (aclInited_) {
aclFinalize();
aclInited_ = false;
}
}
int Init(aclrtStream* stream)
{
auto ret = aclInit(nullptr);
CHECK_RET(ret == ACL_SUCCESS, return ret);
aclInited_ = true;
ret = aclrtSetDevice(deviceId_);
CHECK_RET(ret == ACL_SUCCESS, return ret);
deviceSet_ = true;
ret = aclrtCreateStream(stream);
CHECK_RET(ret == ACL_SUCCESS, return ret);
stream_ = *stream;
return ACL_SUCCESS;
}
private:
int32_t deviceId_;
aclrtStream stream_ = nullptr;
bool aclInited_ = false;
bool deviceSet_ = false;
};
// 设备侧 Tensor 持有者:析构时按创建逆序自动释放 tensor 与 device 内存
class DeviceTensor {
public:
// bytesPerElem=0.5 表示 4-bit 紧凑打包(INT4/FP4,每字节 2 个值),内存按 numel/2 字节申请
DeviceTensor(const std::vector<int64_t>& viewShape, const std::vector<int64_t>& storageShape,
const std::vector<int64_t>& strides, aclDataType dtype, aclFormat format, double bytesPerElem)
{
int64_t storageSize = GetShapeSize(storageShape);
bytes_ = static_cast<int64_t>(storageSize * bytesPerElem);
std::vector<int8_t> hostData(bytes_, 0);
auto ret = aclrtMalloc(&deviceAddr_, bytes_, ACL_MEM_MALLOC_HUGE_FIRST);
CHECK_RET(ret == ACL_SUCCESS, std::cout << "Malloc device memory failed" << std::endl; return);
ret = aclrtMemcpy(deviceAddr_, bytes_, hostData.data(), bytes_, ACL_MEMCPY_HOST_TO_DEVICE);
CHECK_RET(ret == ACL_SUCCESS, std::cout << "Memcpy H2D failed" << std::endl; return);
const int64_t* stridesData = strides.empty() ? nullptr : strides.data();
tensor_ = aclCreateTensor(viewShape.data(), viewShape.size(), dtype, stridesData, 0, format,
storageShape.data(), storageShape.size(), deviceAddr_);
CHECK_RET(tensor_ != nullptr, std::cout << "Create tensor failed" << std::endl; return);
}
~DeviceTensor()
{
if (tensor_ != nullptr) {
aclDestroyTensor(tensor_);
tensor_ = nullptr;
}
if (deviceAddr_ != nullptr) {
aclrtFree(deviceAddr_);
deviceAddr_ = nullptr;
}
}
aclTensor* Get() const { return tensor_; }
bool IsValid() const { return tensor_ != nullptr && deviceAddr_ != nullptr; }
private:
aclTensor* tensor_ = nullptr;
void* deviceAddr_ = nullptr;
int64_t bytes_ = 0;
};
int RunPreprocess(aclTensor* weight, aclTensor* weightScale, aclTensor* weightOffset, aclDataType xDtype,
int64_t kGroupSize, aclTensor* outWeight, aclTensor* outWeightScale, aclTensor* outWeightOffset,
aclrtStream stream)
{
// A16W4 数据流无 xScale 语义,xScaleDtype 固定传 ACL_DT_UNDEFINED
aclDataType xScaleDtype = ACL_DT_UNDEFINED;
// 1. 获取 workspace 与执行器
uint64_t workspaceSize = 0;
aclOpExecutor* executor = nullptr;
auto ret = aclnnWeightQuantPreprocessGetWorkspaceSize(weight, weightScale, weightOffset, nullptr, // biasOptional
xDtype, xScaleDtype, kGroupSize, outWeight, outWeightScale,
outWeightOffset, nullptr, // outBiasOptional
&workspaceSize, &executor);
CHECK_RET(ret == ACL_SUCCESS, std::cout << "GetWorkspaceSize failed, ret=" << ret << std::endl; return ret);
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, std::cout << "Malloc workspace failed" << std::endl; return ret);
workspaceAddrPtr.reset(workspaceAddr);
}
// 2. 执行计算
ret = aclnnWeightQuantPreprocess(workspaceAddr, workspaceSize, executor, stream);
CHECK_RET(ret == ACL_SUCCESS, std::cout << "Preprocess failed, ret=" << ret << std::endl; return ret);
ret = aclrtSynchronizeStream(stream);
CHECK_RET(ret == ACL_SUCCESS, std::cout << "Synchronize failed" << std::endl; return ret);
return ACL_SUCCESS;
}
// 场景一:MM_A16S4_PERCHANNEL(INT4 perchannel,非转置 weight -> FRACTAL_NZ_C0_16 输出,offset 直拷透传)
int TestA16S4PerChannelNz(aclrtStream stream)
{
int64_t k = 256;
int64_t n = 256;
// weight: INT4,非转置连续 [K, N];紧凑 4-bit 打包维(N)须为偶数
DeviceTensor weight({k, n}, {k, n}, {n, 1}, ACL_INT4, ACL_FORMAT_ND, 0.5);
CHECK_RET(weight.IsValid(), return ACL_ERROR_FAILURE);
// weightScale: perchannel {N},FP16;perchannel 场景 kGroupSize 必须为 0
DeviceTensor weightScale({n}, {n}, {1}, ACL_FLOAT16, ACL_FORMAT_ND, 2);
CHECK_RET(weightScale.IsValid(), return ACL_ERROR_FAILURE);
// weightOffsetOptional: A16S4 各数据流支持透传,要求与 weightScale 同形同 dtype
DeviceTensor weightOffset({n}, {n}, {1}, ACL_FLOAT16, ACL_FORMAT_ND, 2);
CHECK_RET(weightOffset.IsValid(), return ACL_ERROR_FAILURE);
// outWeight: 用户自行构造,viewShape 与 weight 相同;NZ_C0_16 的 storageShape 为
// {ceildiv(N, 16), ceildiv(K, 16), 16, 16}(N 块在前,区别于 A8W4 NZ_C0_32 的 K 块在前)
std::vector<int64_t> outStorageShape = {CEIL_DIV(n, 16), CEIL_DIV(k, 16), 16, 16};
DeviceTensor outWeight({k, n}, outStorageShape, {}, ACL_INT4, ACL_FORMAT_FRACTAL_NZ_C0_16, 0.5);
CHECK_RET(outWeight.IsValid(), return ACL_ERROR_FAILURE);
// outWeightScale/outWeightOffset: viewShape、storageShape、dtype 均与输入相同(直拷)
DeviceTensor outWeightScale({n}, {n}, {1}, ACL_FLOAT16, ACL_FORMAT_ND, 2);
CHECK_RET(outWeightScale.IsValid(), return ACL_ERROR_FAILURE);
DeviceTensor outWeightOffset({n}, {n}, {1}, ACL_FLOAT16, ACL_FORMAT_ND, 2);
CHECK_RET(outWeightOffset.IsValid(), return ACL_ERROR_FAILURE);
auto ret = RunPreprocess(weight.Get(), weightScale.Get(), weightOffset.Get(), ACL_FLOAT16, 0, outWeight.Get(),
outWeightScale.Get(), outWeightOffset.Get(), stream);
CHECK_RET(ret == ACL_SUCCESS, std::cout << "TestA16S4PerChannelNz failed" << std::endl; return ret);
std::cout << "TestA16S4PerChannelNz success" << std::endl;
return ACL_SUCCESS;
}
// 场景二:MM_A16S4_PERTENSOR(INT4 pertensor,一律 ND 直拷,与转置无关)
int TestA16S4PerTensorNd(aclrtStream stream)
{
int64_t k = 256;
int64_t n = 256;
// weight: INT4,连续 [K, N](pertensor 对转置/非转置均按字节直拷)
DeviceTensor weight({k, n}, {k, n}, {n, 1}, ACL_INT4, ACL_FORMAT_ND, 0.5);
CHECK_RET(weight.IsValid(), return ACL_ERROR_FAILURE);
// weightScale: pertensor 单元素 {1},FP16;kGroupSize 必须为 0
DeviceTensor weightScale({1}, {1}, {1}, ACL_FLOAT16, ACL_FORMAT_ND, 2);
CHECK_RET(weightScale.IsValid(), return ACL_ERROR_FAILURE);
DeviceTensor weightOffset({1}, {1}, {1}, ACL_FLOAT16, ACL_FORMAT_ND, 2);
CHECK_RET(weightOffset.IsValid(), return ACL_ERROR_FAILURE);
// outWeight: ND 直拷,format 为 ND,storageShape 与 viewShape 相同
DeviceTensor outWeight({k, n}, {k, n}, {}, ACL_INT4, ACL_FORMAT_ND, 0.5);
CHECK_RET(outWeight.IsValid(), return ACL_ERROR_FAILURE);
DeviceTensor outWeightScale({1}, {1}, {1}, ACL_FLOAT16, ACL_FORMAT_ND, 2);
CHECK_RET(outWeightScale.IsValid(), return ACL_ERROR_FAILURE);
DeviceTensor outWeightOffset({1}, {1}, {1}, ACL_FLOAT16, ACL_FORMAT_ND, 2);
CHECK_RET(outWeightOffset.IsValid(), return ACL_ERROR_FAILURE);
auto ret = RunPreprocess(weight.Get(), weightScale.Get(), weightOffset.Get(), ACL_FLOAT16, 0, outWeight.Get(),
outWeightScale.Get(), outWeightOffset.Get(), stream);
CHECK_RET(ret == ACL_SUCCESS, std::cout << "TestA16S4PerTensorNd failed" << std::endl; return ret);
std::cout << "TestA16S4PerTensorNd success" << std::endl;
return ACL_SUCCESS;
}
// WeightQuantPreprocess currently supports Ascend 950 only.
int main()
{
int32_t deviceId = 0;
aclrtStream stream = nullptr;
AclRuntimeGuard aclGuard(deviceId);
auto ret = aclGuard.Init(&stream);
CHECK_RET(ret == ACL_SUCCESS, std::cout << "Init failed" << std::endl; return ret);
ret = TestA16S4PerChannelNz(stream);
CHECK_RET(ret == ACL_SUCCESS, return ret);
ret = TestA16S4PerTensorNd(stream);
CHECK_RET(ret == ACL_SUCCESS, return ret);
std::cout << "All A16S4 examples run success" << std::endl;
return 0;
}