Repository navigation
fix(vk): graceful retired-pool eviction where retired imports were kept (Windows AMD and Intel) - #1906
Conversation
hckrman101
left a comment
There was a problem hiding this comment.
Thanks for picking this up, the session-long accumulation on Windows is real and a grace period is a reasonable idea. A few things before it can go in:
The branch carries two other PRs. The diff has 3 commits: #1439 (FSR 1 EASU+RCAS, the taau shaders and the vk_draw.cpp changes) and #1905 (FR_READBACK, vk_replay.cpp), with this PR's own commit 8fb0290 on top. The description says one file, 44/10, but the PR is +1200/-69 over 7 files, and it is 921 commits behind main and conflicts (#1439 and #1905 are also dirty). Please rebase onto current main with only the vk_memory.cpp commit so it can be reviewed and merged on its own. CI hasn't run on it either.
Linux changes behaviour too, and the description doesn't mention it. On main, Linux already gives retired imports back as soon as the GPU is past them (keep is false there). That was the fix for the amdgpu userptr submit cost (issues 312/413: every retired import checked at each CS, 3-4 fps in Kokiri). With this PR Linux waits 30 s, so the retired pools from a burst of merges stay in every submission's BO list for that long again. I'd keep Linux at once by default and put the grace period on the Windows path only.
The NVIDIA part is already fixed on main. The description and the new comment say every vendor leaked, but main's Windows default already gives retired imports back on NVIDIA (vendorID != 0x10DE, issue 1057, the RTX 3070 case). What this PR really changes is the Windows default for AMD and Intel, where give-back was turned off on purpose after device losses (Arc B390 entering Kokiri every time, issue 17; Ally X 309; RX 6600 571). The idea that those losses came from giving back too soon is plausible, but nothing has tested it yet: the one test is an RX 6700 XT (which OS? on Linux main would already show retired imports given back, so I guess Windows), and the pressure path, which drops the wait to 2 s and is close to the old behaviour, was never exercised. This needs runs on Windows Intel Arc and on the AMD parts that lost the device before the default flips there, and I'd leave that call to NotRexed.
The pressure valve and imports_give_back fire on the same submission and both act on the same overshoot (inline). Freeing retired imports doesn't lower the heap usage until dead_imports is reaped, so imports_give_back reads the same used a moment later and evicts live idle pools for the same deficit. On Linux with a budget under 12 GB both valves are on by default.
Nits: the comment in imports_give_back ("Off by default on Windows, as giving retired imports back is") is out of date after this. RX_VK_IMPORT_HIGH now defaults to 85 for the retired valve on Windows and 0 for the live one, and the retired valve skips the budget < 12 GB gate. That may be what you want, but say so in the comment.
| // 85) the wait shortens to RX_VK_RETIRE_MIN_SECS seconds (default 2), down to RX_VK_IMPORT_LOW percent (75), and | ||
| // the free itself still waits for the GPU (dead_imports). RX_VK_KEEP_RETIRED=1 keeps them for the session; =0 | ||
| // gives them back as soon as the GPU is past, both as before. The vendor split is gone: every vendor leaked by it | ||
| static const bool keep = getenv("RX_VK_KEEP_RETIRED") && getenv("RX_VK_KEEP_RETIRED")[0] != '0'; |
There was a problem hiding this comment.
On Linux this used to be the whole default: keep false, so given back as soon as the GPU is past (issues 312/413, the amdgpu userptr CS cost). With idle_ms applied below, Linux now waits 30 s too. Suggest at_once defaulting to true off Windows, unless you've measured that the 30 s don't bring back the submit stall on RADV.
| // RX_VK_RETIRE_SECS seconds (default 30), oldest first; past the system heap's RX_VK_IMPORT_HIGH percent (default | ||
| // 85) the wait shortens to RX_VK_RETIRE_MIN_SECS seconds (default 2), down to RX_VK_IMPORT_LOW percent (75), and | ||
| // the free itself still waits for the GPU (dead_imports). RX_VK_KEEP_RETIRED=1 keeps them for the session; =0 | ||
| // gives them back as soon as the GPU is past, both as before. The vendor split is gone: every vendor leaked by it |
There was a problem hiding this comment.
"every vendor leaked by it": on main NVIDIA was already excluded from keep (issue 1057), so only AMD and Intel on Windows leaked. Worth saying, since those are the vendors whose device losses the old default was protecting against.
| uint64_t want = 0; | ||
| if (!keep && !at_once && high > 0) { | ||
| static uint64_t calls = 0; | ||
| if (++calls % 16 == 0) { |
There was a problem hiding this comment.
This counter and imports_give_back's both run once per submission and reach % 16 on the same submission. What is pushed to dead_imports here isn't freed until the GPU is past it, so system_heap_usage returns the same used to imports_give_back right after, and it evicts live idle pools for the same want. Maybe pass the bytes queued here to imports_give_back and subtract them, or only probe in one place.
There was a problem hiding this comment.
You're right, and it was worse than one overshoot counted twice. Walking through it at 42ae0cd:
memory_begin_submissionrunsreap_mirrors(), thenretired_imports_give_back(), thenimports_give_back(), all underpools_mu, once per submission.- Each valve has its own
static uint64_t calls, and each only counts when its own valve is enabled. Both conditions are fixed for the run (statics read once), so when both valves are on they count from the same first submission and stay in phase: they always hit% 16 == 0on the same submission, not just sometimes. - The retired valve moved the pool's buffer and memory into
dead_importswithvalue = max(mem_rec_value, last_value). Nothing is freed there.vkFreeMemoryonly happens inreap_mirrors()oncemem_completedreaches that value, andreap_mirrors()has already run for this submission, so the earliest free is the next submission, and only if the GPU has finished this one. system_heap_usageis the driver's figure (VK_EXT_memory_budget), so it cannot drop before that free.imports_give_backread the sameuseda few lines later, computed the samewant, and evicted live idle pools for bytes that were already on their way out.- The same staleness also hit the retired valve itself: if the queued imports were not reaped within 16 submissions (a long GPU frame), its next probe asked for the same bytes again and shortened the wait for more pools than needed.
And as you said, at 42ae0cd this was on by default on Linux under a 12 GB budget, since the retired valve had no OS split.
What the new head (4808b1c) does, taking your first suggestion:
dead_import_bytescounts what sits indead_imports: added at the push, subtracted inreap_mirrors()at the actual free.- The retired valve probes
want(settled(used, dead_import_bytes), ...), so what it queued earlier counts as given back until it really is. imports_give_backdoesused = settled(used, dead_import_bytes)before its watermark test, so it only evicts live pools for what the queued retired imports will not cover.- Off Windows the retired valve no longer probes at all (retired imports go back at every submission again, as on main), and
imports_give_backskips the subtraction there, so Linux reads exactly what it read on main. The interplay is left only on Windows AMD/Intel withRX_VK_IMPORT_HIGHset by hand (the live valve defaults to off there), and that is the case the subtraction covers.
I kept two probes rather than one because the two valves have different gates on purpose (the live one is off on Windows and limited to budgets under 12 GB, the retired one is neither; the comments in both now say so). tests/retired_give_back_test.cpp has the case: 1 GB over, three pools queued, and the next probe with the same used asks for nothing.
8fb0290 to
42ae0cd
Compare
|
Rebased as a single commit off current main (42ae0cd), unstacked per review: only gpu/vk/vk_memory.cpp and the leak's test fixture. Recap of the eviction policy: retired pools return to the driver only when the GPU has been quiet, the pool has been idle at least 2 s (RX_VK_RETIRE_MIN_SECS, default 2000 ms), and committed memory pressure has crossed 85% (relaxing back to 75% once under, so pools don't ping-pong). |
|
That rebase landed as of this morning: the branch is now a single commit (42ae0cd) off current main — only gpu/vk/vk_memory.cpp and the leak's test fixture, +44/-10. The two stacked PRs you saw are gone (they live in #1439, itself rebased to main, and #1905 which closed in favour of #1276). The hysteresis numbers in the recap comment are corrected too: idle floor 2 s (RX_VK_RETIRE_MIN_SECS, default 2000 ms), pressure 85% with the 75% relax. |
hckrman101
left a comment
There was a problem hiding this comment.
Thanks for the rebase. The branch is now the one vk_memory.cpp commit on top of main and mergeable, so the stacking point is fixed.
The code itself is unchanged, though: the vk_memory.cpp diff at 42ae0cd is line for line the same as 8fb0290, so the rest of the last review still applies:
- Linux now waits 30 s before giving retired imports back, where main gives them back once the GPU is past them (the amdgpu userptr submit cost, issues 312/413). Still open, see the inline thread: I'd keep
at_oncethe default off Windows. - The two pressure valves act on the same overshoot on the same 16th submission (
imports_give_backreads the sameusedbeforedead_importsis reaped and evicts live idle pools too). Still open, inline thread. - NVIDIA was already given back on main (issue 1057), so the real change is the Windows default on AMD and Intel, where give-back was turned off after device losses (issues 17, 309, 571). That still has no run on Windows Arc or on those AMD parts, and the pressure path was never exercised. I'd still leave flipping that default to @NotRexed.
- Nits from last time (the stale "Off by default on Windows" comment in
imports_give_back, theRX_VK_IMPORT_HIGHdefault split and the 12 GB gate) are untouched.
New:
- The comments here don't describe the code. The recap says a retired pool goes back only when the heap has crossed 85%; the code gives every retired pool back after 30 s whatever the pressure, and 85% only shortens that wait to 2 s. The commit message says "the keep= default stays true" and "idle >5s", but the code drops the Windows
keepdefault and uses 2 s. You also mention a test fixture for the leak, but the PR has only vk_memory.cpp. Please make the description and the commit message match what the code does, and add the test if you meant to. - CI hasn't run. The build and test workflows are waiting for a maintainer to approve them (first-time contributor), and the PR title check fails: it needs a conventional type, e.g.
fix(vk): give retired pools back after an idle grace period. - Overlap: #2154 also edits vk_memory.cpp (new fields next to
retired_epochinstruct Pool, andmirrors_give_back), so whichever lands second needs a small rebase. #2198, #2368 and #2401 are in the texture cache and replay, not this path.
Joey-Magnus
left a comment
There was a problem hiding this comment.
I checked the head (42ae0cd) against main's retired_imports_give_back in gpu/vk/vk_memory.cpp. The code is the same as in hckrman101's last two reviews, so those points are all still open:
- Linux changes too. On main,
keepis false off Windows, so retired imports go back as soon as the GPU is past them (the amdgpu submit cost, issues 312/413). Hereat_onceis true only withRX_VK_KEEP_RETIRED=0, so by default Linux now waits 30 s. - The real change is the Windows default on AMD and Intel, where give-back was turned off after device losses (issues 17, 309, 571). There's still no run on Arc or on those AMD parts, and the 2 s pressure path has never run.
- The commit message ("keep= default stays true", "idle >5s") and the recap comment don't match the code. The test fixture the comment mentions isn't in the PR.
Smaller things: the three new number switches use atoi(getenv(...)). Main has env_int in gpu/vk/compat/hookshot_env.h for this. at_once only recognises a literal 0, so RX_VK_KEEP_RETIRED=off keeps the 30 s wait. The title still needs a conventional type, e.g. fix(vk): ....
…pt (Windows AMD and Intel) On Windows, AMD and Intel kept every retired pool's import for the session (main's keep default there, set after device losses right after a give-back: issues 17, 309, 571). The retired pools only grew, multi-GB in a long session, until the system heap's budget was passed and new imports failed. NVIDIA on Windows was already given back at once (issue 1057) and so was every vendor off Windows (the amdgpu userptr submit cost, issues 312 and 413): neither changes here. Where they were kept, and only there, a retired pool's import now goes back after a wait: - the GPU is past it (as before: retired_value and last_value completed, no mirror, no inherit pending), and - it has been retired for RX_VK_RETIRE_SECS seconds (default 30), oldest first; - while the system heap is past RX_VK_IMPORT_HIGH percent of its budget (default 85, 0: off) the wait is RX_VK_RETIRE_MIN_SECS seconds (default 2), until what brings it down to RX_VK_IMPORT_LOW percent (75) is queued. The free itself still waits for the GPU (dead_imports). RX_VK_KEEP_RETIRED: 1 keeps them for the session; 0, off, false or no gives them back as soon as the GPU is past them (it was a literal 0 only); unset or empty is the default of the OS and vendor: at once off Windows and on Windows NVIDIA, as on main, the wait on Windows AMD and Intel. The keep default is gone from those two, nothing else moves. The numbers are read with env_int. What is queued in dead_imports is still in the driver's figure of the heap's use until the GPU is past it, so both valves count it as given back: the retired valve at its next probe, and imports_give_back, which probes on the same 16th submission and evicted live idle pools for the same bytes. Only where retired imports wait; elsewhere imports_give_back reads what it read. The rule is in gpu/vk/retired_give_back.h, with a check that needs no game or GPU: tests/test_retired_give_back.py (the defaults by OS and vendor, the wait and its floor, the watermarks, a 20-area tour kept, at once and waited). Not run yet on Windows Intel Arc or on the AMD parts that lost the device (Ally X, RX 6600), and the pressure path has not fired on real hardware. Co-authored-by: GLM-5.3 <noreply@z.ai>
42ae0cd to
4808b1c
Compare
|
Pushed 4808b1c: still one commit, now on current main (ef81aee). This time the code changed, going through both reviews: Linux is back to main's behaviour. Off Windows a retired import goes back as soon as the GPU is past it, by default, exactly as on main: same gates, no wait, and the pressure probe is not even run there. The wait applies only where main's default was to keep them, Windows AMD and Intel. Windows NVIDIA stays at once too (issue 1057).
The two valves and the same overshoot (inline thread, answered there): what is queued in Comments, commit message, title and description now say what the code does. Only AMD and Intel on Windows leaked (NVIDIA was already excluded on main); a retired pool goes back after 30 s whatever the pressure, and 85% only shortens that to 2 s (not 5); the Windows keep default is what goes away for those two vendors. The stale "Off by default on Windows, as giving retired imports back is" comment in The test fixture. My earlier comment mentioned one that was never in the PR, and there was none to bring over. It is written now: the rule moved to Diff against main: 4 files, +288/-13 ( Still not done, and unchanged by this push: no run on Windows Intel Arc or on the AMD parts that lost the device (Ally X, RX 6600), and the pressure path has not fired on real hardware. The RX 6700 XT run in the description was of the earlier revision. Whether the default should flip for those two vendors without those runs is yours and @NotRexed's call; Overlap with #2154 noted: whichever lands second, I'll rebase. |
…s behind them Upstream's main fails 15 of its own source checks at c356bef (PRs merged without a rebase). These are the eleven outside the upscalers: - vk_memory.cpp: RX_VK_CAPTURE_KEEP_OLD and RX_VK_CAP_BLOCKS are on/off switches and were read as set-means-on, so =0 turned them on (NotRexed#2437). env_on, as the rest. - mouse_native.c: the mouse's kept angles are let go when the camera leaves states 3 and 4 (first-person aim, Z-targeting): they were written back on return, and the view snapped to where it was before (NotRexed#2617; test_mouse_native's yaw). - menu_i18n.h: three English texts changed in vk_menu.cpp (NotRexed#1930, NotRexed#2362, NotRexed#2435), so their 13 translations are keyed and translated again (machine-made, as the file's others). - Fixtures that cut source by its text and missed what the source gained: window_title (NotRexed#2116), dead_import_bytes (NotRexed#1906), env_on in reverb_frame, hos_xinput_pads (NotRexed#2435), wait_recorded's forward declaration (NotRexed#2458); and the Dockerfile check's parser, which took --no-update for a PPA. Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
**What this is for:** restore ten game-free checks whose extracted fixtures or expectations no longer match intentional changes on main; [main's CI log](https://github.com/NotRexed/Hookshot/actions/runs/43151254256) shows these failures. **What is measured:** those ten checks on Linux with GCC 16.2.1 and Clang 23.1.3, plus the strict full suite and deliberate regression mutations, as detailed below. **Not measured:** a game run, Windows execution, audio output or GPU behavior; no production file changes in this diff. ## Cause and changes Each row is a separate commit, naming the change that made the fixture stale; the quoted diagnostics come from [main's failed jobs](https://github.com/NotRexed/Hookshot/actions/runs/43149508038). | Introduced by | Checks / CI diagnostic | Repair | |---|---|---| | #1544, after #2042's switch helper | `audio_aux`, `audio_biquad`: `implicit declaration of function ‘env_on’` | Include the real helper before the extracted DSP. | | #2116 | `display_startup_failure`, `wayland_libdecor_hint`: `use of undeclared identifier 'window_title'` | Extract the real title helper along with its callers. | | #1906 | `texel_buffer_ownership`: `use of undeclared identifier 'dead_import_bytes'` | Model the counter and check that it stays charged until completion. | | #2458 | `readback_part`: `extraneous closing brace ('}')` | Select the complete `wait_recorded` definition, not its new forward declaration. | | #2574 | `dlss_checks`: `FAIL under a third wide at line 40`; `framegen_preset` / `rumble_menu`: undeclared `temporal_reset_failure` | Check both sides of the intended two-pixel tolerance; supply a counted reset hook and exercise the real scaling rows. | | #2435 | `mods_size_table`: `undefined reference to hos_xinput_pads` | Supply the fixture's no-controller answer; retain real merge and routing checks. | The diff retains the existing assertions: no check is deleted or skipped, and it does not revert the production behavior named above. ## How it was tested Recorded on 2026-11-11, Linux kernel `7.2.15-1-omarchy`; base `de866017`, head `eeb09c09`, GCC 16.2.1 and Clang 23.1.3 (compiler version output and test records). - Untouched main `a016b7a6`: `455 passed, 15 failed, 20 skipped`; each of these ten failures also reproduced alone with both compilers, matching the linked CI diagnostics. - Branch: all ten repaired checks pass independently with each compiler (`python -I -B tests/run_all.py --strict --jobs 1 <test>` records). - Negative controls: 14 deliberate source regressions were each rejected with both compilers, without a compile failure—28 behavioral rejections (mutation records). These remove Aux sizing, biquad filtering, startup failure handling, the libdecor hint, view ownership release, part-specific waits, preset preservation, temporal reset or mod-table routing; the DLSS controls independently make each axis's tolerance too narrow and too broad. - One full `python -I -B tests/run_all.py --strict` run: **`464 passed, 6 failed, 20 skipped in 141 s`**, 32 workers, load 7.24 → 28.38 (complete suite output). ### Remaining failures and the path to green The complete branch run names these six failures; the first five are outside this fixture-only diff: | Remaining check | Separate fix | |---|---| | `test_env_on.py` | `fix/capture-switch-values`: the three presence-only capture switch reads from #2437. | | `test_linux_docker_packages.py` | `test/docker-ppa-options`: distinguish `--no-update` from its PPA operand. | | `test_mouse_native.py` | `fix/mouse-camera-handover`: discard retained angles while another camera owns the view. | | `test_menu_i18n.py` | `fix/menu-hint-translations`: update three complete hint keys and their translations. | | `test_hdr_encode.py` | Already repaired in #2614; deliberately not duplicated here. | | `test_release_scripts.py` | An intermittent, unchanged release-script concurrency failure: `SHA256SUMS still differs from the files after 5 uploads`. It passed untouched main and current main CI; isolated reruns passed once and failed once. Neither run invokes a C/C++ compiler. | ## Not tested / risks No Windows, game, display, controller, listening or GPU run was performed; the evidence above is game-free only. The fixture extraction still follows source structure, so future helper/signature changes must update its inputs rather than remove its behavioral guards. ## Overlaps The inspected merge hunks overlap #1834's frame-generation fixture helpers and #2622's older readback extraction; neither contains these repairs. #2614 owns the separate HDR initializer check. Other same-file changes merge cleanly in the recorded overlap comparison. Assisted-by: GPT-6 Astra Co-Authored-By: GPT-6 Astra <noreply@openai.com> --------- Co-authored-by: GPT-6 Astra <noreply@openai.com>
Port of upstream NotRexed#1906. Those vendors kept every retired pool's import for the session to avoid the device losses a give-back caused, so a long session grew by gigabytes until new imports failed. They now go back once the GPU is past them and they have been retired for 30 s (2 s while the system heap is past its high watermark). RX_VK_KEEP_RETIRED still chooses keep/at-once, and the wait and watermarks are switches. The pure rule is in retired_give_back.h; tests/retired_give_back_test.cpp checks the defaults, waits, watermarks and a 20-area tour without a GPU, and source-regressions now compiles and runs it.
Record the retired-import grace wait (NotRexed#1906), the green 74a7fd1, and that v2.2.0-rs.3 was built from it with the worker, mirror, image-pool and retired-import changes. Document the retire switches.
What
On Windows, AMD and Intel kept every retired texture pool's import for the whole session. This gives them back after an idle wait instead, so they stop accumulating. Nothing changes off Windows or on Windows NVIDIA.
Why
main's default per OS and vendor for a retired pool's import:
Kept, those retired pools only grow: multi-GB in a long session, until the system heap's budget is passed and new imports fail.
RX_VK_KEEP_RETIRED=0gives them back with no wait at all, which is the behaviour the device losses were reported against.Changes
RX_VK_RETIRE_SECSseconds (default 30), oldest first. This happens whatever the memory pressure.RX_VK_IMPORT_HIGHpercent of its budget (default 85,0turns it off), the wait isRX_VK_RETIRE_MIN_SECSseconds (default 2), until enough is queued to come down toRX_VK_IMPORT_LOWpercent (75). Unlikeimports_give_back's valve this one is on by default on Windows and is not limited to budgets under 12 GB, as it only frees pools nothing uses any more; the comments in both places say so.RX_VK_KEEP_RETIRED:1keeps them for the session;0,off,falseornogives them back as soon as the GPU is past them; unset or empty is the default in the table. Applies to every OS and vendor, as before.dead_importsis still in the driver's figure until the GPU is past it, so both valves count it as given back (only where retired imports wait; elsewhereimports_give_backreads what it read on main).env_int, the on/off switch withenv_on.gpu/vk/retired_give_back.hwith a check that needs no game or GPU:tests/test_retired_give_back.py.Testing
tests/test_retired_give_back.pypasses (the defaults by OS and vendor, the wait and its floor, the watermarks, a 20-area tour kept / at once / waited, the two valves not asking twice);gpu/vk/vk_memory.cppcompiles for Windows with llvm-mingw.AI assistance disclosure: the design and fix were developed with AI assistance.