alexvoss's picture
Publish ECSeg optimization study and experimental FP16 artifacts
9496f98 verified
Raw
History Blame Contribute Delete
4.7 kB
#!/usr/bin/env python3
"""Inspect an ONNX model and emit a machine-readable characterization.
Reports the facts a production-readiness review needs about a checkpoint before and
after optimization: opset, node/initializer counts, the weight-dtype histogram, whether
external data is used, the exact I/O tensor contract (names, dtypes, shapes), and the
operator histogram. Pure `onnx` (no onnxruntime) so it also runs on graphs a given ORT
build cannot execute.
Usage:
python inspect_onnx.py MODEL.onnx [--json OUT.json]
"""
from __future__ import annotations
import argparse
import collections
import hashlib
import json
import os
import sys
import onnx
from onnx import TensorProto
# TensorProto enum value -> human dtype name, for the weight/IO histograms.
DTYPE_NAME = {v: k for k, v in TensorProto.DataType.items()}
def _elem_type_name(elem_type: int) -> str:
return DTYPE_NAME.get(elem_type, f"UNKNOWN({elem_type})")
def _shape_of(value_info) -> list:
dims = []
for d in value_info.type.tensor_type.shape.dim:
if d.HasField("dim_value"):
dims.append(d.dim_value)
elif d.HasField("dim_param") and d.dim_param:
dims.append(d.dim_param)
else:
dims.append("dynamic")
return dims
def _io_spec(value_info) -> dict:
return {
"name": value_info.name,
"dtype": _elem_type_name(value_info.type.tensor_type.elem_type),
"shape": _shape_of(value_info),
}
def inspect(path: str) -> dict:
size_bytes = os.path.getsize(path)
with open(path, "rb") as fh:
raw = fh.read()
sha256 = hashlib.sha256(raw).hexdigest()
model = onnx.load(path, load_external_data=False)
graph = model.graph
# Opset per domain.
opset = {}
for entry in model.opset_import:
opset[entry.domain or "ai.onnx"] = entry.version
# Operator histogram.
op_hist = collections.Counter(node.op_type for node in graph.node)
# Initializer dtype histogram + external-data detection.
init_dtype_hist = collections.Counter()
init_bytes = collections.Counter()
uses_external_data = False
for init in graph.initializer:
dtype = _elem_type_name(init.data_type)
init_dtype_hist[dtype] += 1
# A tensor stored externally has data_location == EXTERNAL(1).
if init.data_location == TensorProto.EXTERNAL:
uses_external_data = True
# Rough in-graph byte estimate (raw_data if present).
init_bytes[dtype] += len(init.raw_data)
# Sidecar external-data files sitting next to the model.
external_files = []
model_dir = os.path.dirname(os.path.abspath(path))
for entry in os.listdir(model_dir) if os.path.isdir(model_dir) else []:
if entry.endswith(".onnx_data") or entry.endswith(".data") or entry.endswith(".bin"):
external_files.append(entry)
# I/O contract, skipping initializers that also appear as graph inputs.
init_names = {init.name for init in graph.initializer}
inputs = [_io_spec(v) for v in graph.input if v.name not in init_names]
outputs = [_io_spec(v) for v in graph.output]
checker_ok = True
checker_error = None
try:
onnx.checker.check_model(model)
except Exception as exc: # noqa: BLE001 - report any checker failure verbatim
checker_ok = False
checker_error = str(exc)
return {
"path": os.path.abspath(path),
"size_bytes": size_bytes,
"size_mib": round(size_bytes / 2**20, 3),
"sha256": sha256,
"ir_version": model.ir_version,
"producer": f"{model.producer_name} {model.producer_version}".strip(),
"opset": opset,
"num_nodes": len(graph.node),
"num_initializers": len(graph.initializer),
"uses_external_data": uses_external_data,
"external_files_in_dir": external_files,
"initializer_dtype_histogram": dict(init_dtype_hist),
"initializer_bytes_by_dtype": dict(init_bytes),
"operator_histogram": dict(sorted(op_hist.items(), key=lambda kv: -kv[1])),
"num_distinct_ops": len(op_hist),
"inputs": inputs,
"outputs": outputs,
"onnx_checker_ok": checker_ok,
"onnx_checker_error": checker_error,
}
def main() -> int:
ap = argparse.ArgumentParser(description=__doc__)
ap.add_argument("model")
ap.add_argument("--json", dest="json_out", default=None)
args = ap.parse_args()
info = inspect(args.model)
text = json.dumps(info, indent=2)
print(text)
if args.json_out:
with open(args.json_out, "w") as fh:
fh.write(text)
return 0
if __name__ == "__main__":
sys.exit(main())