Skip to content

Commit 3e31cd7

Browse files
committed
address third review of deferred releases
Keep DeferredReleases private behind a SyclQueue_DeferRelease C-API function, drop the poll interval cap, restart the thread after a timed-out stop, keep one named thread state, and drop the order manager's runtime deprecation warnings.
1 parent 9f747fd commit 3e31cd7

11 files changed

Lines changed: 519 additions & 257 deletions

File tree

‎CHANGELOG.md‎

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
99
### Added
1010
* Added `dpctl.SyclQueue.keep_args_alive` method, which keeps Python objects alive until the work on that queue using them completes [gh-2359](https://github.com/IntelPython/dpctl/pull/2359)
1111
* Added `add_event` and `add_cleanup_event` methods and `cleanup_events` and `num_cleanup_events` properties to the order manager, for tracking the events that gate the release of objects used by offloaded tasks [gh-2359](https://github.com/IntelPython/dpctl/pull/2359)
12+
* Added `SyclQueue_DeferRelease` C-API function, which defers a release callback until work submitted to a queue completes, and through which `dpctl::utils::keep_args_alive` keeps its arguments alive [gh-2359](https://github.com/IntelPython/dpctl/pull/2359)
1213

1314
### Changed
1415
* Object lifetime during offload is now managed by a list of releases deferred until the events that gate them have completed, rather than by a `host_task`. `dpctl` keeps one thread for looking over the list and running the releases that have come due, started by the first release deferred and again in the child of a `fork`, which takes the GIL to do so and lets a release call into the interpreter and free USM allocations [gh-2359](https://github.com/IntelPython/dpctl/pull/2359)
1516
* The event returned by `dpctl.SyclQueue.keep_args_alive`, `dpctl::utils::keep_args_alive` and `dpctl.SyclQueue._submit_keep_args_alive` is now for an empty kernel that gates the deferred release rather than for a `host_task` that performs it, so its completion means that the objects are no longer in use rather than that they have been released [gh-2359](https://github.com/IntelPython/dpctl/pull/2359)
1617

1718
### Deprecated
19+
* Deprecated `dpctl.SyclQueue._submit_keep_args_alive` in favor of `dpctl.SyclQueue.keep_args_alive`, which it now calls and which behaves identically [gh-2359](https://github.com/IntelPython/dpctl/pull/2359)
20+
* Deprecated the order manager's `add_event_pair`, `host_task_events` and `num_host_task_events`, as `host_task` is no longer used for managing object lifetimes, in favor of `add_event`, `add_cleanup_event`, `cleanup_events` and `num_cleanup_events`; they do not yet emit a `DeprecationWarning` [gh-2359](https://github.com/IntelPython/dpctl/pull/2359)
1821

1922
### Removed
2023
* Removed the deprecated `dpctl.program` submodule, use `dpctl.compiler` instead [gh-2407](https://github.com/IntelPython/dpctl/pull/2407)
@@ -71,8 +74,6 @@ This release extends the SYCL interface exposed by `dpctl` in several directions
7174
* Deprecated `dpctl.program.create_program_from_source` and `dpctl.program.create_program_from_spirv` in favor of `dpctl.compiler.create_kernel_bundle_from_source` and `dpctl.compiler.create_kernel_bundle_from_spirv`; the old functions are kept as aliases that emit a `DeprecationWarning` [gh-2291](https://github.com/IntelPython/dpctl/pull/2291), [gh-2317](https://github.com/IntelPython/dpctl/pull/2317)
7275
* Deprecated `dpctl.program` submodule in favor of `dpctl.compiler`, which provides a better description of the purpose of the submodule in exposing DPC++ compilation-related functionality [gh-2317](https://github.com/IntelPython/dpctl/pull/2317)
7376
* Deprecated `DPCTL_ENABLE_L0_PROGRAM_CREATION` CMake option in favor of `DPCTL_ENABLE_L0_KERNEL_BUNDLE_CREATION` [gh-2317](https://github.com/IntelPython/dpctl/pull/2317)
74-
* Deprecated `dpctl.SyclQueue._submit_keep_args_alive` in favor of `dpctl.SyclQueue.keep_args_alive`, which it now calls and which behaves identically [gh-2359](https://github.com/IntelPython/dpctl/pull/2359)
75-
* Deprecated the order manager's `add_event_pair`, `host_task_events` and `num_host_task_events`, as `host_task` is no longer used for managing object lifetimes, in favor of `add_event`, `add_cleanup_event`, `cleanup_events` and `num_cleanup_events` [gh-2359](https://github.com/IntelPython/dpctl/pull/2359)
7677

7778
### Removed
7879
* Removed unused `platform_typeid_lookup` from `dpctl4pybind11.hpp` [gh-2286](https://github.com/IntelPython/dpctl/pull/2286)

‎docs/doc_sources/api_reference/dpctl/utils.rst‎

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,11 @@
2727

2828
Waiting with ``wait`` also drops the references that
2929
:meth:`dpctl.SyclQueue.keep_args_alive` took for tasks that have since
30-
completed, which ``dpctl`` otherwise does on a thread of its own.
30+
completed, which ``dpctl`` otherwise does on a thread of its own. This is
31+
best-effort: references that thread is already dropping are left to it, so
32+
they may still be held when ``wait`` returns.
3133

32-
.. deprecated:: 0.23.0
34+
.. deprecated:: 0.24.0
3335
``add_event_pair``, ``host_task_events`` and ``num_host_task_events``
3436
are deprecated. Tasks are no longer paired with a host task event, so
3537
``add_event`` takes the computational event alone, and cleanup is

‎dpctl/CMakeLists.txt‎

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -198,12 +198,8 @@ endforeach()
198198
set(_cy_file ${CMAKE_CURRENT_SOURCE_DIR}/_sycl_queue.pyx)
199199
get_filename_component(_trgt ${_cy_file} NAME_WLE)
200200
build_dpctl_ext(${_trgt} ${_cy_file} "dpctl" SYCL)
201-
# _sycl_queue includes _async_dec_ref.hpp, which reaches
202-
# detail/deferred_releases.hpp in the public include directory
203-
target_include_directories(${_trgt} PRIVATE
204-
${CMAKE_CURRENT_SOURCE_DIR}
205-
${CMAKE_CURRENT_SOURCE_DIR}/apis/include
206-
)
201+
# _sycl_queue includes _async_dec_ref.hpp
202+
target_include_directories(${_trgt} PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
207203
target_link_libraries(DpctlCAPI INTERFACE ${_trgt}_headers)
208204

209205
add_subdirectory(compiler)

‎dpctl/_async_dec_ref.hpp‎

Lines changed: 83 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,10 @@
1919
//===----------------------------------------------------------------------===//
2020
///
2121
/// \file
22-
/// This file implements a utility function to decrement reference counts for a
23-
/// given array of Python objects once a given array of sycl events has
24-
/// completed.
22+
/// This file implements the functions that defer releasing what offloaded work
23+
/// uses until that work has completed: decrementing the reference counts of an
24+
/// array of Python objects, or calling a release function another module
25+
/// passes in.
2526
///
2627
/// N.B.: The reference counts are dropped by the thread `dpctl` keeps for
2728
/// running deferred releases, or by a thread that drains them.
@@ -42,6 +43,32 @@
4243
#include "syclinterface/dpctl_data_types.h"
4344
#include "syclinterface/dpctl_sycl_type_casters.hpp"
4445

46+
namespace dpctl
47+
{
48+
namespace detail
49+
{
50+
51+
/*!
52+
* @brief Name of the kernel submitted by `submit_keep_alive_marker`.
53+
*/
54+
class keep_alive_marker;
55+
56+
/*!
57+
* @brief An event that gates the release of objects used by work on `q`.
58+
*
59+
* Submits an empty kernel after `deps`. On an in-order queue the submission is
60+
* also ordered after everything already submitted there, so the event covers
61+
* uses of the objects that the caller never tracked.
62+
*/
63+
inline sycl::event
64+
submit_keep_alive_marker(sycl::queue &q, const std::vector<sycl::event> &deps)
65+
{
66+
return q.single_task<keep_alive_marker>(deps, []() {});
67+
}
68+
69+
} // namespace detail
70+
} // namespace dpctl
71+
4572
namespace
4673
{
4774

@@ -62,6 +89,18 @@ std::vector<sycl::event> unwrap_events(DPCTLSyclEventRef *depERefs,
6289
return depends;
6390
}
6491

92+
/*!
93+
* @brief The queue behind `QRef`, which must not be null.
94+
*/
95+
sycl::queue &unwrap_queue(DPCTLSyclQueueRef QRef)
96+
{
97+
sycl::queue *q = dpctl::syclinterface::unwrap<sycl::queue>(QRef);
98+
if (!q) {
99+
throw std::invalid_argument("A queue is required");
100+
}
101+
return *q;
102+
}
103+
65104
/*!
66105
* @brief Schedule DECREFs of `obj_vec` for once `depends` have completed.
67106
*/
@@ -98,22 +137,18 @@ inline DPCTLSyclEventRef async_dec_ref(DPCTLSyclQueueRef QRef,
98137
size_t nDepERefs,
99138
int *status)
100139
{
101-
using dpctl::syclinterface::unwrap;
102140
using dpctl::syclinterface::wrap;
103141

104142
try {
105-
sycl::queue *q = unwrap<sycl::queue>(QRef);
106-
if (!q) {
107-
throw std::invalid_argument("A queue is required");
108-
}
143+
sycl::queue &q = unwrap_queue(QRef);
109144

110145
std::vector<sycl::event> depends = unwrap_events(depERefs, nDepERefs);
111146
std::vector<PyObject *> obj_vec(obj_array, obj_array + obj_array_size);
112147

113148
static constexpr int result_ok = 0;
114149

115150
const sycl::event marker =
116-
dpctl::detail::submit_keep_alive_marker(*q, depends);
151+
dpctl::detail::submit_keep_alive_marker(q, depends);
117152

118153
// allocated before scheduling, as failing afterwards could not be
119154
// reported: the caller would drop a reference the scheduled DECREFs own
@@ -131,3 +166,42 @@ inline DPCTLSyclEventRef async_dec_ref(DPCTLSyclQueueRef QRef,
131166
return nullptr;
132167
}
133168
}
169+
170+
/*!
171+
* @brief Call `release(data)` once the work using what it releases is done.
172+
*
173+
* Submits an empty kernel to `QRef` after `depERefs`, as `async_dec_ref` does,
174+
* and assigns its event to the `sycl::event` behind `markerERef`. `release` is
175+
* called with the GIL held, on `dpctl`'s thread or on one that drains.
176+
*
177+
* @return 0 on success, and 1 if nothing was deferred, in which case `release`
178+
* is never called.
179+
*/
180+
inline int defer_release(DPCTLSyclQueueRef QRef,
181+
DPCTLSyclEventRef *depERefs,
182+
size_t nDepERefs,
183+
void (*release)(void *),
184+
void *data,
185+
DPCTLSyclEventRef markerERef)
186+
{
187+
using dpctl::syclinterface::unwrap;
188+
189+
try {
190+
sycl::queue &q = unwrap_queue(QRef);
191+
sycl::event *marker = unwrap<sycl::event>(markerERef);
192+
if (!release || !marker) {
193+
throw std::invalid_argument("A release and an event are required");
194+
}
195+
196+
*marker = dpctl::detail::submit_keep_alive_marker(
197+
q, unwrap_events(depERefs, nDepERefs));
198+
199+
dpctl::detail::local_deferred_releases().defer(
200+
{*marker}, [release, data]() { release(data); });
201+
202+
return 0;
203+
} catch (...) {
204+
// no exception may escape into the calling Cython code
205+
return 1;
206+
}
207+
}

0 commit comments

Comments
 (0)