拓冰建站拓冰建站
首页 / 资讯中心 / 正文

CANN ATVOSS 算子开发指南:PlaceHolderTmpLike 临时对象占位符详解

CANN ATVOSS 算子开发指南PlaceHolderTmpLike 临时对象占位符详解【免费下载链接】atvossATVOSSAscend C Templates for Vector Operator Subroutines是一套基于Ascend C开发的Vector算子库致力于为昇腾硬件上的Vector类融合算子提供极简、高效、高性能、高拓展的编程方式。项目地址: https://gitcode.com/cann/atvoss导读PlaceHolderTmpLike是 CANN ATVOSSAscend C Templates for Vector Operator Subroutines算子库中用于在 Compute 表达式中定义临时对象的模板函数。它允许开发者以参照某个已定义参数的类型的方式快速声明中间变量配合PlaceHolder一起使用就能把一条复杂的融合算子计算链拆解成先算中间结果、再参与后续运算的多步表达式从而让算子逻辑更清晰、更贴近手写 Kernel 的书写习惯。读完本文你将掌握PlaceHolderTmpLike的完整签名、三个模板参数N/T/L的语义与默认行为、返回的LocalVar表达式类型以及如何在 ATVOSS 的Compute()中用它承接中间结果并交给后端做 Buffer 复用与表达式化简。一、为什么需要 PlaceHolderTmpLike在 ATVOSS 中算子开发者通过Compute()内部的表达式来声明整个融合计算过程。表达式的基本要素是对象而对象分为两类参数对象Param由 PlaceHolder 创建对应 Host 侧通过ArgumentsBuilder::inputOutput()传入的实参张量或标量代表算子真正的输入、输出临时对象LocalVar由PlaceHolderTmpLike创建对应计算过程中的中间结果不直接对应任何 Host 实参。如果一条计算链很长例如 RMS Norm 这类平方和 → 求均值 → 开方 → 归一化 → 加权的多步流程直接写成一个超长表达式虽然可行但可读性差且中间结果无法显式命名复用。PlaceHolderTmpLike正是为了把这种长表达式分段命名而设计的auto tmp Atvoss::PlaceHolderTmpLike1(in1); // 声明一个临时对象 tmp return (tmp in1 in2, // 先算中间结果 out tmp - in3); // 中间结果参与后续计算这里的tmp是一个LocalVar表达式对象在同一个 Compute 表达式中可以像普通变量一样被多次赋值、被引用。其临时语义在后端有完整支撑ATVOSS 会在表达式线性化阶段见 include/graph/expr_linearizer.h对LocalVar进行收集、去重、编号校验并在 Block 调度阶段为它们分配独立的内部 Buffer见 include/elewise/block/schedule.h 中基于LocalVarUseList的AllocInserter/FreeInserter处理做到临时空间的自动复用。二、函数原型与所属头文件PlaceHolderTmpLike声明于 ATVOSS 表达式模板核心头文件 include/expression/expr_template.h约 L593-L602函数原型如下template std::size_t N, typename T void, typename L __host_aicore__ constexpr auto PlaceHolderTmpLike(ExpressionL /*unused*/)关键信息解读它是constexpr函数标注__host_aicore__可同时用于 Host 侧编译期推导与 AI Core 侧执行环境入参是一个ExpressionL类型的表达式对象通常是某个PlaceHolder创建的参数表达式形参名为unused即运行时不产生任何实际数据读写仅用于在编译期推导模板参数L返回值为ExpressionLocalVarN, T, L类型当T缺省为void时实际返回ExpressionLocalVarN, typename L::Type, L。源码级实现template std::size_t N, typename T void, typename L __host_aicore__ constexpr auto PlaceHolderTmpLike(ExpressionL /*unused*/) { static_assert(IsParam_vL, [ERROR]: [Atvoss][Expression] A LocalVar can only be like a Param); if constexpr (std::is_void_vT) { return ExpressionLocalVarN, typename L::Type, L{}; } else { return ExpressionLocalVarN, T, L{}; } }实现要点编译期约束static_assert(IsParam_vL, ...)强制要求参照对象L必须是Param类型——即PlaceHolderTmpLike只能参照由PlaceHolder声明的参数对象来创建临时对象错误信息明确指出 A LocalVar can only be like a Param类型推导二选一当T为默认的void时临时对象类型取自L::Type即被参照参数的原始类型当显式给出T时临时对象类型即为TLocalVar携带Like信息生成的LocalVarN, T, L中第三个模板参数L记录了仿照对象可供后端在图构建阶段追溯临时对象与哪个参数同构从而正确推导其形状、内存布局与生命周期参见 include/elewise/graph/bind.h 中针对LocalVar/Param的赋值绑定与释放分析逻辑。三、模板参数说明参数名称参数类型输入/输出数据类型参数说明默认值N模板参数输入NA临时对象位序从 1 开始顺序编号NAT模板参数输入NA临时对象类型可以是基础类型和Tensor如果不传使用L模板参数的类型voidL模板参数输入NA临时对象按照L指定的对象来生成L必须是struct Param类型NA参数语义详解N—— 临时对象位序与PlaceHolder中参数位序的语义类似N用于在同一 Compute 表达式中唯一标识一个临时对象从 1 开始顺序编号。在同一表达式中可声明多个临时对象如PlaceHolderTmpLike1、PlaceHolderTmpLike2、PlaceHolderTmpLike3……它们各自独立。源码侧LocalVarN, T, L通过static constexpr std::size_t number N暴露编号LocalVarsT与ParamsT见 include/expression/expr_template.h L301-L337会分别收集表达式中的全部临时对象与参数并静态断言必须从 1 开始连续编号static_assert( Atvoss::Util::All_vInRange, UnsortedType, [ERROR]: [Atvoss][Expression] LocalVars must be numbered sequentially from 1);T—— 临时对象类型可显式指定临时对象的数据类型支持基础类型如float、int32_t与Tensor类型。缺省void时自动取参照参数L的类型。一个典型场景见 examples/muls/muls.cpp 的MulsComputePromtInauto in Atvoss::PlaceHolder1, TensorTensorDtype, Atvoss::ParamUsage::IN(); auto scalar Atvoss::PlaceHolder2, ScalarDtype, Atvoss::ParamUsage::IN(); auto out Atvoss::PlaceHolder3, TensorScalarDtype, Atvoss::ParamUsage::OUT(); auto inTmp Atvoss::PlaceHolderTmpLike1, TensorScalarDtype(in); // 显式指定临时对象类型 return (inTmp Atvoss::CastAtvoss::CastMode::CAST_NONE, ScalarDtype(in), out inTmp * scalar);该示例中输入张量类型为TensorDtype而输出是ScalarDtype因此通过显式指定T TensorScalarDtype让临时对象inTmp承接一次类型转换Cast的结果再参与乘法运算实现输入类型 → 中间类型 → 输出类型的渐进式计算。L—— 参照对象L必须是struct Param类型即由PlaceHolderN, T, U返回的表达式所承载的Param类型。PlaceHolderTmpLike的入参正是ExpressionL编译器通过模板实参推导自动确定L开发者通常无需手写。约束由源码中的static_assert(IsParam_vL, ...)在编译期强制保证。四、返回值说明返回值数据类型返回值说明ExpressionLocalVarN, T, U返回一个LocalVar表达式对象LocalVar是 ATVOSS 表达式系统中的局部变量载体定义于 include/expression/expr_template.h L72-L87template std::size_t N, typename T, typename L void struct LocalVar { static_assert(!std::is_reference_vT, [ERROR]: [Atvoss][Expression] A LocalVar must not be a reference); using Type T; using RetType std::decay_tT; using TensorType RetType; using Like L; static constexpr std::size_t number N; ... };要点LocalVar与Param结构同源都具备Type/RetType/TensorType/number元数据并额外带有Like字段指向其仿照的参数LocalVar不能是引用类型编译期static_assert拦截LocalVar本身不提供赋值运算赋值只能发生在表达式层即ExpressionLocalVar...之间——这保证了所有对临时对象的读写都进入表达式系统、由后端统一线性化与调度而不是在用户侧直接产生内存操作。在表达式语法层面LocalVar与Param一样可以作为赋值左侧Expression::operator允许IsParam_vT || IsLocalVar_vT || 左值引用也可以作为算术运算的操作数完全融入 ATVOSS 的表达式 DSL。五、约束说明原文档标注的约束为 NA无额外约束但从源码实现可确认以下编译期约束属于固有语义使用时须注意L必须是 ParamPlaceHolderTmpLike的参照对象必须由PlaceHolder创建不能以另一个LocalVar或裸类型作为参照static_assert强制N从 1 开始连续编号一个 Compute 表达式内全部LocalVar的编号必须为1, 2, 3, ...连续递增否则触发 LocalVars must be numbered sequentially from 1 静态断言类型安全LocalVar不允许为引用类型T传void时自动继承参照参数类型。六、使用示例AddSub 融合算子原文档给出了一个完整的 AddSub 融合算子示例输入in1、in2Tensor与标量in3计算out (in1 in2) - in3。其中引入PlaceHolderTmpLike1(in1)把in1 in2的中间结果暂存为tmp再参与减法。完整代码如下template typename InputDtype, typename OutputDtype struct AddSubConfig { struct AddSubCompute { template template typename class Tensor __host_aicore__ constexpr auto Compute() const { auto in1 Atvoss::PlaceHolder1, TensorInputDtype, Atvoss::ParamUsage::IN(); auto in2 Atvoss::PlaceHolder2, TensorInputDtype, Atvoss::ParamUsage::IN(); auto in3 Atvoss::PlaceHolder3, InputDtype, Atvoss::ParamUsage::IN(); auto out Atvoss::PlaceHolder4, TensorOutputDtype, Atvoss::ParamUsage::OUT(); // 使用示例声明参照 in1 类型的临时对象 tmp auto tmp Atvoss::PlaceHolderTmpLike1(in1); return (tmp in1 in2, out tmp - in3); }; }; using ArchTag Atvoss::Arch::DAV_3510; using BlockOp Atvoss::Ele::BlockBuilderAddSubCompute, ArchTag; using KernelOp Atvoss::Ele::KernelBuilderBlockOp; using DeviceOp Atvoss::DeviceAdapterKernelOp; }; template typename InputDtype, typename OutputDtype static void Run() { /* ACL init and stream create */ ... Atvoss::TensorInputDtype in1(deviceIn1, {{3, 4, 0, 0, 0, 0, 0, 0}}, 2); Atvoss::TensorInputDtype in2(deviceIn2, {{3, 4, 0, 0, 0, 0, 0, 0}}, 2); InputDtype in3 5.0; Atvoss::TensorOutputDtype out(deviceOut, {{3, 4, 0, 0, 0, 0, 0, 0}}, 2); auto arguments Atvoss::ArgumentsBuilder{}.inputOutput(in1, in2, in3, out).attr(dim, 5).build(); using DeviceOp typename AddSubConfigInputDtype, OutputDtype::DeviceOp; DeviceOp deviceOp; deviceOp.Run(arguments, stream); } int main(int argc, char const* argv[]) { Runfloat, float(); return 0; }示例要点说明声明方式PlaceHolderTmpLike1(in1)中in1是PlaceHolder1, TensorInputDtype, ParamUsage::IN返回的表达式T缺省为void因此tmp自动获得TensorInputDtype类型使用方式tmp先作为赋值左值接收in1 in2的结果再作为操作数参与tmp - in3通过逗号运算符,串接成一条完整的 Compute 返回表达式ATVOSS 的重载operator,见 include/expression/expr_template.h L533-L537会把多语句打包成OpAndThen链位序独立性tmp的编号1与四个PlaceHolder的位序1~4属于两套独立编号体系互不冲突——Param编号对应 Host 实参顺序LocalVar编号对应临时对象顺序Host 侧对应关系ArgumentsBuilder{}.inputOutput(in1, in2, in3, out)的实参顺序与PlaceHolder的位序一一对应而tmp不占用任何实参位。七、典型实战场景多步中间结果与 Buffer 复用场景一连续多个临时对象承接多步计算在 tests/ut/host/test_expr_linearizer.cpp 中测试用例一次性声明了 9 个临时对象temp~temp8演示了典型的多步归一化 残差计算链auto temp Atvoss::PlaceHolderTmpLike1(in1); auto temp1 Atvoss::PlaceHolderTmpLike2(in1); auto temp2 Atvoss::PlaceHolderTmpLike3(in1); // ... 依此类推直到 temp8 PlaceHolderTmpLike9(in1) auto xx1 (temp in1 * in1, temp1 Atvoss::ReduceSumAtvoss::Pattern::AR(temp), temp2 Atvoss::BroadcastAtvoss::Pattern::AB(temp1), temp3 Atvoss::DivsWIDTH(temp2), temp4 temp3 * in3, temp5 temp4 temp, temp6 Atvoss::Sqrt(temp5), temp7 temp6 temp, temp8 in1 / temp7, out in2 * temp8, out2 in2 temp8, out3 in2 / temp5);该用例同时展示了 ATVOSS 的自动临时化能力xx1手写LocalVar版本与Atvoss::ToLinearizerExpr(xx)自动版本其中xx为无显式临时对象的长表达式在std::is_same_v类型对比下完全一致说明用户显式使用PlaceHolderTmpLike与框架自动缓存中间结果为LocalVar是同一套底层机制——后者的实现可见于 include/graph/expr_linearizer.h 中的OptimizeWithLocalVars将每个非 Param 子表达式提升为LocalVar并做后续替换。场景二临时对象的重复赋值与 Buffer 复用在 tests/st/test_compute_buffer_reuse.cpp 中tmp1/tmp2被反复交叉赋值auto tmp1 Atvoss::PlaceHolderTmpLike1(in1); auto tmp2 Atvoss::PlaceHolderTmpLike2(in1); return (tmp1 in1, tmp2 tmp1 in1, tmp1 tmp2 in2, tmp2 tmp1, in1 tmp1, in2 tmp2, out in1 in1);该用例验证了 compute 表达式的 Buffer 复用能力LocalVar的生命周期由编译器按最后一次使用分析参见 include/elewise/graph/bind.h 中LastAssignRhs/IsAbleToFree等机制以及 include/elewise/block/schedule.h 中基于LocalVarUseList的分配/释放插入因此多个临时对象可以在硬件 Buffer 空间上复用同一块内存降低片上存储占用。类似的 buffer 复用、冗余消除测试还见于test_compute_buffer_reuse.cpp系列的 test_compute_tmp_redundant_with_autopolicy.cpp、test_compute_tmp_redundant_with_manupolicy.cpp 与 test_compute_expression_redundant_with_autopolicy.cpp。场景三RMS Norm 风格的真实算子中间量在 tests/ut/host/test_arguments.cpp 的 RMS Norm 配置中temp PlaceHolderTmpLike1(in1)被用作平方和中间结果的载体后续ReduceSum/Broadcast/Divs/Sqrt等逐步作用其上最终仅以out _11一个赋值语句收尾——这正是融合算子把整条数学链写在 Compute 中的标准写法。类似的 RMS Norm 测试用例在 tests/st 下有test_tile_rms_norm_3.cpp至test_tile_rms_norm_17.cpp等多个文件均以auto temp Atvoss::PlaceHolderTmpLike1(in1);开头可作为批量参考。八、PlaceHolderTmpLike 与 PlaceHolder 的对比维度PlaceHolderPlaceHolderTmpLike功能定义参数对象算子输入/输出/标量定义临时对象中间结果对应 Host 实参是位序与ArgumentsBuilder::inputOutput()实参一一对应否不占实参位序模板参数N位序、T类型、U数据流向 IN/OUT/IN_OUTN位序、T类型可缺省、L参照对象必为 Param返回值ExpressionParamN, T, UExpressionLocalVarN, T, L类型来源必须显式指定缺省时自动继承参照对象类型典型用途声明in1/in2/out等参数声明tmp承接in1 in2等中间结果两者搭配使用的通用范式// 1. 先声明参数 auto in Atvoss::PlaceHolder1, Tensorfloat, Atvoss::ParamUsage::IN(); auto out Atvoss::PlaceHolder2, Tensorfloat, Atvoss::ParamUsage::OUT(); // 2. 再声明临时对象参照某个 Param auto tmp Atvoss::PlaceHolderTmpLike1(in); // 3. 多步计算 return (tmp /* 中间计算 */, out /* 使用 tmp 的最终计算 */);九、常见问题FAQQ1PlaceHolderTmpLike可以参照另一个PlaceHolderTmpLike创建的临时对象吗不可以。源码static_assert(IsParam_vL, ...)明确要求L必须是Param类型参照对象只能是PlaceHolder创建的参数对象。Q2临时对象的编号需要和 PlaceHolder 的编号区分开吗不需要刻意区分。两者是两套独立的编号体系PlaceHolderN的N对应 Host 实参顺序PlaceHolderTmpLikeN的N只表示第几个临时对象。但每个体系内部都要求从 1 开始连续编号。Q3一个 Compute 表达式最多能声明多少个临时对象没有硬性数量上限测试用例中曾一次声明 9 个但所有临时对象编号必须连续。实际数量受片上 Buffer 容量与调度策略制约ATVOSS 会通过 Buffer 复用尽量压缩占用。Q4不写PlaceHolderTmpLike直接用长表达式可以吗可以。ATVOSS 的表达式线性化ToLinearizerExpr见 include/graph/expr_linearizer.h会自动将长表达式中的中间结果提升为LocalVar。但显式书写PlaceHolderTmpLike能让计算步骤一目了然也便于在Compute()内复用同一个中间结果多次。Q5临时对象能直接作为算子的输出对应 Host 侧 out 实参吗不能直接对应。算子输出必须由PlaceHolder以ParamUsage::OUT或IN_OUT声明临时对象只承担表达式中途的暂存职责最终结果必须通过赋值语句写入Param输出。十、延伸阅读PlaceHolder参数对象占位符与本文的临时对象占位符配套使用include/expression/expr_template.hPlaceHolderTmpLike、PlaceHolder、LocalVar、Param及全部表达式操作符的源码定义include/graph/expr_linearizer.h表达式线性化与LocalVar自动提升/化简的实现include/elewise/graph/bind.h 与 include/elewise/block/schedule.hLocalVar的赋值绑定、Buffer 分配与释放调度examples/muls/muls.cpp显式指定临时对象类型承接 Cast 结果的真实算子示例tests/ut/host/test_expr_linearizer.cpp 与 tests/st/test_compute_buffer_reuse.cpp多临时对象与 Buffer 复用行为的测试验证。【免费下载链接】atvossATVOSSAscend C Templates for Vector Operator Subroutines是一套基于Ascend C开发的Vector算子库致力于为昇腾硬件上的Vector类融合算子提供极简、高效、高性能、高拓展的编程方式。项目地址: https://gitcode.com/cann/atvoss创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门