How Much Unified Memory Your Mac Gives the GPU: Raising the Wired Limit for Large Local Models (2026)
You bought a Mac with 128GB of unified memory to run a big model locally, and then the model that should fit refuses to stay resident and your tokens per second collapse. The reason is that macOS does not hand the whole pool to the GPU. There is a wired limit, the amount of memory the graphics side is allowed to pin, and by default it sits well below the installed capacity so the CPU, the window server, and the rest of the system keep breathing room. When the weights plus the KV cache push past that ceiling, the runtime spills to slower paths and the decode rate falls off a cliff, which people misread as a bandwidth or quantization problem when it is really an allocation problem. This post measures the default headroom on M4 and M5 machines, shows exactly where common model sizes cross the line, and gives the one sysctl knob that raises the ceiling safely, along with how far you can push it before you starve the OS. If you are speccing or tuning a machine to hold a large model resident, this is the number that actually decides whether it fits.
How Much Unified Memory Your Mac Gives the GPU: Raising the Wired Limit for Large Local Models (2026)
If you bought a Mac with a lot of unified memory specifically to run big models locally, there is a number that decides whether the model fits, and it is not the number on the box. macOS does not let the GPU pin all of installed memory. It enforces a wired limit, a ceiling on how much the graphics side can hold resident, and by default that ceiling sits below capacity so the operating system keeps working memory for itself. When your weights plus the KV cache climb past the ceiling, the runtime spills and your decode speed falls off a cliff. Here is what the default gives you, where common models cross the line, and how to raise it safely.
What the Default Actually Reserves
On Apple Silicon the CPU and GPU share one physical pool of memory, but sharing does not mean the GPU may take all of it. macOS caps the amount of memory that can be wired for GPU use, and the default cap is a fraction of installed capacity. The rule of thumb the system has used is roughly this: on machines with more memory, the GPU may pin most of it, but never all, because the window server, the kernel, your editor, and the runtime process itself all need resident pages that are not GPU allocations. The table below is the practical headroom we measured across current tiers, expressed as how much a model plus its runtime can safely hold before the OS starts to fight back.
| Installed unified memory | Approx default GPU wired ceiling | Practical model + KV budget | Comfortable model size at 4-bit |
|---|---|---|---|
| 36 GB | ~27 GB | ~24 GB | up to ~30B |
| 48 GB | ~36 GB | ~33 GB | up to ~34B |
| 64 GB | ~48 GB | ~45 GB | up to ~70B tight |
| 96 GB | ~72 GB | ~68 GB | 70B comfortable |
| 128 GB | ~96 GB | ~90 GB | 70B with long context, 100B+ dense tight |
Read the last two columns together. The installed number is not the budget. The budget is what is left after macOS holds back its reserve, and then after your own runtime process, the model file mapping, and the KV cache take their share. A 128GB machine does not give a 128GB model home; it gives you roughly 90GB of comfortable resident room once everything else is accounted for. That gap is where people get surprised.
Why Spilling Wrecks Decode Speed
The reason this matters so much is that crossing the ceiling does not fail loudly. It degrades. When the GPU cannot wire all the pages it wants, the system starts moving pages in and out, and on a machine with no discrete VRAM that means the model is effectively being fed through slower memory paths instead of staying pinned and hot. Decode is already the memory bound phase of inference, where every generated token requires reading the active weights out of memory, so anything that slows the path to those weights hits tokens per second directly. We covered why decode speed tracks memory bandwidth almost linearly, and spilling is the ugly version of the same physics: you have not lowered the rated bandwidth, but you have inserted a much slower step into the read path for a portion of the weights, and the average collapses.
The signature is unmistakable once you know it. A model that decodes at a healthy rate for the first few hundred tokens starts to sag, the fans do not necessarily spin up because this is not a thermal problem, and memory pressure in Activity Monitor turns yellow or red. If you have ever watched a 70B model run beautifully and then stutter the moment you loaded a long document into context, you watched the KV cache push the total past the wired ceiling. The weights fit; the weights plus a large KV cache did not.
Where Common Models Cross the Line
The budget has two moving parts: the weights, which are fixed once you pick a model and quantization, and the KV cache, which grows with context length and batch size. The weights are easy to estimate, roughly the parameter count times bits per weight divided by eight. The KV cache is the part people forget, and it is the part that turns a comfortable fit into a spill halfway through a session.
| Model and quant | Weight footprint | KV cache at 32K ctx | KV cache at 128K ctx | Total at 128K |
|---|---|---|---|---|
| 8B 4-bit | ~4 GB | ~1 GB | ~4 GB | ~8 GB |
| 32B 4-bit | ~18 GB | ~3 GB | ~12 GB | ~30 GB |
| 70B 4-bit | ~40 GB | ~5 GB | ~20 GB | ~60 GB |
| 70B 8-bit | ~72 GB | ~5 GB | ~20 GB | ~92 GB |
Put that last row against the 128GB machine's ~90GB comfortable budget and you can see the trap. A 70B model at 8-bit weights fits fine on its own, but push the context toward 128K and the KV cache alone adds another twenty-odd gigabytes, and now you are over the ceiling on a machine that looked like it had room to spare. This is why KV cache growth with context length is not a footnote; it is often the exact thing that decides whether your configuration stays resident. Mixture-of-experts models change the arithmetic again, because their total parameter footprint sits in memory even though only a fraction is active per token, so a large MoE can eat the whole budget with weights before the KV cache even enters the picture.
Raising the Ceiling
macOS exposes the wired limit as a tunable, and raising it is the single most effective fix when your model would fit but the default reserve is squeezing it. The knob is iogpu.wired_limit_mb, set through sysctl, expressed in megabytes.
# Check the current setting (0 means the system default is in effect)
sysctl iogpu.wired_limit_mb
# Raise the GPU wired ceiling to 112 GB on a 128 GB machine
sudo sysctl iogpu.wired_limit_mb=114688
# Revert to the system-managed default
sudo sysctl iogpu.wired_limit_mb=0
The value is a hard cap on what the GPU may wire, so setting it higher gives the model more room to stay resident, but it does so by shrinking the guaranteed reserve for everything else. The setting also does not persist across a reboot on its own; it is a runtime sysctl, so if you want it to survive a restart you script it to run at login rather than trusting it to stick.
How Far You Can Safely Push It
The temptation is to set the ceiling to installed capacity and be done, but that starves the OS and the failure mode is worse than a spill: the whole machine hitches, the window server stutters, and in the extreme the system can become unstable under memory pressure. Leave a real reserve. The guidance below is the reserve we hold back on machines that run large models as a daily workload.
| Installed memory | Reserve to leave for the OS | Safe wired ceiling to set |
|---|---|---|
| 64 GB | ~8 GB | ~56 GB |
| 96 GB | ~10 GB | ~86 GB |
| 128 GB | ~14 GB | ~114 GB |
| 192 GB | ~18 GB | ~174 GB |
| 256 GB | ~24 GB | ~232 GB |
The reserve grows a little in absolute terms with capacity because the bigger machines tend to run bigger everything alongside the model: more browser tabs, a running dev server, a database in the background. Set the ceiling, then watch memory pressure under your real workload for a session. If pressure stays green with the model, the KV cache at your working context, and your normal apps all loaded, the ceiling is right. If it goes yellow, back the ceiling down until it does not. The goal is the largest resident model budget that still leaves the system comfortable, not the theoretical maximum.
Reading Your Own Numbers
If a model that should fit is spilling, walk the budget in order. Estimate the weight footprint from parameters and quantization. Add the KV cache for your actual context length, not the model's maximum. Compare the total against the practical budget for your machine, which is installed memory minus the OS reserve, not installed memory. If the total exceeds the budget, you have three levers: drop to a smaller quantization to shrink the weights, cap the context length to shrink the KV cache, or raise the wired limit to enlarge the budget. Raising the limit is the right move only when the model genuinely fits within a safe ceiling and the default reserve was the only thing in the way. If the model is simply too big for the machine even at a safe ceiling, no sysctl saves you, and the honest answer is a smaller model, a heavier quant, or a bigger box.
When This Applies to Your Stack
If you are putting local inference into a real product, treat the wired limit as a first-class part of the capacity plan, not an afterthought you discover in production. Size the machine so the model plus the KV cache at your worst-case context sits comfortably under a safe wired ceiling with the OS reserve intact, and set the ceiling deliberately rather than trusting the default to be generous. The mistake we see most often is a team buying a high-memory Mac, assuming the whole pool is model budget, and then blaming the runtime or the quantization when decode sags under long context. The budget was always smaller than the sticker, and the KV cache was always going to grow. Plan for the resident total, tune the ceiling to your real workload, and leave the OS its reserve.
If your team is building a local or hybrid inference tier and wants the hardware sized to the resident footprint of your actual model and context budget rather than a spec-sheet capacity number, Contra Collective builds AI infrastructure that models the memory ceiling against your real request pattern, so the machine you deploy holds the model you intended to run.
FAQ
Why does my Mac not use all its unified memory for the model? Because macOS caps how much memory the GPU may wire, and the default cap sits below installed capacity to guarantee the operating system, window server, and your other processes have resident memory. The model gets a large fraction of the pool, but never all of it, so a 128GB machine offers roughly 90GB of comfortable model-plus-KV budget by default rather than the full 128GB.
What is iogpu.wired_limit_mb? It is the macOS sysctl that sets the ceiling, in megabytes, on how much memory the GPU is allowed to wire. Raising it lets a large model stay resident that the default reserve was squeezing out, but it shrinks the guaranteed headroom for everything else, so you set it high enough for the model and no higher than a safe reserve allows.
Why does my model slow down only on long prompts? Because the KV cache grows with context length, and a long prompt can push the weights-plus-cache total past the wired ceiling even when the weights alone fit comfortably. The weights were resident; the weights plus a large KV cache were not, so the system started spilling and decode speed dropped.
How much memory should I leave for macOS? Leave roughly 8GB on a 64GB machine and scale up to around 14GB on 128GB and more on larger configurations. Set the wired ceiling to installed memory minus that reserve, then watch memory pressure under your real workload and back the ceiling down if pressure turns yellow.
Does raising the wired limit make inference faster? Only indirectly. It does not increase memory bandwidth or compute, so a model that already fits sees no speedup. What it prevents is the slowdown from spilling: by keeping a large model resident that would otherwise have been squeezed past the default ceiling, it lets decode run at the machine's real rate instead of the degraded spilling rate.
More from the lab.
The Model Context Protocol's STDIO Problem: Inside the Latest Remote Code Execution Disclosure
Security researchers at OX Security found that the Model Context Protocol's STDIO transport interface runs any process command passed to it on the host system before it ever confirms the subprocess is a valid MCP server, an execute first, validate never pattern that completes an injected command even when the caller eventually gets an error message back. The scope is not small: 150 million downloads across package registries, more than 7,000 publicly accessible MCP servers, and up to 200,000 vulnerable instances across every officially supported SDK, Python, TypeScript, Java, and Rust. Anthropic's response, after OX disclosed in January 2026, was to update documentation and call the behavior intentional. Here is what that actually means if your team runs MCP servers over STDIO.
OpenAI's Decisions API: Real-Time Agentic Decision-Making for Enterprise Workflows, Explained
Buried inside OpenAI's DevDay 2026 announcement list alongside GPT-6.1 Sol and Dots agents was a smaller, more technically specific release: a Decisions API built for real time decision making against predefined answer sets. Where the Agents API and a general purpose agent loop are built for open ended reasoning across an unbounded task, the Decisions API is built for the opposite case, a small number of valid outcomes and a requirement that the choice happen fast. This post works through what that distinction actually means in practice and where a bounded decision primitive fits into an existing agentic workflow.
Offline Batch Inference on an M5 Max: Maximizing Overnight Throughput for a Prompt Backlog (2026)
Most writing about local inference assumes a human is on the other end, watching tokens appear and judging the model by how fast the first one lands. Batch work is the opposite situation, and it is more common than the interactive framing suggests: you have a backlog of tens or hundreds of thousands of prompts, nobody is waiting on any single one, and the only thing that matters is how much of the pile you can clear before morning. Product descriptions for a catalog, classification over a support archive, embeddings and summaries for a corpus, synthetic data for a fine tune. In that regime latency is irrelevant and throughput is everything, and the settings that make a local server feel responsive to a person actively work against you, because they optimize for the wrong number. Running one prompt at a time on an M5 Max leaves most of the machine idle, since a single decode stream cannot saturate the memory bandwidth the chip has to offer. The job is to keep many sequences in flight so the expensive weight reads are amortized across all of them, and to arrange the backlog so the GPU never waits on padding, scheduling, or a slow tail. This post is about doing that deliberately: what actually bounds batch throughput on Apple Silicon, how far batching takes you before memory stops you, and the operational scaffolding that turns a fragile overnight run into one you can resume when it dies at 3am.