onnxruntime
21e9fe39 - [CoreML EP] Add GatherND builder (#28598)

Commit
36 days ago
[CoreML EP] Add GatherND builder (#28598) ### Summary New ML Program op builder: ONNX `GatherND` → CoreML `gather_nd`. - `batch_dims` must be 0 — the iOS15 `gather_nd` op has no `batch_dims` parameter; `IsOpSupportedImpl` rejects other values. - CoreML's `gather_nd` rejects a **bool `x`**, but transformer attention-mask graphs gather from bool tensors. For bool data the builder lowers the op as `cast(bool→int32) → gather_nd → cast(int32→bool)`; int32 represents 0/1 exactly, so the round-trip is lossless. - `validate_indices` is passed explicitly — the ML Program parser rejects `gather_nd` without it (the same quirk the `gather` builder works around). - ML-Program-only; `IsOpSupportedImpl` rejects the NeuralNetwork format. ### Indices handling (CoreML `gather_nd` quirks) Two CoreML behaviours that differ from ONNX are handled in the builder: - **`indices` must be a constant initializer.** CoreML's `gather_nd` miscomputes the result for some data/indices shape combinations when `indices` is a runtime (non-constant) input — it returns slice 0 regardless of the actual index value. With a constant `indices` it is correct, so non-constant cases fall back to CPU. Constant indices is also the common case (e.g. transformer attention masks). - **Negative indices are normalized at build time.** ONNX `GatherND` wraps a negative index by the corresponding data dim; CoreML's `gather_nd` does not and silently returns wrong values. Since `indices` is constant, the builder wraps any negatives into positive int32 indices while building the model (and requires the indexed data dims to be static, otherwise the node falls back to CPU). This was surfaced by fuzzing over randomized shapes/indices and verified on-device (negative indices, scalar outputs, ranks 2–4) against the CPU reference. ### Depends on the bool-Cast PR The bool-data `GatherND` test needs `Cast` as the `int ↔ bool` producer/consumer so the bool tensors stay internal to the CoreML partition (a partition cannot have bool I/O). This branch is **stacked on `coreml-cast-bool`** — the `cb43b7c75f` commit in this PR is the bool-Cast PR and drops from this diff once that one merges. ### Tests (`coreml_basic_test.cc`) - `GatherND_MLProgram` — a float `GatherND` runs on CoreML, matches CPU. - `GatherNDBoolData_MLProgram` — a `Cast → GatherND → Cast` bool chain runs fully on CoreML, exercising the cast round-trip lowering. - `GatherNDNeuralNetworkNotSupported` — `GatherND` falls back on the NeuralNetwork format. - `GatherNDBatchDimsNotSupported` — `GatherND` with `batch_dims=1` falls back to CPU. Doc: `coreml_supported_mlprogram_ops.md` lists `GatherND`. ### Series — CoreML EP coverage for transformer / diffusion graphs - #28595 — Support bool Cast in ML Program *(prerequisite)* - #28596 — Add Sin and Cos unary ops *(independent)* - #28597 — Add Where and And builders *(depends on #28595)* - **#28598 — Add GatherND builder** *(this PR — depends on #28595)* Together with #28278 (scalar-`Gather`), the series takes BERT / GPT-2 / ViT / diffusion-UNet graphs — tiny and full-size — from 2 CoreML partitions to 1, with zero graph breaks. --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Author
Parents
Loading