免费获取学习方案
ARTICLE DETAIL

资讯详情

深耕编程基础知识与建站技术分享的一线实战洞察。

ONNX Runtime Transpose 优化器深度解析:抽象层设计、算子穿越逻辑与 ORT 集成

ONNX Runtime Transpose 优化器深度解析:抽象层设计、算子穿越逻辑与 ORT 集成 ONNX Runtime Transpose 优化器深度解析抽象层设计、算子穿越逻辑与 ORT 集成【免费下载链接】onnxruntimeONNX Runtime: cross-platform, high performance ML inferencing and training accelerator项目地址: https://gitcode.com/GitHub_Trending/on/onnxruntime本篇围绕 ONNX Runtime 仓库中 Transpose 优化器目录文档 展开完整讲解该模块的分层架构抽象 API 层、通用优化逻辑层、ORT 实现层与各文件的职责边界并进一步深入到Optimize()入口参数、算子 Handler 注册机制、默认成本模型与 QDQ 修复流程等源码细节。读完后你将理解 ONNX Runtime 如何把 Transpose 节点沿计算图向后推进push past到布局敏感算子之前、何时值得推进、以及在模型量化后如何保持 QDQ 节点单元node unit的完整性。为什么需要 Transpose 优化器在 ONNX 模型中Transpose节点经常用于在不同布局例如 NCHW 与 NHWC之间转换数据。当图中存在... - Transpose - 算子 - Transpose - ...这类结构时如果两个 Transpose 的perm互为逆排列它们完全可以互相抵消即使不能抵消把 Transpose穿过push past中间的激活函数、逐元素算子等也有机会减少实际执行的 Transpose 数量或让算子落入执行提供方EP更高性能的布局实现中。根据 目录文档 的描述该模块的定位是Generic transpose optimizer that has an abstraction layer so it is independent of the ORT implementation.带有抽象层、独立于 ORT 具体实现的通用 Transpose 优化器文档同时给出了目录内五个组成部分的职责划分文件职责optimizer_api.h定义抽象层 API图、节点、值信息的接口ort_optimizer_api_impl.*抽象层 API 的 ORT 实现onnx_transpose_optimization.*即文档中提到的 transpose_optimizer.* 通用逻辑通用的把 Transpose 节点穿过另一个节点的优化逻辑ort_transpose_optimization.*ORT 专属的扩展针对 contrib 算子、以及存在 NHWC 布局版本的布局敏感算子的特殊 Handler目录中的实际文件包括 optimizer_api.h、ort_optimizer_api_impl.cc、onnx_transpose_optimization.cc、ort_transpose_optimization.cc 等与文档一一对应。抽象层 APIoptimizer_api.h 的完整解读optimizer_api.h 定义在onnx_transpose_optimization::api命名空间下是整个模块与 ORT 实现解耦的边界。文件开头的注释L19-L42说明了核心设计思想接口而非实体api::GraphRef、api::NodeRef等抽象类应被视为操作图/节点的接口而非图/节点本身。实现者可以用自己现有的模型表示在 ORT 中就是onnxruntime::Graph按优化器的请求即时创建接口实例。unique_ptr 即插即用由于接口实例是优化器请求时才创建的例如查找某个输出的节点实现方以unique_ptr返回无需维护缓存或管理这些包装对象的生命周期但实现应让它们尽量轻量理想情况下只包含指向具体对象的指针。图完整性保证所有编辑方法保证图结构完整无环、输入输出名合法但作为 ONNX 模型的合法性可能暂时被破坏算子输入数不足、数据类型不匹配等。输出只能经 MoveOutput 变更为避免编辑过程中出现多个节点产生同一输出输出节点不能直接被 API 操作唯一能转移节点输出的方法是MoveOutput。数据类型与张量接口api::DataTypeL47-L69采用与 ONNX 一致的标准枚举值UNDEFINED0、FLOAT1 … FLOAT8E5M2FNUZ20并鼓励与int32_t相互转换。api::TensorRefL74-L91是对初始化器initializer常量张量的只读接口提供Shape()、NumElements()、DType()和Data()方法其中Data()返回展平的原始字节用于读取指定 axes/pads/scales 的初始化器。值信息与节点接口api::ValueInfoRefL98-L142封装某个值图输入、初始化器或节点输出的 shape/dtype 信息除只读查询Name()、Shape()、ShapeRank()、DType()外还提供三个会修改 shape 的方法且注释明确说明它们只用于节点输出SetShape(const std::vectorint64_t* shape)直接设置推断 shape传 nullptr 表示置为未知 shapePermuteDims(const std::vectorint64_t perm)按置换重排维度注释指出相对于 SetShape 更优先因为它能保持符号 shape 信息preferred since it can maintain symbolic shape informationUnsqueezeDims(const std::vectorint64_t axes)在指定轴插入大小为 1 的维度同样能保留符号 shape 信息。api::NodeRefL148-L274封装节点信息核心方法包括基本属性Name()、OpType()、Domain()、Inputs()、Outputs()可选输入/输出以空字符串表示属性读写GetAttributeInt/String/Ints缺失或类型不符时返回nullopt、SetAttributeInt/Ints、CopyAttributes、ClearAttribute输入编辑SetInput(i, name)支持越界时自动扩展并填充空串用于可选输入便捷方法IsOp(op_type, domain)中把空 domain 与ai.onnx视为等价GetAttributeIntDefault(name, default_value)ORT 相关扩展GetExecutionProviderType()未分配 EP 时返回空串、SinceVersion()、Id()以及用于分层标注layering annotation的GetLayeringAnnotation()/SetLayeringAnnotation()——这些方法在 ORT 侧用于配合图分区与 CPU NHWC 转换。struct ValueConsumersL279-L290承载值的消费者信息nodes是当前图中以该值为输入的节点列表comprehensive标志表明该列表是否穷尽了所有引用——如果该值同时被用作图输出或在子图subgraph中使用则comprehensive为false。这个字段在后续算法里非常关键只有当消费者全面时才允许做移除节点等激进展望。图接口 GraphRef 的关键方法api::GraphRefL306-L476是操作图或图子区域的接口同时暴露一些模型级查询如 opset。文件注释L293-L305特别说明接口不提供访问子图的能力但通过comprehensivefalse标记可以让实现方表达该值还被子图或图输出引用若要限制访问范围到子区域实现方只需在Nodes、GetValueConsumers、GetNodeProducingOutput的返回中过滤子区域外的节点即可。父图访问原则上被禁止唯一例外是GetConstant可以返回父图中的初始化器。主要方法一览方法作用Opset(domain)查询模型 opset_import 中指定 domain 的 opset 版本Nodes()返回拓扑排序后的节点列表优化主循环的遍历依据GetConstant(name)/GetLocalConstant(name)前者可跨父图取常量后者仅限当前图且要求常量可被TransposeInitializer/ReshapeInitializer原地修改GetValueInfo(name)获取值的 shape/dtype 接口GetValueConsumers(name)获取ValueConsumers含 comprehensive 标志GetNodeProducingOutput(name)若该值是节点输出则返回产生它的节点TransposeInitializer(name, perm)原地转置初始化器调用后对应的 ValueInfo 必须返回更新后的 shapeReshapeInitializer(name, shape)原地重排初始化器数据缓冲区不变因此实现上应很快AddNode(name, op_type, inputs, num_outputs, domain)创建新节点节点名与输出名自动生成输出 shape/dtype 未定随后用CopyValueInfo填充CopyNode(source_node, op_type, domain, since_version)按新 op_type/domain 复制节点因为 API 不允许直接改 op_typeRemoveNode(node)/RemoveInitializer(name)删除节点/初始化器要求没有消费者AddInitializer(dtype, shape, data)以原始字节新建初始化器返回生成的名字MoveOutput(src_node, src_idx, dst_node, dst_idx)把一个输出连同其形状、类型和所有消费者包括子图中的转移到另一个节点源节点获得一个替换输出CopyValueInfo(src_name, dst_name)复制 shape/dtype 值信息可能包含 ValueInfo 无法编码的数据如符号 shape 信息HasValueConsumers(name)判断值是否仍有消费者有默认实现实现方可覆写以避免调用 GetValueConsumersIsGraphOutput(name)判断值是否为图输出MoveOutput的注释L427-L445给出了两个典型用法非常值得注意消除成对抵消的 Transpose创建一个 Identity 节点用MoveOutput把最后一个 Transpose 的输出搬到 Identity 上原 Transpose 的所有消费者转而消费 Identity再删掉 Transpose 即可把 Transpose 推过 Relu创建新 Transpose 节点把 Relu 的输出MoveOutput到新 Transpose 上然后把新 Transpose 的输入指向 Relu 新生成的替换输出——这就是穿过一个算子时的标准手法因为旧算子在推进过程中仍需要保留。入口函数与成本检查文件末尾定义了模块的公共入口L480-L546constexpr int64_t kMinSupportedOpset 7; constexpr int64_t kMaxSupportedOpset 27; enum class CostCheckResult { kStop, // 推进 Transpose 预计会损害性能 kPushTranspose, // 推进 Transpose 预计能提升性能 kFallThrough // 落到默认成本检查 }; using CostCheckFn std::functionCostCheckResult(const api::GraphRef graph, const api::NodeRef node, const std::vectorint64_t perm, const std::unordered_setstd::string outputs_leading_to_transpose); const std::unordered_setstd::string_view GetLayoutSensitiveOps(); struct OptimizeResult { std::optionalstd::string error_msg; // 出错时设置 bool graph_modified{false}; }; OptimizeResult Optimize(api::GraphRef graph, const std::string provider_type , CostCheckFn cost_check_fn nullptr, const HandlerMap extended_handlers {});opset 约束模型 opset 超出[7, 27]范围时图将原样返回不支持的 opset 不会报错修改行为在Optimize的注释中写明Models outside the supported opset range will be returned unchanged。provider_type新创建的节点将被指派到该 EP若不指定则要求之后再做图分区graph partitioning来分配节点。cost_check_fn允许调用方覆盖默认成本判断典型用途是引入执行提供方内核实现细节如该算子在 NHWC 下更快ORT 侧正是通过OrtEPCostCheck做到这一点的。extended_handlersHandlerMapop_type →HandlerInfo形式的扩展处理器用于非 ONNX 算子如 contrib 算子或需要特殊处理的 ONNX 算子注释举例ONNXResize本身布局无关但 EP 可能以布局敏感的方式实现它。GetLayoutSensitiveOps()返回 ONNX 标准中定义的布局敏感算子集合具体列表见下文布局敏感算子清单。通用优化逻辑onnx_transpose_optimization 的 Handler 机制onnx_transpose_optimization.h 暴露了通用实现的内部扩展点注释说明其目的是暴露部分内部结构以便按需扩展。Handler 的三元组结构struct HandlerArgs { OptimizerCtx ctx; api::NodeRef transpose; // 正在考虑移动到 node 之后的 Transpose 节点 api::NodeRef node; const std::vectorint64_t perm; // Transpose 的 perm 属性 const std::vectorint64_t perm_inv; // perm 的逆置换 std::vectorsize_t transposible_inputs; // transposible_inputs_fn 的缓存结果 const std::unordered_setstd::string outputs_leading_to_transpose; }; using TransposibleInputsFn std::vectorsize_t (*)(OptimizerCtx ctx, api::NodeRef node); using HandlerFunction bool (*)(HandlerArgs args); struct HandlerInfo { TransposibleInputsFn transposible_inputs_fn; // 哪些输入下标允许被推进 HandlerFunction handler_fn; // 实际的图改写逻辑 bool transposes_outputs true; // 该 Handler 是否需要转置输出用于成本估计 };注释L31-L34指出transposible_inputs_fn只在Transpose 恰好位于某个允许下标上、且启发式判断推进有利时才会触发 Handler大多数算子返回静态值如FirstInput恒返回{0}只有Sum/Concat/QLinearConcat这类多输入算子需要动态计算例如 Concat 的 axis 维度不能被穿越。OptimizerCtxL47-L57则承载一次优化的全局状态opset、graph、provider_type、cost_check_fn与extended_handlers。通用工具函数头文件还声明了一系列被 Handler 复用的工具InvertPerm(perm)计算逆置换HandleSimpleNode转置全部输入和全部输出逐元素算子通用路径HandleSimpleNodeBroadcast所有输入可广播的节点HandleSimpleNodeWithAxis转置输入输出并更新axis属性如 SoftmaxHandleConcat、HandleSoftHardMax、HandleReduceOps、HandleReshape、HandleResize等专用 HandlerTransposeInput/TransposeInputs/TransposeOutput/TransposeOutputs在指定输入/输出上插入 Transpose 节点。注意TransposeInputs的注释特别提醒如果输入上方本来有一个 Transpose 将被取消则应使用其逆置换ChannelFirstToLastPerm(rank)/ChannelLastToFirstPerm(rank)生成 NCHW↔NHWC以及更高维的 NCD…D↔ND…DC的 perm例如 rank4 时分别为[0,2,3,1]与[0,3,1,2]——这正是 ORT 布局转换的底层原语TransposeQuantizeDequantizeAxis按置换更新 QuantizeLinear/DequantizeLinear 的axis属性仅适用于 per-axis 量化。内置算子 Handler 表onnx_transpose_optimization.cc 中通过handler_mapL2760 起注册了 ONNX 标准算子的全部默认 Handler可按语义归为几类逐元素简单节点simple_node_handler即HandleSimpleNode路径Cast、Exp、Gelu、Identity、LeakyRelu、Log、Reciprocal、Relu、Sigmoid、Sqrt、Tanh、Abs、Not、Ceil、Floor、Neg、Erf、HardSigmoid、Round、IsInf、IsNaN、Selu、Shrink、Sign、Softplus、Softsign、ThresholdedRelu、Celu、Elu、HardSwish以及三角函数族Sin、Cos、Tan、Sinh、Cosh、Asin、Acos、Atan、Asinh、Acosh、Atanh广播型二元算子broadcast_node_handlerAdd、Max、Min、Mul、Sub、Div、And、Or、Xor、Mod、PRelu、BitShift、Equal、Greater、Less、GreaterOrEqual、LessOrEqual、Mean、Sum、Pow、Where单输入特殊处理Clip、CastLikenode_1_inp_handler结构/形状算子Transpose 本身合并或抵消、Concat、Split、Shape、Pad、Squeeze、Unsqueeze、Slice、Gather、Tile、Reshape归约类reduce_op_handlerReduceLogSum、ReduceLogSumExp、ReduceMax、ReduceMean、ReduceMin、ReduceProd、ReduceSum、ReduceSumSquare、ReduceL1、ReduceL2以及 ArgMin、ArgMaxSoftmax 族soft_hard_max_handlerSoftmax、Hardmax、LogSoftmax量化算子QuantizeLinear、DequantizeLinearquantize_dequantize_linear_handler。值得注意的一个被注释掉的条目L2836-L2839// Execution providers tend to only implement Resize for specific layouts. Due to that, its safer to not // push a Transpose through a Resize unless the EP specifically checks that it can handle the change via an // extended handler. // {Resize, resize_handler},即 Resize 在默认表中被刻意排除——因为 EP 通常只针对特定布局实现了 Resize 内核除非 EP 通过扩展 Handler 明确表明自己能处理否则不推进 Transpose 是更保守、更安全的选择。Handler 查找函数GetHandlerL2870-L2893体现了扩展表优先的策略ONNX 域算子以 op_type 为 key其他域以domain.op_type为 key先在调用方传入的extended_handlers中查再在内置handler_map中查。Reshape 的两种穿越策略HandleReshapeL2750-L2757先尝试HandleReshapeAsTranspose再尝试HandleReshapeSplit两者覆盖了 Reshape 与 Transpose 的两种等价关系HandleReshapeAsTransposeL2509 起当 Reshape 只是把同一秩张量的维度换序、且所有大于 1 的维度保持原有顺序时Reshape 逻辑上等价于某个 Transpose可直接复用 Transpose 合并逻辑。源码注释给出了例子Reshape(input {1,512,4,1}, shape {1,1,512,4})等价于 perm 为{0,3,1,2}的 TransposeHandleReshapeSplitL2619 起处理 Transpose 之后 Reshape 把一个多个轴拆分成连续输出轴组的场景例如input {1,12,20,24} → Transpose([0,3,1,2]) → {1,24,12,20} → Reshape({1,3,8,12,20})改写后变成先 Reshape 再 Transpose。它要求输入/输出 shape 完全具体无 -1/0 或符号维、Reshape 输出秩大于 Transpose 秩并且输出维恰好按每个转置后轴分成连续分组。这两个策略体现了通用层的设计取向Handler 不是一刀切的置换而是对每一种图模式做严格的可达性校验任何一条假设不成立就返回 false 放弃推进保证不会改写成本升高的图。成本模型与主优化算法成本检查分两级。ProcessTransposeL2946-L2980先查 Handler、确认 Transpose 位于允许的输入下标上然后若提供了cost_check_fn就先调用它返回kFallThrough时才落到DefaultCostCheck。DefaultCostCheckL2930-L2943与CalculateCostL2895-L2927的规则很清晰源码注释解释了动机目标节点是 Transpose 时直接通过合并/抵消总是有益的否则要求输入侧成本该算子前面的 Transpose 数量与总成本都严格下降。严格要求输入成本严格下降是为了保证优化稳定总成本下降只是估计值算子后的 Transpose 未必能与后续 Transpose 抵消若允许持平同一 Transpose 可能在二元算子的两个输入之间来回摆动若 Handler 声明transposes_outputstrue且输出不指向已知的通向 Transpose的输出还要把输出侧的秩计入成本多个输出的算子如 Split 取最大秩。主算法OptimizeImplL3424-L3579分四个阶段阶段一逆拓扑遍历收集outputs_leading_to_transpose。从图末端往前扫L3432-L3453遇到 Transpose 节点就把其输入加入集合若某节点有输出已在集合中、且该节点有transposes_outputstrue的 Handler则把该节点在可穿越输入上的输入也加入集合。这个集合回答的问题是这个值如果提前转置最终能否与某个 Transpose 抵消或合并直接喂给成本模型。阶段二正向遍历推进 Transpose。按拓扑序对每个节点L3495-L3523若其可修改CanModifyNode且某个输入由 Transpose 节点产生、perm 合法就调用ProcessTranspose尝试穿过该节点。成功后立刻break跳出输入循环因为后续输入可能已改变、节点甚至可能已被移除。阶段三量化模型下的常量折叠。若图中存在 DequantizeLinearL3524-L3570对 Transpose/Squeeze 节点调用TryConstantFoldNodeL3328-L3419当其输入是本地常量时把 Transpose/Squeeze 折叠进初始化器通过TransposeInitializer/ReshapeInitializer原地改写并更新所有消费者的输入引用。源码注释给出了触发场景——当 Transpose 被推进穿过某个使用共享初始化器的节点时初始化器会被原地转置而其他消费者就必须插一个撤销用的 Transposeconst → Squeeze → DQ、const → Transpose → Squeeze → DQ这类链条正是靠此折叠消除。阶段四QDQ 节点单元修复。FixQDQNodeUnitsL3287-L3319处理量化模型在推进过程中可能残留的三种破损形态TryRemoveEmptyDQQL3036-L3119消除抵消后残留的空DQ → Q对覆盖中间有消费者与DQ→Q 直接接图输出两个场景TryFixTransposeMissingQL3133-L3164DQ → Transpose → (Op/图输出)缺少输出侧 Q 时在 Transpose 后补插Q[new] → DQ[new]恢复 QDQ node unitTryFixTransposeMissingDQL3174-L3280prev → Transpose → Q缺少输入侧 DQ 时在 Transpose 前插入Q[new] → DQ[new]且 per-axis 量化时按axis perm[axis]重算新 Q/DQ 的 axis。三种运行场景CanModifyNode 与 provider_typeOptimizeImpl的注释L3458-L3492明确列出了该优化器在 ORT 中的三种调用场景CanModifyNodeL3012-L3025则是其统一判据——只有未分配 EP或分配给provider_type所指 EP的节点才允许修改Level 1 优化器图分区前运行此时没有节点被分配provider_type为空可修改任意现有节点并可新增节点布局转换后再次运行时也按只改未分配节点处理如做常量折叠布局转换Layout Transformationprovider_type被设为正在改布局的目标 EP。未分配节点可改分配给其他 EP 的节点绝不能改修改可能使其与该 EP 的内核不兼容目标 EP 的节点可改新建节点暂不指派——图分区随后会对目标 EP 重新调用GetCapability让新节点尽可能被吸收CPU EP 作为分区最后一步兜底接收剩余节点Level 3 NHWC TransformerCPU 专属分区后运行所有节点都已分配provider_type为 CPU EP新节点通过实现层的new_node_ep_机制直接指派给 CPU EP。布局敏感算子清单GetLayoutSensitiveOps()L3581-L3600返回 ONNX 标准中定义布局敏感性的算子集合这是 ORT 判断哪些算子值得包 Transpose 做布局转换的依据static std::unordered_setstd::string_view layout_sensitive_ops { // normalization BatchNormalization, InstanceNormalization, // convolutions Conv, ConvInteger, QLinearConv, ConvTranspose, // pooling AveragePool, LpPool, MaxPool, MaxUnpool, GlobalAveragePool, GlobalLpPool, GlobalMaxPool, // other LRN, GridSample, DepthToSpace, SpaceToDepth};这些算子在 ORT 中往往存在布局相关的多种实现如 NHWC 版内核是布局转换与 Transpose 推进优化的主要受益对象。ORT 实现层从 ApiGraph 到 OrtExtendedHandlers抽象层的 ORT 实现ort_optimizer_api_impl.cc 与 ort_optimizer_utils.h 提供了api::GraphRef/NodeRef/ValueInfoRef/TensorRef在onnxruntime::Graph上的实现ORT 内部以ApiGraph形式桥接使通用层完全不感知 ORT 的类型系统。ORT 专属扩展ort_transpose_optimization.h 声明了 ORT 侧的三个公共组件OrtExtendedHandlers()返回 ORT 专属的HandlerMap。ort_transpose_optimization.cc 中注册了多个扩展 Handler包括ep_aware_resize_handler/ep_aware_reshape_handler带 EP 感知逻辑的 Resize/Reshape 处理呼应通用层Resize 默认不推进的保守策略——由 EP 自己确认能处理才推进q_linear_concat_handler、q_linear_binary_op_handler、q_linear_pool_op_handlerQLinear 族算子的输入筛选与处理max_pool_op_handler注释说明当支持时 NHWC 版本的 MaxPool 性能更好HandleMaxPool 会做支持性检查fast_gelu_handlercom.microsoft.QuickGelu等 GELU 变体的 Handler——源码注释指出 QuickGeluFusion 会发射com.microsoft.QuickGelu也是 SiLU 进入 NHWC 区域的路径之一没有这些 Handler布局传播会在激活函数处中断without these handlers, layout propagation stops at the activation文件开头的注释也点明扩展 Handler 的目标之一allow NCHW - NHWC for now目前只允许 NCHW 与 NHWC 互转其他形式的 Transpose 在真实模型中用途不明。OrtEPCostCheckCostCheckFn的 ORT 实现注释描述其职责是考虑 ORT 执行提供方内核实现细节——例如对布局敏感算子比较 NCHW/NHWC 两种实现的相对开销再决定是否返回kPushTranspose。这就是通用层cost_check_fn参数存在的意义把某 EP 上哪种布局更快这类只有 EP 自己知道的知识注入成本模型。SwapNodeOpTypeAndDomain/SwapNodeOpTypeDomainAndSinceVersion因为抽象 API 不允许直接修改节点的 op_type/domain这两个工具函数通过CopyNode复制出带新 op_type 的节点并移除旧节点属性、输入、输出全部迁移。典型用途是当 EP 存在 NHWC 版本的布局敏感算子时把节点换掉以落到 NHWC 实现上。三个实际调用点该模块在 ORT 优化管线中的三处集成与上文三种运行场景一一对应Level 1 图优化transpose_optimizer.cc 的TransposeOptimizer::ApplyImplL19 起在Optimize()时传入空provider_type与默认成本检查无扩展表作为通用图变换运行EP 布局转换layout_transformation.cc 的TransformLayoutForEPL96 起在 L193-L194 以目标 EP 的Type()为provider_type、PostLayoutTransformCostCheck内部即OrtEPCostCheck见 L30为成本函数、OrtExtendedHandlers()为扩展表调用OptimizeCPU NHWC 转换nhwc_transformer.cc 在 L558 以kCpuExecutionProvider为 provider、同样传入OrtEPCostCheck与OrtExtendedHandlers()。对应的回归测试位于 transpose_optimizer_test.ccgraph_transform_test.cc 中也有针对布局变换路径的用例可作为验证各 Handler 行为包括 Reshape 等价 Transpose、QDQ 修复、常量折叠等的参考。如何为自定义算子扩展该优化器结合 目录文档 的分层描述给该模块添加能力的标准路径是模型表示侧若你要复用到非 ORT 的图实现只需实现 optimizer_api.h 中GraphRef/NodeRef/ValueInfoRef/TensorRef的接口直接调用Optimize(graph)即可opset 需落在 7–27 区间算子侧编写TransposibleInputsFnHandlerFunction组装为HandlerInfo放入HandlerMapkey 为 op_type非 ONNX 域算子为domain.op_type经Optimize的extended_handlers参数注入——扩展表优先级高于内置表性能侧若算子在特定布局下有高性能实现提供CostCheckFn以 EP 视角返回kStop/kPushTranspose避免通用成本模型误判注意事项Handler 内部必须依赖ValueConsumers::comprehensive判断消费者是否完整跨子图或图输出的值不能做删除类改写对含符号 shape 的值优先使用PermuteDims/UnsqueezeDims而非SetShape量化模型还需维护 QDQ 节点单元参考TransposeQuantizeDequantizeAxis与 ORT 侧的 QDQ 修复流程。小结ONNX Runtime 的 Transpose 优化器把消除/合并/推进 Transpose这一图改写问题拆成了清晰的三层optimizer_api.h定义与实现无关的图操作接口并保证图完整性唯一的输出变更通道是MoveOutputonnx_transpose_optimization.cc提供基于 Handler 表 严格成本模型的通用推进算法并处理 Reshape 等价性、常量折叠与量化模型的 QDQ 修复ort_transpose_optimization则注入 EP 知识——通过OrtEPCostCheck反映各 EP 内核的布局性能差异、通过OrtExtendedHandlers覆盖 contrib 算子与 NHWC 实现、通过SwapNodeOpTypeAndDomain切换算子到 NHWC 版本。它同时服务于分区前的 Level 1 优化、EP 布局转换和 CPU Level 3 NHWC 转换三个场景并以provider_typeCanModifyNode机制保证改写永远不会跨 EP 破坏节点兼容性。【免费下载链接】onnxruntimeONNX Runtime: cross-platform, high performance ML inferencing and training accelerator项目地址: https://gitcode.com/GitHub_Trending/on/onnxruntime创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表