---
title: "iogpu.wired_limit_mb on Mac: Raising the Metal Memory Ceiling"
description: "sudo sysctl iogpu.wired_limit_mb raises how much RAM macOS will let the GPU wire down. What it does, how to set it safely, and the reserve you are spending."
date: 2026-08-15
author: "Ben Racicot"
tags: ["Apple Silicon", "Metal", "Unified Memory", "Local LLM", "macOS", "Troubleshooting"]
type: "article"
canonical: "https://modelpiper.com/blog/iogpu-wired-limit-mb-mac"
---

# iogpu.wired_limit_mb on Mac: Raising the Metal Memory Ceiling

> sudo sysctl iogpu.wired_limit_mb raises how much RAM macOS will let the GPU wire down. What it does, how to set it safely, and the reserve you are spending.

## TL;DR

macOS caps how much unified memory the GPU may wire down, and on our M2 Max that cap leaves 7.04GiB of installed RAM out of reach. Running sudo sysctl iogpu.wired_limit_mb=28000 moves the cap, which is how a model that misses by a gigabyte or two becomes loadable. It is also the headroom macOS uses to stay responsive under pressure, so treat it as a session change, put it back with =0, and try a smaller quantization or a shorter context first.

You found a model you want to run, its file is a couple of gigabytes smaller than the RAM in your Mac, and it still refuses to load. Activity Monitor shows free memory sitting right there. The loader will not touch it.

What you are hitting is a ceiling macOS puts between installed memory and the GPU, and there is one line that moves it:

```
sudo sysctl iogpu.wired_limit_mb=28000
```

That line is real and it works. It also gets passed around forums with almost no account of what it costs, which is the part that matters, because the thing it spends is the memory your Mac uses to stay alive under pressure. We measured the default on our own machine, worked out what the reserve is for, and this is where we think the line sits.

## What does sudo sysctl iogpu.wired\_limit\_mb do?

It sets the maximum memory, in megabytes, that macOS will let the GPU wire down, meaning pin into physical RAM so it cannot be compressed or swapped out. The default value is 0, which means the kernel derives a limit from installed memory rather than meaning unlimited. Raising it lets Metal hold more of your RAM, which is how a model that misses the ceiling by a gigabyte or two becomes loadable.

Each part of the name explains a constraint. `iogpu` is the kernel side of the GPU stack. `wired` is a memory state, and wired pages are the one category macOS cannot reclaim when it runs short. `limit_mb` is a ceiling, not an allocation, so raising it consumes nothing on its own. It changes what a later allocation is permitted to do.

On Apple Silicon this bites harder than the equivalent knob would on a PC. There is no second pool. The CPU and the GPU address the same DRAM, so every byte you let the GPU wire is a byte the rest of the machine no longer has. Our piece on [what the GPU offload slider actually does](/blog/lm-studio-gpu-offload-mac) covers why unified memory reshapes this whole problem.

## How much memory is your Mac holding back?

Measure it instead of trusting the rule of thumb. On our M2 Max with 32GiB installed, running macOS 26.5.2, Metal reports a recommended maximum working set of 26,800,603,136 bytes. That is 24.96GiB, or 78 percent of installed memory, and it leaves 7.04GiB the GPU is not allowed to touch. That gap is exactly what the sysctl spends.

Two commands give you your own numbers. The first reads the current limit, and needs no privileges:

```
sysctl iogpu.wired_limit_mb hw.memsize
```

An untouched machine answers `0` for the limit. That is the default, not a failure to read it.

The second asks Metal what it is actually offering, which is the number inference engines read when they decide whether your model fits. Save it as `ceiling.swift` and run `swift ceiling.swift`, no Xcode project required:

```
import Metallet d = MTLCreateSystemDefaultDevice()!print(d.name, d.recommendedMaxWorkingSetSize)
```

Ours prints `Apple M2 Max 26800603136`. Subtract that from `hw.memsize` and you have your machine's reserve, which is the only number that should decide how far you go. The figure quoted everywhere is 75 percent. Ours measured 78. That is close enough to sound right and wrong enough to matter when you are two gigabytes short.

The kernel limit and the number Metal publishes move together, so run that snippet before and after you change the sysctl. If the reported ceiling has not moved, nothing downstream will help either, and you would rather learn that in ten seconds than after a reboot.

## A 27B model on a 32GB Mac, with the arithmetic

Qwen3.8 27B is a useful case because it is dense rather than a mixture of experts. All 27.78 billion parameters are resident for every token, so there is no sparsity to hide behind. Weight arithmetic is parameters times bits per weight, divided by eight:

-   **Q4\_K\_M**, roughly 4.85 bits per weight - 15.7GiB
-   **Q5\_K\_M**, 5.65 - 18.3GiB
-   **Q6\_K**, 6.56 - 21.2GiB
-   **Q8\_0**, 8.5 - 27.5GiB

Against our measured 24.96GiB ceiling, Q4 is comfortable and Q8 is unreachable on this machine whatever you set, because 27.5GiB of weights under a 28GiB ceiling leaves nothing for anything else. Q6 is the interesting one. The weights fit with 3.76GiB to spare, and that spare is where the KV cache, the compute buffers and your whole context have to live. This model's context window goes to 262,144 tokens. You will run out long before you get near it.

Raise the ceiling to 28,000 and that same Q6 model has about 6.1GiB above its weights instead of 3.76GiB. That is the honest value of this setting. It does not rescue anything that was hopeless. It buys back context on a model that was already close.

## How do you set it safely?

Four steps, and the fourth is the one people skip.

## What is the risk?

Wired memory is the one thing macOS cannot page out, compress or reclaim, so raising the limit takes headroom away from the kernel, the window server and the memory compressor. Past a certain point the failure is not an error dialog. It is stalling, an unresponsive cursor, and in the worst case a machine that goes down and takes unsaved work with it.

The part that catches people is that this is not a normal out-of-memory condition. When an app asks for heap memory it cannot have, it gets a failure it can catch, report and recover from. A wired-memory shortfall on Apple Silicon is a kernel-level fault, so the process that caused it is not in a position to tell you anything. The error message you were annoyed about is the good outcome. It is what a system with headroom does instead of freezing.

Three more things are worth knowing before you type it:

**It is global.** The limit is a system-wide kernel setting, not a per-process one. Every GPU-using app on the machine gets the new ceiling, including whatever your browser is doing with hardware acceleration.

**It applies to more than your model.** The reserve was covering the display pipeline and everything else drawing on screen. A large model plus a video call plus a few hundred browser tabs is a different situation from a large model on an idle desktop.

**Save your work first.** Treat this the way you would treat any kernel setting. If the machine is doing something you cannot afford to lose, do it later.

## Does it survive a reboot?

No. The value resets to 0 at every boot, and that is a feature rather than a limitation. A machine that will not start cleanly because of a setting you made three weeks ago is a much worse problem than typing one line again.

You can make it stick with a launch daemon that runs the same command at boot, and we would rather you did not. The setting is safe in the shape of a session: raise it, run the model, put it back. Left permanently raised, it removes your Mac's headroom on every boot, including the boots where you are not running a model at all and get no benefit from it whatsoever. Put it back with `sudo sysctl iogpu.wired_limit_mb=0` when you are done, and if you forget, a restart does it for you.

## Try the cheaper fixes first

This setting is fourth on the list, not first. The three above it cost you nothing in system stability, and two of them cost almost nothing in quality either.

**Drop one quantization level.** Q6 to Q5 on a 27B model saves nearly 3GiB and the quality difference is difficult to notice in ordinary use. Q8 to Q6 saves over 6GiB.

**Cut the context.** The KV cache grows linearly with context length and comes from the same budget as the weights. A model that fails at 32K often loads at 8K with no other change, and most work does not need the long window that failed.

**Quantize the cache instead of the model.** An 8-bit KV cache halves the cache footprint for very little quality cost, which is often the whole shortfall on its own. We went through the numbers in [the KV cache piece](/blog/ollama-kv-cache-quantization).

Work down that list and the model either loads or it does not, and if it does not you now know the gap is real rather than something you could have quantized away.

## Do MLX models need this too?

Sometimes, and they hit the wall differently from llama.cpp models. MLX sets its own internal ceiling at the lower of 1.5 times the Metal recommendation and 95 percent of installed memory, which on our 32GiB machine works out to 30.4GiB. That is well above the 24.96GiB the kernel will wire by default, so MLX will happily keep allocating past the point where a llama.cpp build would have refused.

The practical consequence is that MLX gives you less warning. A llama.cpp engine that checks the Metal recommendation tends to fail early and legibly. MLX's own limit is loose enough that it is not the thing stopping you, so what stops you is the kernel, and that is the failure mode with the worst manners. If you are running MLX models near the ceiling, the sysctl is more likely to be relevant and the headroom rule matters more, not less.

One implementation detail decides your workflow here. Processes read the Metal recommendation once, at startup, and cache it. MLX stores it in a function-local static on first call. Change the sysctl while an app is running and that app carries on with the old number, so the order is always the same: set the limit, then launch the app.

## How far can you actually go?

Our answer, and it comes out of the measurement rather than out of a forum post: your machine's reserve is `hw.memsize` minus the Metal recommendation, and you should not spend more than about half of it. On our 32GiB M2 Max the reserve is 7.04GiB, so 28,000 is roughly the far edge of reasonable and leaves about 4.6GiB for everything that is not the model.

Raise it in steps rather than jumping to your maximum. Add a gigabyte, try the load, stop the moment it works. The value that just barely loads your model is the correct value, and every megabyte past it is headroom you gave up for nothing. Anyone quoting you a single number for all machines is quoting a number they measured on theirs.

## The alternative: read the ceiling, do not move it

Everything above is why ToolPiper does not read or set this sysctl, and will not add a slider for it. Our engine reads the live Metal working-set ceiling and your installed memory at startup, then sizes model suggestions against them, so the model list tells you what your Mac can hold before you spend the download. On the MLX side the same reading is a hard admission gate rather than advice. A load that would not fit is refused before a single allocation happens, instead of being attempted and taking the machine with it.

That is a deliberate split. Reading the ceiling is our job. Deciding to spend your system's reserve is yours, and it should be a decision you made on purpose with the numbers in front of you, not a default we shipped.

Models arrive as plain, named GGUF files in `~/Library/Application Support/ToolPiper/models/`, so nothing here locks you in. If you want to take the same file and run it under something else while the wired limit is raised, it is right there.

The honest limits: Apple Silicon and macOS 26 or newer, so if you are on an older system this is not the app for you yet. And none of this makes inference faster. The ceiling decides what fits. Memory bandwidth decides how fast it runs once it does, which is a [separate measurement entirely](/blog/local-llm-benchmarks-apple-silicon).

## Steps

### 1. Read your machine's real numbers first

Run `sysctl iogpu.wired_limit_mb hw.memsize` and the four-line Swift snippet above. You want three figures: the current limit (almost certainly `0`), your installed bytes, and what Metal is recommending. Installed minus recommended is your reserve, and it is the budget for everything that follows. Do not borrow someone else's number from a forum thread, because their machine has a different one.

### 2. Pick a value, and pick a small one

Take the Metal recommendation in megabytes and add roughly a gigabyte. That is your first attempt, not your maximum. On our 32GiB M2 Max the recommendation is about 25,559MB, so 26,500 is a sensible first step and 28,000 is the far edge of what we would run. Anything that leaves under about 4GiB for the rest of the system is asking for trouble.

### 3. Set it, then launch the app

`sudo sysctl iogpu.wired_limit_mb=26500`. The order matters, because processes read the Metal recommendation once at startup and cache it, so an app that was already running keeps the old ceiling. Set the limit, then start LM Studio or your engine, then load the model. If it still fails, add a gigabyte and repeat rather than jumping straight to your ceiling.

### 4. Put it back when you are done

`sudo sysctl iogpu.wired_limit_mb=0` restores the default. A reboot does the same thing, so the cost of forgetting is low, but do it deliberately anyway. Leaving the limit raised means every future boot runs with less headroom, including the boots where no model is loaded and you get nothing back for it.

## FAQ

### What is a safe value for iogpu.wired_limit_mb?

There is no single safe number, because it depends on your installed memory and on what else the machine is doing. The rule that survives contact with different Macs: measure your reserve as `hw.memsize` minus Metal's recommended maximum working set, then spend no more than about half of it. On our 32GiB M2 Max the reserve measured 7.04GiB, which puts the practical edge around 28,000. On a 64GiB machine the reserve is larger in absolute terms and the same halfway rule still applies. Raise it in one-gigabyte steps and stop at the first value that loads your model.

### Does iogpu.wired_limit_mb survive a reboot?

No. It resets to 0 on every boot. You can persist it with a launch daemon that runs the command at startup, but we recommend against it. The setting removes headroom the system uses to stay responsive, and there is no reason to pay that on boots where you are not running a large model. Set it for the session and put it back with `sudo sysctl iogpu.wired_limit_mb=0`.

### Why does my Mac only give the GPU about 75 percent of its RAM?

Because the remainder is wired-memory headroom the kernel keeps for itself, and unified memory means the GPU is borrowing from the same pool everything else uses. The exact fraction is not 75 percent on every machine. On our M2 Max with 32GiB it measured 78 percent, or 26,800,603,136 bytes out of 34,359,738,368. Query it on your own hardware with `recommendedMaxWorkingSetSize` rather than assuming, because the difference between 75 and 78 percent is a gigabyte on a 32GiB Mac.

### Will raising the wired limit make my model run faster?

No. The ceiling decides what fits, not how fast it runs. Token generation on Apple Silicon is bound by memory bandwidth, and the wired limit does not change bandwidth at all. The one indirect speedup is real but narrow: if the raised ceiling lets you run a larger quantization or keep a longer context resident rather than reprocessing it, you get a better result or fewer reprocessed prompts, which is not the same thing as faster inference.

### Is there an equivalent setting for older macOS versions?

Yes. Older systems used `debug.iogpu.wired_limit`, which takes a value in bytes rather than megabytes. Both names are present on macOS 26 and both read 0 by default. Use `iogpu.wired_limit_mb` on anything current, and check with `sysctl -a | grep iogpu` if you are unsure what your system exposes.
