Skip to content
C.W.K.
Stream
Lesson 04 of 06 · published

How Much of It the GPU May Use

~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.

AliasPhysicalRecommended working setFractionMLX default memory limitiogpu.wired_limit_mbEvidence
air24 GiB17.8 GiB74.0%22.8 GiB (95%)0measured 2026-09-15
pro2023128 GiB107.5 GiB84.0%0measured
music192 GiB161.3 GiB84.0%0measured
office512 GiB464.0 GiB90.6%486.4 GiB (95%)0measured

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

External links

Exercise

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.
Spotted a bug or have feedback on this page?Report an Issue

Comments 0

🔔 Reply notifications (sign in)
Sign inPlease sign in to comment.

No comments yet — be the first.