1–5 min delivery

Dedicated Mac mini M4

$21.5 / day · bare metal
Configure Cloud Mac
Web VNC, no install SSH key access Five regions

FIELD NOTE · GPUHardware

PyTorch 2.14 MPS Out Of Memory: Fix Research Training In 2026

This guide helps researchers diagnose PyTorch 2.14 MPS out of memory errors without immediately disabling memory safeguards. It separates real tensor allocation, cache behavior, retained computation graphs, dynamic input shapes, and CPU fallback, then provides a clean Apple Silicon validation workflow.

The PyTorch 2.14 release, published on September 2, 2026, documents changes to the MPS caching allocator and parts of its memory and copy paths in the official release announcement. That change may improve allocation behavior, but it does not prove that every MPS out-of-memory failure is fixed.

This week's action: do not start by removing MPS memory limits. First identify whether the failure comes from live tensors, cached blocks, retained computation graphs, changing shapes, or CPU fallback. Then reduce one workload variable at a time, fix the code path, and rerun the same minimal script in a clean PyTorch 2.14 environment.

This guide is for:

  • Graduate students training or evaluating models on Apple Silicon.
  • Research software maintainers checking whether Linux GPU and macOS results remain comparable.
  • Lab technical leads who need a temporary, clean Apple Silicon environment because the lab has no Mac.

Last updated: September 12, 2026. Version information was checked against the PyTorch 2.14 release announcement, the stable MPS API documentation, and Apple’s Activity Monitor guidance.

Start with the failure type, not the memory limit

A message containing “MPS out of memory” is not enough to identify the cause. The same project can fail in several different ways:

  • PyTorch raises an allocation error while creating a tensor or copying data.
  • macOS terminates the process because overall memory pressure becomes critical.
  • The process remains alive but becomes progressively slower or stops responding.
  • Resident memory keeps increasing between iterations even after the batch size has been reduced.
  • A model silently uses CPU for part of the graph, creating extra copies and a different allocation pattern.

These symptoms require different tests. A live tensor that no longer fits cannot be repaired with empty_cache(). A retained loss tensor may be released by changing one line in the training loop. A CPU fallback may require an operator audit instead of a smaller batch.

Record the baseline before changing the project:

  • PyTorch version, Python version, and macOS version.
  • Apple Silicon model and available system memory.
  • Model name, parameter settings, batch size, sequence length, and input resolution.
  • Whether the job is training, validation, or inference.
  • The complete error text and the iteration where the failure appears.
  • Whether the input and model begin on mps, cpu, or move between both devices.

The PyTorch 2.14 release matters because its allocator and copy-path changes can alter observations between versions. It does not replace a controlled comparison. Keep the original environment available until the same script has been tested in the target version.

Use these memory signals to separate allocation from pressure

Apple Silicon uses unified memory, but unified memory does not mean that every byte of system memory is safely available for MPS tensors. macOS, the Python process, data-loader workers, file caches, the desktop, and other applications may all compete for the same physical memory pool.

PyTorch exposes several measurements, and they answer different questions:

  • torch.mps.current_allocated_memory() reports memory occupied by tensors allocated through the MPS allocator.
  • torch.mps.driver_allocated_memory() reports memory allocated by the Metal driver for the process.
  • torch.mps.recommended_max_memory() reports the recommended maximum memory value exposed by the MPS backend.
  • macOS Activity Monitor shows broader system pressure, not just PyTorch tensor allocation.

Read the definitions in the current allocated memory API, the driver allocated memory API, and the recommended maximum memory API. Check system-level pressure using Apple’s memory pressure documentation.

A small logging function can establish a useful per-iteration record:

import torch

def log_mps_memory(label):
    if not torch.backends.mps.is_available():
        print(f"{label}: MPS unavailable")
        return

    allocated = torch.mps.current_allocated_memory()
    driver = torch.mps.driver_allocated_memory()
    recommended = torch.mps.recommended_max_memory()

    print(
        f"{label} | "
        f"current={allocated} bytes | "
        f"driver={driver} bytes | "
        f"recommended={recommended} bytes"
    )

Log before the forward pass, after the forward pass, after backward, and after the optimizer step. Use the same input and random seed for each run. Do not treat one metric as a substitute for the others.

A rising current allocation usually points toward live tensors or retained graph references. A larger driver allocation with relatively stable tensor allocation can indicate allocator or backend behavior. High system memory pressure with modest PyTorch allocation suggests that the process is not the only consumer.

Reduce the workload one variable at a time

Model parameters, activations, optimizer state, temporary buffers, and input copies all contribute to the working set. Training usually needs more memory than inference because gradients and optimizer state remain relevant across parts of the step.

Do not change the batch size, sequence length, image resolution, precision, and model architecture in one edit. That may make the error disappear while leaving the cause unknown.

Use this order:

  1. Keep the model and random seed fixed.
  2. Reduce batch size and rerun the same minimal training loop.
  3. If the failure remains, restore the batch size and reduce sequence length or input resolution.
  4. If shape reduction changes the result, test a fixed-shape workload separately.
  5. Compare training, validation, and inference as independent cases.
  6. Confirm that the reduced task completes repeated iterations without a steady memory increase.

The goal is not merely to make one run finish. The reduced configuration must remain stable across the same workload segment used for the research result. If the original task fails and the smaller task passes, record the change as a capacity boundary rather than calling it a backend fix.

This distinction matters for reproducibility. A model that passes only after reducing the input resolution may no longer produce results comparable with a Linux GPU run. The correct outcome may be to use the smaller configuration for debugging while retaining the original configuration for the final comparison.

The decision table for the first diagnostic pass

Use the table as a routing tool rather than as a list of guaranteed fixes. Each row describes what the evidence suggests and what the next controlled action should be.

Observation Most likely path to test Minimal verification Acceptable next decision
Allocation fails during model or input creation Live tensors exceed the available boundary Reduce one input dimension or batch variable while keeping the seed fixed Keep the reduced configuration only if its research output remains valid
Allocation rises after every iteration Graph, output, loss, or hidden state is retained Remove result collection and run a minimal loop Fix references before changing allocator settings
Current allocation stabilizes but driver allocation remains high Cache, fragmentation, or backend allocation path Compare fixed-shape and changing-shape runs Investigate allocator behavior; do not equate cache with a leak
System memory pressure becomes critical macOS and the process compete for unified memory Close unrelated workloads and inspect Activity Monitor Repeat in a clean environment before drawing a PyTorch conclusion
Failure appears only with some operators MPS coverage or CPU fallback changes the path Run the smallest operator-containing script on MPS and CPU Audit device transfers and compare outputs before claiming equivalence

This table should be accompanied by the full error, memory logs, and task shape. Without those records, a later version change can make the original diagnosis impossible to reconstruct.

Why torch.mps.empty_cache() does not release everything

torch.mps.empty_cache() releases unused cached memory held by the allocator. It cannot release memory still referenced by tensors, autograd graphs, optimizer state, Python containers, closures, or logging objects. The official empty_cache() documentation describes this boundary clearly.

Use it as a diagnostic action, not as a universal repair:

import gc
import torch

del temporary_output
gc.collect()
torch.mps.empty_cache()

This only helps when temporary_output was the final reference to an unused allocation. It does nothing if a list still contains the output, if a loss tensor still carries a graph, or if the model retains hidden state by design.

Compare memory before and after the call. If current allocated memory remains high, inspect references first. If current allocation falls but driver allocation or system pressure does not, the allocator and operating system are reporting different layers of memory.

Dynamic shapes make this harder to interpret. A loop that alternates sequence lengths, image sizes, or padding patterns can create a changing allocation history. Run a fixed-shape version with the same model and data type. If fixed shapes remain stable but changing shapes grow over time, the shape pattern is part of the reproduction.

Remove computation-graph and logging references

A common training mistake is storing tensors that still require gradients:

history.append(loss)
predictions.append(output)

Those objects can keep their computation graphs reachable across iterations. Store detached values instead when the research workflow only needs summaries:

history.append(loss.detach().cpu().item())
predictions.append(output.detach().cpu())

The correct form depends on what must be preserved. Moving a full prediction to CPU still consumes host memory. Converting a scalar loss to a Python number releases the tensor reference, but it also removes information needed for later gradient operations. Make that choice explicit.

For inference, check whether gradients are disabled for the complete inference region:

with torch.inference_mode():
    output = model(inputs)

For training, do not wrap the forward pass in inference mode. Instead, inspect gradient clearing, optimizer ownership, and recurrent hidden states. A hidden state passed from one iteration to the next may intentionally preserve history unless it is detached at the required boundary.

Delete nonessential result collection and run a minimal loop. If memory stabilizes, add logging and output storage back one component at a time. The pass condition is not “the process did not crash once.” The pass condition is a stable memory pattern for the same controlled iteration range, with outputs still matching the intended experiment.

Treat CPU fallback as a separate execution path

MPS support is not identical to CPU or CUDA support. Some operators may be unavailable, limited, or handled through a fallback path. The MPS backend notes explain the backend model, while the MPS environment variable documentation defines relevant controls and their meanings.

Do not enable an environment variable simply because an online workaround claims it removes an error. First record:

  • Which operators are present in the failing model.
  • Whether fallback is enabled.
  • Where tensors move between CPU and MPS.
  • Whether inputs, model parameters, and intermediate results share the expected device.
  • Whether the output differs between the MPS and CPU paths.

Use a minimal reproduction containing only the suspected operator or block. Run it on MPS and CPU with the same small input. Record the failure stage and output summary. If CPU fallback allows the program to continue, that is not automatically equivalent to a native MPS execution. It may add copies, alter memory pressure, and change numerical behavior.

A user report in PyTorch GitHub issue 181213 can help identify a similar failure pattern, but an issue is evidence of a report under particular versions and conditions, not proof of a universal PyTorch 2.14 defect. Check its version, hardware, reproduction code, and current status before applying the conclusion to a research project.

Use a clean Apple Silicon environment before choosing a long-term path

When a lab has no Mac, the most efficient next step may be a short-lived clean Apple Silicon environment rather than changing the shared project immediately. A remote real Mac can isolate four variables that are often mixed together:

  • The PyTorch version and lock file.
  • macOS and Apple Silicon behavior.
  • Old package caches and unrelated background software.
  • The exact script and input used in the reported failure.

If you need remote access, review the JexMac remote Mac access options before committing to a longer setup. The goal is not to replace a Linux GPU cluster. It is to determine whether the MPS failure reproduces under controlled conditions.

Prepare an acceptance package:

  • Environment lock file and complete version output.
  • Minimal reproduction script.
  • Small input sample or a legally shareable synthetic input.
  • Model configuration and random seed.
  • Memory logs for current allocation, driver allocation, and system pressure.
  • Full error output.
  • Output summary from MPS and the Linux GPU reference path.
  • A note describing any CPU fallback or explicit device transfer.

Then follow this sequence:

  1. Create the clean environment and install only the locked dependencies.
  2. Run the minimal script without changing the workload.
  3. Repeat with one reduced variable, such as batch size or input shape.
  4. Remove output retention and test fixed versus changing shapes.
  5. Test the suspected operator block on MPS and CPU.
  6. Have a second person rerun the script from the lock file and acceptance package.
  7. Decide whether to fix the code, lock the working version, or retain a dual-track workflow.

If the error appears only in one PyTorch or macOS combination, preserve that combination in the report. Do not announce that Apple Silicon and Linux GPU results are equivalent merely because both complete the same script. Equivalence requires matching inputs, outputs, tolerances, device paths, and relevant numerical behavior.

The final troubleshooting checklist

Use this checklist before changing the model or declaring the environment unusable:

  • [ ] Record PyTorch 2.14, Python, macOS, Apple Silicon, model, task shape, and the complete error.
  • [ ] Log current allocated memory and driver allocated memory at consistent points in the loop.
  • [ ] Check macOS memory pressure separately from PyTorch allocator values.
  • [ ] Run a minimal fixed-shape workload with the original random seed.
  • [ ] Reduce only one workload variable and record whether the failure boundary moves.
  • [ ] Search lists, loggers, losses, predictions, and hidden states for retained tensors.
  • [ ] Check inference mode, gradient clearing, detach(), and recurrent state ownership.
  • [ ] Test empty_cache() only after removing known unused references.
  • [ ] Identify unsupported operators, CPU fallback, and explicit device transfers.
  • [ ] Reproduce the issue in a clean Apple Silicon environment.
  • [ ] Compare the MPS result with the Linux GPU result without claiming equivalence too early.
  • [ ] Have a second person rerun the locked environment and minimal script.

A clean pass means the team can explain why the original failure occurred, what changed, and which research conclusions remain comparable. A process that merely survives after disabling a safeguard is not a reliable pass.

When a remote Mac is the economical next test

The current setup may be a shared Linux GPU, a personal Windows machine, or an unclean Apple Silicon laptop. Each has a real limitation here: Linux may not reproduce the MPS allocator path, Windows cannot validate macOS behavior, and a personal Mac may contain old packages, background processes, or insufficient control over the environment.

For a short reproduction, renting a Mac through JexMac can be more precise than buying hardware or altering a shared lab machine. It provides a real Apple Silicon macOS environment for the minimal script, memory logs, and acceptance package described above. After the cause is confirmed, the team can return to Linux GPU for sustained training, continue renting for macOS validation, or keep both paths for cross-platform testing. See the JexMac pricing information when comparing a temporary validation session with the cost and maintenance burden of purchasing another Mac.

The decision should follow the evidence: use a remote Mac when the missing variable is macOS or Apple Silicon behavior; use Linux GPU when the workload is a long, stable training run; retain both when platform compatibility is part of the research deliverable.

Bare metal · 1–5 min delivery

Move Your Research Training to a Dedicated Mac

Rent a dedicated Mac mini M4 from JexMac when your local unified memory cannot handle your training workload.

Standard spec
ChipApple M4 · 38 TOPS
CPU10-core (4P + 6E)
Memory16 GB unified memory
Network1 Gbps dedicated
SLA99.9% uptime
Delivery1–5 min auto provision