
CANN opbase 框架算子 CopyToNpuHost 侧数据到 Device 侧的异步拷贝接口详解【免费下载链接】opbase本项目是CANN算子库的基础框架库为算子提供公共依赖文件和基础调度能力。项目地址: https://gitcode.com/cann/opbase导读CopyToNpu是 CANN opbase 开源算子库中 framework_op 系列框架算子之一用于在 L2 算子接口的开发过程中将 Host 侧张量数据以异步方式拷贝到 Device 侧并把该拷贝任务挂入算子执行器aclOpExecutor的任务队列随算子一起调度执行。本文将以 CopyToNpu.md 为核心结合仓库中的头文件、实现源码与单元测试讲解该接口的函数原型、参数语义、返回值、约束限制、内部实现原理及典型调用流程帮助读者掌握在自定义算子开发中安全、高效地完成 Host→Device 数据搬运的完整方法。函数原型与接口定位头文件与命名空间CopyToNpu的声明位于 framework_op.hnamespace op { const aclTensor* CopyToNpu(const aclTensor* src, aclOpExecutor* executor); const aclTensor* CopyToNpuSync(const aclTensor* src, aclOpExecutor* executor); aclnnStatus CopyNpuToNpu(const aclTensor* src, const aclTensor* dst, aclOpExecutor* executor); } // namespace op从源码结构可以看到framework_op.h中总共声明了三个框架级拷贝算子构成一个完整的拷贝工具族接口数据流向同步/异步返回类型CopyToNpuHost → Device异步入队执行const aclTensor*CopyToNpuSyncHost → Device同步阻塞至完成const aclTensor*CopyNpuToNpuDevice → Device异步入队执行aclnnStatus其中CopyToNpu是本文的讲解核心另两个接口可作为对照参考。三者均属于op命名空间开发者在使用时需要显式引入opdev/framework_op.h头文件。完整函数原型const aclTensor *CopyToNpu(const aclTensor *src, aclOpExecutor *executor)该接口位于src/nnopbase/composite_op/aclnn_engine/z_framework_op.cpp中实现。由于 RTSRuntime Service层的内存拷贝任务无法被缓存实现中特意注释说明在走 memcpy 路径时放弃算子缓存because rts memcpy cannot be cached, so abandon cache when use memcpy这一点是理解该接口行为的关键前提。参数与返回值语义参数说明参数输入/输出说明src输入需要拷贝到 Device 侧的 Host 侧数据以aclTensor形式封装该张量的数据必须位于 Host 内存上placement 为kOnHostexecutor输入L2 接口中一阶段接口声明的算子执行器对象即aclOpExecutor拷贝任务将挂入该执行器的任务队列关于src参数实现中有明确的放置位置校验见 z_framework_op.cppOP_CHECK(src-GetPlacement() op::TensorPlacement::kOnHost, OP_LOGE(ACLNN_ERR_INNER, Get input params placement:%d when expect kOnHost., src-GetPlacement()), return nullptr);即调用方必须确保src的放置位置是 Host 侧否则函数会记录错误日志并直接返回nullptr。返回值说明任务创建成功返回一个指向拷贝完成后 Device 侧数据的aclTensor即新分配的dst张量任务创建失败返回nullptr。需要特别注意CopyToNpu返回的dst张量并非由调用方直接持有 Device 内存而是由执行器在内部通过AllocTensor分配拷贝任务真正执行发生在后续调用执行器Run()之时。约束说明入参指针不能为空src与executor均不能传入空指针src必须位于 Host 侧src-GetPlacement()必须等于TensorPlacement::kOnHost目标内存容量校验实际执行拷贝前实现会校验dst的字节数不小于src的字节数否则报ACLNN_ERR_INNER错误见源码 z_framework_op.cpp。内部实现原理源码实现全景CopyToNpu的完整实现位于 z_framework_op.cpp核心流程可分为五个阶段缓存放弃与 DFX 打点调用L0_DFX(CopyToNpu, src)记录可观测性信息同时因为 memcpy 任务不可缓存而放弃缓存路径Host 侧校验校验src的 placement 为kOnHost目标张量分配调用executor-AllocTensor(...)依据src的存储形状、原始形状、数据类型、存储格式与原始格式分配一个同规格的dst张量构建 KernelLauncher创建CopyToNpuKernelLauncher对象核心类型标识为op::NO_CALC纯数据搬运、无需计算核并注册操作类型CopyToNpuOpTypeId()入队通过executor-AddToKernelLauncherListCopyTask(...)将拷贝任务挂入执行器的任务队列同时建立输入输出参数关系OpArgList各一个元素。const aclTensor* CopyToNpu(const aclTensor* src, aclOpExecutor* executor) { // because rts memcpy cannot be cached, so abandon cache when use memcpy L0_DFX(CopyToNpu, src) OP_CHECK(src-GetPlacement() op::TensorPlacement::kOnHost, ...); auto dst executor-AllocTensor(src-GetStorageShape(), src-GetOriginalShape(), src-GetDataType(), src-GetStorageFormat(), src-GetOriginalFormat()); op::internal::ProfilingInfoId profilingInfoId; auto* launcher new CopyToNpuKernelLauncher{CopyToNpuOpTypeId(), op::NO_CALC, profilingInfoId, executor, src, dst}; // 构建 srcArg / dstArg 两个 OpArg 并分别包装为长度为 1 的 OpArgList auto ret executor-AddToKernelLauncherListCopyTask(CopyToNpuOpTypeId(), launcher, srcArgList, dstArgList, emptyArgList); OP_CHECK(ret ACLNN_SUCCESS, ..., return nullptr); return dst; }实际拷贝发生在 Launch 阶段CopyToNpu返回时只完成了任务创建与入队真正的内存搬运发生在执行器调用Run()之后由CopyToNpuKernelLauncher::Launch()完成。该类的定义同样位于 z_framework_op.cpp关键逻辑如下aclnnStatus Launch() override { uint64_t dstNByptes 0; uint64_t srcNByptes 0; auto calcRet CalcTensorNBytes(src_, srcNByptes); CHECK_RET(calcRet ACLNN_SUCCESS, calcRet); calcRet CalcTensorNBytes(dst_, dstNByptes); CHECK_RET(calcRet ACLNN_SUCCESS, calcRet); OP_CHECK(dstNByptes srcNByptes, ...); auto ret aclrtMemcpyAsync(dst_-GetData(), dstNByptes, src_-GetData(), srcNByptes, ACL_MEMCPY_HOST_TO_BUF_TO_DEVICE, executor_-GetStream()); OP_CHECK(ret ACL_SUCCESS, ..., return ACLNN_ERR_RUNTIME_ERROR); return ACLNN_SUCCESS; }底层调用了运行时库的aclrtMemcpyAsync并以ACL_MEMCPY_HOST_TO_BUF_TO_DEVICE作为拷贝类型——该类型意味着数据先经过中间缓冲再落到 Device 内存同时绑定执行器所在的流executor_-GetStream()从而保证拷贝任务与算子任务在同一流上按序执行。字节数计算的溢出保护CalcTensorNBytesz_framework_op.cpp负责计算张量字节数取数据类型的大小TypeSize与存储形状元素数GetStorageShape().GetShapeSize()相乘全程使用ge::MulOverflow做乘法溢出保护并针对小于 1 字节的数据类型如 4-bit 类型做位偏移换算。这保证了超大张量场景下字节数计算的正确性。调用示例官方示例原文档 CopyToNpu.md 给出的最小调用示例为// Initialize a tensor on the host side and copy it to dst, which is a tensor on the device side. void Func(aclOpExecutor *executor) { int64_t myArray[10]; auto src executor-ConvertToTensor(myArray, 10, DT_INT64); auto dst CopyToNpu(src, executor); }流程拆解如下在 Host 侧定义一个普通数组myArray[10]即待拷贝的数据源调用执行器的ConvertToTensor把 Host 数组包装为aclTensor此时数据仍位于 Host 内存placement 为kOnHost调用CopyToNpu(src, executor)创建拷贝任务并入队得到指向 Device 侧数据的dst张量。结合源码的可运行扩展示例参考单元测试 test_framework_op.cpp 的写法一个更完整的创建执行器 → 构造 Host 张量 → 拷贝 → 运行 → 释放生命周期如下// 基于 opbase 测试框架的完整生命周期示例对照 UT 用例编写 #include opdev/framework_op.h #include opdev/make_op_executor.h void CopyHostToDeviceDemo() { // 1. 创建算子执行器对应 L2 接口一阶段 auto executor CREATE_EXECUTOR(); // 2. 在 Host 侧构造数据并包装为 aclTensor std::vectorfloat value(10, 1); auto srcArray executor.get()-AllocFloatArray(value.data(), value.size()); auto srcTensor executor.get()-ConvertToTensor(srcArray, op::DataType::DT_FLOAT); // 3. 创建 Host - Device 异步拷贝任务并入队 auto dstTensor op::CopyToNpu(srcTensor, executor.get()); // 任务创建失败时返回 nullptr if (dstTensor nullptr) { // 错误处理 return; } // 4. 释放执行器指针并运行此时才真正执行内存拷贝 aclOpExecutor* executorPtr nullptr; executor.ReleaseTo(executorPtr); auto ret executorPtr-Run(); // ret 应为 ACLNN_SUCCESS // 5. 清理 delete executorPtr; }单元测试中的验证要点仓库在 tests/nnopbase/ut/composite_op/test_framework_op.cpp 中提供了CopyToNpu对应的 UT 用例可验证以下几点行为返回值非空EXPECT_NE(dstTensor, nullptr)验证任务创建成功时返回有效张量不可重复执行EXPECT_EQ(executorPtr-CheckLauncherRepeatable(), false)验证 memcpy 类拷贝任务不支持 Repeatable 特性与源码中放弃缓存的设计一致执行成功EXPECT_EQ(ret, ACLNN_SUCCESS)验证Run()返回成功数据一致性在CopyToNpuSyncTest中用例构造 75 × 1024 × 256 个 float共 75 MB 数据进行同步拷贝并用memcmp逐字节比对源数据与目标数据完全一致验证了拷贝正确性。与 CopyToNpuSync 的差异对比CopyToNpuSync与CopyToNpu名字相近但语义差别显著对照实现见 z_framework_op.cpp 与文档 CopyToNpuSync.md对比维度CopyToNpuCopyToNpuSync任务入队是进入 executor 任务队列否立即执行阻塞行为异步返回后未完成阻塞直至拷贝完成底层调用aclrtMemcpyAsync异步aclrtMallocWithCfgaclrtMemcpy同步内存管理由执行器统一管理调用方负责释放 Device 内存返回值任务入队成功后返回dst拷贝完成后返回dstCopyToNpuSync的实现还会先调用executor-AbandonCache(true)主动放弃缓存并通过ACL_RT_MEM_ATTR_MODULE_ID属性moduleId 36对应 AICPU使用aclrtMallocWithCfg为数据分配高带宽内存ACL_MEM_TYPE_HIGH_BAND_WIDTH随后用aclrtMemcpy同步完成 Host→Device 拷贝。总结CopyToNpu是 CANN opbase 为 L2 算子接口开发者提供的最基础的 Host→Device 数据搬运原语它把校验 Host 侧放置位置 → 分配目标张量 → 构建拷贝 Launcher → 挂入执行器任务队列这一整套流程封装为单个函数调用底层通过aclrtMemcpyAsync与ACL_MEMCPY_HOST_TO_BUF_TO_DEVICE在算子流上异步执行。理解它与CopyToNpuSync同步、不入队、自管内存、CopyNpuToNpuDevice→Device的差异能帮助开发者根据数据生命周期与同步需求选择正确的拷贝接口。相关声明与实现可进一步查阅 framework_op.h、z_framework_op.cpp 及单元测试 test_framework_op.cpp。【免费下载链接】opbase本项目是CANN算子库的基础框架库为算子提供公共依赖文件和基础调度能力。项目地址: https://gitcode.com/cann/opbase创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考