Two ANE profiling tools, complementary:
anemll-profile— measured runtime, bandwidth, graph interruptionsane-costplan— compile-time compiler cost weights (no prediction run)
git clone https://github.com/shipstuff/anemll-profile.git
cd anemll-profile
make && sudo make installVerify:
anemll-profile --version
ane-costplan --help 2>&1 | head -3Build from source gets both tools. (Upstream homebrew tap installs
anemll-profile only.)
Required:
- macOS 14+ with the system
CoreMLandFoundationframeworks - Xcode Command Line Tools for
xcrun clang,swiftc,make, and SDK headers
Recommended for agent workflows:
jqfor reading-jJSON reports
No Python or pip dependencies.
| Question | Tool | Why |
|---|---|---|
| How fast does it run? | anemll-profile |
Runs predictions, measures wall-clock |
| What's the bandwidth per op? | anemll-profile |
Reads live cost model features |
| Why did ops fall back? | anemll-profile |
Captures Espresso compiler messages |
| What does the compiler score as expensive? | ane-costplan |
Reads MLComputePlan cost weights |
| Is op X placed on ANE? | ane-costplan |
deviceUsage.preferredComputeDevice |
| Did a graph change reduce compiler cost? | ane-costplan |
No prediction needed, seconds to run |
| Fast iteration on graph optimizations | ane-costplan |
~2 s vs ~30 s for anemll-profile |
| End-to-end decision: ship it? | both | Cost model + measured perf should agree |
Both tools support JSON output. Prefer it for agent workflows:
anemll-profile -j profile.json model.mlmodelc
ane-costplan -j costplan.json model.mlmodelcText output is good for humans; JSON is the source of truth for automation.
Focus on these JSON sections:
summarymeasured_predictiongraph_interruptionsfallback_opsop_type_breakdowntop_operations
Steps:
- Run with
-j. - Check
graph_interruptions. - If interruptions are
0, shift attention tofallback_ops,op_type_breakdown,top_operations. - If interruptions are nonzero, rank fixes by
estimated_total_tax_ms. - Use
measured_predictionto compare real impact after model changes.
graph_interruptions: Non-ANE islands that break continuous ANE execution. Detour can be CPU or GPU.fallback_ops: Best place to see why specific ops didn't stay on ANE.op_type_breakdown: High-level memory-bound vs compute-bound hotspots.top_operations: Repeated expensive state updates, slices, LUT expansion, or large memory moves.
Unsupported tensor data type: int32: Reduce or isolate int32 control and indexing.- Repeated
slice_update,slice_by_index, orread_state: Investigate KV-cache layout. - Large
constexpr_lut_to_densecost: LUT expansion may dominate memory bandwidth. - Few CPU fallbacks with
0interruptions: Model is mostly on ANE; focus on memory traffic.
Focus on these JSON sections:
total_costtotal_opswith_device/no_deviceoperations(sorted descending by cost)
Steps:
- Run with
-j. - Check
no_device— ops without aMLComputeDeviceare CPU ops (gathers, int32 work). - Examine top operations by
cost— these are what the compiler sees as expensive. - After a graph modification, re-run and diff
total_cost. Drops indicate the compiler agrees with your change.
total_costis dimensionless, not milliseconds — compare relatively (before/after) not absolutely.with_devicecount = ops the compiler can place on ANE/GPU.no_device= CPU-only.- The cost weights reflect the compiler's scheduling decisions, not runtime. A high-cost op may still pipeline well; a low-cost op on the critical path can still bottleneck.
- Use it to iterate on graph structure (fusion, layout, quantization choices) without paying full compile + predict costs.
This is a signal. Examples:
- High
ane-costplancost but low measured runtime → ANE is pipelining that op effectively - Low
ane-costplancost but high measured runtime → probably a bandwidth or memory stall the static cost model doesn't capture - Op has
preferred: ANEinane-costplanbutanemll-profileshows it on CPU → check whether the op got placed off-ANE at runtime due to shape or tile constraints
If a folder contains multiple .mlmodelc or .mlpackage bundles, profile the
important submodels individually, e.g.:
- embeddings
- prefill
- decode or FFN body
- lm_head
Do not assume a parent directory is directly profileable unless it contains a
single model bundle. Both tools auto-detect .mlmodelc and .mlpackage.