CANN Runtime 流内存操作接口详解:aclrtValueWrite 与 aclrtValueWait 的异步内存值写入与等待

发布时间:2026/9/18 9:46:45
CANN Runtime 流内存操作接口详解:aclrtValueWrite 与 aclrtValueWait 的异步内存值写入与等待 CANN Runtime 流内存操作接口详解aclrtValueWrite 与 aclrtValueWait 的异步内存值写入与等待【免费下载链接】runtime本项目提供CANN运行时组件和维测功能组件。项目地址: https://gitcode.com/cann/runtime本篇文章聚焦 CANN Runtime 提供的流内存操作Stream Memory Operation接口aclrtValueWrite与aclrtValueWait讲解如何在指定 Stream 上异步地向 Device 内存写入数据、以及等待内存数据满足条件后解除阻塞。文章以 11-09 流内存操作 为骨架结合 仓库源码实现、单元测试 与 多流内存同步样例帮助你掌握这两个接口的参数语义、比较模式、底层调用链以及基于内存语义实现跨流同步的完整实战方法。一、流内存操作概述在 CANN Runtime 中Stream 上的任务通常按提交顺序执行。当需要跨 Stream 协调算子的执行节奏时传统方案是使用 Event / Notify 同步机制。而内存语义同步Memory Semantic Synchronization提供了另一条更灵活、更底层的路径它基于通用 Device 内存实现同步与 Event/Notify 机制最大的不同在于——算子本身可以作为同步参与方即算子可以在执行过程中与另一条流进行同步详见 内存语义同步指南。流内存操作接口正是实现这种内存语义同步的基础设施由两个配套接口组成接口作用异步性aclrtValueWrite向指定 Device 内存中写入数据异步接口aclrtValueWait等待指定内存中的数据满足一定条件后解除阻塞异步接口两者都以任务Task的形式下发到指定的 Stream由 Stream 调度执行。aclrtValueWait负责阻塞等待aclrtValueWrite或其他算子对内存的写入负责写值唤醒一写一等形成完整的同步闭环。产品支持情况由于该机制依赖底层硬件指令能力并非所有产品都支持。两个接口的产品支持情况一致汇总如下产品是否支持Ascend 950PR / Ascend 950DT支持Atlas A3 训练系列产品 / Atlas A3 推理系列产品支持Atlas A2 训练系列产品 / Atlas A2 推理系列产品支持Atlas 200I/500 A2 推理产品不支持Atlas 推理系列产品不支持Atlas 训练系列产品不支持IPV350不支持说明在 架构 5162 不支持接口清单 中同样登记了这两个接口与文档中部分老型号产品不支持的信息相互印证。使用时请先确认目标设备的支持情况。二、aclrtValueWrite向指定内存异步写值2.1 函数原型与功能说明aclError aclrtValueWrite(void* devAddr, uint64_t value, uint32_t flag, aclrtStream stream)aclrtValueWrite向指定的 Device 内存地址写入数据是一个异步接口接口返回后写值任务尚未执行完成实际写入动作发生在 Stream 上任务调度执行时。在内存语义同步场景中它通常被用来写值唤醒另一条 Stream 上阻塞的aclrtValueWait任务或正在轮询该内存的算子。2.2 参数说明参数名输入/输出说明devAddr输入Device 侧内存地址。此处需用户提前申请 Device 内存例如调用aclrtMalloc接口devAddr要求8 字节对齐有效内存位宽为64 bit。value输入需向内存中写入的数据uint64_t。flag输入预留参数当前固定设置为 0。stream输入执行写数据任务的 Stream。类型定义请参见 aclrtStream。此处支持传NULL表示使用默认 Stream。2.3 返回值说明返回0表示成功ACL_SUCCESS返回其他值表示失败具体错误码请参见 aclError。2.4 源码实现与调用链在 memory.cpp 中aclrtValueWrite的实体实现aclrtValueWriteImpl的调用逻辑非常清晰aclError aclrtValueWriteImpl(void* devAddr, uint64_t value, uint32_t flag, aclrtStream stream) { ACL_PROFILING_REG(acl::AclProfType::AclrtValueWrite); ACL_LOG_INFO(start to execute aclrtValueWrite); ACL_REQUIRES_NOT_NULL_WITH_INPUT_REPORT(devAddr); // 校验 devAddr 非空 ACL_REQUIRES_RTS_OK(rtsValueWrite(devAddr, value, flag, static_castrtStream_t(stream))); return ACL_SUCCESS; }从源码结构可以看出完整的调用链aclrtValueWrite (对外 API头文件声明于 include/external/acl/acl_rt.h) └─ aclrtValueWriteImpl (src/acl/aclrt_impl/memory.cpp) ├─ 参数校验devAddr 为空时上报输入错误并返回 ACL_ERROR_INVALID_PARAM ├─ 性能打点ACL_PROFILING_REG(AclrtValueWrite) 接入 Profiling 采集 └─ rtsValueWrite(devAddr, value, flag, stream) 下发任务到 RTS 层其中rtsValueWrite是 ACL 层对 RTSRuntime System层的封装调用任务最终由驱动在 Stream 上异步调度执行。接口声明位于 acl_rt.h。三、aclrtValueWait等待内存值满足条件后解除阻塞3.1 函数原型与功能说明aclError aclrtValueWait(void* devAddr, uint64_t value, uint32_t flag, aclrtStream stream)aclrtValueWait等待指定内存中的数据满足一定条件后解除阻塞同样是一个异步接口。调用方将等待任务下发到 Stream任务在执行期间会持续比较内存值与给定条件条件满足前后续任务保持阻塞满足后解除阻塞继续执行。它是内存语义同步中等待侧的核心接口。3.2 参数说明参数名输入/输出说明devAddr输入Device 侧内存地址有效内存位宽为 64 bit。value输入需与内存中的数据作比较的值uint64_t。flag输入比较方式满足条件后解除阻塞。取值见下表。stream输入执行等待任务的 Stream。类型定义请参见 aclrtStream。此处支持传NULL表示使用默认 Stream。3.3 flag 比较模式详解flag参数决定了内存值与value的比较逻辑共支持 4 种模式宏定义位于 acl_rt.hflag 宏值解除阻塞条件伪代码ACL_STREAM_WAIT_VALUE_GEQ0x00000000U(int64_t)(*devAddr - value) 0即内存值大于等于valueACL_STREAM_WAIT_VALUE_EQ0x00000001U*devAddr value即内存值等于valueACL_STREAM_WAIT_VALUE_AND0x00000002U(*devAddr value) ! 0即内存值与value按位与结果非 0ACL_STREAM_WAIT_VALUE_NOR0x00000003U~(*devAddr | value) ! 0即内存值与value按位或后取反结果非 0其中ACL_STREAM_WAIT_VALUE_EQ是最常用的模式语义最直观——等到内存值精确等于目标值。GEQ模式常用于计数/水位类场景等到计数达到阈值AND/NOR模式适合利用位标志实现多个信号量的组合等待。需要特别注意的是以上比较均基于无符号 64 位内存值进行GEQ模式内部按int64_t做差值判断使用时需结合业务语义合理取值。3.4 返回值说明返回0表示成功返回其他值表示失败具体错误码请参见 aclError。3.5 源码实现与调用链与写值接口对称aclrtValueWaitImpl实现在 memory.cpp 中aclError aclrtValueWaitImpl(void* devAddr, uint64_t value, uint32_t flag, aclrtStream stream) { ACL_PROFILING_REG(acl::AclProfType::AclrtValueWait); ACL_LOG_INFO(start to execute aclrtValueWait); ACL_REQUIRES_NOT_NULL_WITH_INPUT_REPORT(devAddr); // 校验 devAddr 非空 ACL_REQUIRES_RTS_OK(rtsValueWait(devAddr, value, flag, static_castrtStream_t(stream))); return ACL_SUCCESS; }调用链与aclrtValueWrite完全对称aclrtValueWait (对外 API头文件声明于 include/external/acl/acl_rt.h) └─ aclrtValueWaitImpl (src/acl/aclrt_impl/memory.cpp) ├─ 参数校验devAddr 为空时返回 ACL_ERROR_INVALID_PARAM ├─ 性能打点ACL_PROFILING_REG(AclrtValueWait) └─ rtsValueWait(devAddr, value, flag, stream) 下发等待任务到 RTS 层四、完整实战基于流内存操作实现跨流同步4.1 场景一双线程多流内存同步样例仓库中的 9_multistream_sync_memory 样例 完整演示了这两个接口的配合使用。其核心流程是线程 A 在 Stream A 上下发aclrtValueWait等待devPtrA处的内存值等于 100线程 B 在 Stream B 上下发aclrtValueWrite向devPtrA写入 100写入完成前Stream A 上的等待任务持续阻塞写入完成后等待条件满足Stream A 解除阻塞继续执行。样例核心代码main.cpp中的等待线程与写入线程如下// 等待线程在 streamA 上等待 devPtr 处的内存值等于 valueCompare int ThreadWait(aclrtStream stream, int32_t deviceId, void* devPtr, uint64_t valueCompare, const char* filePath) { aclrtSetDevice(deviceId); CHECK_ERROR(aclrtValueWait(devPtr, valueCompare, ACL_STREAM_WAIT_VALUE_EQ, stream)); // 等待任务解除阻塞后同步等待该 Stream 上所有任务完成 CHECK_ERROR(aclrtSynchronizeStream(stream)); return 0; } // 写入线程在 streamB 上向 devPtr 写入 valueWrite int ThreadWrite(aclrtStream stream, int32_t deviceId, void* devPtr, uint64_t valueWrite, const char* filePath) { aclrtSetDevice(deviceId); CHECK_ERROR(aclrtValueWrite(devPtr, valueWrite, 0, stream)); CHECK_ERROR(aclrtSynchronizeStream(stream)); return 0; }主流程则完整演示了申请内存 → 创建双流 → 启动等待线程 → 启动写入线程的标准步骤int32_t main() { aclInit(nullptr); int32_t deviceId 0; aclrtSetDevice(deviceId); // 1. 在 Device 上申请同步用内存 uint64_t size 1 * 1024 * 1024; void* devPtrA; CHECK_ERROR(aclrtMalloc(devPtrA, size, ACL_MEM_MALLOC_HUGE_FIRST)); // 2. 创建两条独立 Stream aclrtStream streamA nullptr; CHECK_ERROR(aclrtCreateStream(streamA)); aclrtStream streamB nullptr; CHECK_ERROR(aclrtCreateStream(streamB)); // 3. 先启动等待线程再启动写入线程 uint64_t valueCompare 100; std::thread threadA(ThreadWait, streamA, deviceId, devPtrA, valueCompare, filePath); (void)usleep(waitTime); // 确保等待任务已下发并进入阻塞 uint64_t valueWrite 100; std::thread threadB(ThreadWrite, streamB, deviceId, devPtrA, valueWrite, filePath); threadB.join(); threadA.join(); aclrtDestroyStreamForce(streamA); aclrtDestroyStreamForce(streamB); aclrtFree(devPtrA); aclrtResetDeviceForce(deviceId); aclFinalize(); return 0; }编译运行方式详见样例 README# 1. 切换到样例目录 cd ${git_clone_path}/example/1_basic_features/memory/9_multistream_sync_memory # 2. 设置环境变量${install_root} 替换为 CANN 安装根目录默认 /usr/local/Ascend source ${install_root}/cann/set_env.sh source ${git_clone_path}/example/set_sample_env.sh # 3. 运行样例 bash run.sh运行输出的关键日志验证了同步时序的正确性Stream A: wait ... to meet the condition表明等待任务已阻塞随后Stream B: write data写入值后Stream A: the data ... has met the condition表明等待被成功唤醒且等待线程读取到的标志位123证明写入线程执行前等待线程确实处于阻塞状态。4.2 场景二算子参与的内存语义同步流内存操作接口更强大的能力体现在算子级同步等待侧既可以是aclrtValueWait任务也可以是算子核函数内部的轮询逻辑。在 内存语义同步指南 中给出了完整的 Host 侧参考示例// 创建 Stream aclrtStream stream1; aclrtStream stream2; aclrtCreateStream(stream1); aclrtCreateStream(stream2); // 申请 Device 内存 void* syncMem; aclrtMalloc(syncMem, sizeof(uint64_t), ACL_MEM_MALLOC_NORMAL_ONLY); // 在 stream1 上下发 wait 任务阻塞等待直到 syncMem 指向内存中的值为 1 aclrtValueWait(syncMem, 1, ACL_STREAM_WAIT_VALUE_EQ, stream1); // 在 stream2 上下发 myKernel1该 kernel 内部向 syncMem 指向内存写 1解除 stream1 上 wait 任务的阻塞 myKernel1numBlocks, nullptr, stream2(syncMem); // 在 stream1 上下发 myKernel2该 kernel 内部轮询等待直到 syncMem 指向内存的值为 2 myKernel2numBlocks, nullptr, stream1(syncMem); // 在 stream2 上下发 write 任务向 syncMem 写入 2解除 stream1 上 myKernel2 的阻塞 aclrtValueWrite(syncMem, 2, 0, stream2);对应的 Device 侧核函数实现要点源码参考写侧核函数直接向同步内存写入值后调用dcci指令做缓存一致性刷新__gm__ uint64_t* flag reinterpret_cast__gm__ uint64_t*(syncMem); *flag 1; dcci(flag, 0, 2);确保写值对其他执行单元可见读侧核函数将内存声明为volatile后轮询等待目标值轮询间隙调用dcci保持缓存一致性while (*flag ! 2) { dcci(flag, 0, 2); }。这个示例揭示了两个关键实践细节其一同步内存必须通过aclrtMalloc等接口申请且内存值按 64 bit 读写其二跨执行单元的可见性依赖缓存一致性指令dcciHost 侧aclrtValueWrite与 Device 侧核函数写入在硬件层面共用同一套内存语义。此外由于内存语义同步基于通用 Device 内存实现可以使用aclrtMemset/aclrtMemsetAsync对同步内存做初始化和清除。五、单元测试验证与错误处理仓库在 tests/ut/acl/testcase/acl_runtime_unittest.cpp 中为两个接口提供了完整的 UT 用例覆盖了成功路径与非法参数路径// 非法参数devAddr 传 nullptr应返回 ACL_ERROR_INVALID_PARAM TEST_F(UTEST_ACL_Runtime, aclrtValueWrite_failed_with_invalid_args) { auto ret aclrtValueWrite(nullptr, 100, 0, nullptr); EXPECT_EQ(ret, ACL_ERROR_INVALID_PARAM); // 底层 rtsValueWrite 返回错误时错误码应被透传 EXPECT_CALL(MockFunctionTest::aclStubInstance(), rtsValueWrite(_, _, _, _)) .WillOnce(Return(ACL_ERROR_RT_PARAM_INVALID)); auto devAddr reinterpret_castvoid*(0x1000U); ret aclrtValueWrite(devAddr, 100, 0, nullptr); EXPECT_EQ(ret, ACL_ERROR_RT_PARAM_INVALID); } // 成功路径 TEST_F(UTEST_ACL_Runtime, aclrtValueWrite_success) { auto devAddr reinterpret_castvoid*(0x1000U); const auto ret aclrtValueWrite(devAddr, 100, 0, nullptr); EXPECT_EQ(ret, ACL_SUCCESS); }aclrtValueWait的测试用例结构完全对称aclrtValueWait_failed_with_invalid_args与aclrtValueWait_success。从测试可以总结出错误处理要点devAddr为空时接口在 ACL 层即返回ACL_ERROR_INVALID_PARAM不会下发到 RTS底层rtsValueWrite/rtsValueWait返回错误时错误码原样透传给调用方如ACL_ERROR_RT_PARAM_INVALIDstream传NULL表示使用默认 Stream是合法入参成功时统一返回ACL_SUCCESS0。六、使用注意事项与最佳实践内存对齐与位宽devAddr必须为 8 字节对齐有效位宽为 64 bit且需通过aclrtMalloc等接口在 Device 侧提前申请不能使用未经对齐或越界的内存地址。产品兼容性当前仅 Ascend 950PR/950DT、Atlas A3 系列、Atlas A2 系列产品支持该接口老型号 Atlas 训练/推理系列产品及 IPV350 不支持跨版本开发时需按目标设备做能力判断。同步内存的生命周期同步所用内存可用aclrtMemset/aclrtMemsetAsync初始化与清除多线程场景下等待线程与写入线程需各自调用aclrtSetDevice绑定设备参见样例 main.cpp。阻塞语义理解aclrtValueWait阻塞的是其所在 Stream 上后续任务的执行而非 Host 线程若需要 Host 侧同步等待需配合aclrtSynchronizeStream使用。与 Event/Notify 的选型如果同步双方都是 Host 侧任务Event/Notify 即可满足当需要算子在执行过程中参与同步如核函数内轮询等待其他流写值时应使用内存语义同步方案。flag 参数aclrtValueWrite的flag为预留参数必须固定传 0aclrtValueWait的flag则需从 4 种比较模式宏中取值非法值会导致任务行为不确定。七、小结aclrtValueWrite与aclrtValueWait是 CANN Runtime 实现内存语义同步的一对基础接口前者在 Stream 上异步写入 64 位内存值后者在 Stream 上按 4 种比较模式等待内存值满足条件后解除阻塞。二者通过 memory.cpp 中的实体实现下发至 RTS 层执行支持与核函数内部的dcci 轮询逻辑配合实现算子参与的跨流同步。结合 9_multistream_sync_memory 样例 与 内存语义同步指南 中的参考代码你可以在实际项目中按申请对齐内存 → 下发等待任务 → 下发写值任务 → 同步等待收尾的标准流程构建可靠的跨流协作逻辑。【免费下载链接】runtime本项目提供CANN运行时组件和维测功能组件。项目地址: https://gitcode.com/cann/runtime创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考