CANN ops-math ConcatV2 算子实战指南:aclnn 接口调用与 NPU 双张量拼接实现解析

发布时间:2026/9/19 12:24:26
CANN ops-math ConcatV2 算子实战指南:aclnn 接口调用与 NPU 双张量拼接实现解析 CANN ops-math ConcatV2 算子实战指南aclnn 接口调用与 NPU 双张量拼接实现解析【免费下载链接】ops-math本项目是CANN提供的数学类基础计算算子库实现网络在NPU上加速计算。项目地址: https://gitcode.com/cann/ops-math导读ConcatV2 是 CANN 数学类基础计算算子库 ops-math 中用于实现两个张量沿指定维度拼接concatenate的转换类算子它通过 CANN 的统一 [aclnnConcatV2] 接口对外提供能力可在 Atlas A2 训练系列产品、Atlas 800I A2 推理产品与 A200I A2 Box 异构组件上高效运行。阅读本文后你将完整掌握 ConcatV2 的算子功能语义、输入/输出参数约束、ND 格式下的数据类型支持范围并能够基于仓库中可运行的 aclnn 调用示例 完成从设备初始化、tensor 构造、两段式接口调用到结果回拷的完整 NPU 编程闭环同时本文会结合仓库源码剖析其算子定义、tiling 分核策略与 Kernel 实现帮助你理解一个完整算子是如何从图注册、形状推断走向多核并行计算的。ConcatV2 算子功能说明ConcatV2 实现的是张量的指定维度拼接操作按照指定的维度 dim 将两个输入张量拼接为一个输出张量。以官方 README 给出的语义为例输入 selfX 与 selfY 的 shape 分别为[2, 3, 4, 5]与[2, 4, 4, 5]当拼接维度dim 1时输出张量 shape 为[2, 7, 4, 5]。可以看到两个输入张量除拼接维度外的所有维度长度必须完全相同此处维度 0、2、3 均为 2、4、5拼接维度上的长度可以不同3 与 4拼接后该维度长度等于两者之和7。该算子对应的仓库主体位于 experimental/conversion/concat_v2是 ops-math 中 conversion转换类算子的一个典型代表用于网络模型在前向或特征拼接场景下的张量合并。产品支持情况产品是否支持Atlas A2 训练系列产品/Atlas 800I A2 推理产品/A200I A2 Box 异构组件√从算子定义源码可以看出该算子的 AICore 配置仅注册了ascend910b这一个 SoC 版本见 concat_v2_def.cpp 中this-AICore().AddConfig(ascend910b, aicoreConfig)及注释“其他的 soc 版本补充部分配置项”与上表所列产品族Atlas A2 系列对应。参数说明参数名输入/输出/属性描述数据类型数据格式x输入张量需要进行拼接的输入张量见下方NDy输入张量需要进行拼接的输入张量见下方NDdim指定维度需要进行拼接的指定维度INT32-out输出维度为 4 维shape 由 dims 和原 selfx 的 shape 共同决定dtype 需要与 selfx 一致同 xND数据类型支持Atlas A2 训练系列产品/Atlas 800I A2 推理产品/A200I A2 Box 异构组件上数据类型支持FLOAT、FLOAT16。参数说明的源码印证参数表中 x/y/out 的 dtype、format 与必选属性与 concat_v2_def.cpp 中通过算子注册框架OpDef声明的接口完全一致两个输入x1、x2均为ParamType(REQUIRED)必选输入输出y同样为必选输出输入输出统一使用FORMAT_ND数据格式且声明了AutoContiguous()内存自动连续化与UnknownShapeFormat支持动态 shape 场景下的格式推导拼接维度在算子注册中对应属性dthis-Attr(d).AttrType(OPTIONAL).Int(0)即可选属性、默认值为 0。值得注意的是README 参数表标注的数据类型仅列出 FLOAT、FLOAT16而实际注册信息concat_v2_def.cpp在输入输出上同时支持DT_FLOAT、DT_INT32、DT_INT16、DT_FLOAT16四种类型op_host/CMakeLists.txt 中的set(SUPPORT_DTYPE_LIST float;float16;int32;int16)也印证了这一点tiling 实现concat_v2_tiling.cpp在计算元素字节大小时同样对DT_FLOAT16 / DT_FLOAT / DT_INT32 / DT_INT16 / DT_UINT8做了分支处理。因此若你的业务需要拼接整型张量仓库源码层面已具备相应支持可在实际部署环境中进一步验证。动态 shape 支持算子注册中开启了一系列动态化能力concat_v2_def.cpp配置项取值含义DynamicCompileStaticFlagtrue支持动态编译静态化DynamicFormatFlagfalse不启用动态格式DynamicRankSupportFlagtrue支持动态 rank维度数可变DynamicShapeSupportFlagtrue支持动态 shapeNeedCheckSupportFlagfalse无需额外检查支持性PrecisionReduceFlagtrue允许精度降低优化ExtendCfgInfoopFile.value concat_v2指定 Kernel 入口文件名其中ExtendCfgInfo(opFile.value, concat_v2)将算子与 Kernel 源文件 concat_v2.cpp 建立映射。约束说明README 中约束为“无”即当前版本对输入输出没有额外格式/布局限制输入输出均为 ND 格式即可满足要求。从源码可补充的隐含约束是tiling 阶段固定使用BLOCK_DIM 8个核见 concat_v2_tiling.cpp且要求平台能查询到非 0 的 coreNum 与 UB 内存大小否则 tiling 直接失败GetPlatformInfo。调用说明调用方式样例代码说明aclnn 接口test_concat_v2通过 aclnnConcatV2 接口方式调用 concat_v2 算子。调用流程概述CANN aclnn 算子调用遵循固定的“两段式接口”模式ConcatV2 的完整调用链为aclnnConcatV2GetWorkspaceSize(x, y, dim, out, workspaceSize, executor)第一段接口完成算子执行所需的 workspace 大小计算与 executor 创建根据返回的workspaceSize申请 device 侧 workspace 内存示例中使用aclrtMallocACL_MEM_MALLOC_HUGE_FIRSTaclnnConcatV2(workspaceAddr, workspaceSize, executor, stream)第二段接口在指定 stream 上异步下发算子任务aclrtSynchronizeStream(stream)同步等待任务执行结束通过aclrtMemcpy将输出从 device 侧拷贝回 host 侧校验结果依次释放 aclTensoraclDestroyTensor、device 内存aclrtFree、stream 与设备资源最后aclFinalize()去初始化。完整可运行示例详解仓库提供的 test_aclnn_concat_v2.cpp 是一个可直接参考的完整示例其关键步骤拆解如下。1. 设备与 stream 初始化auto ret aclInit(nullptr); ret aclrtSetDevice(deviceId); // deviceId 0 ret aclrtCreateStream(stream); // 创建执行流2. 构造输入输出 aclTensor示例中通过CreateAclTensor辅助函数完成“申请 device 内存 → host 数据拷入 device → 计算连续 strides →aclCreateTensor创建描述符”的完整流程std::vectorint64_t selfXShape {2, 2, 3, 2}; std::vectorDataType selfXHostData(24, 7); // 全 7 填充 ret CreateAclTensor(selfXHostData, selfXShape, selfXDeviceAddr, aclDataType::ACL_FLOAT, selfX); std::vectorint64_t selfYShape {2, 2, 3, 2}; std::vectorDataType selfYHostData(24, 9); // 全 9 填充 ret CreateAclTensor(selfYHostData, selfYShape, selfYDeviceAddr, aclDataType::ACL_FLOAT, selfY); std::vectorint64_t outShape {1, 1, 3, 8}; // 注意示例中为预申请的输出创建 tensor 时统一使用aclFormat::ACL_FORMAT_ND与算子注册的 ND 格式约束一致strides 按连续内存计算从最后一维向前累乘。示例中同时用using DataType float;声明了测试数据类型便于切换验证不同的 dtype。3. 两段式接口调用uint64_t workspaceSize 0; int32_t axis 1; // 拼接维度 dim aclOpExecutor* executor; ret aclnnConcatV2GetWorkspaceSize(selfX, selfY, axis, out, workspaceSize, executor); void* workspaceAddr nullptr; if (workspaceSize 0) { ret aclrtMalloc(workspaceAddr, workspaceSize, ACL_MEM_MALLOC_HUGE_FIRST); } ret aclnnConcatV2(workspaceAddr, workspaceSize, executor, stream); ret aclrtSynchronizeStream(stream);4. 结果回拷与资源释放std::vectorDataType resultData(size, 0); aclrtMemcpy(resultData.data(), size * sizeof(DataType), *deviceAddr, size * sizeof(DataType), ACL_MEMCPY_DEVICE_TO_HOST); aclDestroyTensor(selfX); aclDestroyTensor(selfY); aclDestroyTensor(out); aclrtFree(selfXDeviceAddr); aclrtFree(selfYDeviceAddr); aclrtFree(outDeviceAddr); if (workspaceSize 0) { aclrtFree(workspaceAddr); } aclrtDestroyStream(stream); aclrtResetDevice(deviceId); aclFinalize();算子底层实现原理算子定义与注册Host 侧concat_v2_def.cpp 通过OpDef类注册算子原型最终由OP_ADD(ConcatV2)将算子写入算子信息库。其输入输出声明与上文参数表一一对应且两个输入均标注为必选属性d为可选、默认 0。形状推断InferShapeconcat_v2_infershape.cpp 通过IMPL_OP_INFERSHAPE(ConcatV2)注册形状推断函数。当前实现仅读取输入 x 的 shape 指针并做空指针校验输出 shape 的具体推导交由框架侧依据算子语义完成。Tiling多核任务划分Host 侧concat_v2_tiling.cpp 是性能调度的核心通过IMPL_OP_OPTILING(ConcatV2)注册 tiling 函数其主要工作包括平台信息获取通过PlatformAscendC查询 AIV 核数GetCoreNumAiv与 UB 内存大小GetCoreMemSize(UB)并校验非 0workspace 计算GetWorkspaceSize返回WS_SYS_SIZE16 MB 固定值 平台库 workspace 大小之和核间划分固定BLOCK_DIM 8个核将拼接后的总行数(x1 y1)均分到各核前sbig_core_num个核为“大核”多分 1 行其余为“小核”核内分块以core_tile_s1为单次搬入 UB 的行数采用倍增-回溯策略while (FitsUB(...) core_tile_x1 big_tile_length) core_tile_x1 * DOUBLE;在 UB 容量约束下尽量放大分块UB 预算按BUFFER_NUM(2) * (xBytes yBytes) * 2估算并预留 5% 余量同时按 32 字节BLOCK_SIZE对齐拼接维度分区当d ! dimNum - 1时输出行按周期partnum拼接维及以后维度的组合周期划分每行根据行号 % partnum partnumX判断该行属于 x 还是 y从而精确统计每个核需要从 x/y 各取多少行tiling 数据落盘将核间/核内划分结果写入ConcatV2TilingData见 concat_v2_tiling_data.h包含各核的 start/end/rows、tile 次数与尾部余量等字段供 Kernel 侧消费。Kernel 执行NPU 侧concat_v2.cpp 定义了__global__ __aicore__入口函数内部实例化 concat_v2.h 中NsConcatV2::ConcatV2T类模板Init根据GetBlockIdx()与 tiling 中的sbig_core_num判断当前核是大核还是小核进而设置blockLength / tileNum / tailNum并通过SetGlobalBuffer将 x、y、z 的全局内存指针定位到本核负责的行区间流水结构TPipeTQuePOSITION::VECIN, 2双缓冲输入队列与TQuePOSITION::VECOUT, 2输出队列CopyIn按d dimNum - 1末维拼接与一般维度拼接分两条路径处理。末维拼接时分别将 x、y 对应行连续搬入两个输入队列一般维度拼接时先计算该行属于 x 还是 y行号 % partnum partnumX再从对应全局张量搬入CopyOut从输出队列 DeQue 后写回 z 全局张量。末维拼接时 x 写入输出行前段、y 写入后段偏移tiling.x2实现真正的“拼接”一般维度拼接时按行归属仅搬入 x 或 y 之一Process外层按 tile 次数循环内层按core_tile_s1行粒度依次执行 CopyIn/CopyOut最后一个 tile 处理尾部余量行。从源码结构看该实现通过“按行归属判定 行级搬运”把不规则维度的拼接转化为规则的行搬运问题配合 8 核均分与 UB 双缓冲可在 Atlas A2 系列产品上获得较好的带宽利用。构建与集成方式算子目录采用标准 ops-math 目录组织CMakeLists.txt 递归添加op_host、op_kernel、examples等子目录并在ENABLE_TEST开启时才加入tests目录op_host/CMakeLists.txt 通过add_modules_sources(OPTYPE concat_v2 ACLNNTYPE aclnn)声明算子类型与 aclnn 接口类型并列出支持的数据类型float;float16;int32;int16。算子原型还配套了 concat_v2_binary.json记录了四种 dtype 组合下的 bin 文件、输入输出与属性d的默认值与 concat_v2_simplified_key.ini 配置以及 concat_v2_tiling_key.h 中通过ASCENDC_TPL_ARGS_DECL声明的schMode模板参数0/1 两种调度模式这些文件共同构成了算子从原型注册、二进制编译到 tiling 分发的完整配置链。总结ConcatV2 作为 CANN ops-math 中的一个典型双输入转换类算子其核心价值在于为 Atlas A2 系列 NPU 上的指定维度张量拼接提供标准化的 aclnn 编程入口。本文从 README 的功能语义与参数约束出发逐层深入其算子定义、形状推断、tiling 分核策略与 Kernel 双缓冲实现并给出了可复制的 aclnn 调用示例。无论你是想快速在 NPU 上完成张量拼接的算法验证还是希望理解 CANN 算子“注册—推断—tiling—kernel”的完整开发范式ConcatV2 都是一个结构清晰、易于上手的参考范本。【免费下载链接】ops-math本项目是CANN提供的数学类基础计算算子库实现网络在NPU上加速计算。项目地址: https://gitcode.com/cann/ops-math创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考