CoreMLModel
CoreMLModel(path: str, input_names: list[str], output_names: list[str], compute_units: ComputeUnits = ComputeUnits.ALL, precision: str = 'FLOAT32', output_shapes: dict[str, tuple[int, ...]] | None = None, image_input: ImageInput | None = None, classifier: Classifier | None = None, function_name: str = '', deployment_target: object = None, noise: list[tuple[str, tuple[int, ...], str, Tensor | None]] | None = None, traced_outputs: dict[str, Tensor] | None = None)A Core ML package written by Lucid, loaded and ready to run.
Holds the .mlpackage on disk plus the compiled model Core ML
produced from it. Compilation happens once, when the handle is
created, because it is the expensive step — hundreds of milliseconds
for a real network — and every prediction afterwards reuses it.
Attributes
pathstr.mlpackage this handle opened.input_names, output_nameslist of strpredict accepts a tuple in this order or a mapping.noise_inputslist of tuple(name, shape) for each input that stands in for a random
draw, when the export lifted one — see
lucid.coreml.Draws. Empty otherwise. A caller who
passes nothing for these gets a fresh sample.deployment_targetDeploymentTargetcompute_unitsComputeUnitsCPU_AND_NE
whatever the request, because Core ML's GPU path unpacks small
palettes incorrectly.precisionstr"FLOAT32" or "FLOAT16", as the package was written.palettizedboolimage_inputImageInput or NoneclassifierClassifier or Noneclassify rather than predict.Examples
>>> import shutil, tempfile
>>> import lucid, lucid.nn as nn, lucid.coreml as cml
>>> model = nn.Sequential(
... nn.Conv2d(3, 16, 3, padding=1), nn.ReLU(),
... nn.AdaptiveAvgPool2d(1), nn.Flatten(), nn.Linear(16, 10),
... ).eval()
>>> x, room = lucid.randn(1, 3, 32, 32), tempfile.mkdtemp()
>>> package = cml.export(model, x, f"{room}/m.mlpackage")
>>> package.predict(x).shape
(1, 10)
>>> package.verify(model, x) < 1e-5 # float32; a float16 package is nearer 1e-3
True
>>> package.close()
A handle owns a compiled model, so close it — or use it as a context
manager if you only need it for one call.
>>> with cml.load(f"{room}/m.mlpackage") as reopened:
... print(reopened.predict(x).shape)
(1, 10)
>>> shutil.rmtree(room)Used by 1
Constructors
1__init__
→None__init__(path: str, input_names: list[str], output_names: list[str], compute_units: ComputeUnits = ComputeUnits.ALL, precision: str = 'FLOAT32', output_shapes: dict[str, tuple[int, ...]] | None = None, image_input: ImageInput | None = None, classifier: Classifier | None = None, function_name: str = '', deployment_target: object = None, noise: list[tuple[str, tuple[int, ...], str, Tensor | None]] | None = None, traced_outputs: dict[str, Tensor] | None = None)Properties
1Instance methods
7How long one prediction takes, measured the way it should be.
The first calls are not the model: Core ML defers work to them — specialising for the units it was given, laying out weights the accelerator wants — and a timing that includes them reports the setup. Hence a warmup that is thrown away, and a median over repeats rather than a mean, since a scheduling hiccup on a shared machine moves a mean and not a median.
Measured on an M1 Pro with a ResNet-18 at 224 square: 18.6 ms eager, 4.5 ms as a float32 package on the CPU, 2.7 ms at float16 on the CPU, and 1.5 ms with the Neural Engine allowed — so the accelerator is worth about 12x against eager and 3x against the same package on the CPU. Those are this machine's numbers and will not be yours, which is why this exists rather than a documented figure.
Parameters
Returns
LatencyMedian and best of the timed calls, in milliseconds.
Raises
ValueErrorrepeats is not positive.Run a classifier package and read back what it names.
Parameters
Returns
tuple[str, dict[str, float]]The winning label, and every label with its probability.
Raises
TypeErrorRelease the compiled model and the artifacts Core ML cached.
Safe to call twice, so a finally beside a with is fine.
compute_plan()Which device Core ML assigns each operation to.
Requires macOS 14.4+; an empty plan there means unknown, not unaccelerated.
Run the model.
Inputs must be host tensors: Core ML reads host memory, and moving
a Metal tensor here would hide a copy the caller did not ask for.
Move it explicitly with .to("cpu").
Parameters
Returns
Forget everything the package has accumulated.
A state persists across predictions by design, so starting a fresh sequence has to be asked for; there is no other way back to the value it began at.
Raises
ValueErrorLargest difference against the eager model.
Shapes agreeing is not evidence: a package missing a layer has the right shape and returns plausible numbers. This runs both and compares values — every output, not just the first, since a detector that exported its class scores and dropped its boxes would otherwise pass.
The default is an absolute difference, which is only interpretable against outputs of a known size. A model whose outputs differ in magnitude makes that trap easy to fall into: RealNVP returns a latent of order 1 beside a log-probability of order 1e4, so the absolute worst is set by the second, and dividing it by the first reads as a 4% error when every output agrees to 1e-6.
relative=True scales each output's difference by that
output's own magnitude — but only down to one, which is the
other half of the same trap: VQ-VAE's latent has values around
2e-3, and dividing float32 noise by that reads as 3e-4 when the
difference is 5e-7. Dividing by max(scale, 1) is relative
where relative means something and absolute where it does not,
which is the same bargain a tolerance pair makes.
Parameters
Returns
floatThe worst max|coreml - eager| across the outputs, or the
worst of those divided by each output's own scale when
relative. Expect ~1e-7 for a float32 export and ~1e-3
relative for float16.
Notes
For an image export the pixel buffer is eight bits per channel,
so anything but whole numbers in [0, 255] is rounded on the
way in and the two sides see different pixels. That is refused
rather than reported: for randn values the rounding is most
of the signal and the answer would be around 3e-1, which reads
as a broken export. Feed pixels and the comparison is the usual
one.
Dunder methods
3__enter__()Return the handle, so a package can be opened in a with.
A handle owns a compiled model and a directory Core ML wrote it
into, and every use of one in this codebase was already a
try/finally around close.
Returns
CoreMLModelThis handle.
Close the handle, whether the block ended well or not.
Parameters
kindtype or NonevalueBaseException or NonetraceTracebackType or None