Skip to content

Remove the deprecated MPS backend - #22181

Merged
shoumikhin merged 1 commit into
pytorch:mainfrom
shoumikhin:remove-mps-backend
Aug 26, 2026
Merged

Remove the deprecated MPS backend#22181
shoumikhin merged 1 commit into
pytorch:mainfrom
shoumikhin:remove-mps-backend

Conversation

@shoumikhin

@shoumikhin shoumikhin commented Aug 26, 2026

Copy link
Copy Markdown
Contributor

The MPS backend was deprecated in ExecuTorch 1.2 and its own deprecation
notices say it will be removed in 1.4. The tree is now at 1.5.0, so the
removal is overdue. Delete it.

MPS delegated model execution to Apple's MPSGraph framework. The replacements
its deprecation notice names are still here: Core ML for general Apple
acceleration, and the Metal backend for macOS GPU work.

What goes away:

  • backends/apple/mps/ and examples/apple/mps/
  • the EXECUTORCH_BUILD_MPS build option, and the places that set it
  • the backend_mps SwiftPM product and its Apple framework build rules
  • the --mps export flag, the backend.mps.enabled config key, and
    get_mps_partitioner
  • the MPS CI jobs, benchmark configurations, and documentation pages
  • EXECUTORCH_BUILD_KERNELS_TORCHAO_MPS, which built torchao's Metal kernels
    for this runtime. Those kernels include a header from this backend and call
    into its namespace, so they cannot outlive it. The option was off by default
    and set by nothing in the tree.

Two things that look like MPS are deliberately kept, because they are unrelated
to this backend:

  • The Metal backend links Apple's MetalPerformanceShaders framework directly for
    some fallback operations. That is a system library, not this delegate.
  • TORCHAO_BUILD_EXPERIMENTAL_MPS builds torchao's own Metal kernels for eager
    PyTorch. It never sets TORCHAO_BUILD_EXECUTORCH_OPS, so it does not reach
    the variant that depended on this backend.

--device mps in an export script is PyTorch's device name for Apple GPUs, and
MPS in a codegen fixture is a PyTorch dispatch key. Both stay.

Removed documentation pages redirect to the backend overview, which lists the
Apple alternatives, rather than returning 404.

Callers passing -DEXECUTORCH_BUILD_MPS=ON will find the option is gone. CMake
reports it as an unused variable rather than failing, and the build produces a
runtime without the backend, which is the correct end state. The --mps flag of
the Apple framework script is rejected outright, since that script validates its
arguments.

Test Plan:
No test exercises this backend after the change, so verification focused on
proving nothing else broke.

Configured the Apple build and the LLM build to completion. Both previously
turned this backend on by default, and both configure with it absent. Then built
and linked the example runner: it contains no symbol from this backend, still
registers Core ML and XNNPACK, and runs a delegated model to the correct result.

The two source lists in the build glue are consumed positionally, so both were
checked to still be the same length, and every list name the build asks for was
confirmed to still resolve.

Ran the benchmark configuration logic directly: the iOS configuration list no
longer offers this backend, one model's expected list matches, and no disabled
entry names a configuration that no longer exists.

Parsed the changed workflow files as YAML and confirmed no job remains. Parsed
both Xcode projects and confirmed no reference to the removed package product
and no dangling object references. Checked the SwiftPM manifest still has a stub
directory for every product it declares, and no orphan directories.

Built the affected documentation table with Sphinx and MyST to confirm it still
renders as a table rather than being replaced by a parse error, which is what a
row with the wrong number of cells causes. Confirmed every redirect target
resolves to a real page, and that no remaining cross-reference points at a
deleted one.

Syntax-checked the changed shell scripts, and confirmed formatting and lint are
clean on the changed Python, CMake and HTML files.

Finally, swept the tree with a word-boundary search rather than a token search,
and audited every remaining match. What remains is PyTorch's device name, a
PyTorch dispatch key, torchao's eager Metal kernels, tokenizer vocabulary
entries, Arm Corstone board names, and historical notes under .wiki/.

Also make the SwiftPM release step check that the manifest and the built frameworks
describe the same set, in both directions, since each failure is silent on its own.
A placeholder with no framework publishes literal text where a checksum belongs:
eight consecutive nightly manifests in August 2025 shipped that way, which makes the
package unresolvable for every consumer rather than only users of that product. A
framework with no product is the reverse, and matters here because release branches
share one template, so a branch that still builds a removed framework would drop the
product from the manifest silently. Both checks run only on a publishing run, so a
template that is momentarily out of step with a branch does not redden unrelated
pull requests. Removing a framework is exactly the change that exercises this, so
the guard belongs with it.

