Repository navigation
Conversation
…d of freezing the Mac A session of Ocarina of Time ended in a GPU out-of-memory and a stuck game, and seedds#1326 reports a Mac frozen until the power button: GPU memory is wired, IOGPUScheduler's freeToWireMemory fails at recommendedMaxWorkingSetSize, and the submit thread then waits in the kernel where the process cannot be killed. Nothing in the host looked at the working set. res_commit now reads the larger of the set's allocatedSize and the device's currentAllocatedSize before it commits; the set's size leaves out what was never committed (measured: a new 64 MB texture adds 0 to it until the commit), the device's counts it from its creation. From 80% of the working set the submit thread lets go of what can go: idle sharing entries nothing holds, finished deferred releases, spares and the upload slots' staging. From 95%, if that was not enough, the game stops with a [RED] report of what the set holds, by kind. A commit that adds to the set is not made at the working set itself. A 100 ms timer reads the sizes too, as a stuck submit thread never comes back to look. The stop reaches the launcher as a normal exit: the Mac app closes the graphics gate, leaves full screen, shows an alert and quits as Cmd-Q does, the path PucaVaz's PR seedds#1332 takes for a revoked GPU (front_stop_alert is written so front_gpu_lost can call it); without a window the host takes its graceful exit. DM_NVN_RES_LIMIT_MB and the report's distinct pages and texture formats come from JioChoi's PR seedds#1319, moved from after the commit to before it. iOS only evicts unless the switch sets a limit: no iPhone has tried the stop. t_res_guard covers the levels, the commit refused and removals passing, what stays (a live object, the parent of a live sRGB view, a recent lookup), the second look at the stop level, and the timer with the lock held.
Only the generated symptom index conflicted; regenerated (docs_symptoms.py). Checked: test_docs.py, docs_symptoms.py --check. No code conflict.
…atedSize currentAllocatedSize counts each no-copy buffer in full however many cover the same pages: 50 over one 64 MB region add 3,200 MB. In Ocarina of Time's Kokiri Forest on a 24 GB Mac it read 17.3 GB within a minute or a few, with 6.5 GB in the residency set and the driver's In use system memory near 7.7 GB, so the guard stopped the game each time. With its stop raised past Metal's working set, the refused commit was followed by InnocentVictim command buffer errors. The guard now reads the set's own size, so an addition counts from its commit. A large burst is seen one commit later than before, and the timer sees the size at the last commit. Checked: t_res_guard, both runs. Ocarina of Time v131072 on an M6 Mac with 24 GB, on main with seedds#1357, seedds#1390, seedds#1391, seedds#1453 and seedds#1455: about 49 minutes and 170,400 frames through Kokiri Forest and the Great Deku Tree, currentAllocatedSize at about 20 GB, driver in use at most 9.5 GB, wired about 10 GB, no GPU errors. Not checked: other games, a smaller Mac.
…re the Mac runs out of RAM (kern.memorystatus_level, DM_NVN_SYS_MARGIN_MB) Symptom: on a 16 GB M2 Pro, Ocarina of Time with every pool resident (DM_NVN_GMEM_ALL=1) kernel-panicked the Mac at 10,463 MB wired, with 48 MB free and 3 GB compressed. That was under the guard's 95% working-set stop (11,517 MB of a 12,124 MB working set), so the guard never tripped. Cause: GPU memory is wired out of the same RAM as everything else, so on a 16 GB Mac the system can run out before the residency set reaches Metal's working set. The working-set side alone can't see that. Fix: guard_sys_tick, called from the guard's 100 ms timer, reads kern.memorystatus_level (memory available, free plus reclaimable) and raises the guard's level when it falls below a margin (stop) or twice the margin (evict). The margin starts at DM_NVN_SYS_MARGIN_MB (default 1024; 0 turns this side off) and grows by 512 MB, up to 4 GB, at each memory-pressure warning. The stop line says which side tripped. The working-set side is seedds#1482's (the residency set's own allocatedSize), unchanged here: that part of the original change was the same as seedds#1482's and is left out. Checked: t_guard_sys (new, 8 checks) passes, and the host builds. In Ocarina of Time runs to f7,500 on the M2 Pro (with seedds#1482), the guard no longer stopped at the Kokiri load. The system side evicted once at 12% available (1,966 MB left), and the runs stayed at 18% or more available, wired at most 8.8 GB. The reference entry for the guard says so, and env_switches.tsv lists the switch.
|
Rebase status: this PR's own commit (the system-memory side, cddc1c0) is unchanged. It conflicts with main only through its base: #1391's guard commit conflicts with main's new GPU-lost alert (5636e7a, #1332), since both reshape the same front-end alert in |
…a, gmem commit repoint on the test stack seedds#1589 (carrying seedds#1391 + seedds#1482) the GPU memory guard reads the residency set's size and stops before macOS runs out of RAM; hand-merged with seedds#1590's ghost-active alias tables. seedds#1599 soft textures (gated, default off). seedds#1662's tip commit: the ocarina plays into the Mac's microphone (DM_MIC=1). seedds#1427 raw pool table words follow the pools at commit, as a candidate for seedds#1326's page faults (its window is skipped, not faulted: kept for the record). Full-map shader library (42,848), clearing the no-stamp and c1_full red lines. In-game: the GPU page fault at scene changes (seedds#1326) remains on this stack - one session lost the picture at frame 55,940 with the device revoked; the fault predates seedds#1585, which was excluded after the M2 Pro report but is not its cause. Front-ends: host change only, chain 2027aa29b1b7.
…he game with an alert from about 95%, and follow the system's available memory Net change of PR seedds#1589 at cddc1c0 (seedds/nxnative), which stacks seedds#1391 and seedds#1482, merged by hand: front_stop_alert serves both the GPU-lost alert (exit status 70 kept) and the memory stop; alias_let_go retires as this tree does (g_retire_held covers ghosts); the memory line keeps this tree's form. DM_NVN_RES_LIMIT_MB=0 turns the guard off.
…nto main: alias_let_go with main's retire, one stop alert for GPU lost (exit 70) and memory (cmd-Q) Upstream pull requests applied in this commit (their authors' work, brought onto this tree with a three-way merge; the description is in each PR): - seedds#1589 by nafields (open): GPU memory guard, the system's side: stop before the Mac runs out of RAM (kern.memorystatus_level, DM_NVN_SYS_MARGIN_MB; on seedds#1482)
|
Five faults in the guard as it stands at
Not checked: a run where the guard really evicts or stops. On an 18 GB M3 Pro with Ocarina of Time ( |
…aves the table takes its finalized serials with it, and an idle sweep lets go of entries nothing looked up for 600 submissions Ocarina of Time lays a frame's render targets out with a linear allocator in one GPU-only pool. When one of them changes size the ones after it move, and each move is a new entry in the sharing table (pool, offset, description) with a Metal texture of its own. Walking into another area of Castle Town on a 24 GB M5, the textures in the residency set went from 2.3 GB to 5.4-6.5 GB, about 4.8 GB of them render targets the game had already let go (1,744 MB Depth32Float, 624 MB RGBA16Float, while 796 live targets took 1.5 GB). The Mac ran out of free pages (60-120 MB), compressed 5 GB in 10 s, and frames took 62-92 ms for tens of seconds. Three things kept those textures: - the ghost ring. A finalized serial that a replayed record resolved keeps its texture until the ring turns. With 512 entries the ring is too small for this game: under about 10,000 entries the serials its registered descriptors still read are evicted and entered again every frame (59 to 155 million puts in 100 s, submit CPU from 7 to 16-23 ms; the bursts in issue seedds#2213 look alike). PR seedds#1731 makes it 32,768, and then at the 400 textures a second this game finalizes it takes about 80 s to turn, holding what the game let go for that long. The ring is 32,768 here, and an entry that leaves the sharing table now takes its ghost serials with it (ghost_purge; DM_NVN_GHOST_TAKEOVER=0 for the old behaviour, DM_NVN_GHOST_N to measure other sizes); - nothing let an entry go by its age. Only a takeover over the same storage or the pool's Finalize removed one. Every 120 frames a sweep now lets go of the entries nothing holds and no lookup found for 600 submissions (alias_sweep, with PR seedds#1589's idle eviction; DM_NVN_ALIAS_SWEEP, DM_NVN_ALIAS_SWEEP_IDLE); - a takeover spares an entry looked up in the last 16 submissions. For compressible entries that was too short: at full screen, while the game's dynamic resolution moves, its alternating layouts were made again every frame (16,819 textures, 30 GB of allocations, in 300 frames; submit CPU 17.7 ms against 7.9; frames of 34 ms). A compressible entry now outlives a takeover for 1,800 submissions while the GPU's allocation is under half of Metal's working set (PR seedds#1731's hold; DM_NVN_ALIAS_KEEP_MB sets the bound, 0 turns it off), and for 600 over it. Ending the hold at the bound instead was a cliff: a session that crossed it on entering an area (7,855 to 10,308 MB) made 23,100 textures in 300 frames, frames of 93 ms and a footprint of 17 GB. Measured on an M5 with 24 GB, on a branch that also carries PR seedds#1731 and PR seedds#1589, from the shell on a scripted walk through Castle Town from one save (handheld, dynamic resolution on; the host's residency report every 10 s, the system's memory every 2 s), one run each: after before residency set at its peak 8.0 GB 11.5 GB footprint 12.3 GB 14.9 GB wired, system-wide 10.8 GB 13.6 GB 300-frame windows > 17.5 ms 0 of 54 10 of 32 (seven of them 42-69 ms) textures made in the walk 18,410 32,071 In the app at full screen, 150 s of the same walk: worst 300-frame window 19-20 ms against 67.7 ms. With 8 GB of the Mac held wired, so the game and the rest live in 16 GB, the walk completed with two windows over 17.5 ms. 1,200 consecutive frames around the area change, captured one by one, show no dark frame and no one-frame flicker. One behaviour changes for a texture ID that names a finalized texture: when a takeover lets that texture go, the ID's serial goes with it and the ID resolves anew in the takeover's submission (patched, no table built), where before it kept the old texture until the ring let the serial go and then resolved anew the same way. t_pool_notes's takeover case now expects that (it fails with DM_NVN_GHOST_TAKEOVER=0). Checked: t_alias_takeover under Metal validation, with three new cases: a finalized serial a replayed record resolved goes with the texture a takeover lets go (fails with DM_NVN_GHOST_TAKEOVER=0), the sweep lets go of an entry nothing resolves to at the first sweep after 600 submissions and keeps the live ones (fails with DM_NVN_ALIAS_SWEEP=0), and over the hold's bound a compressible entry outlives a takeover past 16 submissions and goes past 600. The whole graphics suite (make -C dm/gfx/tests run, M5, macOS 27.2) passes, except t_pool_retire once on its 1 s wait while the Mac was building something else; alone it passed three times out of three. test_docs passes; the Mac host builds. Not run: the game on main, whose shaders do not convert there since 0662f9d (issue seedds#2272); other areas than Castle Town, interiors, sessions longer than 34 minutes; regress.py on other games. The system still reaches its free-page floor at the area change (65 MB): this takes the stalls and about 3 GB away, not all of the pressure. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014eD437mxrMnW2UwLZhpwc7
Stacked on #1482, which is stacked on #1391. Only the last commit (cddc1c0) is new. Happy for it to be folded into either one instead.
What and why
Symptom. On a 16 GB M2 Pro, Ocarina of Time with every pool resident (
DM_NVN_GMEM_ALL=1) kernel-panicked the Mac at 10,463 MB wired, with 48 MB free and 3 GB compressed. That was under #1391's 95% stop (11,517 MB of a 12,124 MB working set), so the guard never tripped.Cause. GPU memory is wired out of the same RAM as everything else. On a 16 GB Mac the system can run out before the residency set reaches Metal's working set, and the working-set side can't see that, whether it counts
currentAllocatedSize(#1391) or the set's own size (#1482).Change.
guard_sys_tick, on A GPU memory guard before each residency commit stops the game instead of freezing the Mac #1391's 100 ms timer, readskern.memorystatus_level(available memory, free plus reclaimable).DM_NVN_SYS_MARGIN_MB(default 1024;0turns this side off) and grows by 512 MB, up to 4 GB, at each memory-pressure warning.The guard's reference entry in
docs/reference/memory.mdhas a paragraph on this, andenv_switches.tsvlists the switch.Checked
check_t_guard_sys(new, 8 checks, 0 failed);make -C dm/host GAME=0400486024DEC000;test_docs.py;env_switches.py --check.t_nro, which doesn't link (_mtl_present_screen). That comes from The GPU memory guard reads the residency set's size, not currentAllocatedSize (on #1391) #1482's base, not from this commit, which touches onlynvn_mtl.m, the graphics test Makefile and its new test.regress.py checkwasn't run: only Ocarina of Time is available here.Docs
docs/reference/memory.md),docs_symptoms.pyrun