ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

CANN ops-math 算子指南:aclnnHansEncode 两段式无损压缩算子详解

2026/9/20 17:04:47 拓冰建站 浏览量
CANN ops-math 算子指南:aclnnHansEncode 两段式无损压缩算子详解 CANN ops-math 算子指南aclnnHansEncode 两段式无损压缩算子详解【免费下载链接】ops-math本项目是CANN提供的数学类基础计算算子库实现网络在NPU上加速计算。项目地址: https://gitcode.com/cann/ops-mathaclnnHansEncode 是 CANN ops-math 数学算子库中基于 ANSAsymmetric Numeral Systems思想的指数位无损压缩算子它对输入张量指数位所在字节做 PDF 概率密度统计【可选】并依据 PDF 分布执行无损编码压缩结果可存储在 Device 侧 HBM 上也可 offload 到 Host 侧配套的 aclnnHansDecode 可无损还原数据。读完本文你将掌握 aclnnHansEncode / aclnnHansEncodeGetWorkspaceSize 两段式接口的完整参数语义、错误码含义、数据规模约束以及一套可复制运行的 C 调用示例并了解该算子在 ops-math 仓库中的算子定义、tiling 与 kernel 源码实现。功能概述HansEncode 的核心功能是对输入张量input_tensor的指数位所在字节进行 PDF 统计【可选】按照 PDF 分布统计进行无损压缩压缩后的结果可存储在 Device 的 HBM 上或 offload 到 Host 侧。PDFProbability Density Function概率密度分布统计得到的是输入数据中指数位字节的取值分布它是后续编码的基础越常出现的字节值分配越短的编码从而实现压缩。压缩产物由三部分构成mantissa尾数部分原样保存的尾数位数据fixed定长部分指数位压缩后的定长编码段var变长部分指数位压缩后超出 fixed 容量的变长编码段。三者与 PDF 一起交给解码端即可无损还原原始张量。仓库中与 HansEncode 配套的解码算子为 math/hans_encode/README.md 中提到的 HansDecode示例代码中通过aclnn_hans_decode.h的aclnnHansDecode调用。产品支持情况产品是否支持Ascend 950PR / Ascend 950DT不支持Atlas A3 训练系列产品 / Atlas A3 推理系列产品支持Atlas A2 训练系列产品 / Atlas A2 推理系列产品支持Atlas 200I/500 A2 推理产品不支持Atlas 推理系列产品不支持Atlas 训练系列产品不支持注意虽然 Ascend 950 产品不在 aclnn 接口支持列表内但仓库中确实存在ascend950的算子配置op_host/config/ascend950/hans_encode_binary.json以及专用 kernelop_kernel/hans_encode_apt.cpp、op_kernel/arch35/hans_encode_simt.h并从源码结构看 Ascend 950 走的是 SIMT 共享格式头hans_format.h的编码路径本文以文档声明的 Atlas A2/A3 产品支持为准。两段式接口概览与其他 aclnn 算子一样HansEncode 采用两段式接口设计必须先调用第一段aclnnHansEncodeGetWorkspaceSize获取计算所需 workspace 大小以及包含算子计算流程的执行器再调用第二段aclnnHansEncode执行计算。两个接口的函数原型如下aclnnStatus aclnnHansEncodeGetWorkspaceSize(const aclTensor *inputTensor, aclTensor *pdfRef, bool statistic, bool reshuff, const aclTensor *mantissaOut, const aclTensor *fixedOut, const aclTensor *varOut, uint64_t *workspaceSize, aclOpExecutor **executor); aclnnStatus aclnnHansEncode(void *workspace, uint64_t workspaceSize, aclOpExecutor *executor, aclrtStream stream);其中aclnnHansEncodeGetWorkspaceSize的入参校验与 workspace 计算逻辑在算子 host 侧实现见 op_host/hans_encode_tiling.cppexecutor中封装了算子计算流程第二段接口拿到 executor 后在指定 stream 上异步下发计算任务。aclnnHansEncodeGetWorkspaceSize 参数说明参数表参数输入/输出描述inputTensor计算输入待压缩张量Device 侧的 aclTensor。数据类型支持 FLOAT16、BFLOAT16、FLOAT32数据元素个数仅支持 64 的倍数且大于等于 32768支持非连续的 Tensor数据格式支持 ND。pdfRef计算输入/计算输出inputTensor 指数位所在字节的概率密度分布Device 侧的 aclTensor。数据类型支持 INT32shape 要求为 (1, 256)其中每个元素的值表示其对应索引在 input 中出现的次数支持非连续 Tensor数据格式支持 ND。statistic计算输入是否进行 PDF 统计。reshuff计算输入是否对各核编码后的结果进行内存重整。mantissaOut计算输出输出的尾数部分Device 侧 aclTensor。数据类型支持 FLOAT16、BFLOAT16、FLOAT32需与 inputTensor 保持一致支持非连续 Tensor数据格式支持 ND。fixedOut计算输出压缩的第一段定长输出Device 侧 aclTensor。数据类型支持 FLOAT16、BFLOAT16、FLOAT32需与 inputTensor 保持一致支持非连续 Tensor数据格式支持 ND。varOut计算输出压缩超过 fixedOut 后的变长输出Device 侧 aclTensor。数据类型支持 FLOAT16、BFLOAT16、FLOAT32需与 inputTensor 保持一致支持非连续 Tensor数据格式支持 ND。workspaceSize出参返回需要在 Device 侧申请的 workspace 大小。executor出参返回 op 执行器包含算子计算流程。返回值与错误码返回aclnnStatus状态码具体参见 aclnn 返回码。第一段接口完成入参校验出现以下场景时报错返回值错误码描述ACLNN_ERR_PARAM_NULLPTR161001inputTensor、pdf、mantissaOut、fixedOut、varOut 是空指针。ACLNN_ERR_PARAM_INVALID161002pdf 长度错误或 mantissa 长度错误。ACLNN_ERR_PARAM_INVALID161002累积编码空间设置过小压缩存在溢出风险需满足(size(fixedOut) size(mantissaOut)) (len(inputTensor) len(inputTensor) / 64 8448 * processCoreDim)。ACLNN_ERR_PARAM_INVALID161002size(fixedOut) 小于 512Byte无法存储压缩元信息。ACLNN_ERR_PARAM_INVALID161002输入的元素个数不为 64 的倍数或小于 32768。ACLNN_ERR_PARAM_INVALID161002inputTensor、pdf、outputMantissaTensorOut 数据类型不支持。校验背后的源码逻辑上述校验在 op_host/hans_encode_tiling.cpp 的ParamCheck()中均有对应实现pdfNumel ! 256报错对应 pdf length must equal to 256PDF_NUMEL_LENGTH 256mantissaSize ! (dtypeBytes - 1) * inputSize报错说明 mantissa 大小必须精确等于每个元素去掉指数位后剩余字节数 × 元素个数FLOAT32 为 3 字节/元素FLOAT16/BFLOAT16 为 1 字节/元素inputSize % 64 ! 0 || inputSize 32768报错对应 tiling 中PROCESS_SIZE_PER_LOOP 64与PROCESS_MIN_SIZE_PER_CORE 32768两个常量fixedByteSize 512报错对应ENCODE_META_INFO_BYTES 512这 512 字节用于存放压缩元信息header见下文格式说明错误码表公式len(inputTensor) len(inputTensor)/64 8448*processCoreDim与 tiling 中的压缩上界计算一致compressUpperBoundBytes inputSize inputSize/PROCESS_SIZE_PER_LOOP ENCODE_TAIL_INFO_BYTES_PER_CORE*processCoreDim ENCODE_META_INFO_BYTES其中ENCODE_TAIL_INFO_BYTES_PER_CORE 8448正是每个核的尾信息字节数对应格式定义中TAIL_BYTES 8448的 static_assert。另外在 Init 阶段 tiling 会依据输入规模与可用 AIV 核数计算实际参与计算的核数processCoreDimint64_t properAivNum dataSize / PROCESS_MIN_SIZE_PER_CORE; return properAivNum maxUseAivNum ? maxUseAivNum : properAivNum;即每个核至少处理 32768 个元素Ascend 950 上还受MAX_FORMAT_CORE_NUM 56上限约束这是由 512 字节 header 最多描述 56 个核的格式限制决定的。aclnnHansEncode 参数说明参数表参数名输入/输出描述workspace输入在 Device 侧申请的 workspace 内存地址。workspaceSize输入在 Device 侧申请的 workspace 大小由第一段接口 aclnnHansEncodeGetWorkspaceSize 获取。executor输入op 执行器包含算子计算流程。stream输入指定执行任务的 Stream。返回值返回aclnnStatus状态码具体参见 aclnn 返回码。约束说明确定性计算aclnnHansEncode 默认是确定性实现即相同输入与配置下多次执行结果一致便于调试与结果复现。从源码看实现原理算子定义op_host/hans_encode_def.cppop_host/hans_encode_def.cpp 通过OpDef注册了算子原型两个输入input_tensorREQUIREDDT_FLOAT / DT_BF16 / DT_FLOAT16ND 格式AutoContiguous、pdfREQUIREDDT_INT32四个输出pdfINT32、mantissa、fixed、var均与输入同 dtype两个可选属性statistic默认 false、reshuff默认 falseAICore 配置覆盖ascend910b、ascend910_93、ascend950三个平台。其中reshuff属性在 tiling 中会触发 workspace 申请opWorkspaceSize reshuff ? compressUpperBoundBytes : 0且当reshuff为 true 时要求fixedByteSize compressUpperBoundBytesIf reshuff, the space of fixed must be greater than the upper bound。从源码结构看reshuff 开启时各核先写入临时 workspace完成内存重整后再统一搬移到 fixed 输出从而保证多核结果按核连续排布。数据格式op_kernel/hans_format.h 与 hans_const.hHansFormat 命名空间定义了编码比特流的关键布局常量BLOCK_SIZE 64、STATE_COUNT 4096每个 tile 含 4096 个状态值64 个分组 × 64PDF_LENGTH 256PDF 直方图长度HEADER_INT32_COUNT 128即 512 字节每个输出块头部存放压缩元信息其中第 0 槽为魔数MAGIC 12138第 1~4 槽分别记录核数、总 loop 数、fixed loop 数、var loop 数DEVICE_START_IDX 8到HOST_START_IDX 64之间的 56 个槽用于记录各核信息这与最多 56 个核MAX_CORE_COUNT 56的 static_assert 相呼应RECORD_BITS 16单条溢出记录位宽uint16_t编码与解码共用state 16 / state 16与counter - 16的位操作约定TAIL_BYTES 8448每核尾部状态表 计数器表字节数与错误码公式中的8448 * processCoreDim完全对应。op_kernel/hans_const.h 则定义了 kernel 侧常量和工具函数例如每个 loop 处理 4096 个元素EACH_LOOOP_PROCESS_NUM 4096即 64 个 repeat × 64、ENCODE_TAIL_INFO_BYTES_PER_CORE 8448以及 PDF 排序工具SortPdf利用Sort32MrgSort对 256 长度的 PDF 做排序以建立编码映射表还有多路 MTE2/MTE3/V/S 硬件事件的 Ping-Pong 同步管理EventManager。Kernel 入口op_kernel/hans_encode.cppop_kernel/hans_encode.cpp 是 device 侧入口函数hans_encode它根据 tiling key 区分数据类型TILING_KEY_IS(2)对应 FLOAT16/BFLOAT16TILING_KEY_IS(4)对应 FLOAT32映射关系定义在 op_host/hans_encode_tiling.cpp 的TILING_KEY_HALF/TILING_KEY_FLOAT/TILING_KEY_BFLOAT16if (TILING_KEY_IS(4)) { TPipe pipe; if (tilingData.statistic) { HansEncodeNS::AnsPdfStatisticfloat op; op.Init(pipe, config); op.Process(); // 可选PDF 统计阶段 } HansEncodeNS::HansEncodefloat opEncode; opEncode.Init(pipe, config); opEncode.Process(); // 编码阶段 }当statistic true时先执行AnsPdfStatistic统计得到 PDF 分布写入 pdf 输出再执行HansEncode编码当statistic false时跳过统计直接使用用户传入的 PDF 进行编码。编码核心类HansEncodedataTypeop_kernel/hans_encode_base.h在 Init 中按GetBlockIdx()划分各核的输入区间、mantissa 区间与 fixed 定长槽位fixedLengthPerCore/fixedLengthLastCore最后一个核处理余数部分并按 4096 元素/次 的 tile 粒度流水处理。调用示例以下示例展示了完整的编码 → 解码闭环流程仅供参考具体编译和执行过程请参考编译与运行样例。仓库中还提供了一份 RAII 版本的可编译样例 examples/test_aclnn_hans_encode.cpp使用std::unique_ptr管理 stream、device 内存与 aclTensor 生命周期二者核心调用序列一致。#include iostream #include vector #include acl/acl.h #include aclnn_hans_encode.h #include aclnn_hans_decode.h #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::vectorint64_t shape) { int64_t shapeSize 1; for (auto i : shape) { shapeSize * i; } return shapeSize; } int Init(int32_t deviceId, aclrtStream* stream) { // 固定写法资源初始化 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::vectorT hostData, const std::vectorint64_t shape, void** deviceAddr, aclDataType dataType, aclTensor** tensor) { auto size GetShapeSize(shape) * sizeof(T); // 调用aclrtMalloc申请device侧内存 auto ret aclrtMalloc(deviceAddr, size, ACL_MEM_MALLOC_HUGE_FIRST); CHECK_RET(ret ACL_SUCCESS, LOG_PRINT(aclrtMalloc failed. ERROR: %d\n, ret); return ret); // 调用aclrtMemcpy将host侧数据拷贝到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); // 计算连续tensor的strides std::vectorint64_t strides(shape.size(), 1); for (int64_t i shape.size() - 2; i 0; i--) { strides[i] shape[i 1] * strides[i 1]; } // 调用aclCreateTensor接口创建aclTensor *tensor aclCreateTensor(shape.data(), shape.size(), dataType, strides.data(), 0, aclFormat::ACL_FORMAT_ND, shape.data(), shape.size(), *deviceAddr); return 0; } int main() { // 1.固定写法device/stream初始化参考acl API // 根据自己的实际device填写deviceId 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. 构造输入与输出需要根据API的接口自定义构造 std::vectorfloat inputHost(65536, 0); std::vectorfloat mantissaHost(49152, 0); std::vectorfloat fixedHost(16384, 0); std::vectorfloat varHost(16384, 0); std::vectorint32_t pdfHost(256, 0); std::vectorfloat recoverHost(65536, 0); bool statistic true; bool reshuff false; int64_t outHostAddr -1; int64_t outHostLength 0; void* inputAddr nullptr; void* outMantissaAddr nullptr; void* outFixedAddr nullptr; void* outVarAddr nullptr; void* pdfAddr nullptr; void* recoverAddr nullptr; aclTensor* input nullptr; aclTensor* outMantissa nullptr; aclTensor* outFixed nullptr; aclTensor* pdf nullptr; aclTensor* outVar nullptr; aclTensor* recover nullptr; // 创建out aclTensor ret CreateAclTensor(inputHost, {1, 65536}, inputAddr, aclDataType::ACL_FLOAT, input); CHECK_RET(ret ACL_SUCCESS, return ret); ret CreateAclTensor(mantissaHost, {1, 49152}, outMantissaAddr, aclDataType::ACL_FLOAT, outMantissa); CHECK_RET(ret ACL_SUCCESS, return ret); ret CreateAclTensor(fixedHost, {1, 16384}, outFixedAddr, aclDataType::ACL_FLOAT, outFixed); CHECK_RET(ret ACL_SUCCESS, return ret); ret CreateAclTensor(varHost, {1, 16384}, outVarAddr, aclDataType::ACL_FLOAT, outVar); CHECK_RET(ret ACL_SUCCESS, return ret); ret CreateAclTensor(pdfHost, {1, 256}, pdfAddr, aclDataType::ACL_INT32, pdf); CHECK_RET(ret ACL_SUCCESS, return ret); ret CreateAclTensor(recoverHost, {1, 65536}, recoverAddr, aclDataType::ACL_FLOAT, recover); CHECK_RET(ret ACL_SUCCESS, return ret); // 3. 调用CANN算子库API需要修改为具体的API名称 uint64_t workspaceSize 0; aclOpExecutor* executor; // 调用aclnnHansEncode第一段接口 ret aclnnHansEncodeGetWorkspaceSize(input, pdf, statistic, reshuff, outMantissa, outFixed, outVar, workspaceSize, executor); CHECK_RET(ret ACL_SUCCESS, LOG_PRINT(aclnnHansEncodeGetWorkspaceSize failed. ERROR: %d\n, ret); return ret); // 根据第一段接口计算出的workspaceSize申请device内存 void* workspaceAddr nullptr; 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); } // 调用aclnnHansEncode第二段接口 ret aclnnHansEncode(workspaceAddr, workspaceSize, executor, stream); CHECK_RET(ret ACL_SUCCESS, LOG_PRINT(aclnnHansEncode failed. ERROR: %d\n, ret); return ret); // 4.固定写法同步等待任务执行结束 ret aclrtSynchronizeStream(stream); CHECK_RET(ret ACL_SUCCESS, LOG_PRINT(aclrtSynchronizeStream failed. ERROR: %d\n, ret); return ret); // 5. 获取输出的值将device侧内存上的结果拷贝至host侧需要根据具体API的接口定义修改 auto size 16384 * sizeof(float); std::vectorfloat resultData(16384, 0); ret aclrtMemcpy(resultData.data(), size, outFixedAddr, size, 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 128; i) { int32_t intVal *reinterpret_castint32_t*(resultData[i]); LOG_PRINT(result header[%ld] is: %d\n, i, intVal); } uint64_t workspaceSizeDecode 0; aclOpExecutor* executorDecode; // 调用aclnnHansDecode第一段接口 ret aclnnHansDecodeGetWorkspaceSize(outMantissa, outFixed, outVar, pdf, reshuff, recover, workspaceSizeDecode, executorDecode); CHECK_RET(ret ACL_SUCCESS, LOG_PRINT(aclnnHansDecodeGetWorkspaceSize failed. ERROR: %d\n, ret); return ret); // 根据第一段接口计算出的workspaceSize申请device内存 void* workspaceAddrDecode nullptr; if (workspaceSizeDecode 0) { ret aclrtMalloc(workspaceAddrDecode, workspaceSizeDecode, ACL_MEM_MALLOC_HUGE_FIRST); CHECK_RET(ret ACL_SUCCESS, LOG_PRINT(allocate workspace failed. ERROR: %d\n, ret); return ret); } // 调用aclnnHansDecode第二段接口 ret aclnnHansDecode(workspaceAddrDecode, workspaceSizeDecode, executorDecode, stream); CHECK_RET(ret ACL_SUCCESS, LOG_PRINT(aclnnHansDecode 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); std::vectorfloat recoverData(65536, 0); ret aclrtMemcpy(recoverData.data(), 65536 * sizeof(recoverData[0]), recoverAddr, 65536 * sizeof(recoverData[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 256; i) { LOG_PRINT(reco[%ld] is: %f org is: %f\n, i, recoverData[i], inputHost[i]); } // 6. 释放aclTensor需要根据具体API的接口定义修改 aclDestroyTensor(input); aclDestroyTensor(outMantissa); aclDestroyTensor(outFixed); aclDestroyTensor(outVar); aclDestroyTensor(pdf); aclDestroyTensor(recover); // 7. 释放device资源需要根据具体API的接口定义修改 aclrtFree(inputAddr); aclrtFree(outMantissaAddr); aclrtFree(outFixedAddr); aclrtFree(outVarAddr); aclrtFree(pdfAddr); aclrtFree(recoverAddr); if (workspaceSize 0) { aclrtFree(workspaceAddr); } if (workspaceSizeDecode 0) { aclrtFree(workspaceAddrDecode); } aclrtDestroyStream(stream); aclrtResetDevice(deviceId); aclFinalize(); return 0; }示例要点解读数据规模示例中 input 为 65536 个 FLOAT32 元素满足64 的倍数且 ≥ 32768的约束mantissa 为 49152 65536 × 3fixed 与 var 各 16384pdf 为 (1, 256) 的 INT32。固定调用序列aclInit → aclrtSetDevice → aclrtCreateStream → 构造 aclTensor → GetWorkspaceSize → 申请 workspace → 执行算子 → aclrtSynchronizeStream → 拷贝结果回 Host → 释放资源。workspace 条件申请只有当workspaceSize 0时才调用aclrtMalloc申请 workspace 内存本例中reshuff false编码阶段 workspace 主要由系统库接口预留sysWorkspaceSize解码阶段同理。结果校验方式示例将 fixed 输出前 128 个 int32 打印为 header 内容对应 512 字节元信息头并将解码还原结果recoverData与原始输入inputHost逐元素对比打印验证无损性。相关文件导航接口文档docs/aclnnHansEncode.md本文所依据的原始文档算子 READMEmath/hans_encode/README.md产品支持表、参数表与调用说明可编译调用样例examples/test_aclnn_hans_encode.cpp算子定义输入/输出/属性/平台注册op_host/hans_encode_def.cppTiling 与参数校验op_host/hans_encode_tiling.cpp、op_host/hans_encode_tiling.hShape 推导op_host/hans_encode_infershape.cppKernel 入口与核心实现op_kernel/hans_encode.cpp、op_kernel/hans_encode_base.h格式与常量定义op_kernel/hans_format.h、op_kernel/hans_const.h各平台算子二进制配置op_host/config/ascend910_93/hans_encode_binary.json另含 ascend910b、ascend950 目录单测用例tests/ut/op_host/test_hans_encode_tiling.cpp、tests/ut/op_host/test_hans_encode_infershape.cpp、tests/ut/op_kernel/test_hans_encode.cpp 及数据生成脚本 tests/ut/op_kernel/hans_encode_data/gen_data.py【免费下载链接】ops-math本项目是CANN提供的数学类基础计算算子库实现网络在NPU上加速计算。项目地址: https://gitcode.com/cann/ops-math创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考