ADR-0006: Apple Metal / PyTorch MPS boundaries
- Status: Accepted (milestone 1)
- Date: 2026-10-29
Context
Apple Silicon is a common developer machine. PyTorch exposes its GPU through the MPS backend. MPS is not CUDA: operators, dtypes, custom extensions, memory semantics and distributed collectives differ. Apple Silicon also uses unified memory shared with the CPU.
Decision
- MPS is detected only through PyTorch APIs (
torch.backends.mps.is_built()andis_available()), inside the isolated torch probe (gpu/discovery.py). Missing torch, non-macOS hosts and built-but-unavailable MPS are handled without failing and explained innotes. - The MPS device is reported with
memory_kind=unifiedandmemory_gbequal to host RAM. It is never presented as dedicated VRAM.ResourceRequest.gpu_memory_kindlets a job demanddedicatedmemory, which then excludes Macs. Memory rejection reasons mention "unified memory shared with the CPU". - Supported: independent jobs (
accelerator="mps"). The workload itself selectstorch.device("mps"). GPUlayer only routes and exposesGPULAYER_ACCELERATOR. - Unsupported: distributed MPS.
JobSpecvalidation rejectsDistributedJobwithaccelerator="mps", and the support matrix marks it unsupported. CPU/gloo distributed jobs can run on Macs, because they do not use MPS. - A CUDA job is never placed on an MPS worker, and the reverse also holds.
Validation
- Mock-based tests:
tests/unit/test_discovery.py(available, built-but-unavailable, missing torch) andtests/unit/test_scheduler.py(MPS vs CUDA, unified memory). These run on CPU-only CI. - Hardware:
tests/hardware/test_gpu_smoke.py::test_mps_independent_jobran a 512×512 matmul on MPS through the full SDK path on an Apple Silicon Mac (arm64, 32 GB, torch from the project venv). This covers one smoke workload only and makes no claim about operator coverage.
Alternatives considered
- Treating MPS as "a GPU" interchangeable with CUDA: produces confusing runtime failures. Rejected.
- Multi-Mac distributed with gloo over MPS tensors: not validated, so it is out of scope.
Consequences and trade-offs
- Workloads must be written for MPS (device selection, fallbacks such as
PYTORCH_ENABLE_MPS_FALLBACK). GPUlayer does not translate CUDA code. - Unified memory numbers overstate what a GPU workload can actually use.
Security and operational implications
- None beyond ADR-0009. MPS jobs run as ordinary processes.
Follow-up
- Optional operator and dtype probes for MPS capability detail.
- A separate design and validation effort before any distributed MPS claim.
Amendment (2026-10-29, ADR-0015)
The mps accelerator now means the Apple GPU reached through Metal, by any framework: PyTorch
MPS, MLX and others. It is reported on every Apple Silicon worker; PyTorch jobs additionally
require the torch.mps capability. MLX matrix products have been run on the Apple GPU through
GPUlayer (examples/mlx_metal). The single-process rule is unchanged.