CANN ops-math 算子 aclnnChunkCat 接口详解:分块级联(Chunk + Cat)的两段式调用实战
2026/9/18 4:02:24 网站建设 项目流程

CANN ops-math 算子 aclnnChunkCat 接口详解:分块级联(Chunk + Cat)的两段式调用实战

【免费下载链接】ops-math本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。项目地址: https://gitcode.com/cann/ops-math

本文围绕 CANN 开源数学算子库 ops-math 中 ChunkCat 算子的官方接口文档 aclnnChunkCat.md 展开,完整介绍 aclnnChunkCat 的产品支持情况、计算语义、两段式 API 的函数原型与全部入参约束、错误码场景,并结合仓库内 op_host / op_api / op_kernel 的源码实现说明其 Shape 推导、Tiling 切分与 Kernel 计算原理,最后给出可直接编译运行的完整调用示例。读者读完可掌握在 Ascend NPU 上通过 aclnn 接口完成“先分块、再级联、再转类型”的张量变换。

一、功能说明:ChunkCat 到底做了什么

ChunkCat 算子的功能是将输入的 tensor 列表tensors中所有 tensor:

  1. 先按照维度dim切分为numChunks
  2. 再按照dim后一维进行级联(concatenate);
  3. 最后转换为out的数据类型输出。

out = Cast(Concat(Chunk(tensors, dim, numChunks), dim+1), out_dtype)。该算子位于仓库的 conversion 目录(conversion/chunk_cat),属于数据转换类基础算子,适用于需要对张量做“分块后横向拼接”的场景。

从算子定义 chunk_cat_def.cpp 可以看到它的完整 IO 与属性声明:

  • 输入x:动态个数(ParamType(DYNAMIC))的 tensor 列表,数据类型支持 FLOAT16、BF16、FLOAT;
  • 输出y:必选输出,数据类型支持 FLOAT16、BF16、FLOAT(可以与输入不同);
  • 属性dim:必选整型属性,切分维度;
  • 属性num_chunks:必选整型属性,切分块数;
  • 支持的硬件配置为ascend910bascend910_93ascend950ascend350四类 AICore 配置。

输出 Shape 的推导规则

从 chunk_cat_infershape.cpp 的实现可以看出输出 shape 的计算方式:

  • 输出维度数为dim + 2dim维之后的所有维度被合维);
  • 输出的第dim维大小为numChunks
  • 对每个输入 tensor:chunkCol = (chunkDimSize + numChunks - 1) / numChunks(向上取整,即每个分块的列数);
  • dim维之前各维度保持输入形状(所有输入需一致);
  • dim + 1维的大小为所有输入 tensor 的chunkCol × (dim 之后各维的乘积)之和。

以文档调用示例为例:两个输入 shape 分别为{2, 3}{1, 3}dim = 0numChunks = 2,则输出 shape 为{2, 6}——第 0 维为 2(numChunks),第 1 维为(3/2 向上取整)=2(3/2 向上取整)=2之和再乘以后续维(无)的乘积,即 2+2 再按规则累加得到 6。

二、产品支持情况

ChunkCat 算子通过 aclnnChunkCat 接口调用时,各产品的支持情况如下:

产品是否支持
Ascend 950PR/Ascend 950DT支持
Atlas A3 训练系列产品 / Atlas A3 推理系列产品支持
Atlas A2 训练系列产品 / Atlas A2 推理系列产品支持
Atlas 200I/500 A2 推理产品不支持
Atlas 推理系列产品不支持
Atlas 训练系列产品不支持

该支持矩阵与算子定义中AICore().AddConfig(...)声明的ascend910bascend910_93ascend950ascend350四类配置一一对应(chunk_cat_def.cpp),同时在 chunk_cat/README.md 中有同样的产品支持表。

三、两段式接口与函数原型

每个 aclnn 算子采用两段式接口(转换为仓库根路径:两段式接口说明)设计,必须先调用第一段接口aclnnChunkCatGetWorkspaceSize获取计算所需 workspace 大小以及包含了算子计算流程的执行器,再调用第二段接口aclnnChunkCat执行计算。

第一段接口:aclnnChunkCatGetWorkspaceSize

aclnnStatus aclnnChunkCatGetWorkspaceSize( const aclTensorList *tensors, int64_t dim, int64_t numChunks, aclTensor *out, uint64_t *workspaceSize, aclOpExecutor **executor)

第二段接口:aclnnChunkCat

aclnnStatus aclnnChunkCat( void *workspace, uint64_t workspaceSize, aclOpExecutor *executor, aclrtStream stream)

从源码 aclnn_chunk_cat.cpp 可以看到两段接口的真实分工:

  • 第一段接口aclnnChunkCatGetWorkspaceSize内先校验dim != 0numChunks <= 0tensors空指针等前置条件,然后通过CheckParams完成数据类型/格式/维度的完整入参校验,再调用SplitToChunkCat构建算子执行图(对输入做 Contiguous、MergeLastDims、按每 512 个输入为一组分批 ChunkCat),最后把uniqueExecutor->GetWorkspaceSize()写入workspaceSize并将执行器通过ReleaseTo释放给调用方;
  • 第二段接口aclnnChunkCat直接调用CommonOpExecutorRun(workspace, workspaceSize, executor, stream)在指定 Stream 上异步执行。

四、aclnnChunkCatGetWorkspaceSize 参数说明

参数名输入/输出描述使用说明数据类型格式类型维度(shape)非连续Tensor
tensors(aclTensorList*)输入输入的 tensor 列表列表内数据类型需一致FLOAT、FLOAT16、BFLOAT16ND1-8
dim(int64_t)输入需要切分块的维度int 类型的值,当前仅支持为 0INT64---
numChunks(int64_t)输入需要切分块的数量必须大于 0INT64---
out(aclTensor*)输出输出 tensor数据类型所占字节数需要是输入数据类型所占字节数的非 0 整数倍FLOAT、FLOAT16、BFLOAT16ND--
workspaceSize(uint64_t*)输出返回需要在 Device 侧申请的 workspace 大小-----
executor(aclOpExecutor**)输出返回 op 执行器,包含了算子计算流程-----

对应源码中的入参校验逻辑(aclnn_chunk_cat.cpp)可以进一步印证上述约束:

  • CheckNotNulltensors及其内部每个 tensor、out均不能为空指针;
  • CheckDtypeValid:输入列表内所有 tensor 数据类型必须一致,且必须落在{DT_FLOAT, DT_FLOAT16, DT_BF16}支持列表内;当输入为 FLOAT 时,输出也必须是 FLOAT(output dtype must be float when input dtype is float);输出数据类型同样必须在支持列表内;
  • CheckFormat:输入输出不允许私有 format(仅支持 ND、NCHW、NHWC、HWCN、NDHWC、NCDHW 等公共格式,文档中要求为 ND);
  • CheckShape:每个输入 tensor 的维数不能为 0,且不能超过MAX_SUPPORT_DIMS_NUMS(对应文档 1~8 维)。

另外,numChunks与输入个数的关系在接口层有特殊处理:当tensors->Size() == 0时,第一段接口直接返回成功;当输入数量超过CAT_INPUT_NUM = 512时,SplitToChunkCat会按每 512 个一组分批执行 ChunkCat,再对中间结果继续分批,直到收敛为单个 tensor(见 aclnn_chunk_cat.cpp)。

返回值

aclnnStatus:返回状态码,具体参见 aclnn返回码。

第一段接口完成入参校验,出现以下场景时报错:

返回值错误码描述
ACLNN_ERR_PARAM_NULLPTR161001传入的 tensors 或 out 是空指针。
ACLNN_ERR_PARAM_INVALID161002tensors 列表中 tensor 的数据类型和数据格式不在支持的范围之内。
ACLNN_ERR_PARAM_INVALID161002tensors 列表中 tensor 数据类型不一致。
ACLNN_ERR_PARAM_INVALID161002tensors 列表中 tensor 的 shape 不在 1~8 维。
ACLNN_ERR_PARAM_INVALID161002dim 不为 0。
ACLNN_ERR_PARAM_INVALID161002numChunks 小于等于 0。

五、aclnnChunkCat 参数说明

参数名输入/输出描述
workspace输入在 Device 侧申请的 workspace 内存地址。
workspaceSize输入在 Device 侧申请的 workspace 大小,由第一段接口 aclnnChunkCatGetWorkspaceSize 获取。
executor输入op 执行器,包含了算子计算流程。
stream输入指定执行任务的 Stream。

返回值

aclnnStatus:返回状态码,具体参见 aclnn返回码。

六、约束说明

  • 确定性计算:aclnnChunkCat 默认确定性实现。
  • 输入 tensor 列表内数据类型需一致,且仅支持 FLOAT、FLOAT16、BFLOAT16;
  • 输入输出数据格式为 ND,shape 维度为 1~8 维;
  • dim当前仅支持为 0;
  • numChunks必须大于 0;
  • 输出数据类型所占字节数需为输入数据类型所占字节数的非 0 整数倍(这一点与算子内核对“对齐/转置”路径的选择密切相关,见下文)。

七、源码级实现原理

7.1 图融合/编译配置

算子为每个目标平台提供了 binary 配置。以 ascend910b/chunk_cat_binary.json 为例,它声明了 7 组输入/输出数据类型组合对应的 kernel 二进制:

  • ChunkCat_fp16_fp16ChunkCat_fp16_bf16ChunkCat_fp16_fp32
  • ChunkCat_bf16_fp16ChunkCat_bf16_bf16ChunkCat_bf16_fp32
  • ChunkCat_fp32_fp32

其中输入xparamType: "dynamic"的动态个数输入,输出yparamType: "required",属性dimnum_chunks为运行时传入(value: null),shape 均以-2表示动态 shape。同理的配置还存在于 ascend350、ascend910_93、ascend950 目录下。

7.2 L0 层算子入口

chunk_cat.cpp 中的l0op::ChunkCat是底层入口:它通过executor->AllocTensor(outDtype, FORMAT_ND, FORMAT_ND)分配输出 tensor,随后调用INFER_SHAPE完成 shape 推导,再通过ADD_TO_LAUNCHER_LIST_AICORE把算子加入 AICore 启动列表。该入口被上层 aclnn_chunk_cat.cpp 的SplitToChunkCat调用,并配合l0op::Contiguous(转连续)、l0op::ViewCopy(视图拷贝)完成整条计算链。

7.3 Tiling 策略

chunk_cat_tiling.cpp 展示了该算子的 tiling 设计要点:

  • 通过PlatformAscendC获取 AIV 核数coreNum_与 UB 大小ubSize_,并申请系统 workspace;
  • 根据输入输出数据类型是否相同决定 UB 切分比例:类型不同时inUbSize_ = (ubSize_ + reserveUb) / 3(UB 三切分),类型相同时inUbSize_ = (ubSize_ + reserveUb) / 2(UB 对半切分),其余留给输出;
  • 计算每个输入 tensor 的chunkCol = (chunkDimSize + numChunks - 1) / numChunks,累加得到outputCol_,并判断整列是否按 32B/16B 对齐(isAllAlign_isHalfAlign_);
  • DoUbSplit依据isRegBaseisAllAlign_isOneConcat_等标志选择不同的行/列切分路径(列切优先,必要时行切),DoBlockSplit按核数做二维 block 切分,最终SetBlockDim(usedCoreNum_)设置启动核数,并把全部 tiling 参数写入ChunkCatTilingData

7.4 Kernel 计算路径

op_kernel/chunk_cat.cpp 为 kernel 入口,根据__CCE_AICORE__宏选择通用实现(chunk_cat.h)或 Arch35 实现(chunk_cat_arch35.h),并通过ORIG_DTYPE_X == ORIG_DTYPE_Y模板分支决定是否启用NEED_CAST类型转换。

通用实现 chunk_cat.h 的主流程Process对每个 UB 子块执行“清零 UB → CopyIn 搬入 → Compute 计算 → CopyOut 搬出”的流水:

  • Compute依据场景分派四条路径:isOneConcat_(单行且不对齐)走ComputeOneConcat直接搬移;全对齐走ComputeAllAlign(UB 重排后 concat);单输入且列对齐走简单拷贝;其余情况走ComputeNotAlign——通过UBRearrange4Trans(UB 重排)、Trans1(跨 block 对齐转置,调用TransDataTo5HD)、UBRearrange4TransConcatTrans2完成分块数据的拼接与对齐;
  • 当输入输出类型不同(NEED_CAST)时,DoCast根据字节数是否相同选择“经 FP32 中转的CAST_RINT两次 Cast”或“直接CAST_NONECast”;
  • ComputeOver32会在单次 UB 累积超过 32 个输入 tensor 时提前做部分 concat,避免输入个数过多导致的 UB 溢出。

八、调用示例

示例代码如下,仅供参考,具体编译和执行过程请参考 编译与运行样例。该示例与仓库中的真实样例 examples/test_aclnn_chunk_cat.cpp 一致,两个输入 shape 分别为{2, 3}{1, 3}dim = 0numChunks = 2,输出 shape 为{2, 6}

