CANN opbase DFX_IN 宏详解:L2 一阶段接口入参封装与 DFX 插桩机制
2026/9/18 9:56:35 网站建设 项目流程

CANN opbase DFX_IN 宏详解:L2 一阶段接口入参封装与 DFX 插桩机制

【免费下载链接】opbase本项目是CANN算子库的基础框架库,为算子提供公共依赖文件和基础调度能力。项目地址: https://gitcode.com/cann/opbase

DFX_IN 是 CANN opbase 算子开发框架提供的一个核心插桩宏,用于封装 L2 一阶段接口aclnn_Xxx_GetWorkspaceSize的全部 Host 侧输入参数。本文以 DFX_IN.md 为骨架,结合 op_dfx.h 源码实现与 test_profiling.cpp 测试用例,讲解 DFX_IN 的用法、底层实现原理及其在算子 DFX(诊断、调试与性能统计)链路中的实际作用。读完本文,你将掌握如何在自定义算子的 L2 接口中正确使用 DFX_IN,并理解参数打印、Tensor 收集与缓存命中判断的完整数据流。

宏功能:封装 L2 一阶段接口的输入参数

在 CANN 算子库的 L2 接口体系中,每个算子对外暴露两个阶段接口:

  • 一阶段:aclnn_Xxx_GetWorkspaceSize,负责参数校验、shape 推导与 workspace 大小计算;
  • 二阶段:aclnn_Xxx,负责真正的算子执行。

DFX_IN 宏专用于一阶段,其作用是将 Host 侧 L2 一阶段接口的输入参数整体打包,以便后续 DFX 框架统一处理这些参数。它通常与 DFX_OUT(封装输出参数)、L2_DFX_PHASE_1(L2 一阶段 DFX 入口)配合使用,构成一阶段接口最前方的三行固定代码。在 0_opdev_api_list.md 和 1_opdev_api_introduction.md 中,DFX_IN 被定位为“在 L2_DFX_PHASE_1 中用于打包所有 Host 侧 API 输入参数”,所属头文件为aclnn/opdev/op_dfx.h

宏原型与参数说明

DFX_IN(...)
参数输入/输出说明
...输入Host 侧 L2 一阶段接口的输入参数,可变长参数。

约束说明:无。

参数个数与顺序必须与 L2 一阶段接口的原生参数列表严格一致。可变长参数设计使该宏可以适配任意签名的算子接口,例如 Add 算子的 3 个入参、Abs 算子的 1 个入参,都能用同一宏表达。

源码实现:宏展开为 std::tuple

在头文件 include/nnopbase/opdev/op_dfx.h 中,DFX_IN 与 DFX_OUT 的实际定义极为简洁:

#define DFX_IN(...) std::make_tuple(__VA_ARGS__) #define DFX_OUT(...) std::make_tuple(__VA_ARGS__)

也就是说,DFX_IN(self, other, alpha)在预处理阶段会被展开为std::make_tuple(self, other, alpha)。选择std::tuple作为载体有两个关键原因:

  1. 保持参数的编译期类型信息:tuple 是异构容器,能够容纳aclTensor*aclScalar*int64_t等不同类型的参数,且类型在编译期完整保留;
  2. 支持 std::apply 展开:后续OpDfxGuard通过std::apply对 tuple 逐元素遍历,实现对每个参数的类型感知处理(详见下文)。

同一份代码在 L2_DFX_PHASE_1.md 的“约束说明”一节也被显式列出,与头文件实现完全一致,可直接对照验证。

完整调用链:DFX_IN 在 L2_DFX_PHASE_1 中的角色

DFX_IN 不是独立使用的宏,它的真正价值体现在被 L2_DFX_PHASE_1 消费时。该宏用于“L2 一阶段接口时延统计及入参打印”,必须在一阶段接口最前方调用,其原型为:

L2_DFX_PHASE_1(APIName, IN, OUT)
参数输入/输出说明
APIName输入Host 侧 L2 接口名,如 aclnnXxx。
IN输入算子的输入参数,由 DFX_IN 封装。
OUT输入算子的输出参数,由 DFX_OUT 封装。

op_dfx.h 中 L2_DFX_PHASE_1 的完整展开体揭示了 DFX_IN 产生的 tuple 在整条链路中的去向:

#define L2_DFX_PHASE_1(APIName, IN, OUT) \ static_assert(op::ValidDfxName(#APIName "GetWorkspaceSize", __func__), \ "Invalid DFX:" #APIName "GetWorkspaceSize"); \ if (CheckPhase1Params(executor, workspaceSize) != ACLNN_SUCCESS) { \ return ACLNN_ERR_PARAM_NULLPTR; \ } \ InitL2Phase1Context(#APIName, executor); \ L2_OP_PROF_PHASE_1(#IN, #OUT, IN, OUT); \ do { \ if (op::internal::GetFromCache(executor, workspaceSize, #APIName, IN, OUT)) { \ return ACLNN_SUCCESS; \ } \ } while (false)

依次拆解这条链路中各环节对 DFX_IN 参数包的处理:

  1. 编译期校验static_assert通过ValidDfxName校验当前函数名是否为APIName+"GetWorkspaceSize"。从 op_dfx.h 可见,Debug 构建下直接放行,Release 构建下用std::string_view(a) == b做精确匹配,将函数名错误提前到编译期暴露。

  2. 公共参数校验CheckPhase1Params(executor, workspaceSize)校验一阶段公共入参(aclOpExecutor**uint64_t*)是否为 nullptr,失败直接返回ACLNN_ERR_PARAM_NULLPTR。该函数的实现位于 op_dfx.cpp。

  3. 参数打印与 Tensor 收集L2_OP_PROF_PHASE_1(#IN, #OUT, IN, OUT)构造OpDfxGuard对象(见 op_dfx.h)。构造时把宏参数名的字符串形式(如"DFX_IN(self, other, alpha)")与 tuple 实参一起传入,完成两件事:

    • 调用BuildParamStringWithBrackets将每个实参格式化为参数名: 值的日志文本,超长日志按 700 字符分片输出(见 op_dfx.h);
    • IsDumpEnabled()为真,则通过AddInputTensorsToThreadLocalCtx/AddOutputTensorsToThreadLocalCtx遍历 tuple,仅筛出类型为aclTensoraclTensorList的成员,写入线程局部上下文(见 op_dfx.h)。
  4. 缓存命中判断GetFromCache(executor, workspaceSize, #APIName, IN, OUT)同样接收 DFX_IN 的 tuple,用于判断算子缓存是否可复用,命中则直接返回ACLNN_SUCCESS

可见 DFX_IN 产生的 tuple 是“参数打印 → Tensor 收集 → 缓存判断”三个环节共享的输入载体,一份参数包多处复用。

OpDfxGuard:DFX_IN 参数的实际消费者

OpDfxGuard是与 DFX 宏配套的核心类,声明于 op_dfx.h。其 L2 一阶段专用构造函数如下:

template <typename INPUT_TUPLE = void*, typename OUTPUT_TUPLE = void*> OpDfxGuard(const char* file, int line, OpLevel level, const char* funcName, const char* paramNamesIn, const char* paramNamesOut, const INPUT_TUPLE&& in, const OUTPUT_TUPLE&& out)

构造函数内部依次执行:

OP_DFX_LOGI(file, line, funcName_, "Entering function %s.", funcName); OP_LOGI("Entering function in params end. %d", BuildParamStringWithBrackets(paramNamesIn, in)); OP_LOGI("Entering function out params end. %d", BuildParamStringWithBrackets(paramNamesOut, out)); if (op::internal::opProfilingSwitch.reportFlag) { opDfxProfiler_ = CreateDfxProfiler(funcName); } if (IsDumpEnabled()) { InitThreadLocalContext(); AddInputTensorsToThreadLocalCtx(in); AddOutputTensorsToThreadLocalCtx(out); }

其中BuildParamStringWithBrackets首先调用StringToVecWithBrackets解析形如DFX_IN(aa, bb, cc)的参数名串——该解析函数也在 op_dfx.cpp 有对应实现,随后用std::apply展开 tuple,按参数类型分别格式化:

  • 基础类型(fundamental):直接std::to_string
  • 指针类型:打印指针指向的值,nullptr 时打印nullptr
  • char*/const char*:打印字符串内容;
  • 其余类型:经ToString转换为ge::AscendString

此外,若 profiling 开关打开,CreateDfxProfiler会创建统计器,在析构时通过MsprofReportApi上报接口耗时(见 op_dfx.cpp),从而完成 L2 一阶段的时延统计。这也解释了文档中“必须在 L2 一阶段接口的入口处调用,否则可能导致时延统计出现误差”的约束——guard 对象的构造/析构时间即被计入接口耗时。

调用示例

参照 DFX_IN.md 与 L2_DFX_PHASE_1.md 中的示例,Add 算子与 Abs 算子的完整插桩写法如下:

// add算子的输入参数,共有3个:self,other和alpha L2_DFX_PHASE_1(aclnnAdd, DFX_IN(self, other, alpha), DFX_OUT(out)); // abs算子的L2接口一阶段时延统计及参数打印,self为abs算子的输入,out为abs算子的输出 L2_DFX_PHASE_1(aclnnAbs, DFX_IN(self), DFX_OUT(out));

与一阶段对应的二阶段接口,则单独使用 L2_DFX_PHASE_2 进行时延统计:

// abs算子的L2接口二阶段时延统计 L2_DFX_PHASE_2(aclnnAbs);

测试验证与工程佐证

仓库测试代码提供了 DFX_IN 链路的行为验证。在 tests/nnopbase/st/composite_op/test_profiling.cpp 中,l2_phase_one_api_profiling用例以字符串"DFX_IN(in)""DFX_OUT(out)"和两个std::make_tuple实参直接构造OpDfxGuard,模拟 L2 一阶段插桩并断言 profiling 回调被触发;l2_phase_two_api_profiling则验证二阶段路径。此外:

  • tests/nnopbase/ut/composite_op/test_profiling.cpp 直接调用StringToVecWithBrackets("DFX_IN(aa, bb, cc)", v),验证括号参数名解析的正确性;
  • tests/nnopbase/common/depends/op/aclnn_mul_stub.cpp 中L2_DFX_PHASE_1(aclnnMulStub, DFX_IN(intput1, intput2), DFX_OUT(out))展示了多入参算子在 stub 层的真实用法。

这些用例共同印证了 DFX_IN 从“宏展开为 tuple”到“guard 解析参数名并打印/收集”再到“profiling 上报”的完整行为闭环。

使用注意事项

  1. 必须与 L2_DFX_PHASE_1 协同使用:DFX_IN 单独不产生任何效果,只有作为L2_DFX_PHASE_1(APIName, IN, OUT)IN实参传入,其 tuple 才能被 OpDfxGuard 消费。
  2. 位置约束:包含L2_DFX_PHASE_1的插桩语句必须置于一阶段接口最前方,否则时延统计会包含前置代码的执行时间,产生误差;二阶段同理需在aclnn_Xxx入口处调用L2_DFX_PHASE_2
  3. 参数一致性:DFX_IN 中的参数必须与 L2 接口签名严格一致(顺序与数量均需匹配),否则参数名与值的映射会发生错位,影响日志可读性与缓存 key 的正确性。
  4. 类型覆盖:DFX_IN 对aclTensoraclTensorList类型会自动纳入 dump 收集;其他类型(标量、属性等)仅参与日志打印,不影响 dump 数据。

总结

DFX_IN 是 CANN opbase 算子 DFX 体系中的基础插桩宏:它以std::make_tuple实现轻量参数打包,向上承接L2_DFX_PHASE_1的时延统计、参数打印、Tensor 收集与缓存判断,向下对接OpDfxGuard的类型感知格式化与 msprof 上报。理解这一宏的展开与消费链路,是在自定义算子 L2 接口中正确接入 DFX 能力、保证日志可读与性能数据准确的前提。更多同类宏(如 DFX_OUT、L0_DFX、L2_DFX_PHASE_2)可在 common_macros_and_classes.md 中查看完整索引,DFX 底层接口清单参见 op_dfx.md。

【免费下载链接】opbase本项目是CANN算子库的基础框架库,为算子提供公共依赖文件和基础调度能力。项目地址: https://gitcode.com/cann/opbase

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询