CANN Runtime CMO 缓存操作接口解析:aclrtCmoAsync 系列 API 使用与实现原理
【免费下载链接】runtime本项目提供CANN运行时组件和维测功能组件。项目地址: https://gitcode.com/cann/runtime
CMO(Cache Maintenance Operations,缓存维护操作)是 CANN Runtime 提供的 Device 侧缓存刷新与失效接口族,用于在 Host 侧对 NPU 上的 Cache 与 DDR 内存做显式的预取、写回、失效等控制。本文以 11-06_CMO_memory_operation.md 为骨架,完整梳理aclrtCmoAsync、aclrtCmoAsyncWithBarrier、aclrtCmoWaitBarrier、aclrtCmoGetDescSize、aclrtCmoSetDesc、aclrtCmoAsyncWithDesc六个接口的产品支持情况、参数约束与典型用法,并结合本仓库源码(acl_rt.h、api_c_memory.cc、api_c_standard_soc.cc)剖析其底层校验逻辑与任务下发链路,帮助开发者正确、安全地使用 CMO 缓存操作。
一、CMO 缓存操作是什么
在异构计算场景中,数据在 Device 侧 Cache 与 DDR 内存之间的流动并不总是由硬件自动维护。某些场景下,开发者需要主动控制缓存行为以提升访存效率:
- 预取(Prefetch):提前把内存数据加载到 Cache,减少后续算子访问时的等待;
- 写回(Writeback):把 Cache 中的脏数据刷回内存,同时保留 Cache 副本;
- 失效(Invalid):丢弃 Cache 中的数据,强制后续访问从内存重新加载;
- 冲刷(Flush):把 Cache 中的数据刷回内存,但不保留 Cache 副本。
CANN Runtime 将这类操作抽象为CMO(Cache Maintenance Operations),并以aclrtCmo*系列异步接口向用户开放。除aclrtCmoAsync直接指定内存地址外,该接口族还提供基于内存描述符(Descriptor)的进阶用法,以及携带 barrierId 的屏障式同步用法,满足从简单预取到精细化缓存一致性控制的多种需求。
CMO 操作类型通过枚举 aclrtCmoType 表达,其定义位于 include/external/acl/acl_rt.h:
typedef enum aclrtCmoType { ACL_RT_CMO_TYPE_PREFETCH = 0, // 内存预取,从内存预取到Cache ACL_RT_CMO_TYPE_WRITEBACK, // 把Cache中的数据刷新到内存中,并在Cache中保留副本 ACL_RT_CMO_TYPE_INVALID, // 丢弃Cache中的数据 ACL_RT_CMO_TYPE_FLUSH, // 把Cache中的数据刷新到内存中,不保留Cache中的副本 } aclrtCmoType;二、接口总览与产品支持矩阵
CMO 接口族共包含 6 个接口,按能力可分为三类:
| 接口 | 功能定位 | 异步 |
|---|---|---|
| aclrtCmoAsync | 直接指定地址/大小的 Cache 内存操作 | 是 |
| aclrtCmoAsyncWithBarrier | 携带 barrierId 的 Cache 内存操作 | 是 |
| aclrtCmoWaitBarrier | 等待指定 barrierId 的 Invalid 任务完成 | 是 |
| aclrtCmoGetDescSize | 获取 Cache 内存描述符占用大小 | — |
| aclrtCmoSetDesc | 将源地址/大小写入内存描述符 | — |
| aclrtCmoAsyncWithDesc | 基于内存描述符的 Cache 内存操作 | 是 |
各接口的产品支持情况差异显著,汇总如下(对应各接口"产品支持情况"小节):
| 产品系列 | CmoAsync | CmoAsyncWithBarrier | CmoWaitBarrier | CmoGetDescSize / CmoSetDesc / CmoAsyncWithDesc |
|---|---|---|---|---|
| Ascend 950PR / Ascend 950DT | 支持 | 不支持 | 不支持 | 支持 |
| Atlas A3 训练系列 / 推理系列 | 支持 | 不支持 | 不支持 | 支持 |
| Atlas A2 训练系列 / 推理系列 | 支持 | 不支持 | 不支持 | 支持 |
| Atlas 200I/500 A2 推理产品 | 不支持 | 支持 | 支持 | 不支持 |
| Atlas 推理系列产品 | 不支持 | 不支持 | 不支持 | 不支持 |
| Atlas 训练系列产品 | 不支持 | 不支持 | 不支持 | 不支持 |
| IPV350 | 不支持 | 不支持 | 不支持 | 不支持 |
从源码可以印证这一"产品分流"的设计:rtsCmoAsyncWithBarrier在入口处即检查芯片特性位RT_FEATURE_TASK_ASYNC_CMO,不满足则直接返回RT_ERROR_FEATURE_NOT_SUPPORT(见 api_c_memory.cc);而rtsLaunchBarrierTask的注释同样明确"only CHIP_MINI_V3, CHIP_AS31XM1 and cmoType=invalid support this function"(见 api_c_memory.cc),与文档中"仅 Atlas 200I/500 A2 推理产品支持带 Barrier 的 CMO 接口"保持一致。
三、接口详解
3.1 aclrtCmoAsync:直接 Cache 内存操作
aclError aclrtCmoAsync(void *src, size_t size, aclrtCmoType cmoType, aclrtStream stream)功能说明:在指定 Stream 上实现 Device 侧 Cache 内存操作,异步接口。
参数说明:
| 参数名 | 输入/输出 | 说明 |
|---|---|---|
| src | 输入 | 待操作的 Device 内存地址。只支持本 Device 上的 Cache 内存操作。 |
| size | 输入 | 待操作的 Device 内存大小,单位 Byte。 |
| cmoType | 输入 | Cache 内存操作类型,类型定义参见 aclrtCmoType。当前仅支持ACL_RT_CMO_TYPE_PREFETCH(内存预取)。 |
| stream | 输入 | 执行内存操作任务的 Stream,类型定义参见 aclrtStream。 |
返回值说明:返回 0 表示成功,返回其他值表示失败,错误码参见 aclError。
实现要点:该接口直接透传到底层运行时接口rtCmoAsync。从 api_c_standard_soc.cc 可以看到,rtCmoAsync会把入参封装为rtCmoTaskInfo_t(记录 opCode、lengthInner、sourceAddr、logicId 等),再调用rtCmoTaskLaunch下发任务;rtCmoTaskLaunch最终经Api::Instance()->CmoTaskLaunch走完任务提交链路。典型调用示例如下:
// 假设已在当前线程设置好 Device 并持有合法 Stream void *devPtr = nullptr; size_t size = 1024 * 1024; // 1MB // 申请 Device 内存(接口参见 aclrtMalloc,见 11-01 章节) aclrtMalloc(&devPtr, size, ACL_MEM_MALLOC_HUGE_FIRST); aclrtStream stream = nullptr; aclrtCreateStream(&stream); // 将 1MB Device 内存预取到 Cache,异步下发 aclError ret = aclrtCmoAsync(devPtr, size, ACL_RT_CMO_TYPE_PREFETCH, stream); if (ret != 0) { // 错误处理,可调用 aclGetRecentErrMsg 获取最近一次错误信息 } aclrtDestroyStream(stream); aclrtFree(devPtr);3.2 aclrtCmoAsyncWithBarrier:携带屏障标识的 Cache 内存操作
aclError aclrtCmoAsyncWithBarrier(void *src, size_t size, aclrtCmoType cmoType, uint32_t barrierId, aclrtStream stream)功能说明:在指定 Stream 上实现 Device 侧 Cache 内存操作,同时携带barrierId,barrierId表示 Cache 内存操作的屏障标识。异步接口。
参数说明:
| 参数名 | 输入/输出 | 说明 |
|---|---|---|
| src | 输入 | 待操作的 Device 内存地址。只支持本 Device 上的 Cache 内存操作。 |
| size | 输入 | 待操作的 Device 内存大小,单位 Byte。 |
| cmoType | 输入 | Cache 内存操作类型,类型定义参见 aclrtCmoType。 |
| barrierId | 输入 | 屏障标识。当cmoType为ACL_RT_CMO_TYPE_INVALID时有效,支持传入大于 0 的数字,配合 aclrtCmoWaitBarrier 使用,等待具有指定 barrierId 的 Invalid 内存操作任务执行完成;当cmoType为其他值时,barrierId固定传 0。 |
| stream | 输入 | 执行内存操作任务的 Stream。此处只支持与模型绑定过的 Stream,绑定模型与 Stream 需调用 aclmdlRIBindStream 接口。 |
返回值说明:返回 0 表示成功,返回其他值表示失败,错误码参见 aclError。
实现要点:rtsCmoAsyncWithBarrier对参数组合做了严格校验(见 api_c_memory.cc):
cmoType为RT_CMO_INVALID时,logicId(即 barrierId)不能为 0,否则返回RT_ERROR_INVALID_VALUE,并报出错误码 EE1011,提示 "If parameter cmoType is equal to RT_CMO_INVALID, the value of parameter logicId cannot be 0";cmoType为RT_CMO_PREFETCH、RT_CMO_WRITEBACK或RT_CMO_FLUSH时,logicId必须为 0;- 其余未知类型直接返回不支持。
此外接口还要求src非空、size大于 0,校验通过后封装rtCmoTaskInfo_t并经rtCmoTaskLaunch下发。
3.3 aclrtCmoWaitBarrier:等待屏障任务完成
aclError aclrtCmoWaitBarrier(aclrtBarrierTaskInfo *taskInfo, aclrtStream stream, uint32_t flag)功能说明:等待具有指定 barrierId 的 Invalid 内存操作任务执行完成。异步接口。
参数说明:
| 参数名 | 输入/输出 | 说明 |
|---|---|---|
| taskInfo | 输入 | Cache 内存操作的任务信息,类型定义参见 aclrtBarrierTaskInfo。任务信息中的cmoType当前仅支持ACL_RT_CMO_TYPE_INVALID。 |
| stream | 输入 | 执行等待任务的 Stream。此处只支持与模型绑定过的 Stream,绑定方式同上,需调用 aclmdlRIBindStream。 |
| flag | 输入 | 预留参数,当前固定配置为 0。 |
返回值说明:返回 0 表示成功,返回其他值表示失败,错误码参见 aclError。
配套结构体:aclrtBarrierTaskInfo及其内部结构定义于 include/external/acl/acl_rt.h:
typedef struct { aclrtCmoType cmoType; // Cache 操作类型,此处必须为 ACL_RT_CMO_TYPE_INVALID uint32_t barrierId; // 屏障标识,必须大于 0 } aclrtBarrierCmoInfo; #define ACL_RT_CMO_MAX_BARRIER_NUM 6U typedef struct { size_t barrierNum; // 屏障数量,范围 [1, ACL_RT_CMO_MAX_BARRIER_NUM] aclrtBarrierCmoInfo cmoInfo[ACL_RT_CMO_MAX_BARRIER_NUM]; } aclrtBarrierTaskInfo;实现要点:底层rtsLaunchBarrierTask(见 api_c_memory.cc)对taskInfo做了三重校验:
taskInfo不能为空;logicIdNum(即 barrierNum)必须落在[1, ACL_RT_CMO_MAX_BARRIER_NUM](即 1~6)区间内;- 遍历
cmoInfo数组,要求每个元素的cmoType必须是RT_CMO_INVALID,且logicId不能为 0。
校验通过后调用rtBarrierTaskLaunch下发屏障任务,从而实现对"指定 barrierId 的 Invalid 缓存操作"完成状态的等待。
典型用法(在支持该接口的 Atlas 200I/500 A2 推理产品上):
// 下发带屏障标识的 Invalid 缓存操作 uint32_t barrierId = 1; aclrtCmoAsyncWithBarrier(devPtr, size, ACL_RT_CMO_TYPE_INVALID, barrierId, modelBoundStream); // 构造等待任务信息,等待 barrierId=1 的 Invalid 操作完成 aclrtBarrierTaskInfo taskInfo = {}; taskInfo.barrierNum = 1; taskInfo.cmoInfo[0].cmoType = ACL_RT_CMO_TYPE_INVALID; taskInfo.cmoInfo[0].barrierId = barrierId; aclError ret = aclrtCmoWaitBarrier(&taskInfo, modelBoundStream, 0); if (ret != 0) { // 错误处理 }3.4 aclrtCmoGetDescSize:获取描述符大小
aclError aclrtCmoGetDescSize(size_t *size)功能说明:获取当前 Device 上的 Cache 内存描述符占用的内存大小。
参数说明:
| 参数名 | 输入/输出 | 说明 |
|---|---|---|
| size | 输出 | Cache 内存描述符大小,单位 Byte。 |
返回值说明:返回 0 表示成功,返回其他值表示失败,错误码参见 aclError。
实现要点:底层rtsGetCmoDescSize通过Api::Instance()->GetCmoDescSize(size)查询(见 api_c_standard_soc.cc),描述符大小与具体芯片型号相关,因此必须在运行时动态获取,不要硬编码。
3.5 aclrtCmoSetDesc:设置内存描述符
aclError aclrtCmoSetDesc(void *cmoDesc, void *src, size_t size)功能说明:设置 Cache 内存描述符。此接口调用完成后,会将源内存地址、内存大小记录到 Cache 内存描述符中。
参数说明:
| 参数名 | 输入/输出 | 说明 |
|---|---|---|
| cmoDesc | 输入 | Cache 内存描述符地址指针。需先调用aclrtCmoGetDescSize获取描述符所需内存大小,再申请 Device 内存(例如通过aclrtMalloc,参见 11-01_device_memory_malloc_and_free.md),将 Device 内存地址作为入参传入此处。 |
| src | 输入 | 待操作的 Device 内存地址。只支持本 Device 上的 Cache 内存操作。 |
| size | 输入 | 待操作的 Device 内存大小,单位 Byte。 |
返回值说明:返回 0 表示成功,返回其他值表示失败,错误码参见 aclError。
实现要点:底层rtsSetCmoDesc(cmoDesc, srcAddr, srcLen)调用Api::Instance()->SetCmoDesc(见 api_c_standard_soc.cc),本质是把"源地址 + 长度"这对信息固化到 Device 侧描述符内存中,供后续aclrtCmoAsyncWithDesc直接复用。
3.6 aclrtCmoAsyncWithDesc:基于描述符的 Cache 内存操作
aclError aclrtCmoAsyncWithDesc(void *cmoDesc, aclrtCmoType cmoType, aclrtStream stream, const void *reserve)功能说明:使用内存描述符(二级指针方式)操作 Device 上的 Cache 内存。异步接口。
参数说明:
| 参数名 | 输入/输出 | 说明 |
|---|---|---|
| cmoDesc | 输入 | Cache 内存描述符地址指针,Device 侧内存地址。此处需先调用aclrtCmoSetDesc设置内存描述符,再将内存描述符地址指针作为入参传入本接口。 |
| cmoType | 输入 | Cache 内存操作类型,类型定义参见 aclrtCmoType。当前仅支持ACL_RT_CMO_TYPE_PREFETCH(内存预取)。 |
| stream | 输入 | 执行内存操作任务的 Stream,类型定义参见 aclrtStream。 |
| reserve | 输入 | 预留参数,当前固定传 NULL。 |
返回值说明:返回 0 表示成功,返回其他值表示失败,错误码参见 aclError。
实现要点:底层rtsLaunchCmoAddrTask调用rtCmoAddrTaskLaunch(见 api_c_standard_soc.cc),并做了两点约束:reserve必须为nullptr,且任务信息中的地址信息由描述符承载。同时从 api_c_memory.cc 的rtsLaunchCmoTask可以看到,底层下发时会将qos固定设置为 6,注释说明这是"to avoid the cross-chip D2D problem"(避免跨芯片 D2D 问题),这是 CMO 任务在驱动侧的一条重要保底策略。
完整使用流程(在支持该接口的产品上,例如 Atlas A2/A3 训练系列):
size_t descSize = 0; aclrtCmoGetDescSize(&descSize); // 1. 获取描述符大小 void *cmoDesc = nullptr; aclrtMalloc(&cmoDesc, descSize, ACL_MEM_MALLOC_HUGE_FIRST); // 2. 为描述符申请 Device 内存 aclrtCmoSetDesc(cmoDesc, devPtr, size); // 3. 将源地址与大小写入描述符 aclError ret = aclrtCmoAsyncWithDesc(cmoDesc, ACL_RT_CMO_TYPE_PREFETCH, stream, NULL); // 4. 基于描述符下发预取任务 if (ret != 0) { // 错误处理 } aclrtFree(cmoDesc); // 5. 使用完毕后释放描述符内存四、CMO 任务下发调用链
综合源码,CMO 接口从用户态到任务下发的调用链可归纳为:
- 用户态 ACL 层:
aclrtCmo*系列接口(声明见 include/external/acl/acl_rt.h 及 L4524-L4536、L5499-L5523); - RT 接口层:
rtsCmoAsync、rtsCmoAsyncWithBarrier、rtsLaunchBarrierTask、rtsGetCmoDescSize、rtsSetCmoDesc、rtsLaunchCmoAddrTask(位于 api_c_memory.cc 与 api_c_standard_soc.cc),负责参数合法性校验、错误码归一化与芯片特性位检查; - API 分发层:
rtCmoTaskLaunch/rtCmoAddrTaskLaunch/rtBarrierTaskLaunch等,通过Api::Instance()分发到具体实现; - 驱动/任务队列:最终封装为任务描述符下发到 Stream 对应的硬件队列执行。
从 api_c_memory.cc 的实现细节看,rtsCmoAsync甚至直接透传rtCmoAsync不做额外校验,而rtsCmoAsyncWithBarrier则承担了最重的参数组合校验,这种"入口收敛、校验前置"的写法值得参考。
五、使用注意事项与实践建议
- 先确认产品支持情况:CMO 接口族在产品间差异极大——地址直传式(
aclrtCmoAsync)与描述符式(aclrtCmoAsyncWithDesc)面向 950/A3/A2 系列,而带屏障式(aclrtCmoAsyncWithBarrier/aclrtCmoWaitBarrier)仅面向 Atlas 200I/500 A2 推理产品。编写可移植代码时应通过产品型号分支处理,避免在不支持的产品上调用导致ACL_ERROR_RT_FEATURE_NOT_SUPPORT。 - 注意 Stream 绑定约束:
aclrtCmoAsyncWithBarrier与aclrtCmoWaitBarrier的 stream 参数只支持与模型绑定过的 Stream,需先通过 aclmdlRIBindStream 完成绑定,普通aclrtCreateStream创建的 Stream 不满足要求。 - 严格遵循参数组合规则:
barrierId仅在cmoType == ACL_RT_CMO_TYPE_INVALID时有效且必须大于 0,其余类型固定传 0;aclrtCmoWaitBarrier的taskInfo中barrierNum上限为 6(ACL_RT_CMO_MAX_BARRIER_NUM),且所有cmoType必须为 Invalid。违反组合规则会返回RT_ERROR_INVALID_VALUE并带出错误码 EE1011(详见 EE1011 资源不足问题 同族的错误码体系说明,具体以运行时错误码文档为准)。 - 描述符的生命周期管理:
aclrtCmoGetDescSize必须先于内存申请调用,描述符内存必须使用 Device 内存(如aclrtMalloc,参见 11-01_device_memory_malloc_and_free.md),并在aclrtCmoAsyncWithDesc使用完成后及时释放;reserve参数当前固定传 NULL。 - 异步语义:本接口族均为异步接口,任务在 Stream 上排队执行。若需同步等待结果,可配合 Stream 同步(
aclrtSynchronizeStream)或事件(Event)机制使用,相关背景可参考 Stream同步与Event同步的区别与选择。 - 地址归属:所有接口的
src均只支持本 Device上的 Cache 内存操作,跨 Device 地址请先通过合法方式迁移数据。
六、总结
CMO 缓存操作接口族是 CANN Runtime 在"地址直传"与"描述符传参"两条路径上提供的缓存维护能力,配合枚举 aclrtCmoType 的四种操作类型,可覆盖预取、写回、失效、冲刷四类典型需求;而barrierId机制则为 Invalid 操作提供了精细的完成等待语义。使用前务必核对产品支持矩阵与参数约束,尤其是 Stream 的模型绑定要求与 barrierId 的取值规则。若需深入理解底层实现,可继续阅读 api_c_memory.cc 与 api_c_standard_soc.cc 中的 RT 层实现,以及 acl_rt.h 中的结构体定义。
【免费下载链接】runtime本项目提供CANN运行时组件和维测功能组件。项目地址: https://gitcode.com/cann/runtime
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考