Chapters Β· 05
Deployment Workflow: PyTorch β Core ML β ANE
Part of the ANE Knowledge Base. Sources: both Apple articles and the export code in
references/ml-ane-transformers(README tutorial) andreferences/ml-vision-transformers-ane/export.py.
The pipeline is identical for text and vision models:
PyTorch model (ANE-optimized, eval mode)
β torch.jit.trace (TorchScript)
β coremltools ct.convert(mlprogram) (.mlpackage)
β Xcode Performance tab (verify ANE dispatch + measure)
β ship in app, load asynchronously
1. Prepare the model#
- Build/instantiate the ANE-optimized variant and load the pretrained weights (state-dict hooks handle LinearβConv2d and LayerNorm order β see doc 03).
model.eval()always; these ports are inference-only.- For HF models: load the baseline with
return_dict=Falseandtorchscript=True(coremltools does not support dict outputs). - Wrap tracing in
torch.no_grad()(asexport.pydoes) to avoid autograd baggage.
2. Trace to TorchScript#
# Text (fixed sequence length β pad to max_length)
tokenized = tokenizer(["sample"], return_tensors="pt",
max_length=128, padding="max_length")
traced = torch.jit.trace(model, (tokenized["input_ids"], tokenized["attention_mask"]))
# Vision (fixed input shape)
x = torch.rand((1, 3, 256, 256))
traced = torch.jit.trace(model, (x,))
Tracing bakes in static shapes β pick the sequence length / resolution / batch size you will serve (Apple's artifacts encode them in the filename: ..._seqLen128_batchSize1, ..._batch1_256x256_...). Export one package per shape you need.
3. Convert with coremltools#
import coremltools as ct, numpy as np
mlpackage = ct.convert(
traced,
convert_to="mlprogram", # ML Program, not neuralnetwork
inputs=[
# text: token ids are int32
ct.TensorType("input_ids", shape=(1, 128), dtype=np.int32),
ct.TensorType("attention_mask", shape=(1, 128), dtype=np.int32),
# vision: ct.TensorType("x", shape=x.shape)
],
compute_units=ct.ComputeUnit.ALL, # allow CPU+GPU+ANE (default)
)
mlpackage.save("model.mlpackage")
Key points:
convert_to="mlprogram"targets the modern ML Program backend (FP16 by default on ANE).compute_units=ct.ComputeUnit.ALLlets Core ML build the hybrid plan; useCPU_AND_NEduring debugging if you want to detect GPU fallbacks.- Integer inputs (token ids, masks) are declared
np.int32.
4. Verify on device: Xcode performance reports#
- Add the
.mlpackageas a resource in any Xcode project. - Open it β Performance tab β generate a report on a locally available device (the Mac itself or a connected iPhone/iPad).
- The report shows per-op compute-unit dispatch (CPU / GPU / ANE) and measured latency.
How to read it:
- Expect the transformer trunk on ANE. Some CPU ops are normal β DistilBERT keeps 4/606 ops (embedding lookups) on CPU because that's genuinely faster.
- Op count grows with chunking β higher one-time load/compile time; steady-state latency is what improves. Load the model asynchronously at app start.
- Re-run reports across target devices/OS versions β dispatch decisions can differ by chip and OS (Apple published curves for iPhone 12/13, iOS 15/16, M1 macOS 13).
- If latency is flat while you shrink the workload, you are bandwidth-bound β consider bigger batches or quantization/pruning (Principle 4).
5. Common pitfalls checklist#
| Symptom | Likely cause | Fix |
|---|---|---|
| Ops falling back to GPU/CPU mid-graph | Unsupported op or hostile layout (rank > 5, small last axis) | 5D relay reshapes; BC1S/NHWC layouts; check each new op in the report |
| Wildly wrong outputs vs PyTorch | FP16 (eps too small, mask βinf/1e9, overflow) | eps β₯ 1e-7, masks β1e4, optional clamping |
| Conversion error about dict outputs | HF return_dict=True |
return_dict=False (+ torchscript=True) |
| Checkpoint won't load into optimized model | Linear vs Conv2d weight shapes; LayerNorm scale/bias order | state-dict pre-hooks (linear_to_conv2d_map, bias/weight correction) |
| Great latency, slow first load | 100s of chunked ops compiling | Accept as one-time cost; async load; cache compiled model |
| Latency doesn't drop with shorter sequences | Bandwidth-bound regime | Increase batch; quantize/prune weights |
| Padding blow-up / 32Γ slowdown | Small or singleton last axis | Reorder dims so a large (64-byte-aligned) axis is last; channels multiple of 32 |
6. Reproducing Apple's exports from the cloned repos#
# Vision: exports tiny-moat-0 at 512x512 and 256x256, global and local attention
cd references/ml-vision-transformers-ane
pip install torch coremltools pytest timm
python export.py # writes ./exported_model/*.mlpackage
pytest tests.py # unit tests / usage examples
# Text: follow the README tutorial
cd ../ml-ane-transformers
pip install ane_transformers # or: pip install -e .
# then run the DistilBERT tutorial code from README.md
That closes the loop: 01 hardware constraints β 02 principles β 03/04 applied architectures β this deployment/verification workflow.