#include <iostream> #include <vector> #include "acl/acl.h" #include "aclnnop/aclnn_chunk_cat.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::vector<int64_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::vector<T>& hostData, const std::vector<int64_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::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]; } // 调用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::vector<int64_t> selfShape1 = {2, 3}; std::vector<int64_t> selfShape2 = {1, 3}; std::vector<int64_t> outShape = {2, 6}; void* input1DeviceAddr = nullptr; void* input2DeviceAddr = nullptr; void* outDeviceAddr = nullptr; aclTensor* input1 = nullptr; aclTensor* input2 = nullptr; aclTensor* out = nullptr; std::vector<float> input1HostData = {1, 2, 3, 4, 5, 6}; std::vector<float> input2HostData = {7, 8, 9}; std::vector<float> outHostData(12, 0); // 创建input1 aclTensor ret = CreateAclTensor(input1HostData, selfShape1, &input1DeviceAddr, aclDataType::ACL_FLOAT, &input1); CHECK_RET(ret == ACL_SUCCESS, return ret); // 创建input2 aclTensor ret = CreateAclTensor(input2HostData, selfShape2, &input2DeviceAddr, aclDataType::ACL_FLOAT, &input2); CHECK_RET(ret == ACL_SUCCESS, return ret); // 创建out aclTensor ret = CreateAclTensor(outHostData, outShape, &outDeviceAddr, aclDataType::ACL_FLOAT, &out); CHECK_RET(ret == ACL_SUCCESS, return ret); std::vector<aclTensor*> tmp{input1, input2}; aclTensorList* tensorList = aclCreateTensorList(tmp.data(), tmp.size()); // 3.调用CANN算子库API,需要修改为具体的API名称 int64_t dim = 0; int64_t numChunks = 2; uint64_t workspaceSize = 0; aclOpExecutor* executor; // 调用aclnnChunkCat第一段接口 ret = aclnnChunkCatGetWorkspaceSize(tensorList, dim, numChunks, out, &workspaceSize, &executor); CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclnnChunkCatGetWorkspaceSize 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); } // 调用aclnnChunkCat第二段接口 ret = aclnnChunkCat(workspaceAddr, workspaceSize, executor, stream); CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclnnChunkCat 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 = GetShapeSize(outShape); std::vector<float> outData(size, 0); ret = aclrtMemcpy(outData.data(), outData.size() * sizeof(outData[0]), outDeviceAddr, size * sizeof(outData[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, outData[i]); } // 6.释放aclTensor和aclScalar,需要根据具体API的接口定义修改 aclDestroyTensorList(tensorList); aclDestroyTensor(out); // 7.释放Device资源,需要根据具体API的接口定义修改 aclrtFree(input1DeviceAddr); aclrtFree(input2DeviceAddr); aclrtFree(outDeviceAddr); if (workspaceSize > 0) { aclrtFree(workspaceAddr); } aclrtDestroyStream(stream); aclrtResetDevice(deviceId); aclFinalize(); return 0; }

示例要点拆解

  1. Init 初始化aclInitaclrtSetDeviceaclrtCreateStream,固定写法;
  2. 构造 aclTensorCreateAclTensor模板函数通过aclrtMalloc申请 Device 内存、aclrtMemcpy拷入 Host 数据、按行主序计算连续 strides,最后aclCreateTensor创建 ND 格式 tensor;多个输入通过aclCreateTensorList打包为aclTensorList
  3. 两段式调用:先aclnnChunkCatGetWorkspaceSize拿到workspaceSizeexecutor,再按需aclrtMalloc申请 workspace,最后aclnnChunkCat(workspaceAddr, workspaceSize, executor, stream)提交执行;
  4. 同步与取数aclrtSynchronizeStream等待执行结束,aclrtMemcpy(DEVICE_TO_HOST)取回结果并打印;
  5. 资源释放:依次释放 tensor list、out tensor、各 Device 内存、workspace、Stream,最后aclrtResetDeviceaclFinalize

九、进一步阅读

  • 算子总览与调用方式对照表:conversion/chunk_cat/README.md
  • 两段式接口设计说明:docs/zh/context/two_phase_api.md
  • aclnn 返回码说明:docs/zh/context/aclnn_return_code.md
  • 编译与运行样例:docs/zh/context/compile_and_run_sample.md
  • 算子注册与 Shape 推导:op_host/chunk_cat_def.cpp、op_host/chunk_cat_infershape.cpp
  • Tiling 与 Kernel 实现:op_host/chunk_cat_tiling.cpp、op_kernel/chunk_cat.h
  • 平台 binary 配置:op_host/config/ascend910b/chunk_cat_binary.json(ascend350 / ascend910_93 / ascend950 目录下配置结构相同)

【免费下载链接】ops-math本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。项目地址: https://gitcode.com/cann/ops-math

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询