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作为载体有两个关键原因:
- 保持参数的编译期类型信息:tuple 是异构容器,能够容纳
aclTensor*、aclScalar*、int64_t等不同类型的参数,且类型在编译期完整保留; - 支持 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 参数包的处理:
编译期校验:
static_assert通过ValidDfxName校验当前函数名是否为APIName+"GetWorkspaceSize"。从 op_dfx.h 可见,Debug 构建下直接放行,Release 构建下用std::string_view(a) == b做精确匹配,将函数名错误提前到编译期暴露。公共参数校验:
CheckPhase1Params(executor, workspaceSize)校验一阶段公共入参(aclOpExecutor**与uint64_t*)是否为 nullptr,失败直接返回ACLNN_ERR_PARAM_NULLPTR。该函数的实现位于 op_dfx.cpp。参数打印与 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,仅筛出类型为aclTensor或aclTensorList的成员,写入线程局部上下文(见 op_dfx.h)。
- 调用
缓存命中判断:
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 上报”的完整行为闭环。
使用注意事项
- 必须与 L2_DFX_PHASE_1 协同使用:DFX_IN 单独不产生任何效果,只有作为
L2_DFX_PHASE_1(APIName, IN, OUT)的IN实参传入,其 tuple 才能被 OpDfxGuard 消费。 - 位置约束:包含
L2_DFX_PHASE_1的插桩语句必须置于一阶段接口最前方,否则时延统计会包含前置代码的执行时间,产生误差;二阶段同理需在aclnn_Xxx入口处调用L2_DFX_PHASE_2。 - 参数一致性:DFX_IN 中的参数必须与 L2 接口签名严格一致(顺序与数量均需匹配),否则参数名与值的映射会发生错位,影响日志可读性与缓存 key 的正确性。
- 类型覆盖:DFX_IN 对
aclTensor、aclTensorList类型会自动纳入 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),仅供参考