From 22e5aaef1e9715eb2cddf87c669afb17c0bfc520 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 28 Jul 2026 06:33:52 +0000 Subject: [PATCH] Add eliminate_initializer_from_input pass IR<4 exports (e.g. the onnx-caffe2 models in the ONNX Model Zoo) list every initializer in graph.input as well. ONNX treats an initializer that is also a graph input as an overridable default rather than a constant, so Graph::is_constant_initializer -- and hence IsConstantTensor -- reports the weights as non-constant. That silently disables every constant-dependent pass (fuse_bn_into_conv, fuse_add_bias_into_conv, fuse_matmul_add_bias_into_gemm, constant folding, ...), so these models leave the optimizer completely unchanged. Add a pass that removes an initializer's redundant graph.input entry (keeping the initializer), i.e. normalises the legacy graph to the IR>=4 form, and register it so it runs before the constant-dependent fusions. Verified on onnxmodelzoo/resnet101-v1-7 (IR version 3, 588 initializers all listed as inputs): with the inputs stripped, fuse_bn_into_conv folds all 104 BatchNormalization nodes it previously left untouched, matching onnxslim's output. Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01CzPBPhoPYRKPVcjZcVFy5h Signed-off-by: take-cheeze --- onnxoptimizer/pass_registry.h | 2 + .../passes/eliminate_initializer_from_input.h | 83 +++++++++++++++++++ 2 files changed, 85 insertions(+) create mode 100644 onnxoptimizer/passes/eliminate_initializer_from_input.h diff --git a/onnxoptimizer/pass_registry.h b/onnxoptimizer/pass_registry.h index 3fdfa4978..9ec251bf6 100644 --- a/onnxoptimizer/pass_registry.h +++ b/onnxoptimizer/pass_registry.h @@ -22,6 +22,7 @@ #include "onnxoptimizer/passes/eliminate_deadend.h" #include "onnxoptimizer/passes/eliminate_duplicate_initializer.h" #include "onnxoptimizer/passes/eliminate_identity.h" +#include "onnxoptimizer/passes/eliminate_initializer_from_input.h" #include "onnxoptimizer/passes/eliminate_if_with_const_cond.h" #include "onnxoptimizer/passes/eliminate_nop_cast.h" #include "onnxoptimizer/passes/eliminate_nop_concat.h" @@ -79,6 +80,7 @@ struct GlobalPassRegistry { registerPass(); registerPass(); registerPass(); + registerPass(); registerPass(); registerPass(); registerPass(); diff --git a/onnxoptimizer/passes/eliminate_initializer_from_input.h b/onnxoptimizer/passes/eliminate_initializer_from_input.h new file mode 100644 index 000000000..723fa5291 --- /dev/null +++ b/onnxoptimizer/passes/eliminate_initializer_from_input.h @@ -0,0 +1,83 @@ +// Copyright (c) ONNX Project Contributors +// +// SPDX-License-Identifier: Apache-2.0 + +// ATTENTION: The code in this file is highly EXPERIMENTAL. +// Adventurous users should note that the APIs will probably change. + +#pragma once + +// Before: +// graph.input = [X, W, B, ...] (W, B, ... also appear in graph.initializer) +// graph.initializer = [W, B, ...] +// After: +// graph.input = [X] +// graph.initializer = [W, B, ...] +// +// Models exported under IR version < 4 (e.g. the onnx-caffe2 exports in the +// ONNX Model Zoo) list *every* initializer in graph.input as well. Under ONNX +// semantics an initializer that is also a graph input is an overridable default, +// not a constant, so `Graph::is_constant_initializer` returns false for it and +// `IsConstantTensor` treats the weight as a runtime value. That silently +// disables every constant-dependent pass -- fuse_bn_into_conv, +// fuse_add_bias_into_conv, fuse_matmul_add_bias_into_gemm, constant folding, +// ... -- so such models come out of the optimizer completely unchanged. +// +// This pass removes an initializer's entry from graph.input (keeping the +// initializer itself), which is the standard IR>=4 form and lets the downstream +// passes see the weights as the constants they are. It should run early, before +// the constant-dependent fusions. +// +// this pass can handle the case satisfy all following conditions: +// condition 1: the value is a graph input +// condition 2: the value also has a graph initializer of the same name + +#include + +#include "onnxoptimizer/pass.h" + +namespace ONNX_NAMESPACE { +namespace optimization { + +struct EliminateInitializerFromInput final : public FullGraphBasedPass { + explicit EliminateInitializerFromInput() + : FullGraphBasedPass(PassType::Nop, PassEfficiency::Complete, + PassOptimizationType::Compute) {} + + std::string getPassName() const override { + return "eliminate_initializer_from_input"; + } + + PassAnalysisType getPassAnalysisType() const override { + return PassAnalysisType::Empty; + } + + void eliminate_initializer_from_input(Graph& graph) { + const std::unordered_set initializer_names( + graph.initializer_names().begin(), graph.initializer_names().end()); + + // Walk inputs back to front so erasing an entry keeps the remaining + // indices valid. + for (int i = static_cast(graph.inputs().size()) - 1; i >= 0; --i) { + if (initializer_names.count(graph.inputs()[i]->uniqueName()) > 0) { + graph.eraseInput(i); + } + } + + // Subgraphs (If/Loop/Scan bodies) can carry the same legacy encoding. + for (auto it = graph.begin(); it != graph.end(); ++it) { + DescendOnGraphAttributesUnconstrained( + *it, [this](Graph& subgraph) { + eliminate_initializer_from_input(subgraph); + }); + } + } + + std::shared_ptr runPass(Graph& graph) override { + eliminate_initializer_from_input(graph); + return std::shared_ptr(new PostPassAnalysis()); + } +}; + +} // namespace optimization +} // namespace ONNX_NAMESPACE