examples/llama: lower to Core ML with to_edge_transform_and_lower - #22052
examples/llama: lower to Core ML with to_edge_transform_and_lower#22052msluszniak wants to merge 1 commit into
Conversation
The Core ML branch of _to_edge_and_lower_llama() still used the deprecated export_to_edge() + to_backend() split, and CoreMLPartitioner logs a deprecation warning about it on every invocation. The split matters beyond the warning. It decomposes the graph before the partitioner runs, so the ops Core ML has its own implementations for are already broken into primitives by the time it sees them. CoreMLPartitioner.ops_to_not_decompose() asks to keep every op Core ML supports, and only to_edge_transform_and_lower honours that request. Adds _to_edge_and_lower_llama_coreml(), matching the existing xnnpack and mlx helpers, and routes to it when Core ML is the only backend enabled. Enabling Core ML together with Vulkan, MPS or QNN still takes the old combined path, which builds one partitioner list; QNN in particular needs the edge manager for its pass pipeline and model sharding, so it is left alone. Etrecord generation comes along for free: to_edge_transform_and_lower takes generate_etrecord directly, so the helper does not need the deepcopy of the edge manager the old path used. Measured on LFM2.5 350M, fp32, seq 512, ios 18, cpu_and_ne: before: 1 subgraph, 1156 delegated nodes, 13 non-delegated, 1 warning after: 1 subgraph, 893 delegated nodes, 13 non-delegated, 0 warnings Same partitioning, 263 fewer nodes for Core ML to reassemble; expand_copy drops from 133 to 49 and unsqueeze_copy from 375 to 99. Also adds examples/models/lfm2/config/lfm2_coreml_fp32.yaml, alongside the existing lfm2_xnnpack_fp32.yaml and lfm2_mlx_4w.yaml, so the Core ML path has a config to run. Fixes pytorch#19634
🔗 Helpful Links🧪 See artifacts and rendered test results at hud.pytorch.org/pr/pytorch/executorch/22052
Note: Links to docs will display an error until the docs builds have been completed.
|
This PR needs a
|
|
@claude review this code |
|
Claude finished @metascroy's task in 5m 7s —— View job Code ReviewI read the diff against the surrounding code in 1. ETRecord does not actually land on disk in this path (behavior regression vs. the old CoreML path)The PR body says "Etrecord generation comes along for free," but I don't think that's accurate. The new helper does: builder_exported.generate_etrecord = generate_etrecord
builder = builder_exported.pt2e_quantize(quantizers).to_edge_transform_and_lower(partitioners)
...
return builder.to_executorch(passes=additional_passes)That flag flows into Contrast with the old path ( generate_etrecord_func(et_record="etrecord.bin", edge_dialect_program=..., executorch_program=builder.export_program)So a user running This is consistent with XNNPACK, so it's defensible to ship — but I'd suggest either (a) toning down the PR description so it doesn't claim ETRecord works, or (b) actually persisting it (e.g. 2. Minor:
|
Summary
Fixes #19634.
The Core ML branch of
_to_edge_and_lower_llama()still used the deprecatedexport_to_edge()+to_backend()split, andCoreMLPartitionerlogs a deprecation warning about it on every invocation.The split matters beyond the warning. It decomposes the graph before the partitioner runs, so the ops Core ML has its own implementations for are already broken into primitives by the time it sees them.
CoreMLPartitioner.ops_to_not_decompose()asks to keep every op Core ML supports, and onlyto_edge_transform_and_lowerhonours that request.This adds
_to_edge_and_lower_llama_coreml(), matching the existing xnnpack and mlx helpers, and routes to it when Core ML is the only backend enabled.Scope
Enabling Core ML together with Vulkan, MPS or QNN still takes the old combined path, which builds a single partitioner list. QNN in particular needs the edge manager for its pass pipeline and model sharding, so it is left alone; migrating it is a separate piece of work.
Etrecord generation comes along for free:
to_edge_transform_and_lowertakesgenerate_etrecorddirectly, so the helper does not need the deepcopy of the edge manager the old path used.Results
LFM2.5 350M, fp32,
max_seq_length=512,ios: 18,compute_units: cpu_and_ne:Same partitioning, 263 fewer nodes for Core ML to reassemble.
expand_copydrops from 133 to 49 andunsqueeze_copyfrom 375 to 99.A note on the original report
The issue also reported a
SpecViolationError("Mutation node aten_index_put_default_N is neither a buffer nor a user input") from the split path on LFM2 hybrid models, where the short-convconv_state.copy_()decomposes toslice_copy + index_putand only one of the two is recorded as the mutation source. That no longer reproduces onmain: the same export now completes on both the old and the new path. Only the deprecation and the extra decomposition remain, which is what this PR addresses.Test plan
Adds
examples/models/lfm2/config/lfm2_coreml_fp32.yamlalongside the existinglfm2_xnnpack_fp32.yamlandlfm2_mlx_4w.yaml, so the Core ML path has a config to run: