Guidance for contributors working in this repo. Keep changes surgical, tested, and consistent with the patterns below.
An ONNX → Core ML converter producing .mlpackage (ML Program / MIL) and .mlmodel
(NeuralNetwork), built on top of coremltools' MIL builder.
ONNX → _io.load → _passes.run → _fusion.run → coverage-gate → Converter.to_mil
→ _backend.program_to_mlmodel → MLModel (.mlpackage / .mlmodel) → _verify
_mil.pyand_backend.pyare the only modules that import coremltools. Keep it that way — this is how the project stays maintainable as Core ML evolves.converter.pyorchestrates: coverage gate, then a topological walk emitting MIL ops._target.pymaps deployment-target/format/precision strings to coremltools enums. The MIL function is authored at iOS17 for.mlpackageand iOS15 for.mlmodel(the NeuralNetwork backend rejects newer opsets).
- Pick the right family module under
src/onnx2coreml/_lowering/(e.g._conv.py). - Write
def lower(ctx, node) -> Var | list[Var]:— read inputs viaoperands(ctx.values_map, node, [...]), emitmb.*ops, and name the final op for each output withname=node.output[i](so Core ML's predicted output keys match ONNX names). Never passname=Noneto an intermediate op — omit the kwarg. - Add the op key to that module's
REGISTRYdict. Keys are unique across modules (a duplicate raises at import). - Find the exact MIL op + parameter names in
../coremltools/coremltools/converters/mil/mil/ops/defs/— do not guess. - Add a parity test in
tests/test_ops_<family>.pyparametrized overfmt in ["mlpackage", "mlmodel"].
Helpers in _lowering/_common.py: binary(mb_op), unary(mb_op),
const_array(ctx, node, idx), get_attr, operands.
.venv/bin/python -m pytest tests/ -q # full suite
.venv/bin/python -m pytest tests/test_ops_conv.py -q- The harness (
tests/helpers.py) builds a single-op ONNX model, runs ONNX Runtime as the reference, converts, and compares againstMLModel.predict. - Parity is pinned to fp32 + CPU_ONLY so a mismatch means a real bug, not fp16 rounding.
assert_parityserializes predictions with a file lock (the Core ML runtime races on concurrent loads).- Mark genuine, verified platform limits as
xfailwith the reason inline — never to hide a lowering bug.
- Python 3.11–3.13,
uvfor env/lock,setuptoolsbuild backend. - Every file: BSD header,
from __future__ import annotations, a module docstring, type hints. Comments only where the why is non-obvious. - Gates (must pass before done):
.venv/bin/ruff check src tests,.venv/bin/mypy src, fullpytest. - Errors derive from
Onnx2CoreMLError; the coverage gate runs before any MIL is emitted. - Core ML compute is 32-bit: int64→int32 and float64→float32 narrowing lives in
_types.py.