microsoft / microsoft/onnxruntime
Basic optimization can produce invalid graph for Identity feeding folded Mul-one
Nobody has claimed this yet.
- Dominant language
- C++
- Stars
- 21.9k
- Forks
- 4.2k
- Avg merge
- 4d 11h
- Merged PRs (30d)
- 184
Description
Describe the issue
ORT Basic optimization can produce an invalid graph for Identity feeding folded Mul-one
Summary
ONNX Runtime 1.23.2 and the latest tested PyPI CPU wheel 1.29.0 accept and execute this model at ORT_DISABLE_ALL, but fail from ORT_ENABLE_BASIC onward after graph optimization. The failing optimized graph refers to the
output of an eliminated Identity node.
Environment
- onnxruntime: 1.23.2; reproduced again on latest tested PyPI CPU wheel 1.29.0
- onnx: 1.22.0
- provider: CPUExecutionProvider
- OS: Linux x86_64
Reproducer
Minimal graph:
x -> Identity -> mid
1.0 / 1.0 -> folded_one
mid * folded_one -> wrapped
wrapped + 0.0 -> y
Run:
python repro.py
Observed result:
ORT_DISABLE_ALL: ok
ORT_ENABLE_BASIC: Invalid model. Node input 'mid' is not a graph input, initializer, or output of a previous node.
ORT_ENABLE_EXTENDED: same error
ORT_ENABLE_ALL: same error
Basic ablation in the reproducer:
disabled_optimizers=["EliminateIdentity"]: ok
disabled_optimizers=["DivMulFusion"]: ok
disabled_optimizers=["Level1_RuleBasedTransformer"]: ok
disabled_optimizers=["EliminateDropout"]: same error
disabled_optimizers=["NoopElimination"]: same error
disabled_optimizers=["ConstantFolding"]: same error
Why this looks like an optimizer interaction
The graph is valid and runs with optimization disabled. The pattern is semantic identity for finite float inputs. In real-seed insertion campaigns, the same pattern failed on SqueezeNet, MobileNetV2, DeiT-tiny, ResNet50, and
ConvNeXt-tiny. Disabling either EliminateIdentity or DivMulFusion makes the failure disappear, while disabling ConstantFolding or NoopElimination does not.
This suggests an interaction between the producer-deletion rule and DivMulFusion: the Identity producer of mid is removed, but a downstream folded Mul-one fusion path still references the old mid value.
Additional probe evidence:
- A producer/consumer matrix also reproduces the same failure for the commuted consumer form
Mul(folded_one, mid). - The commuted pattern was inserted into a real SqueezeNet seed and failed the same way.
- These variants still collapse to the same root:
EliminateIdentityremoves the producer value, andDivMulFusionleaves a stale reference.
To reproduce
Full minimal reproducer
Save as repro.py and run with python repro.py:
#!/usr/bin/env python3
from __future__ import annotations
import argparse
import json
from pathlib import Path
import numpy as np
import onnx
import onnxruntime as ort
from onnx import TensorProto, helper, numpy_helper
def build_model(path: Path) -> None:
x = helper.make_tensor_value_info("x", TensorProto.FLOAT, [1, 3, 5, 5])
y = helper.make_tensor_value_info("y", TensorProto.FLOAT, [1, 3, 5, 5])
zero = numpy_helper.from_array(np.asarray(0.0, dtype=np.float32), name="zero")
one_a = numpy_helper.from_array(np.asarray(1.0, dtype=np.float32), name="one_a")
one_b = numpy_helper.from_array(np.asarray(1.0, dtype=np.float32), name="one_b")
nodes = [
helper.make_node("Identity", ["x"], ["mid"], name="producer_identity"),
helper.make_node("Div", ["one_a", "one_b"], ["folded_one"], name="producer_constant_div"),
helper.make_node("Mul", ["mid", "folded_one"], ["wrapped"], name="consumer_mul_one"),
helper.make_node("Add", ["wrapped", "zero"], ["y"], name="tail_add_zero"),
]
graph = helper.make_graph(nodes, "identity_folded_mul_basic_toposort", [x], [y], initializer=[zero, one_a, one_b])
model = helper.make_model(graph, opset_imports=[helper.make_opsetid("", 13)])
model.ir_version = 8
onnx.checker.check_model(model)
onnx.save_model(model, str(path))
def run(model_path: Path, level: str, disabled: list[str] | None = None) -> dict:
levels = {
"disable": ort.GraphOptimizationLevel.ORT_DISABLE_ALL,
"basic": ort.GraphOptimizationLevel.ORT_ENABLE_BASIC,
"extended": ort.GraphOptimizationLevel.ORT_ENABLE_EXTENDED,
"all": ort.GraphOptimizationLevel.ORT_ENABLE_ALL,
}
so = ort.SessionOptions()
so.graph_optimization_level = levels[level]
feeds = {"x": np.ones((1, 3, 5, 5), dtype=np.float32)}
kwargs = {"sess_options": so, "providers": ["CPUExecutionProvider"]}
if disabled:
kwargs["disabled_optimizers"] = disabled
try:
sess = ort.InferenceSession(str(model_path), **kwargs)
out = sess.run(None, feeds)[0]
return {"level": level, "disabled": disabled or [], "status": "ok", "sum": float(out.sum())}
except Exception as exc:
return {"level": level, "disabled": disabled or [], "status": "error", "error": f"{type(exc).__name__}: {exc}"}
def main() -> None:
parser = argparse.ArgumentParser()
parser.add_argument("--model", default="model.onnx")
parser.add_argument("--out", default="result.json")
args = parser.parse_args()
model_path = Path(args.model)
build_model(model_path)
payload = {
"onnxruntime": ort.__version__,
"providers": ort.get_available_providers(),
"runs": [run(model_path, level) for level in ("disable", "basic", "extended", "all")],
"ablation": [
run(model_path, "basic", [name])
for name in (
"EliminateIdentity",
"DivMulFusion",
"Level1_RuleBasedTransformer",
"EliminateDropout",
"NoopElimination",
"ConstantFolding",
)
],
}
Path(args.out).write_text(json.dumps(payload, indent=2) + "\n", encoding="utf-8")
print(json.dumps(payload, indent=2))
if __name__ == "__main__":
main()
Urgency
No response
Platform
Linux
OS Version
x86_64
ONNX Runtime Installation
Built from Source
ONNX Runtime Version or Commit ID
1.23.2
ONNX Runtime API
Python
Architecture
X86
Execution Provider
Default CPU
Execution Provider Library Version
No response
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Run the supplied repro.py with the listed optimization levels and disabled optimizers to confirm the invalid graph. Then trace the EliminateIdentity and DivMulFusion optimizer paths and their handling of the mid value. Done means the optimized graph remains valid for the reproducer, including the commuted Mul form, with regression coverage for the failure.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- cpp, python
- Domain
- machine-learning
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 55/100