~14 min · memory, working-set, wired-limit, iogpu, mlx, measured
Level 0Spec-Sheet Skimmer
0 XP0/91 lessons0/19 achievements
0/100 XP to next level100 XP to go0% complete
"The GPU does not own the pool. It is allowed a share, and the share is a number you can read."
One Pool, One Referee
Because the GPU has no memory of its own, every byte it uses is a byte the rest of the machine cannot. macOS therefore publishes, per device, a recommended maximum working set — in Apple's words, "an approximation of how much memory, in bytes, this GPU device can allocate without affecting its runtime performance". The fraction of physical memory that recommendation represents is not documented as a formula, and the figure most often quoted — "about 75%" — turns out to be one point on a curve. The code block reads the number on each fleet Mac.
Alias
Physical
Recommended working set
Fraction
MLX default memory limit
iogpu.wired_limit_mb
Evidence
air
24 GiB
17.8 GiB
74.0%
22.8 GiB (95%)
0
measured 2026-09-15
pro2023
128 GiB
107.5 GiB
84.0%
—
0
measured
music
192 GiB
161.3 GiB
84.0%
—
0
measured
office
512 GiB
464.0 GiB
90.6%
486.4 GiB (95%)
0
measured
The shape is clearer than any rule of thumb: the operating system holds back a reserve that is large relative to a small pool and small relative to a large one — 6.2 GiB on the Air, 20.5 on the 128 GB MacBook Pro, 30.7 on the two 192 GB Studios, 48 on office. Whether that is a fixed formula or a table inside the kernel is not something user space can tell; what user space can tell is the number, and the number is the one to plan against. A neighbouring quest in this series quotes a "practical ceiling closer to 380 GB" for a 512 GB M3 Ultra; on macOS 26.6.2 the device itself reports 464 GiB, and this quest reports what it read. Fit decisions (previous lesson) and decode predictions use this column, not the sticker.
Two Knobs, Neither a Fence
iogpu.wired_limit_mb is a kernel sysctl that caps how much of the pool the GPU may wire — pin so it cannot be paged. It is 0 on every fleet Mac, meaning the kernel chooses; setting it (with root) lets a GPU process wire more, and the only place it is documented is the mlx-lm README, which advises a value "larger than the size of the model in megabytes but smaller than the memory size of the machine". MLX's own set_memory_limit is the process-side knob, and its default is 95% of physical memory on both machines it was read on — above the operating system's recommendation. Neither knob fences anyone else: raising the wired limit takes memory from the rest of the machine, and a process that ignores the recommendation takes it from the display. The GPU track's word for this was discipline; this lesson's is arithmetic — the share is recommended, not enforced, and the runtime you choose decides whether the recommendation is honoured.
What This Buys a Fleet
The household's inference hub runs an embedding model, two rerankers and a chat model side by side on one 512 GB pool with a memory guard set below the recommendation, and has done so without incident. The same numbers explain why the Air is a witness rather than a host: with 17.8 GiB for the GPU, a 16 GB model leaves nothing for its own context, and the operating system pushed 1.7 GB of other memory to swap to make even the short run work.
Code
gpu_share.py — the GPU's recommended share on every reachable Mac·python
#!/usr/bin/env python3
"""Read physical memory, the GPU's recommended working set and the wired-limit
sysctl on each ssh alias. The fraction is not a constant; look at the reserve."""
import subprocess
import sys
ALIASES = sys.argv[1:] or ["air", "pro2023", "music", "office"]
PY = ("~/miniconda3/envs/silicon-lab/bin/python -c "
"'import mlx.core as mx; i=mx.device_info(); print(i[\"memory_size\"], i[\"max_recommended_working_set_size\"])'")
print(f"{'alias':8} {'physical GiB':>12} {'recommended GiB':>15} {'fraction':>9} {'reserve GiB':>12} {'wired_limit_mb':>14}")
for alias in ALIASES:
out = subprocess.run(["ssh", alias, PY + "; sysctl -n iogpu.wired_limit_mb"], capture_output=True, text=True).stdout.split()
phys, rec, wired = int(out[0]), int(out[1]), out[2]
print(f"{alias:8} {phys/2**30:12.0f} {rec/2**30:15.1f} {rec/phys:9.1%} {(phys-rec)/2**30:12.1f} {wired:>14}")
# 2026-09-15, macOS 26.6.2:
# air 24 17.8 74.0% 6.2 0
# pro2023 128 107.5 84.0% 20.5 0
# music 192 161.3 84.0% 30.7 0
# office 512 464.0 90.6% 48.0 0
The same number without MLX, from the shell·bash
sysctl iogpu.wired_limit_mb # 0 = the kernel decides the GPU's wired ceiling
sysctl hw.memsize # physical bytes
# recommendedMaxWorkingSetSize is a Metal device property; MLX exposes it as
# mx.device_info()['max_recommended_working_set_size']. In Swift:
# MTLCreateSystemDefaultDevice()!.recommendedMaxWorkingSetSize
# mlx-lm README: to let the GPU wire more of the pool (root, moves the fence, adds none)
# sudo sysctl iogpu.wired_limit_mb=N # N > model MB, N < machine memory MB
Run gpu_share.py (or the shell block) on your Mac and add the recommended working set, its fraction, and the reserve in GiB to your card. Then, from the previous lesson's peak table, name the largest model-and-context pair your Mac can hold inside the recommendation, and the first one that would need the wired limit raised. Write one sentence on what raising it would cost the rest of the machine.
Hint
The reserve is what the display, the kernel and every other process share. Raising the wired limit into it does not create memory; it moves the shortfall from the model to the window server, which is the process you least want to starve.
Progress
Progress is local-only — sign in to sync across devices.