ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

CANN opbase 中 aicpu_utils 预留接口解析:AICpu 算子任务时间戳记录与阶段耗时统计机制

CANN opbase 中 aicpu_utils 预留接口解析:AICpu 算子任务时间戳记录与阶段耗时统计机制 CANN opbase 中 aicpu_utils 预留接口解析AICpu 算子任务时间戳记录与阶段耗时统计机制【免费下载链接】opbase本项目是CANN算子库的基础框架库为算子提供公共依赖文件和基础调度能力。项目地址: https://gitcode.com/cann/opbase导读本文以 CANN opbase 仓库中 docs/zh/api/nnopbase/opdev/aicpu_related_interface/aicpu_utils.md 文档为主线深入剖析其中列出的RecordAicpuTime、lk(gTimeStampMutex)与clock_gettime三个接口在 AICpuAI CPU算子任务执行流程中的真实作用。读完本文你将理解 AICpu 任务从“查找任务”到“更新输出 Shape”七个阶段的时间戳采集原理、统计开关的开启方式以及如何借助这些源码级细节进行任务耗时定位。接口定位一套面向内部耗时统计的预留接口原文档明确指出aicpu_utils中的接口为预留接口reserved interface后续有可能变更或废弃官方不建议开发者直接使用开发者无需关注。这意味着它不是面向算子开发者的对外 API而是框架内部用于采集 AICpu 算子任务执行时间戳的一套基础能力。原文档给出的接口列表共三条覆盖了“采集时间戳 → 加锁保护 → 调用系统时钟”的完整动作接口定义功能说明RecordAicpuTime(const size_t index)记录执行算子任务时当前时间戳。lk(gTimeStampMutex)时间戳数据结构的锁。clock_gettime(CLOCK_MONOTONIC, (gAicpuTimeStamp.tp[index]))系统函数获取当前系统时间戳。这三条接口虽然单独列出但实际上是同一套时间戳记录机制的三层组成RecordAicpuTime是封装入口lk(gTimeStampMutex)提供并发保护clock_gettime负责真正读取单调时钟。它们全部定义在头文件 include/nnopbase/opdev/aicpu/aicpu_utils.h 中属于op::internal内部命名空间从命名空间划分上也能印证其“内部实现、非对外契约”的属性。底层数据结构AicpuTimeStamp 与 10 个时间戳槽位时间戳不是零散存储的而是由全局结构体统一管理。在 aicpu_utils.h 中定义了一套预定义索引与时间戳结构static constexpr size_t kFindTaskStart 0U; static constexpr size_t kFindTaskEnd 1U; static constexpr size_t kUpdateShapeStart 2U; static constexpr size_t kUpdateShapeEnd 3U; static constexpr size_t kShapeH2DEnd 4U; static constexpr size_t kUpdateArgsStart 5U; static constexpr size_t kUpdateArgsEnd 6U; static constexpr size_t kLaunchEnd 7U; static constexpr size_t kShapeD2hCopyEnd 8U; static constexpr size_t kUpdateOutputShapeEnd 9U; static constexpr size_t kAicpuTimeStampNum kUpdateOutputShapeEnd 1U; struct AicpuTimeStamp { struct timespec tp[kAicpuTimeStampNum]; bool isEnable; }; extern AicpuTimeStamp gAicpuTimeStamp;每个索引对应 AICpu 任务执行链路中的一个关键节点10 个槽位索引 09记录了 7 个阶段的首尾时间阶段起始索引结束索引含义FindTaskkFindTaskStart(0)kFindTaskEnd(1)在任务缓存TaskSpace 哈希表中查找可复用任务UpdateShapekUpdateShapeStart(2)kUpdateShapeEnd(3)更新输入/输出 Tensor 的 Shape 信息ShapeH2DkUpdateShapeEnd(3)kShapeH2DEnd(4)将 Shape 等扩展信息从 Host 拷贝到 DeviceUpdateArgskUpdateArgsStart(5)kUpdateArgsEnd(6)更新算子入参与 I/O 地址LaunchkUpdateArgsEnd(6)kLaunchEnd(7)向 Device 侧下发 AICpu 内核启动CopyShapeD2HkLaunchEnd(7)kShapeD2hCopyEnd(8)将输出 Shape 结果从 Device 拷回 HostUpdateOutputShapekShapeD2hCopyEnd(8)kUpdateOutputShapeEnd(9)用返回结果更新输出 Tensor 的 Shape全局时间戳实例gAicpuTimeStamp的定义位于 src/nnopbase/aicpu/task_handler/aicpu_task_base.cppAicpuTimeStamp gAicpuTimeStamp {};通过头文件中的extern声明在多个编译单元间共享供 task 执行与扩展信息处理模块共同读写。RecordAicpuTime带开关与越界保护的封装原文档列出的第一个接口RecordAicpuTime(const size_t index)完整实现如下aicpu_utils.hstatic std::mutex gTimeStampMutex; static inline void RecordAicpuTime(const size_t index) { if (gAicpuTimeStamp.isEnable index kAicpuTimeStampNum) { const std::lock_guardstd::mutex lk(gTimeStampMutex); clock_gettime(CLOCK_MONOTONIC, (gAicpuTimeStamp.tp[index])); } }逐行解读其设计要点开关优先先判断gAicpuTimeStamp.isEnable统计功能未开启时整个函数近乎零开销不影响算子执行性能越界保护index kAicpuTimeStampNum保证不会越界写入tp数组即使后续扩展索引出错也不会造成内存破坏锁保护通过std::lock_guardstd::mutex lk(gTimeStampMutex)加锁这正是原文档表格中的第二个接口lk(gTimeStampMutex)单调时钟clock_gettime(CLOCK_MONOTONIC, ...)是原文档表格中的第三个接口采用CLOCK_MONOTONIC单调时钟不受系统时间跳变影响适合计算耗时差inline 属性static inline保证该函数在头文件中展开无跨编译单元的链接开销。gTimeStampMutex多线程并发下的时间戳写保护原文档将lk(gTimeStampMutex)单列为一条接口说明锁机制是本套能力中不可分割的一环。AICpu 任务可在多个 Host 线程上并发执行例如不同的 stream 或不同的调用线程同时命中RecordAicpuTime若不加锁多个线程对gAicpuTimeStamp.tp[]的写入会形成数据竞争导致统计结果错乱甚至未定义行为。实现上使用static std::mutex配合std::lock_guard的 RAII 写法进入RecordAicpuTime时自动lock()函数退出时自动unlock()即使后续代码中途插入return也能保证锁一定被释放避免了手工管理锁的生命周期风险。全局时间戳结构由多线程共享写入这也是它被设计为全局变量而非线程局部变量的原因——统计的是一次完整任务执行的端到端阶段耗时。统计开关GE_PROFILING_TO_STD_OUT 环境变量时间戳采集默认关闭由环境变量控制。开关判定逻辑位于 aicpu_task_base.cppbool EnableAicpuTimeStamp() { constexpr uint32_t kProfilingStdLengh 128U; std::arraychar, kProfilingStdLengh profilingToStdOut {}; auto ret mmGetEnv(GE_PROFILING_TO_STD_OUT, profilingToStdOut[0U], kProfilingStdLengh); return ((ret EN_OK) (profilingToStdOut[0U] 1)); }即当环境变量GE_PROFILING_TO_STD_OUT的值为1时时间戳统计功能被启用。开关状态在任务创建入口AicpuTaskSpace::GetOrCreateTask中刷新aicpu_task_base.cppgAicpuTimeStamp.isEnable EnableAicpuTimeStamp();。由于isEnable是结构体中的普通bool字段而非原子量其刷新时机与各线程的RecordAicpuTime调用是异步关系这一点从源码结构看是框架内部耗时定位用途属于可接受的近似统计不应用于精确性能计费。七个统计阶段在源码中的完整埋点链路时间戳采集分布在 task 执行链路的各个文件中下面是各阶段埋点与源码位置的对照阶段埋点代码源码位置FindTask 开始RecordAicpuTime(kFindTaskStart)aicpu_task_base.cppFindTask 结束RecordAicpuTime(kFindTaskEnd)aicpu_task_base.cppUpdateShape 开始RecordAicpuTime(kUpdateShapeStart)aicpu_task_base.cppUpdateShape 结束RecordAicpuTime(kUpdateShapeEnd)aicpu_ext_info_handle.cppShapeH2D 结束RecordAicpuTime(kShapeH2DEnd)aicpu_ext_info_handle.cppUpdateArgs 开始/结束kUpdateArgsStart/kUpdateArgsEndaicpu_task.cppLaunch 结束RecordAicpuTime(kLaunchEnd)aicpu_task.cppCopyShapeD2H 结束RecordAicpuTime(kShapeD2hCopyEnd)aicpu_ext_info_handle.cppUpdateOutputShape 结束RecordAicpuTime(kUpdateOutputShapeEnd)aicpu_task.cpp可以看到AicpuTfTask::Run与AicpuCCTask::Run两条执行路径分别对应 TF 框架任务与自定义算子任务都完整插桩了同样的阶段保证两类 AICpu 任务得到一致的耗时统计口径。Shape 相关阶段UpdateShape / ShapeH2D / CopyShapeD2H的埋点则集中在 aicpu_ext_info_handle.cpp 的AicpuExtInfoHandler中与UpdateInputAndOutputShape、UpdateOutputShapeFromExtInfo的职责一一对应。耗时计算与结果输出采集到首尾时间戳后由两个辅助函数完成耗时换算与打印位于 aicpu_task_base.cppvoid AicpuPrintTimeInfo(const char* info, unsigned short startIndex, unsigned short endIndex) { unsigned long time 0U; if (startIndex kAicpuTimeStampNum endIndex kAicpuTimeStampNum) { if (gAicpuTimeStamp.tp[startIndex].tv_sec ! gAicpuTimeStamp.tp[endIndex].tv_sec) { // 1000000000 for time stamp time gAicpuTimeStamp.tp[endIndex].tv_nsec 1000000000 - gAicpuTimeStamp.tp[startIndex].tv_nsec; } else { time gAicpuTimeStamp.tp[endIndex].tv_nsec - gAicpuTimeStamp.tp[startIndex].tv_nsec; } printf(%s : %f \n, info, time / 1000.0); // 1000.0 for time us } }该函数做了两件关键处理一是跨秒修正——当起止时间戳落在不同秒内时将纳秒差值加上1000000000即 1 秒后再相减保证跨秒场景耗时计算正确二是单位换算——timespec的tv_nsec单位是纳秒除以1000.0后以微秒us输出便于阅读。PrintAicpuAllTimeStampInfo则在开关开启时按固定格式输出全部七个阶段并在每条任务打印前附加算子类型与自增序号aicpu_task_base.cpptest case opType: 算子类型, index: 自增序号 FindTask : 耗时us UpdateShape : 耗时us ShapeH2D : 耗时us UpdateArgs : 耗时us Launch : 耗时us CopyShapeD2H : 耗时us UpdateOutputShape : 耗时us该打印函数在每次Run结束时被调用aicpu_task.cpp因此只要设置了GE_PROFILING_TO_STD_OUT1每次 AICpu 任务执行的阶段耗时就会实时输出到标准输出方便在不打断流程的情况下快速定位耗时瓶颈例如Launch阶段异常偏大则问题大概率在 Device 侧内核启动环节CopyShapeD2H偏大则需关注 Shape 回传的同步开销。使用方式与注意事项结合以上源码分析该时间戳统计能力的完整使用方式为在执行 AICpu 算子的进程启动前设置环境变量GE_PROFILING_TO_STD_OUT1正常运行算子经由AicpuTaskSpace::GetOrCreateTask创建任务时间戳开关会在任务创建时自动生效观察标准输出中每个算子的七个阶段耗时。需要特别注意的边界条件该能力面向框架内部耗时定位属于预留接口范畴后续可能变更或废弃生产代码不应依赖其输出格式统计开关依赖环境变量在进程启动时读取运行中动态修改环境变量不保证立即生效输出通过printf直接写到标准输出开启后可能产生大量日志仅建议在问题定位场景临时开启时间戳采集本身引入锁与系统调用开销虽然开关关闭时几乎无影响但开启状态下会带来少量性能损耗。进一步阅读aicpu_related_interface 总览AICpu 相关预留接口的完整清单aicpu_task.mdAicpuTask 任务执行接口说明AicpuTaskClass.mdAicpuTask 类接口定义AicpuTaskSpaceClass.md任务空间与缓存查找机制aicpu_ext_info_handle.mdShape 扩展信息处理接口对应 ShapeH2D / CopyShapeD2H 阶段源码实现头文件 include/nnopbase/opdev/aicpu/aicpu_utils.h实现与埋点 src/nnopbase/aicpu/task_handler/aicpu_task_base.cpp、aicpu_task.cpp、aicpu_ext_info_handle.cpp【免费下载链接】opbase本项目是CANN算子库的基础框架库为算子提供公共依赖文件和基础调度能力。项目地址: https://gitcode.com/cann/opbase创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表