Field note · macOS internals

How much VRAM does a Mac have? One pool, two ceilings

There is no separate VRAM on Apple Silicon. The GPU draws from the same pool as everything else, and macOS decides how much of it the GPU may hold.

Measured on a MacBook Pro, Apple M5 Pro, 48 GB, macOS 27.0 (build 26A5421a). Metal reported these limits on this machine only. The ratios below are observations from one configuration, not documented guarantees, and Apple may change them between releases.

On a 48 GB M5 Pro, Metal reports a recommended maximum GPU working set of 40.20 GB and a maximum single buffer of 30.15 GB. Physical memory is 51.54 GB. Those three numbers explain most of what surprises people when a model that should fit does not.

There is no dedicated video memory to run out of. There is one pool, and several different limits applied to it.

One pool, several ceilings

A discrete GPU has its own memory, and loading a model means copying it across a bus into that separate space. Apple Silicon does not work this way. The CPU and GPU address the same physical memory, so a tensor the CPU wrote is already where the GPU needs it. That is the advantage, and it is a real one.

The consequence is that GPU memory and system memory compete. Every gigabyte the GPU holds is a gigabyte the rest of the system does not have.

Metal exposes the ceilings directly. Apple documents hasUnifiedMemory as indicating whether the GPU shares all of its memory with the CPU, recommendedMaxWorkingSetSize as an approximation of how much memory the device can allocate without affecting runtime performance, and maxBufferLength as the largest amount it can allocate to a single buffer.

let device = MTLCreateSystemDefaultDevice()!
device.hasUnifiedMemory              // true
device.recommendedMaxWorkingSetSize  // 40200896512
device.maxBufferLength               // 30150672384
Metal limits, M5 Pro 48 GB, macOS 27.0
ValueBytesSizeShare of physical memory
Physical memory51,539,607,55251.54 GB100%
recommendedMaxWorkingSetSize40,200,896,51240.20 GB78.0%
maxBufferLength30,150,672,38430.15 GB58.5%

Two of those ratios are worth stating precisely, because the difference between "about" and "exactly" is the kind of thing that gets quoted back later.

The maximum buffer length is exactly three quarters of the recommended working set. That is an integer relationship, not an approximation: 30,150,672,384 multiplied by 4 equals 40,200,896,512 multiplied by 3.

The recommended working set is not exactly 78% of physical memory. It is 78.000005%, which rounds to 78.0% but is not the clean fraction it appears to be. Whatever macOS is computing here, it is not a flat 0.78 multiple of installed RAM.

Both are observations from one Mac on one macOS build. Do not assume the same fractions on a different machine or a different release. Query them.

The two ceilings fail differently

The working set limit is a total. It is the amount Metal advises you keep resident across all your allocations combined, and Apple's wording is deliberate: it is an approximation of what the device can allocate without affecting its runtime performance, not a cap that is enforced. Exceeding it does not throw an error. It degrades: the system starts making room by other means, and performance falls away.

The buffer length limit is per allocation, and it is a hard boundary. Apple defines it as the largest amount of memory the device can allocate to a buffer instance, and a single buffer larger than maxBufferLength cannot be created. On this machine that ceiling is 30.15 GB, well below the 40.20 GB working set.

This is why the failure mode people hit is often not the one they expected. A 35 GB model can be under the total working-set ceiling and still fail, if the runtime attempts to place any single tensor or weight block in one allocation larger than the buffer limit. Most inference runtimes shard weights across many buffers specifically to avoid this, which is why it usually works. When it does not, the error is about buffer creation, not about memory exhaustion.

What is left for everything else

The working-set ceiling is not the amount of memory available for a model. It is an upper bound the GPU is permitted to reach, on a machine that still has to run everything else.

On the machine used here, an ordinary desktop session with a browser, an editor, and a handful of helpers occupied a 27.18 GB working set before any inference started. Physical memory is 51.54 GB. That leaves roughly 24 GB of genuinely available memory, which is well under both Metal ceilings.

The binding constraint in practice is usually not the Metal limit. It is the Metal limit minus whatever the rest of the system is already holding. A model sized against recommendedMaxWorkingSetSize alone will be sized wrong on a machine that is actually in use.

On raising the limit

macOS exposes iogpu.wired_limit_mb, which reads 0 by default and is widely discussed as a way to let the GPU hold more memory. It is undocumented, it is not a supported interface, and raising it takes memory away from the system that macOS was reserving deliberately. This article does not recommend a value. If you change it, you own the resulting stability.

Watching what the GPU is actually holding

The GPU driver publishes its own statistics through IORegistry. On this Mac the accelerator entry carries both utilisation and memory keys:

ioreg -r -c IOAccelerator -d 1 | grep -A 30 PerformanceStatistics

The keys present on this machine included Device Utilization %, Renderer Utilization %, Tiler Utilization %, In use system memory, and Alloc system memory. Searoom reads from this same source and takes the highest value among the key names it recognises. Device Utilization % and Renderer Utilization % are in that set; Tiler Utilization % is not.

This is best-effort access, and the qualifier is not decoration. Key names and availability vary by Mac model and macOS release. Where no supported key exists, Searoom reports GPU utilisation as unavailable and substitutes no value. A zero would be a claim that the GPU is idle, which is a different statement from not knowing.

Sizing implications

Three practical consequences follow from all of this.

Model size is not the whole requirement. Weights are the largest term, but the KV cache grows with context length and also lives in the same pool. A model that loads comfortably at a short context can exceed available memory at a long one, with no change to the model itself.

Free memory at the moment you load is not the number that matters. macOS will reclaim cache to satisfy a large allocation, as it did in the memory pressure measurements, where free pages fell from 4.05 GB to 0.24 GB without anything going wrong. Available memory is the meaningful figure.

Compression will not save you here. macOS absorbed 8.59 GB of zero-filled test allocation into 3.75 GB of working-set growth, but model weights are high-entropy and do not compress usefully.

Resident cost is not simply the file size either. On this machine, loading a 14.33 GB GGUF moved the working set by 11.50 GB, about 80% of the file, most likely because llama.cpp memory-maps its weights so the pages are file-backed rather than anonymous. A model in MLX format behaved differently. The figure to size against is measured resident cost for your runtime and format, not the file listing. The two-model measurement has the numbers.

Sizing guidance with real measurements across specific models and RAM tiers needs hardware this site does not yet have access to across the range. Rather than publish estimates dressed as results, those articles are waiting on the measurement runs. The definitions used above are in the metric reference.

Sources