Skip to content

Hold queues by weak reference in SequentialOrderManager - #2426

Open
vlad-perevezentsev wants to merge 3 commits into
masterfrom
order-manager-weak-keys
Open

vlad-perevezentsev wants to merge 3 commits into
masterfrom
order-manager-weak-keys

Conversation

@vlad-perevezentsev

@vlad-perevezentsev vlad-perevezentsev commented Nov 11, 2026 •

Copy link
Copy Markdown
Collaborator

SequentialOrderManager keeps an order manager per queue in a thread-local dict keyed by the queue. Only clear() removes entries, so every queue that ran a dpnp operation stays alive, along with its Level Zero command lists and event pools.
On Level Zero v2 this costs up to ~15 MB per queue, and a program that keeps creating queues runs out of memory.
On Lunar Lake and Battlemage this often made dpnp CI fail with UR_RESULT_ERROR_DEVICE_LOST. dpnp CI passes only because its conftest.py calls clear() after every test file.

This PR makes the map hold queues by weak reference:

  • SyclQueue supports weak references. The new slot follows the vtable pointer, so extensions built against an earlier dpctl keep working. SyclQueue also gets an empty __dealloc__. Without it, Cython reuses _SyclQueue's deallocator, which does not clear weak references.
  • An order manager keeps a copy of its queue, not the queue it is keyed by.
  • Nothing waits when a queue is gone. The weak reference callback only records it, and the owning thread drops the order manager on a later lookup, once its work is complete. Waiting in the callback is not safe: the last reference is often dropped inside a dpnp host_task, which would then wait for itself.
  • An equal queue (same sycl::queue) looked up later gets the same order manager while that manager still has work or is still referenced, so ordering is kept.

A lookup costs ~30 ns more for a known queue and ~4 us more for a new one. Creating a queue and running its first operation takes ~1 ms.

Compatibility: dpnp built against an earlier dpctl works, but warns at import that SyclQueue size changed and fails to import with -W error::RuntimeWarning. dpnp should be rebuilt against this version.

Testing: the dpctl test suite passes (3870 tests, 10 of them new) on Max 1100 with Level Zero v1 and v2 and on OpenCL CPU, and also passes on free-threaded Python 3.14t. Peak memory of the dpnp test suites, with all tests passing in every run:

machine, suite master, clear() per file (CI) this PR, no clear()
Max 1100, dpnp/tests/tensor 5.1 GB host, 4.7 GB device, 105 s 0.7 GB host, 0.4 GB device, 107 s
Max 1100, dpnp/tests 7.3 GB host, 5.2 GB device, 613 s 3.1 GB host, 1.0 GB device, 606 s
Lunar Lake, dpnp/tests/tensor 16.0 GB, 91 s 1.1 GB, 89 s
Lunar Lake, dpnp/tests 18.1 GB, 8:36 4.5 GB, 9:09

Without clear(), master reaches 17.6 GB host and 17.3 GB device memory in dpnp/tests/tensor on Max 1100, and is killed at 20 GB on Lunar Lake.

  • Have you provided a meaningful PR description?
  • Have you added a test, reproducer or referred to an issue with a reproducer?
  • Have you tested your changes locally for CPU and GPU devices?
  • Have you made sure that new changes do not introduce compiler warnings?
  • Have you checked performance impact of proposed changes?
  • Have you added documentation for your changes, if necessary?
  • Have you added your changes to the changelog?
  • If this PR is a work in progress, are you opening the PR as a draft?

Add a __weakref__ slot to SyclQueue. It follows the vtable pointer, so
extensions built against an earlier dpctl find the existing fields and
the vtable where they expect them; only the object grows, which they
report as a size change at import.

SyclQueue gets an empty __dealloc__, so that Cython gives it a
deallocator of its own, which clears the weak references. Cython does
not count __weakref__ as a field that needs one and would reuse the one
of _SyclQueue, leaving weak references to freed memory.
An order manager now holds its sycl::queue in a SyclQueue object of its
own, rather than the queue it was looked up by, so that the values of
the order manager map do not keep its keys alive. _InOrderManager used
to hold its key.

The in-order test that counts waits on the queue now makes the manager
itself, as the managers of the map wait on their copy.
The order manager map kept every queue it was asked for alive until
clear(), so a program creating queues in a loop ran out of device or
system memory (up to ~15 MB per live queue on Level Zero v2).

Queues are now map keys by weak reference. A queue that is gone is only
recorded by its weak reference callback; the owning thread drops its
order manager on a later lookup once the work recorded in it is
complete, without waiting, as the queue may be dropped on any thread,
including inside a host task that the order manager would wait on.
Until then, and while anything else holds it, the order manager is
handed to an equal queue, so that its work stays ordered.

Whether an in-order manager is idle is decided from the events recorded
in it only: Level Zero v2 reports the last event of an in-order queue
as submitted until it is waited on.
@github-actions

Copy link
Copy Markdown

@coveralls

Copy link
Copy Markdown
Collaborator

Coverage Status

coverage: 75.419% (+0.2%) from 75.173% — order-manager-weak-keys into master

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants