DEV Community

Cover image for MPS backend out of memory on a Mac: what the numbers mean and how to set the watermark
Rodion Kazennov
Rodion Kazennov

Posted on Originally published at allkeep.org

MPS backend out of memory on a Mac: what the numbers mean and how to set the watermark

When a PyTorch job runs out of memory on a Mac, the error ends with a piece of advice:

RuntimeError: MPS backend out of memory (MPS allocated: 18.50 GiB, other allocations: 448.00 KiB, max allowed: 18.72 GiB). Tried to allocate 512.00 MiB on shared pool. Use PYTORCH_MPS_HIGH_WATERMARK_RATIO=0.0 to disable upper limit for memory allocations (may cause system failure).
Enter fullscreen mode Exit fullscreen mode

I wanted to know what following that advice does, so I filled MPS memory in 512 MiB steps on my 48 GB M5 Pro (macOS 26.5.2, PyTorch 2.14.0), once per setting, under a guard that stops the process when swap grows by 2 GB. Two things came out of it. On this Mac the default limit is higher than the RAM, so a job that outgrows memory slides into swap and never gets to this error. And lowering the limit takes two variables, not one.

Where "max allowed" comes from

max allowed is the limit: PYTORCH_MPS_HIGH_WATERMARK_RATIO times what torch.mps.recommended_max_memory() returns. On my Mac that is 37.44 GiB, and 0.5 × 37.44 = 18.72 GiB, the number in the error above. MPS allocated matched torch.mps.current_allocated_memory() in my log, the memory held by tensors. To see your own base number:

import torch
print(torch.mps.recommended_max_memory() / 2**30, "GiB")
Enter fullscreen mode Exit fullscreen mode

Why a 48 GB Mac swaps first

PyTorch's documentation gives the default high ratio as 1.7. On my Mac that puts the limit at about 63.6 GiB, more memory than the machine has. This is what each setting did:

Tensors held when each run stopped: with the default ratio, 0.0 and 1.0 the Mac went into swap at 31 to 34 GiB and the guard stopped the run; with 0.8 and 0.5 PyTorch raised out of memory at its limit

setting what stopped the fill tensors held swap during the run
default (1.7) the guard 38.5 GiB 6.1 → 12.0 GB
0.0 the guard 36.0 GiB 5.1 → 9.8 GB
1.0, low 1.0 the guard 34.5 GiB 4.6 → 8.7 GB
0.8, low 0.8 PyTorch, max allowed 29.95 GiB 29.5 GiB no change
0.5, low 0.5 PyTorch, max allowed 18.72 GiB 18.5 GiB no change

With the default, with 0.0 and with 1.0, swap started to grow once the process passed 33.5 to 36.2 GB, and PyTorch never raised: its limit was further away than the end of the RAM. So 0.0, the advice inside the error, changed nothing I could see here. The limit was already out of reach, and nothing in PyTorch stopped the run.

Setting it: two variables

Setting only the high ratio fails at the first MPS call:

$ PYTORCH_MPS_HIGH_WATERMARK_RATIO=0.8 python fill.py
RuntimeError: invalid low watermark ratio 1.4
Enter fullscreen mode Exit fullscreen mode

The low ratio defaults to 1.4 on Apple Silicon and may not be above the high one (unless the high one is 0.0, which turns the limit off). Set both:

PYTORCH_MPS_HIGH_WATERMARK_RATIO=0.8 PYTORCH_MPS_LOW_WATERMARK_RATIO=0.8 python run.py
Enter fullscreen mode Exit fullscreen mode

From Python, set them before the first tensor goes to mps. PyTorch reads them when MPS starts, not at import torch, and ignores changes made after that:

import os
os.environ["PYTORCH_MPS_HIGH_WATERMARK_RATIO"] = "0.8"
os.environ["PYTORCH_MPS_LOW_WATERMARK_RATIO"] = "0.8"
import torch
Enter fullscreen mode Exit fullscreen mode

The documentation calls the low ratio a soft limit and says that "setting 0.0 disables adaptive commit and garbage collection". I set it equal to the high one and did not measure it on its own.

Picking a value

The limit should stay below the memory your Mac really has free, with your usual apps open. On mine 1.0 (37.44 GiB) was already too much, and 0.8 (29.95 GiB) gave a clean error with no swap. After a clean error you can lower the batch size or the resolution and try again; a run sliding into swap slows down the whole Mac first.

A trap when you test this

My first run filled the tensors with torch.ones. With the default setting it reached the 46 GiB cap I had set, with no error and no swap at all: macOS compresses pages of identical values almost to nothing. Model weights don't compress like that, so a memory test on a Mac needs random data.

When the model really doesn't fit

A clean error only tells you the model doesn't fit. For a pipeline of several models, loading only the models of the current stage does more than any ratio, which is why I wrote stageload. The fill script and the traces of every run above are in its repository, in bench/results/2026-10-13-mps-watermark.

Top comments (0)