perf(vk): cap the system-heap budget for idle live imports on Linux #2839
Closed
github-sync
wants to merge 3 commits from
github/pr/2839 into main
pull from: github/pr/2839
merge into: MagicBeanDev:main
MagicBeanDev:main
MagicBeanDev:github/pr/2893
MagicBeanDev:github/pr/2890
MagicBeanDev:github/pr/2887
MagicBeanDev:github/pr/2878
MagicBeanDev:github/pr/2875
MagicBeanDev:github/pr/2874
MagicBeanDev:github/pr/2873
MagicBeanDev:github/pr/2871
MagicBeanDev:github/pr/2756
MagicBeanDev:github/pr/2897
MagicBeanDev:github/pr/2896
MagicBeanDev:github/pr/2718
MagicBeanDev:github/pr/2842
MagicBeanDev:github/pr/2691
MagicBeanDev:github/pr/2895
MagicBeanDev:github/pr/2743
MagicBeanDev:github/pr/2765
MagicBeanDev:github/pr/2708
MagicBeanDev:github/pr/2732
MagicBeanDev:github/pr/2755
MagicBeanDev:github/pr/2817
MagicBeanDev:github/pr/2876
MagicBeanDev:release-please--branches--main
MagicBeanDev:release-please--branches--main--release-notes
MagicBeanDev:github/pr/2778
MagicBeanDev:github/pr/2888
MagicBeanDev:github/pr/2866
MagicBeanDev:github/pr/2877
MagicBeanDev:github/pr/2800
MagicBeanDev:github/pr/2829
MagicBeanDev:github/pr/2850
MagicBeanDev:github/pr/2889
MagicBeanDev:github/pr/2882
MagicBeanDev:github/pr/2860
MagicBeanDev:github/pr/2849
MagicBeanDev:github/pr/2886
MagicBeanDev:github/pr/2794
MagicBeanDev:github/pr/2840
MagicBeanDev:github/pr/2830
MagicBeanDev:github/pr/2834
MagicBeanDev:github/pr/2719
MagicBeanDev:github/pr/2742
MagicBeanDev:github/pr/2741
MagicBeanDev:github/pr/2820
MagicBeanDev:github/pr/2771
MagicBeanDev:github/pr/2859
MagicBeanDev:github/pr/2858
MagicBeanDev:github/pr/2823
MagicBeanDev:github/pr/2757
MagicBeanDev:github/pr/2769
MagicBeanDev:github/pr/2844
MagicBeanDev:github/pr/2843
MagicBeanDev:github/pr/2673
MagicBeanDev:github/pr/2855
MagicBeanDev:github/pr/2739
MagicBeanDev:github/pr/2807
MagicBeanDev:github/pr/2851
MagicBeanDev:github/pr/2789
MagicBeanDev:github/pr/2814
MagicBeanDev:github/pr/2818
MagicBeanDev:github/pr/2774
MagicBeanDev:github/pr/2772
MagicBeanDev:github/pr/2750
MagicBeanDev:github/pr/2786
MagicBeanDev:github/pr/2785
MagicBeanDev:github/pr/2782
MagicBeanDev:github/pr/2779
MagicBeanDev:github/pr/2775
MagicBeanDev:github/pr/2706
MagicBeanDev:github/pr/2766
MagicBeanDev:github/pr/2747
MagicBeanDev:github/pr/2759
MagicBeanDev:github/pr/2745
MagicBeanDev:github/pr/2740
MagicBeanDev:github/pr/2733
MagicBeanDev:github/pr/2725
MagicBeanDev:github/pr/2727
MagicBeanDev:github/pr/2728
MagicBeanDev:github/pr/2729
MagicBeanDev:github/pr/2721
MagicBeanDev:github/pr/2722
MagicBeanDev:github/pr/2715
MagicBeanDev:github/pr/2714
MagicBeanDev:github/pr/2707
MagicBeanDev:github/pr/2703
MagicBeanDev:github/pr/2705
MagicBeanDev:github/pr/2697
MagicBeanDev:github/pr/2679
MagicBeanDev:github/pr/2676
MagicBeanDev:github/pr/2669
MagicBeanDev:docs/what-hookshot-connects-to
MagicBeanDev:fix/hdr-encode-test
MagicBeanDev:fix/main-tests
MagicBeanDev:fix/play-cache-files
MagicBeanDev:perf/game-barrier-order
MagicBeanDev:feat/heap-lazy
MagicBeanDev:feat/texture-detail
MagicBeanDev:perf/astc-opaque-bc1
MagicBeanDev:fix/fs-dir-lock
MagicBeanDev:fix/framegen-native-kept
MagicBeanDev:fix/update-no-downgrade
MagicBeanDev:port/k10-gcn-f16-mirrors
MagicBeanDev:fix/framegen-mult-cap
MagicBeanDev:chore/release-as-2.1.1
MagicBeanDev:perf/pcache-load-switch
MagicBeanDev:fix/prepare-progress-log
MagicBeanDev:fix/update-closes-stale-game
MagicBeanDev:fix/hdr-scaled-sampled
MagicBeanDev:perf/first-start-preset
MagicBeanDev:perf/zero-upload
MagicBeanDev:fix/vcore-escape-2s
MagicBeanDev:test/fake-gh-atomic
MagicBeanDev:perf/batch-idle-flush
MagicBeanDev:test/finish-stub-a
MagicBeanDev:chore/record-2.1.0
MagicBeanDev:fix/main-red-tests
MagicBeanDev:fix/test-display-startup-driver-cache
MagicBeanDev:feat/mesh-shader-mode
MagicBeanDev:fix/menu-probes-at-startup
MagicBeanDev:fix/fps-counter-classic
MagicBeanDev:fix/test-log-batch-flake
MagicBeanDev:fix/narrow-screen-hud
MagicBeanDev:ci/prereleases
MagicBeanDev:release/v2.0.15
MagicBeanDev:hotfix/v2.0.15
MagicBeanDev:fix/present-ahead-replay
MagicBeanDev:release/v2.0.13
MagicBeanDev:fix/bracket-paths-2013
MagicBeanDev:fix/hdr-overlays-2013
MagicBeanDev:fix/fps-counter-scale-2013
MagicBeanDev:fix/stick-seen-at-rest
MagicBeanDev:hotfix/menu-fits-screen
MagicBeanDev:fix/f1-always-opens-menu
MagicBeanDev:fix/ini-keep-lines
MagicBeanDev:claude/cranky-pike-7cf247
MagicBeanDev:perf/texture-answer-flat
MagicBeanDev:fix/stamp-test-modules
MagicBeanDev:fix/appimage-fonts
MagicBeanDev:fix/display-awake
MagicBeanDev:release/v2.0.12
MagicBeanDev:fix/discord-windows
MagicBeanDev:rc/v2.0.12-tests
MagicBeanDev:fix/dlss-keep-feature
MagicBeanDev:rc/v2.0.12
MagicBeanDev:button-icons
No reviewers
Labels
Clear labels
accessibility
Barrier affecting people with disabilities
autorelease: pending
autorelease: tagged
bug
Something isn't working
dependencies
Pull requests that update a dependency file
documentation
Improvements or additions to documentation
duplicate
This issue or pull request already exists
enhancement
New feature or request
github_actions
Pull requests that update GitHub Actions code
good first issue
Good for newcomers
help wanted
Extra attention is needed
invalid
This doesn't seem right
question
Further information is requested
testing wanted
wontfix
This will not be worked on
No labels
accessibility
autorelease: pending
autorelease: tagged
bug
dependencies
documentation
duplicate
enhancement
github_actions
good first issue
help wanted
invalid
question
testing wanted
wontfix
Milestone
Clear milestone
No items
No milestone
Assignees
Clear assignees
No assignees
1 participant
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".
No due date set.
Dependencies
No dependencies set
Reference
MagicBeanDev/Hookshot!2839
Loading…
Reference in a new issue
No description provided.
Delete branch "github/pr/2839"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
On Linux PCs with 32 GB+ RAM (
budget >= 12 GB),imports_give_backwas off by default, letting 200+ idle unmirrored pools from earlier areas stay inimportedover a two-hour session (7.4 GB live): on RADV (amdgpu), that doubledqueue submittime (0.55 → 1.14 ms), tripled GPU write-back waits (2.19 → 6.29–7.03 ms), grew the submit-time capture scan from 2.4 GB to 7.2 GB, and faded the frame rate from 60+ fps down to 44–48 fps. This keeps 16 GB PCs and the Steam Deck (budget < 12 GB) unchanged, turnsimports_give_backon forbudget >= 12 GBLinux PCs with a default 4,500 MB cap (~3,825 MB high / ~3,375 MB low), and addsRX_VK_IMPORT_CAP_MB(0= off) so the cap can be tuned or disabled without rebuilding.Part of #993
Part of #1000
What changed
retired_give_back::live_budget(gpu/vk/retired_give_back.h,gpu/vk/vk_memory.cpp).live_budgetreturnsbudget, so 85% / 75% applies to the full ~7.5 GB budget as in #479, unlessRX_VK_IMPORT_CAP_MB > 0is explicitly set).imports_give_backwas off):live_budgetcaps the effective budget at4,500 MBby default (~3,825 MB high / ~3,375 MB low) so unmirrored live pools whose VRAM mirrors were already freed bymirrors_give_back(mstate == MIRROR_NONE,was_mirrored == true) do not stay in RADV's per-submit BO list andcap_rangesfor the whole session.RX_VK_IMPORT_CAP_MB:RX_VK_IMPORT_CAP_MB=<MB>(> 0) sets the cap in MB on any budget so testers can try other values without rebuilding;RX_VK_IMPORT_CAP_MB=0turns the>= 12 GBcap off (restoringmain's behavior on 32 GB+ PCs while keeping< 12 GBPCs unchanged).RX_VK_IMPORT_HIGH/RX_VK_IMPORT_LOW: setting either watermark switch withoutRX_VK_IMPORT_CAP_MBlifts the default 4,500 MB cap and uses the full system-heapbudget(RX_VK_IMPORT_HIGH=0still turns the valve off everywhere). Windows remains off by default (high = 0unlessRX_VK_IMPORT_HIGHis set).imports_give_backreads its switches viaenv_intand sharesretired_give_back::want, covered bytests/test_retired_give_back.py.Tested
Arch Linux (KDE Plasma Wayland, 2560x1440 window,
/dev/udmabufactive), AMD Radeon RX 9070 XT 16 GB (Mesa RADV 26.2.6), Intel Core i5-13600KF, 32 GB DDR4 (15,434 MBsystem heap budget),v2.1.0 (source e561602e), multi-area play across Hyrule:hookshot_log.txt)40,920presents, defaulthookshot_env.txt)684,120presents, defaulthookshot_env.txt,imports_give_backoff)176,640presents,RX_VK_IMPORT_HIGH=25,RX_VK_IMPORT_LOW=20→ ~3,858 / ~3,086 MB;RX_VK_MIRROR_MB=4096,RX_VK_RECORDER=thread,RX_VK_BATCH=0)vk: imports: ... live55 live (2000 MB)221 live (7392 MB)(67 retired given back, 2992 MB)50–64 live (2256–2720 MB)off (budget >= 12 GB)off257 (7872 MB) in all(system heap 3878 of 15434 MB)capture: a submit0.27 ms(scan 0.05 over 734 ranges, 2417 MB)0.50 ms(scan 0.09 over 334 ranges, 7218 MB)0.18–0.43 ms(scan 0.08 over 3923–4251 MB)queue submit0.55 ms1.11–1.14 ms0.32–0.73 msreadbacks of GPU resultswait2.19 ms each(270 of 3000)6.29–7.03 ms each(312–340 of 3000)0.64–1.14 ms each15351 MB (peak 16107)/1511 MBswapratio 1.352, game's frame rate 44)gpu/vk/vk_memory.cppandretired_give_backchecks pass (tests/test_retired_give_back.py,tests/test_texel_buffer_ownership.py,tests/test_import_buffer_binding.py,tests/test_mem_trim.py,tests/test_vram_budget_row.py, etc.).docs/tested-configurations.md.Cost where not measured / caveats:
budget < 12 GB) are unchanged by default (live_budgetreturns the fullbudgetbelow 12 GB).Where does 4500 MB come from? The measured run was 25%/20% of the full 15,434 MB budget (3858/3086 MB), not 85%/75% of 4500 (3825/3375 MB). Also, this is a constant on a figure that includes everything else in the system heap. On a 16 GB PC or the Deck, it moves the threshold from ~6.4 GB down to 3.8 GB, which hasn't been measured. An
RX_VK_IMPORT_CAP_MBoverride, or applying the cap only where the budget is >= 12 GB (the PCs that were off before), would keep this change scoped to the case you measured.usedhere is the whole non-device-local heap, not just live imports: your run sat at 3878 MB with 2.3-2.7 GB of live imports. Wherever the rest of that heap stays near the cap,wantis never 0, and every unmirrored pool idle for 600 submissions (under 2 s) goes back at each probe. That also makes the zero-read caveat in the block comment much more likely than before. Integrated GPUs have no mirrors, so there it applies to every import. It would be good to see theidle imports given backcounts from a Deck or 16 GB run.Thanks for this, and for the long-session numbers. A 2 h run that shows the fade from 60+ to 44-48 fps, with the live-import count and the submit costs next to it, is the kind of evidence these memory PRs need. The diff is small and the mechanism is sound. Moving the env reads to
env_intand the watermarks toretired_give_back::wantis a nice cleanup.What happens at the cap. It only gives memory back. Nothing stalls, and no allocation is refused. Past 85% of
min(budget, 4500 MB), the valve takes the idle unmirrored pools that are done on the GPU, zeroes their page-table entries, and frees their memory once the GPU is past them. They are imported again at their next binding, and that import is the one that already runs in the background. That is the same path 16 GB Linux PCs have run by default since #479, so I don't see a new device-loss route on Linux. What changes is how often it runs, and on which PCs:usedis the whole non-device-local heap as VMA/the driver reports it, not just live imports. Your own capped run sat atsystem heap 3878 of 15434 MB, right on the line. Wherever the rest of the heap alone is near 3.8 GB,wantstays above zero at every probe, and every import unbound forkImportIdle(600 submissions, under 2 s at 6 submits a frame and 60 fps) goes back. Most of the time that is fine. But the known caveat in the comment applies far more often now: a pool reached only through the page table reads zeros until it is bound again. Before this change, that could only happen at 85% of the full budget.mirror_on), so on the Deck and the 780M-class APUs every live import is unmirrored and can be taken. I'd expect the most churn there, and nothing in the PR measures it. The Deck is Tier 1 (docs/workflow.md).Is 4500 MB justified? Not yet. The run behind the numbers is main with
RX_VK_IMPORT_HIGH=25/RX_VK_IMPORT_LOW=20against the full budget (3858/3086 MB), not this PR's default (3825/3375 MB). The high mark is close, but the low mark is ~300 MB different. The value itself has only been looked at on a 32 GB PC. Under our testing rule (before/after on the same route, hardware named), what I'd need before merging:RX_VK_IMPORT_*variables, on the same route and for about the same length as the 2 h baseline. Please also say whether the baseline had the sameRX_VK_MIRROR_MB=4096,RX_VK_RECORDER=threadandRX_VK_BATCH=0as the capped run. The second table row doesn't say.idle imports given backlines, the fps, and a revisit of an earlier area (any stall while its pools are imported again). If you don't have one, say so and we'll ask a tester.Design note. The cost you measured comes from the count and bytes of live imports (amdgpu's BO list, the capture ranges), not from pressure on the system heap. A cap on idle live-import bytes, or a cap on the system-heap figure applied only above 12 GB, would target that more directly and leave 16 GB PCs and the Deck as they are. In this PR's form, at least make the cap tunable (say
RX_VK_IMPORT_CAP_MB, with 0 meaning off), so testers can try other values without rebuilding.Overlap with #2759. That PR retires the imports of pools the game has finalized. On its 32 GB test PC, it took live imports from
257 live (8,720 MB)to74 live (2,816 MB). That is the same pile-up you measured here, fixed at the source. If most of your 221 live pools are from areas the game has unloaded, #2759 removes them without a cap. I'd land #2759 first and re-measure this PR on top of it. If live imports still grow past ~3 GB over a long session, the cap is clearly earned. #2825 (trimming partly-live mirrored imports) is complementary, and the two shouldn't conflict textually.Nits:
RX_VK_IMPORT_HIGH=85now behaves differently from leaving it unset (full budget vs. capped). That's a reasonable escape hatch, but please say so in the block comment. The current wording ("RX_VK_IMPORT_HIGH= uses the full budget") is easy to miss. Note thatRX_VK_IMPORT_LOWset on its own does not lift the cap.live_budgetis recomputed at every probe, and the driver's budget moves, so the logged cap can go stale. That's harmless; just noting it.CI hasn't run on this head yet (first-time contributor runs are awaiting approval). There are no merge conflicts with main.
Thanks for the thorough review! I pushed
6c9c7f4to address your points:budget < 12 GB) are left untouched by default:retired_give_back::live_budgetnow applies the default4,500 MBcap only whenbudget >= 12 GB(kSmallBudget = 12ull << 30), i.e. the 32 GB+ systems whereimports_give_backwas completely off before. Under 12 GB it returnsbudgetas in #479, so integrated GPUs / the Deck and 16 GB PCs keep their existing ~6.4 GB threshold and aren't churned.RX_VK_IMPORT_CAP_MB(env_int("RX_VK_IMPORT_CAP_MB", -1)):RX_VK_IMPORT_CAP_MB=<MB>(> 0) overrides the cap in MB on any budget so testers can try other values without rebuilding.RX_VK_IMPORT_CAP_MB=0turns the>= 12 GBcap off (restoringmain's behavior on 32 GB+ systems while leaving< 12 GBsystems as before).RX_VK_IMPORT_HIGH/RX_VK_IMPORT_LOWescape hatch & block comment: setting eitherRX_VK_IMPORT_HIGHorRX_VK_IMPORT_LOWwithoutRX_VK_IMPORT_CAP_MBnow lifts the default 4,500 MB cap and uses the full system-heapbudget, and the block comment aboveimports_give_backspells both out explicitly.docs/tested-configurations.md& PR description): clarified that the 2 h baseline (684,360presents) ran with the defaulthookshot_env.txt(default 2,048 MB mirror budget, default recorder), whereas the 1.5 h capped run hadRX_VK_MIRROR_MB=4096,RX_VK_RECORDER=thread,RX_VK_BATCH=0alongside the import watermarks.Regarding #2759 and hardware coverage:
RX_VK_IMPORT_CAP_MBcan either sit as a higher safety ceiling or opt-in switch.Done in
6c9c7f4:live_budgetnow leavesbudget < 12 GB(16 GB PCs and the Deck) untouched by default and addsRX_VK_IMPORT_CAP_MB(> 0sets the cap in MB,0turns the>= 12 GBcap off). SettingRX_VK_IMPORT_HIGHorRX_VK_IMPORT_LOWwithoutRX_VK_IMPORT_CAP_MBalso uses the fullbudget.Scoped to
budget >= 12 GBin6c9c7f4so 16 GB PCs and integrated GPUs / the Steam Deck keep their existingbudgetthreshold (~6.4 GB on 16 GB RAM) unlessRX_VK_IMPORT_CAP_MBis explicitly set.Nit: main decided "under 12 GB" once, at the first probe (the static
small). Here it's evaluated at every probe against the budget the driver reports right then. Where that budget sits near 12 GB, the threshold jumps between 3.8 GB (capped) and ~10 GB (85% of the full budget) from one probe to the next. Deciding the branch once, as main did, would keep it steady, and would match the config line that's logged once.Thanks for the quick turnaround. The scoping is what I hoped for, and the override is useful.
Fixed
live_budgetnow returns the heap's own budget whateverRX_VK_IMPORT_HIGH/LOWsay, unlessRX_VK_IMPORT_CAP_MB> 0 is set. So 16 GB PCs (~7.5 GB budget), the Deck and 24 GB PCs keep main's 85%/75% of the full budget.RX_VK_IMPORT_CAP_MB. It goes throughenv_int, so unset, empty or non-numeric falls back to -1 (the default). A negative value also means the default,0turns it off at >= 12 GB, and a value above the budget clamps to it.want()now returns 0 for a zero budget. That all parses safely.Still open: a run of this PR's defaults. The reply doesn't cover it, and it's what merging needs (our rule: before/after on the same route, hardware named). Both tested rows are still v2.1.0. The capped one is main with
RX_VK_IMPORT_HIGH=25/LOW=20, not this PR's 3825/3375 MB. Your clarification also shows the two rows differ in three more settings:RX_VK_MIRROR_MB=4096against the default 2048,RX_VK_RECORDER=threadandRX_VK_BATCH=0. The mirror budget changes how many pools lose their mirror and become candidates for this valve. The recorder and batch settings move the submit times and the frame rate. So the step from 44-48 to 63-68 fps can't be credited to the cap yet.Main has also moved under this since the baseline. Three PRs merged today all shrink live import bytes: #2685 (4 MB import chunks where mirrors are on), #2825 (an import mostly outside the game's pools is trimmed to its live parts) and #2795 (a retired pool with a refused mirror gives its import back). The 221 live / 7.4 GB pile was measured before all three. What I'd like, after merging main:
hookshot_env.txt, noRX_VK_IMPORT_*, the other settings the same in both), on the same route, each for about the 2 h of the baseline, on your 9070 XT. Please report thevk: importslive line, theidle imports given backcount and how often it logs, the system-heap figure, capture/queue submit, the readback wait and the fps.That one A/B answers the 4500 MB question, and the one about the valve firing in steady state (
usedis the whole heap). If main alone now stays near 2-3 GB live over 2 h, the cap may want to be higher. If it still climbs, 4500 is earned. I wouldn't wait for #2759. It isn't ready, andRX_VK_IMPORT_CAP_MBlets us retune once it lands.New: PCs with no VRAM mirrors and 32 GB+. The description says the cap is for pools whose mirrors were already given back (
MIRROR_NONE,was_mirrored). The idle filter doesn't checkwas_mirrored, though: it takes anyMIRROR_NONE/MIRROR_REFUSEDpool. Wheremirror_on()is off by default (integrated GPUs, and GCN cards since #2343), every import is unmirrored. On a 780M/890M laptop or an RX 580 PC with 32 GB, the default would now take every import idle for 600 submissions once the heap passes 3.8 GB. The valve was fully off there before, and that is the case furthest from the one you measured, with the page-table zero-read caveat on every pool. I'd apply the default cap only wheremirror_on()is true, which is the measured case.RX_VK_IMPORT_CAP_MB> 0 can still turn it on anywhere for testing. NVIDIA and Intel Linux at 32 GB+ stay untested. That's fine to say in the description, and we'll ask testers.Nits:
RX_VK_IMPORT_LOWon a 32 GB PC now turns the valve on against the full budget. On main, onlyRX_VK_IMPORT_HIGHdid. That's reasonable; one line in the description would do.Merge conflict. It's only
docs/tested-configurations.md, because both sides add rows at the top of the table.vk_memory.cpp, the header and the tests merge cleanly with today's main. Please merge main. On this head only the title check has run: the build and test workflows don't start while the PR conflicts, and on the previous head they were waiting for a maintainer's approval. We'll approve them once main is in.Thanks, the scoping and the override look right (details in the review). The 4500 itself still has only the 25%/20% run behind it. A run of this PR's defaults against current main, which has had #2685, #2795 and #2825 since your baseline, is what would settle it.
Scoping fixes the 16 GB PCs and the Deck, thanks. On 32 GB+ PCs,
usedis still the whole heap, so whether the valve now runs all the time there is what the defaults run should show (the count and how oftenidle imports given backlogs). Separately, on 32 GB+ PCs with no mirrors (integrated GPUs, GCN), every import passes this filter: see the review for scoping the default cap tomirror_on().