Skip to content

Don't wait for the GPU when freeing memory - #720

Merged
maleadt merged 1 commit into
mainfrom
tb/defer-free
Oct 20, 2026
Merged

maleadt merged 1 commit into
mainfrom
tb/defer-free

Conversation

@maleadt

@maleadt maleadt commented Oct 19, 2026

Copy link
Copy Markdown
Member

Outside the LTS stack, pooled memory is now released with ZE_DRIVER_MEMORY_FREE_POLICY_EXT_FLAG_DEFER_FREE instead of a blocking free. Because kernels access memory indirectly, a blocking free waits for all work submitted so far. From a finalizer, that stalled the thread (~860 ms behind a busy kernel in the measurements on #717), and it deadlocked when that work waited on a task on the same thread.

NEO only processes its deferred-free list during a later free (or when an allocation fails), so memory released this way could stay allocated indefinitely. sweep_deferred_frees frees a small dummy host allocation with the deferring policy, which processes the list without waiting for the GPU. It runs where we try to get memory back: after each GC in retry_reclaim, after trimming the pools in _maybe_gc, and after the scratch hedge's GC.

The LTS stack keeps the old behavior (synchronize, then a blocking free). NEO doesn't report which free policies it supports, so this relies on the rolling stack implementing DEFER_FREE.

Tests:

  • freeing a large array while a kernel waits on the host no longer blocks (it hangs on main);
  • a deferred free is not processed while the GPU still uses the memory, and is processed by a sweep once the GPU is done.

Part of #717. Not covered yet: the finalizer of a busy immediate command list still drains it, and the LTS path still blocks.

Kernels access memory indirectly, so a blocking free waits for all work
submitted so far. Memory is mostly freed from finalizers, so that stalled
whichever thread collected garbage for as long as unrelated kernels ran
(~860 ms with a 1 s kernel), and deadlocked when that work waited for a
task on the thread.

On the rolling stack, free with the DEFER_FREE policy, which has the
driver release the memory once the GPU is done with it. NEO only checks
that when memory is freed again, so reclaiming memory now frees a small
allocation after collecting garbage. That doesn't wait for the GPU
either: reclaiming can happen inside any driver call, with locks held.
The LTS stack doesn't honor the free policies and keeps synchronizing.

Part of #717.
@codecov

codecov Bot commented Oct 19, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 88.23529% with 2 lines in your changes missing coverage. Please review.
✅ Project coverage is 82.49%. Comparing base (e95e38f) to head (f6be2c5).
⚠️ Report is 3 commits behind head on main.

Files with missing lines Patch % Lines
src/pool.jl 33.33% 2 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #720      +/-   ##
==========================================
- Coverage   84.08%   82.49%   -1.59%     
==========================================
  Files          52       51       -1     
  Lines        4386     4268     -118     
==========================================
- Hits         3688     3521     -167     
- Misses        698      747      +49     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@maleadt
maleadt merged commit 70d831a into main Oct 20, 2026
4 of 6 checks passed
@maleadt
maleadt deleted the tb/defer-free branch October 20, 2026 07:54
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

performance Runtime performance.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant