Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
332fed4
Fix fuse_pad_into_pool folding zero-padding into MaxPool
claude Jul 24, 2026
fd811b8
Add tests for ONNX function preservation in optimizer
claude Jul 24, 2026
55c4f59
Support fuse bn into ConvTranspose
Jul 25, 2026
725a177
Add option to treat initializers as non-constants
claude Jul 28, 2026
c9a482c
Add fuse_consecutive_mul and batched MatMul+Add->Gemm passes
claude Jul 29, 2026
e666a0a
Add fuse_mul_into_conv pass
claude Jul 30, 2026
70bcd43
Fuse Add-bias into ConvTranspose and drop no-op opset-12 Dropout
claude Jul 31, 2026
03c30fe
fuse_consecutive_unsqueezes: fuse non-negative axes without a static …
claude Jul 31, 2026
5e8d5af
fuse_matmul_add_bias_into_gemm_batched: match MatMul as either Add op…
claude Jul 31, 2026
651227b
Bump ONNX submodule to latest main (1.23.0-dev)
claude Jul 31, 2026
ea37c7d
Add eliminate_reshape_around_elementwise pass
claude Aug 2, 2026
2a3dce0
Merge commit '651227b' into claude/model-regression-node-reduction-8l…
claude Aug 2, 2026
972c91e
Remove onnxsim-specific and onnxsim-patched passes (now in onnxsim)
claude Aug 3, 2026
9fdbf28
Point third_party/onnx submodule at onnxsim's onnx fork
claude Aug 15, 2026
296f5bc
Update third_party/onnx submodule pin (attributeNames() allocation fix)
claude Aug 15, 2026
d8e30a2
Avoid attributeNames() allocation in DescendOnGraphAttributes*
claude Aug 15, 2026
0feea66
Revert DescendOnGraphAttributes* to attributeNames()+kindOf()
claude Aug 15, 2026
2994907
Update third_party/onnx submodule pin (rebased onto onnx/onnx main)
claude Aug 15, 2026
9e5ca02
Update third_party/onnx submodule pin (isNameUnique subgraph tracking)
claude Aug 16, 2026
9dc4ed7
Add optional graph-modification report to optimize()
claude Aug 17, 2026
10c9d34
Make eliminate_unused_initializer report a transform count
claude Aug 17, 2026
11f0d18
Add consuming (moving) Optimizer::optimize() overload
claude Aug 18, 2026
641a99a
Add Graph-native optimize entry points (port of onnx/optimizer#319)
claude Aug 18, 2026
229d9bc
Avoid ParseTensorData's double copy in CSE tensor hash/equality for r…
claude Aug 18, 2026
9206bd4
Trust BLAKE3 tensor content hash in CSE dedup, with a disable option
claude Aug 18, 2026
9fb4783
Fix CSE dedup regression: canonicalize signed zero before hashing
claude Aug 18, 2026
a4615fd
Stop re-hashing tensors: cache typed-field digests, skip BLAKE3 for r…
claude Aug 19, 2026
cd9f9f7
Update Tensor* users for Graph::initializers_'s new unique_ptr storage
claude Aug 19, 2026
d4664a9
Extend TensorContentDigest's cache across rounds, keyed by tensor_id()
claude Aug 19, 2026
f86c2a0
Cache CSETensorHash's raw_data hash by tensor_id(), the actual bottle…
claude Aug 19, 2026
cb0f543
Instrument eliminate_common_subexpression and eliminate_deadend inter…
claude Aug 19, 2026
e0d8538
eliminate_deadend/eliminate_common_subexpression: avoid uses()'s O(N)…
claude Aug 20, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion .gitmodules
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
[submodule "third_party/onnx"]
path = third_party/onnx
url = https://github.com/onnx/onnx.git
url = https://github.com/onnxsim/onnx.git
[submodule "third_party/blake3"]
path = third_party/blake3
url = https://github.com/BLAKE3-team/BLAKE3.git
22 changes: 22 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,30 @@ file(GLOB onnx_opt_srcs CONFIGURE_DEPENDS "onnxoptimizer/*.cc"
)
list(REMOVE_ITEM onnx_opt_srcs "${PROJECT_SOURCE_DIR}/onnxoptimizer/cpp2py_export.cc")

# BLAKE3 (third_party/blake3, a git submodule pinned to a specific upstream
# commit -- see .gitmodules / `git submodule status`, and LICENSE_A2 /
# LICENSE_CC0 in that directory) backing tensor_content_hash.cc's
# TensorContentDigest, used by cse_util.h's CSETensorHash/CSETensorCompare.
# Only the portable C sources (under the submodule's own c/ subdirectory --
# upstream's layout) are compiled, with every SIMD backend disabled: that
# makes blake3_dispatch.c's runtime CPU-feature dispatch fall through to
# blake3_portable.c unconditionally, which is what makes omitting the SIMD
# source files (and their per-architecture assembly) correct rather than
# merely omitted. Mirrors onnxsim's own onnxsim/tensor_pool_hash.cpp
# vendoring of the same library.
set(ONNXOPT_BLAKE3_SOURCES
third_party/blake3/c/blake3.c
third_party/blake3/c/blake3_dispatch.c
third_party/blake3/c/blake3_portable.c)
set(ONNXOPT_BLAKE3_COMPILE_DEFS
BLAKE3_NO_SSE2 BLAKE3_NO_SSE41 BLAKE3_NO_AVX2 BLAKE3_NO_AVX512
BLAKE3_USE_NEON=0)
list(APPEND onnx_opt_srcs ${ONNXOPT_BLAKE3_SOURCES})

onnxopt_add_library(onnx_optimizer ${onnx_opt_srcs})
target_link_libraries(onnx_optimizer PUBLIC ${ONNX_TARGET_NAME})
target_compile_definitions(onnx_optimizer PRIVATE ${ONNXOPT_BLAKE3_COMPILE_DEFS})
target_include_directories(onnx_optimizer PRIVATE ${PROJECT_SOURCE_DIR}/third_party)
target_include_directories(onnx_optimizer PUBLIC
$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}>
$<INSTALL_INTERFACE:include>
Expand Down
26 changes: 25 additions & 1 deletion onnxoptimizer/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,13 +33,23 @@


def optimize(
model: onnx.ModelProto, passes: list[str] | None = None, fixed_point: bool = False
model: onnx.ModelProto,
passes: list[str] | None = None,
fixed_point: bool = False,
initializers_as_constants: bool = True,
) -> onnx.ModelProto:
"""Apply the optimization on the serialized ModelProto.

Arguments:
model: ONNX model.
passes: Optimization names.
fixed_point: Whether to run the passes to a fixed point.
initializers_as_constants: Whether the passes may treat graph
initializers as constant tensors (the default, ``True``). When set
to ``False`` initializers are treated as non-constant, so
value-baking passes such as ``fuse_bn_into_conv`` leave
initializer-backed weights untouched; ``Constant`` nodes are still
treated as constants.

Return:
Optimized model.
Expand All @@ -49,6 +59,20 @@ def optimize(
passes = get_fuse_and_elimination_passes()
if not isinstance(model, onnx.ModelProto):
raise TypeError(f"Optimizer only accepts ModelProto, incorrect type: {type(model)}")
# The C++ core reads this switch from thread-local state deep inside the
# passes, so set it around the call and restore it afterwards to avoid
# leaking the setting to unrelated callers on the same thread.
previous = _c.initializers_as_constants()
_c.set_initializers_as_constants(initializers_as_constants)
try:
return _optimize_impl(model, passes, fixed_point)
finally:
_c.set_initializers_as_constants(previous)


def _optimize_impl(
model: onnx.ModelProto, passes: list[str], fixed_point: bool
) -> onnx.ModelProto:
try:
model_str = model.SerializeToString()
if fixed_point:
Expand Down
23 changes: 19 additions & 4 deletions onnxoptimizer/cpp2py_export.cc
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,13 @@ NB_MODULE(onnx_opt_cpp2py_export, onnx_opt_cpp2py_export) {
[](const nb::bytes& bytes, const std::vector<std::string>& names) {
ModelProto proto{};
ParseProtoFromPyBytes(&proto, bytes);
auto const result = optimization::Optimize(proto, names);
// Explicitly const: pins overload resolution to the copying
// Optimize(const ModelProto&, ...) even though `proto` happens to be
// unused afterward here, so this call site's behavior can't change
// silently if a consuming overload is ever added upstream of it (see
// Optimizer::optimize(ModelProto&, ...) in optimize.h).
auto const result = optimization::Optimize(
static_cast<const ModelProto&>(proto), names);
std::string out;
result.SerializeToString(&out);
return nb::bytes(out.data(), out.size());
Expand All @@ -42,7 +48,8 @@ NB_MODULE(onnx_opt_cpp2py_export, onnx_opt_cpp2py_export) {
[](const nb::bytes& bytes, const std::vector<std::string>& names) {
ModelProto proto{};
ParseProtoFromPyBytes(&proto, bytes);
auto const result = optimization::OptimizeFixed(proto, names);
auto const result = optimization::OptimizeFixed(
static_cast<const ModelProto&>(proto), names);
std::string out;
result.SerializeToString(&out);
return nb::bytes(out.data(), out.size());
Expand All @@ -55,7 +62,8 @@ NB_MODULE(onnx_opt_cpp2py_export, onnx_opt_cpp2py_export) {
const std::string& export_data_file_name) {
ModelProto proto{};
optimization::loadModel(&proto, import_model_path, true);
auto result = optimization::Optimize(proto, names);
auto result = optimization::Optimize(
static_cast<const ModelProto&>(proto), names);
optimization::saveModel(&result, export_model_path, true,
export_data_file_name);
});
Expand All @@ -68,13 +76,20 @@ NB_MODULE(onnx_opt_cpp2py_export, onnx_opt_cpp2py_export) {
const std::string& export_data_file_name) {
ModelProto proto{};
optimization::loadModel(&proto, import_model_path, true);
auto result = optimization::OptimizeFixed(proto, names);
auto result = optimization::OptimizeFixed(
static_cast<const ModelProto&>(proto), names);
optimization::saveModel(&result, export_model_path, true,
export_data_file_name);
});
onnx_opt_cpp2py_export.def("get_available_passes",
&optimization::GetAvailablePasses);
onnx_opt_cpp2py_export.def("get_fuse_and_elimination_passes",
&optimization::GetFuseAndEliminationPass);
// Toggle whether the passes treat graph initializers as constant tensors
// (default true). See SetInitializersAsConstants in optimize.h.
onnx_opt_cpp2py_export.def("set_initializers_as_constants",
&optimization::SetInitializersAsConstants);
onnx_opt_cpp2py_export.def("initializers_as_constants",
&optimization::InitializersAsConstants);
}
} // namespace ONNX_NAMESPACE
42 changes: 38 additions & 4 deletions onnxoptimizer/optimize.cc
Original file line number Diff line number Diff line change
Expand Up @@ -31,16 +31,50 @@ Optimizer::~Optimizer() {}

ModelProto Optimize(
const ModelProto& mp_in,
const std::vector<std::string>& names) {
const std::vector<std::string>& names,
std::map<std::string, unsigned int>* report) {
Optimizer current_opt(names, false);
return current_opt.optimize(mp_in);
return current_opt.optimize(mp_in, report);
}
ModelProto OptimizeFixed(
const ModelProto& mp_in,
const std::vector<std::string>& names) {
const std::vector<std::string>& names,
std::map<std::string, unsigned int>* report) {
Optimizer current_opt(names, true);
return current_opt.optimize(mp_in, report);
}
void OptimizeGraph(
Graph& graph,
const std::vector<std::string>& names,
std::map<std::string, unsigned int>* report,
bool clear_tensor_digest_cache) {
Optimizer current_opt(names, false);
current_opt.optimize(graph, report, clear_tensor_digest_cache);
}
void OptimizeGraphFixed(
Graph& graph,
const std::vector<std::string>& names,
std::map<std::string, unsigned int>* report,
bool clear_tensor_digest_cache) {
Optimizer current_opt(names, true);
current_opt.optimize(graph, report, clear_tensor_digest_cache);
}
#ifdef ONNX_IR_PB_CONVERTER_HAS_CONSUMING_OVERLOADS
ModelProto Optimize(
ModelProto& mp_in,
const std::vector<std::string>& names,
std::map<std::string, unsigned int>* report) {
Optimizer current_opt(names, false);
return current_opt.optimize(mp_in, report);
}
ModelProto OptimizeFixed(
ModelProto& mp_in,
const std::vector<std::string>& names,
std::map<std::string, unsigned int>* report) {
Optimizer current_opt(names, true);
return current_opt.optimize(mp_in);
return current_opt.optimize(mp_in, report);
}
#endif // ONNX_IR_PB_CONVERTER_HAS_CONSUMING_OVERLOADS
const std::vector<std::string> GetAvailablePasses() {
return Optimizer::passes.GetAvailablePasses();
}
Expand Down
153 changes: 144 additions & 9 deletions onnxoptimizer/optimize.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,10 @@
#include "onnx/common/ir.h"
#include "onnx/common/ir_pb_converter.h"
#include "onnx/proto_utils.h"

#include "onnxoptimizer/pass_manager.h"
#include "onnxoptimizer/pass_registry.h"

#include "onnxoptimizer/passes/cse_util.h"
#include "onnxoptimizer/passes/tensor_content_hash.h"
#include "vector"

namespace ONNX_NAMESPACE {
Expand All @@ -26,8 +26,51 @@ struct Optimizer {
Optimizer(const std::vector<std::string> &names, const bool fixed_point);
~Optimizer();

ModelProto optimize(const ModelProto &_mp_in) {
const ModelProto* mp_in = &_mp_in;
// Optimize the ONNX C++ IR (Graph) in place, running the configured passes
// directly on the graph. This avoids the ModelProto <-> Graph round-trip
// entirely and is intended for C++ callers that already hold a Graph (e.g.
// onnxsim's OptAndShape fixed point, which imports once and can keep
// re-running passes on the same Graph across rounds where shape inference
// made no change -- see onnxsim issue #633). Proto-level concerns such as
// the ir_version upgrade and function copying are the caller's
// responsibility, since those live on ModelProto rather than on Graph.
//
// If `report` is non-null it is filled with a map from pass name to the
// total number of positive transforms that pass applied to the graph,
// matching the ModelProto-based optimize() below.
//
// If `clear_tensor_digest_cache` is true (the default, and correct for
// essentially every caller), the two tensor-hash caches consulted by
// eliminate_duplicate_initializer and eliminate_common_subexpression --
// TensorContentDigest's (tensor_content_hash.h, the typed-field path) and
// CSETensorHash's raw_data-branch cache (cse_util.h's g_raw_hash_cache,
// the common path for real exported models) -- are cleared before
// running the passes, bounding their memory to the tensors this one
// optimize() call touches. Pass false only if the caller itself manages
// those caches' lifetime across several optimize() calls on the *same*
// resident Graph (e.g. onnxsim's OptAndShape fixed point, which calls
// this once per round but wants hashes computed in an earlier round to
// stay cached in a later one) -- see ClearTensorContentDigestCache's
// header comment for why that's safe to do explicitly (the same
// reasoning applies to ClearRawHashCache).
void optimize(Graph &graph,
std::map<std::string, unsigned int> *report = nullptr,
bool clear_tensor_digest_cache = true) {
if (clear_tensor_digest_cache) {
ClearTensorContentDigestCache();
ClearRawHashCache();
}
auto analysis = this->pass_manager->run(graph);
if (report != nullptr && analysis != nullptr) {
*report = analysis->transform_counts;
}
}

// If `report` is non-null it is filled with a map from pass name to the
// total number of positive transforms that pass applied to the graph.
ModelProto optimize(const ModelProto &_mp_in,
std::map<std::string, unsigned int> *report = nullptr) {
const ModelProto *mp_in = &_mp_in;
std::unique_ptr<ModelProto> copy_in;
if (mp_in->ir_version() == 3) {
// Upgrade ir_version to 4 so that initializer can be not in input
Expand All @@ -46,21 +89,68 @@ struct Optimizer {
}

ModelProto mp_out = PrepareOutput(*mp_in);
this->pass_manager->run(*g);
this->optimize(*g, report);
ExportModelProto(&mp_out, g);

// Maybe we can optimize these functions, now just copy
AddFunctionsToModel(*mp_in, mp_out);
return mp_out;
}

#ifdef ONNX_IR_PB_CONVERTER_HAS_CONSUMING_OVERLOADS
// Consuming overload: same as above, but moves each initializer's raw
// bytes out of `mp_in` on Import and out of the internal Graph on Export,
// instead of copying them at each end of the ModelProto<->Graph round
// trip. This roughly halves the memory traffic of one optimize() call for
// weight-heavy models (see onnxsim issue #633), at the cost of leaving
// `mp_in`'s initializer tensors with empty raw data afterward. Only call
// this when `mp_in` is about to be discarded or overwritten by the caller
// -- e.g. onnxsim's OptAndShape fixed point, which immediately
// move-assigns this call's return value back over its input model on
// every iteration.
//
// Only defined when compiled against an onnx fork that provides the
// matching consuming ImportModelProto/ExportModelProto overloads (see
// ONNX_IR_PB_CONVERTER_HAS_CONSUMING_OVERLOADS in ir_pb_converter.h) --
// e.g. absent when this library is linked against onnxruntime's own
// bundled, unpatched onnx copy instead.
ModelProto optimize(ModelProto &mp_in,
std::map<std::string, unsigned int> *report = nullptr) {
if (mp_in.ir_version() == 3) {
// Rare legacy path; not worth threading the moving Import/Export
// through, so fall back to the copying overload above.
const ModelProto &const_mp_in = mp_in;
return optimize(const_mp_in, report);
}
std::shared_ptr<Graph> g(ImportModelProto(mp_in));

if (g.get() == nullptr) {
std::cerr << "Warning: onnx optimizer is unable to parse input model. "
<< "(The IR version of the ONNX model may be too old.)"
<< std::endl;
// If we can't parse the file, just return the input. ImportModelProto
// fails before touching any tensor data (it only checks ir_version),
// so mp_in is still intact here.
return mp_in;
}

ModelProto mp_out = PrepareOutput(mp_in);
this->optimize(*g, report);
ExportModelProto(&mp_out, g, /*consume_tensor_data=*/true);

// Maybe we can optimize these functions, now just copy
AddFunctionsToModel(mp_in, mp_out);
return mp_out;
}
#endif // ONNX_IR_PB_CONVERTER_HAS_CONSUMING_OVERLOADS

private:
std::shared_ptr<PassManager> pass_manager;

void AddFunctionsToModel(const ModelProto &original_model,
ModelProto &output_model) {
for (const auto& function_proto : original_model.functions()) {
auto* p_f = output_model.add_functions();
for (const auto &function_proto : original_model.functions()) {
auto *p_f = output_model.add_functions();
p_f->CopyFrom(function_proto);
}
}
Expand Down Expand Up @@ -94,10 +184,55 @@ const std::vector<std::string> GetAvailablePasses();

const std::vector<std::string> GetFuseAndEliminationPass();

// Control whether the optimizer passes treat graph initializers as constant
// tensors. The default (true) is onnxoptimizer's historical behaviour, in which
// an initializer-backed value is a constant and value-baking passes
// (fuse_bn_into_conv, fuse_add_bias_into_conv, nop-reshape/expand on a constant
// shape, ...) may consume and fold it. When set to false, initializers are
// treated as non-constant, so those passes leave initializer-backed values --
// and the weights they represent -- untouched; Constant *nodes* are still
// treated as constants. The setting is thread-local and stays in effect until
// changed, so callers that flip it should restore it afterwards.
void SetInitializersAsConstants(bool value);
bool InitializersAsConstants();

ModelProto Optimize(const ModelProto &mp_in,
const std::vector<std::string> &names);
const std::vector<std::string> &names,
std::map<std::string, unsigned int> *report = nullptr);

ModelProto OptimizeFixed(const ModelProto &mp_in,
const std::vector<std::string> &names);
const std::vector<std::string> &names,
std::map<std::string, unsigned int> *report = nullptr);

// In-place counterparts that operate directly on the ONNX C++ IR (Graph),
// skipping the ModelProto <-> Graph conversion entirely. For C++ callers
// that already hold a Graph -- see Optimizer::optimize(Graph&, ...)'s doc
// comment. Unlike the consuming ModelProto overloads below, these do not
// depend on ONNX_IR_PB_CONVERTER_HAS_CONSUMING_OVERLOADS: they never touch
// ModelProto at all, so they work identically whether this library is
// linked against onnxsim's onnx fork or onnxruntime's bundled, unpatched
// onnx copy.
// `clear_tensor_digest_cache`: see Optimizer::optimize(Graph&, ...)'s doc
// comment above -- the default (true) is correct for essentially every
// caller.
void OptimizeGraph(Graph &graph, const std::vector<std::string> &names,
std::map<std::string, unsigned int> *report = nullptr,
bool clear_tensor_digest_cache = true);

void OptimizeGraphFixed(Graph &graph, const std::vector<std::string> &names,
std::map<std::string, unsigned int> *report = nullptr,
bool clear_tensor_digest_cache = true);

#ifdef ONNX_IR_PB_CONVERTER_HAS_CONSUMING_OVERLOADS
// Consuming overloads: see Optimizer::optimize(ModelProto&, ...)'s doc
// comment. Only call these when `mp_in` is about to be discarded or
// overwritten by the caller.
ModelProto Optimize(ModelProto &mp_in, const std::vector<std::string> &names,
std::map<std::string, unsigned int> *report = nullptr);

ModelProto OptimizeFixed(ModelProto &mp_in,
const std::vector<std::string> &names,
std::map<std::string, unsigned int> *report = nullptr);
#endif // ONNX_IR_PB_CONVERTER_HAS_CONSUMING_OVERLOADS
} // namespace optimization
} // namespace ONNX_NAMESPACE
Loading