Verified the guard against the real manifests from that incident: it fails on the
affected days, naming the surviving placeholders, and passes on the day the
incident ended.

The guard compares each built framework against the product base name, because the
manifest declares each product once and synthesises its debug variant in Swift.

The SwiftPM package needs a matching change on its own branch: #22182.

Copilot AI lite review requested due to automatic review settings August 26, 2026 16:43
@pytorch-bot

pytorch-bot Bot commented Aug 26, 2026

Copy link
Copy Markdown

🔗 Helpful Links

🧪 See artifacts and rendered test results at hud.pytorch.org/pr/pytorch/executorch/22181

Note: Links to docs will display an error until the docs builds have been completed.

❌ 1 New Failure, 155 Pending, 1 Unrelated Failure

As of commit 5f8cfbd with merge base 04b3446 (image):

NEW FAILURE - The following job has failed:

BROKEN TRUNK - The following job failed but were present on the merge base:

👉 Rebase onto the `viable/strict` branch to avoid these failures

This comment was automatically generated by Dr. CI and updates every 15 minutes.

@meta-cla meta-cla Bot added the CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. label Aug 26, 2026

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@github-actions

Copy link
Copy Markdown

This PR needs a release notes: label

If your change should be included in the release notes (i.e. would users of this library care about this change?), please use a label starting with release notes:. This helps us keep track and include your important work in the next release notes.

To add a label, you can comment to pytorchbot, for example
@pytorchbot label "release notes: none"

For more information, see
https://github.com/pytorch/pytorch/wiki/PyTorch-AutoLabel-Bot#why-categorize-for-release-notes-and-how-does-it-work.

@shoumikhin

Copy link
Copy Markdown
Contributor Author

The SwiftPM package needs a matching change, opened separately because it lives
on a different branch: #22182 against swiftpm.

That branch holds Package.swift.template, which the release job fills in with
one checksum per framework this workflow actually builds. Removing backend_mps
from the framework list here means __SHA256_backend_mps__ never gets
substituted, so the published manifest would carry the literal placeholder as a
checksum and a URL for an archive that was never uploaded.

Worth flagging because the failure is quiet and affects everyone: the manifest
still parses, and SwiftPM downloads every binary target during resolution, so any
project depending on the package would fail to resolve it, not just projects
using MPS.

The two can land in either order. The artifact is live today, so this is a real
break rather than a theoretical one.

Copilot AI review requested due to automatic review settings August 26, 2026 17:12

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@shoumikhin

Copy link
Copy Markdown
Contributor Author

Thanks, all three findings were correct and are fixed. I reproduced each one
before changing anything.

1, the broken table. Confirmed and it was worse than a bad row. Building the
real markup with Sphinx and MyST:

ERROR: ... row 7 does not contain the same number of items as row 1 (3 vs 4).
build succeeded, 1 warning.

Exit 0, and the rendered page contained zero <table> elements: the whole
directive is replaced by the error node, so the entire "Backend Selection by
Platform and Hardware" table disappears, not just the malformed row. And
docs/Makefile has -WT --keep-going commented out while the workflow runs
plain make html, so CI cannot catch it.

Fixed by deleting the row outright, since Metal GPU on iOS has no backend to
point at now. Rebuilt: one table, twelve rows. Also removed the two other stale
mentions in that file.

2, the torchao claim. You are right and my sentence was wrong. It is stronger
than a link error, too: the kernel source includes
backends/apple/mps/runtime/operations/OperationUtils.h and calls
executorch::backends::mps::delegate::getMTLBufferStorage, both of which this PR
deletes, so that path cannot compile at all.

Since it cannot outlive the backend, I removed EXECUTORCH_BUILD_KERNELS_TORCHAO_MPS
and its block rather than guarding them. Verified nothing else sets the option,
and that root's EXECUTORCH_BUILD_KERNELS_TORCHAO path never adds that
subdirectory, so this closes the path completely. The claim is gone from the
description.

3, the sweep. Also correct, and the cause is instructive: I swept for
backend-specific tokens (EXECUTORCH_BUILD_MPS, mpsdelegate, backends/apple/mps,
...), which by construction cannot match prose like "CoreML, MPS, XNNPACK". Redid
it with a word-boundary search and fixed every file you listed, including the
### MPS section documenting the --mps flag this PR deletes, the apple/mps
links that would 404, and the landing-page card. The description now says what
the sweep actually was and what legitimately remains.

On the scope creep. Fair, and checking it found a real problem: MLX needs an
uninitialised submodule, so -DEXECUTORCH_BUILD_MLX=ON on a fresh clone hits a
FATAL_ERROR. Recommending it would have been worse than what it replaced. Those
examples now use flags with no submodule prerequisite. MLX stays in the README
platform table, which is a statement of fact rather than a build instruction, and
matches what the backend overview already lists.

I left .wiki/ and the recorded benchmark fixtures alone, as you suggested.

Separately, your point about docs/Makefile is the root cause here and worth its
own issue: an ERROR-level directive failure currently ships to the site with a
green build.

Copilot AI review requested due to automatic review settings August 26, 2026 19:27

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

Copilot AI review requested due to automatic review settings August 26, 2026 19:53

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

Copilot AI review requested due to automatic review settings August 26, 2026 19:56

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@shoumikhin

Copy link
Copy Markdown
Contributor Author

Updated after review. All five documentation and comment findings are fixed, and
two claims in the commit message were wrong and are corrected.

The redirect comment promised something the target does not offer. It said old
MPS pages point at the backend list so readers reach "Core ML and Metal". Measured:
after this change that page mentions Metal zero times, because the Metal backend
has no docs page at all, its guide lives at backends/apple/metal/README.md
outside the docs tree. The comment now says the target is where a reader finds the
Apple backends that remain, which is what is actually true.

The build example no longer illustrated its own sentence. The sentence is about
enabling additional backends, and I had replaced the MPS example with the
training extension. Now uses Vulkan, matching what the agent skill file in this
same change switched to.

A stale comment explained live code by naming the deleted backend
(rope.py). Reworded to describe the actual constraint, a backend requiring
static shapes, rather than naming a backend that no longer exists.

Two commit message claims were wrong:

  • I wrote that passing -DEXECUTORCH_BUILD_MPS=ON would fail at configuration
    time. It does not: CMake reports an unused variable and succeeds. The end state
    is still correct, a runtime without the backend, but the mechanism I described
    was not. The --mps script flag is rejected outright, and the message now
    distinguishes the two.
  • The test plan said the affected table "produces one table with twelve rows". No
    changed table has twelve rows. Replaced with what the check actually
    establishes: that the table still renders rather than being swallowed by a parse
    error.

One finding I am deliberately not acting on, with the reasoning, since a
silent non-answer reads as agreement:

torchao:fpa<N>w remains an accepted llama export mode. I verified the runtime
kernels for the ops it emits did live in the file this change makes unbuildable,
so the concern is real in substance. But the export path loads
libtorchao_ops_mps_aten, and that target is built unconditionally at
third-party/ao/torchao/experimental/ops/mps/CMakeLists.txt:63, outside the
TORCHAO_BUILD_EXECUTORCH_OPS guard, and its build.sh never sets that flag. The
option this change removes drove a different target. So the AOT path is untouched
here, and the runtime side was already unreachable before this change: nothing in
the tree ever set EXECUTORCH_BUILD_KERNELS_TORCHAO_MPS. Narrowing the accepted
CLI patterns is a real cleanup, but it belongs with the Metal backend's own
fpa4w story rather than inside a removal.

On landing order: agreed, and #22182 should go first. The nightly cron makes the
reverse order worse than a manual release, since the workflow force-pushes the
generated manifest.

Copilot AI review requested due to automatic review settings August 26, 2026 20:25

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@shoumikhin

Copy link
Copy Markdown
Contributor Author

Second round addressed. Two new items, both verified independently before acting.

C1 is correctly a blocker, and the fix already committed is the one you
recommend.
I had not measured the ROCm consequence, so I reproduced it with real
pip using a wheel carrying the same local version segment those jobs install:

### stable floor >=0.18.0
   before: 0.18.0.dev20260729+rocm7.1
   after : 0.18.0            <- Uninstalling torchao-0.18.0.dev20260729+rocm7.1
   the job's own assert '+rocm' in torchao.__version__  ->  FAILS

### bounded floor >=0.18.0.dev20260729,<0.19
   after : 0.18.0.dev20260729+rocm7.1   (Requirement already satisfied)
   the job's own assert  ->  PASSES

Worth recording that my first attempt at this reproduction produced a false
negative: the consumer install was failing with BackendUnavailable and I nearly
read the unchanged version as "no eviction". It only reproduces once the build
backend is present.

R3 checks out, and it strengthens the case for #22182 rather than sitting
alongside it.
I verified the 2025 incident on the published branches directly:

swiftpm-0.8.0.20250819  leftover placeholders: 2
swiftpm-0.8.0.20250826  leftover placeholders: 2
swiftpm-0.8.0.20250827  leftover placeholders: 0

So this mechanism has already produced a real multi-day outage, for a different
product, by exactly the route #22182 prevents.

Because of that I took the optional guard you suggested rather than leaving it. The
release step now fails when any checksum placeholder survives substitution, naming
the ones left. Verified against the real manifests from that incident: it fails on
the affected days and passes on the day it ended. Removing a framework is precisely
the change that triggers this class of bug, so the guard belongs here.

C5a is real and needs no code change. Confirmed release/1.0 still builds the
removed framework, checks out this same shared template, force-pushes to a fixed
swiftpm-1.0.0 that applications can pin, and has no guardrail step where main
has one. Recorded as an operator note in #22182's description, since re-dispatching
the Apple workflow on that branch after this lands would republish that manifest
with the product missing. Nothing is scheduled there, so it is a constraint rather
than an imminent break.

Note the new guard also limits the C5a exposure: a release/1.0 run would still
build the framework, so its substitution stays complete and the guard would not
fire, but any future run that stops building a declared product now fails instead
of publishing.

@shoumikhin

Copy link
Copy Markdown
Contributor Author

C7 and C8 are both already fixed on the current head; I think the re-read caught the
pre-fix text. Checked rather than asserted:

live PR body        "twelve rows"    -> absent
                    "honest outcome" -> absent
                    "parse error"    -> present   (the corrected C7 wording)
                    "unused variable"-> present   (the corrected C8 wording)
commit 5a3659410f   line 40: "reports it as an unused variable rather than failing"
                    line 68: "renders as a table rather than being replaced by a parse error"

For C8 specifically, the message now distinguishes the two cases your measurement
separated: the CMake option is reported as unused and the build succeeds without the
backend, while the --mps script flag is rejected outright because that script
validates its arguments.

C9 I am still not acting on, and here is the reasoning rather than silence.

The runtime kernels for the ops torchao:fpa<N>w emits did live in the file this
change makes unbuildable, so the concern is correct in substance. But the export path
loads libtorchao_ops_mps_aten, and that target is built unconditionally at
third-party/ao/torchao/experimental/ops/mps/CMakeLists.txt:63, outside the
TORCHAO_BUILD_EXECUTORCH_OPS guard, with its own build.sh never setting that flag.
The option this change removes drove a different target. So the AOT path is untouched,
and the runtime side was already unreachable before this change, since nothing in the
tree ever set EXECUTORCH_BUILD_KERNELS_TORCHAO_MPS.

Narrowing the accepted CLI patterns is a real cleanup and I agree it will cost
somebody time as it stands. It belongs with the Metal backend's own fpa4w story,
where a replacement exists to point at, rather than inside a removal that would
otherwise be pure deletion.

Copilot AI review requested due to automatic review settings August 26, 2026 22:37

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

The MPS backend was deprecated in ExecuTorch 1.2 and its own deprecation
notices say it will be removed in 1.4. The tree is now at 1.5.0, so the
removal is overdue. Delete it.

MPS delegated model execution to Apple's MPSGraph framework. The replacements
its deprecation notice names are still here: Core ML for general Apple
acceleration, and the Metal backend for macOS GPU work.

What goes away:

- `backends/apple/mps/` and `examples/apple/mps/`
- the `EXECUTORCH_BUILD_MPS` build option, and the places that set it
- the `backend_mps` SwiftPM product and its Apple framework build rules
- the `--mps` export flag, the `backend.mps.enabled` config key, and
  `get_mps_partitioner`
- the MPS CI jobs, benchmark configurations, and documentation pages
- `EXECUTORCH_BUILD_KERNELS_TORCHAO_MPS`, which built torchao's Metal kernels
  for this runtime. Those kernels include a header from this backend and call
  into its namespace, so they cannot outlive it. The option was off by default
  and set by nothing in the tree.

Two things that look like MPS are deliberately kept, because they are unrelated
to this backend:

- The Metal backend links Apple's MetalPerformanceShaders framework directly for
  some fallback operations. That is a system library, not this delegate.
- `TORCHAO_BUILD_EXPERIMENTAL_MPS` builds torchao's own Metal kernels for eager
  PyTorch. It never sets `TORCHAO_BUILD_EXECUTORCH_OPS`, so it does not reach
  the variant that depended on this backend.

`--device mps` in an export script is PyTorch's device name for Apple GPUs, and
`MPS` in a codegen fixture is a PyTorch dispatch key. Both stay.

Removed documentation pages redirect to the backend overview, which lists the
Apple alternatives, rather than returning 404.

Callers passing `-DEXECUTORCH_BUILD_MPS=ON` will find the option is gone. CMake
reports it as an unused variable rather than failing, and the build produces a
runtime without the backend, which is the correct end state. The `--mps` flag of
the Apple framework script is rejected outright, since that script validates its
arguments.

Test Plan:
No test exercises this backend after the change, so verification focused on
proving nothing else broke.

Configured the Apple build and the LLM build to completion. Both previously
turned this backend on by default, and both configure with it absent. Then built
and linked the example runner: it contains no symbol from this backend, still
registers Core ML and XNNPACK, and runs a delegated model to the correct result.

The two source lists in the build glue are consumed positionally, so both were
checked to still be the same length, and every list name the build asks for was
confirmed to still resolve.

Ran the benchmark configuration logic directly: the iOS configuration list no
longer offers this backend, one model's expected list matches, and no disabled
entry names a configuration that no longer exists.

Parsed the changed workflow files as YAML and confirmed no job remains. Parsed
both Xcode projects and confirmed no reference to the removed package product
and no dangling object references. Checked the SwiftPM manifest still has a stub
directory for every product it declares, and no orphan directories.

Built the affected documentation table with Sphinx and MyST to confirm it still
renders as a table rather than being replaced by a parse error, which is what a
row with the wrong number of cells causes. Confirmed every redirect target
resolves to a real page, and that no remaining cross-reference points at a
deleted one.

Syntax-checked the changed shell scripts, and confirmed formatting and lint are
clean on the changed Python, CMake and HTML files.

Finally, swept the tree with a word-boundary search rather than a token search,
and audited every remaining match. What remains is PyTorch's device name, a
PyTorch dispatch key, torchao's eager Metal kernels, tokenizer vocabulary
entries, Arm Corstone board names, and historical notes under `.wiki/`.

Also make the SwiftPM release step check that the manifest and the built frameworks
describe the same set, in both directions, since each failure is silent on its own.
A placeholder with no framework publishes literal text where a checksum belongs:
eight consecutive nightly manifests in August 2025 shipped that way, which makes the
package unresolvable for every consumer rather than only users of that product. A
framework with no product is the reverse, and matters here because release branches
share one template, so a branch that still builds a removed framework would drop the
product from the manifest silently. Both checks run only on a publishing run, so a
template that is momentarily out of step with a branch does not redden unrelated
pull requests. Removing a framework is exactly the change that exercises this, so
the guard belongs with it.

Verified the guard against the real manifests from that incident: it fails on the
affected days, naming the surviving placeholders, and passes on the day the
incident ended.

The guard compares each built framework against the product base name, because the
manifest declares each product once and synthesises its debug variant in Swift.
@shoumikhin
shoumikhin merged commit fd677f3 into pytorch:main Aug 26, 2026
397 of 402 checks passed
shoumikhin added a commit to meta-pytorch/executorch-examples that referenced this pull request Aug 27, 2026
The MPS delegate was removed from ExecuTorch in pytorch/executorch#22181, so
nothing produces mv3_mps_float16.pte any more and no backend_mps SwiftPM product
is published. The mv3 demo still declared both, which fails the build before it
starts:

  error: .../Resources/Models/MobileNet/mv3_mps_float16.pte:
  No such file or directory (in target 'App' from project 'ExecuTorchDemo')

That is the build-demo-ios job on executorch main, which has been red since that
change landed.

Drop the product references from both Xcode projects, the model resource and the
picker case from the mv3 demo, and the export that produced the file. The export
was already misleading: the MPS path had been commented out and the program was
re-exported with portable ops under an mps name, so the option ran the same graph
as the portable build while claiming to be GPU accelerated.

The Metal references under voxtral_realtime/ are deliberately left alone. Those
are TORCHAO_BUILD_EXPERIMENTAL_MPS, a torchao Metal-kernel flag that the Metal
backend still uses upstream, not the removed delegate.

Test Plan:
No MPS reference remains outside voxtral_realtime/, checked with a word-boundary
grep over every file type.

Both Xcode projects lint clean with plutil, and their object graphs are intact:
109 objects in mv3 and 63 in etLLM, with zero ids referenced but not defined and
zero defined but never referenced. The originals measured the same way, so the
edit preserved the property rather than happening to satisfy it. Confirmed
backend_coreml and backend_xnnpack survive in both.

ClassificationController.swift parses with swiftc, and the switch over Mode is
still exhaustive over the two remaining cases. Nothing else referenced Mode.mps;
MobileNetClassifierTest covers portable, Core ML and XNNPACK and never had an MPS
case. export.py compiles and the workflow artifact list no longer names the file
it stopped producing.

Co-authored-by: Devmate <devmate@meta.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants