Uni20
Asynchronous tensor and runtime library.
Loading...
Searching...
No Matches
tbb_scheduler.hpp
Go to the documentation of this file.
1#pragma once
4
5#include "async_errors.hpp"
6#include "scheduler.hpp"
7#include "task_registry.hpp"
9#include <algorithm>
10#include <atomic>
11#include <cstddef>
12#include <memory>
13#include <mutex>
14#include <oneapi/tbb/concurrent_queue.h>
15#include <oneapi/tbb/parallel_for.h>
16#include <oneapi/tbb/task.h>
17#include <oneapi/tbb/task_arena.h>
18#include <oneapi/tbb/task_group.h>
19#include <optional>
20#include <uni20/config.hpp>
21#include <utility>
22#include <vector>
23
24namespace uni20::async
25{
26
27namespace detail
28{
29inline void service_tbb_task_registry_debug_requests()
30{
31#if UNI20_DEBUG_ASYNC_TASKS
33#endif
34}
35} // namespace detail
36
39{
41 std::optional<std::size_t> maximum_concurrency{std::nullopt};
42};
43
57class TbbNumaScheduler;
58
60 class SuspendedWait;
61
62 public:
69 explicit TbbScheduler(int max_concurrency = oneapi::tbb::task_arena::automatic,
70 TbbSchedulerBatchOptions batch_options = {})
71 : arena_(max_concurrency, /*reserved_for_application_threads=*/max_concurrency == 1 ? 0 : 1,
72 oneapi::tbb::task_arena::priority::normal, oneapi::tbb::task_arena::leave_policy::fast),
73 batch_options_(std::move(batch_options)), paused_(false)
74 {
75 CHECK(!batch_options_.maximum_concurrency || *batch_options_.maximum_concurrency > 0);
76 }
77
81 explicit TbbScheduler(oneapi::tbb::task_arena::constraints constraints, TbbSchedulerBatchOptions batch_options = {})
82 : arena_(constraints, /*reserved_for_application_threads=*/constraints.max_concurrency == 1 ? 0 : 1,
83 oneapi::tbb::task_arena::priority::normal, oneapi::tbb::task_arena::leave_policy::fast),
84 batch_options_(std::move(batch_options)), paused_(false)
85 {
86 CHECK(!batch_options_.maximum_concurrency || *batch_options_.maximum_concurrency > 0);
87 }
88
89 ~TbbScheduler() noexcept override
90 {
91 // ensure all tasks finish before destruction
93 }
94
96 void schedule(AsyncTask&& t) override
97 {
98 TaskRegistry::record_task_scheduled(t.coroutine_handle());
99 if (t.set_scheduler(this)) this->enqueue_task(std::move(t));
100 }
101
108 void run_all()
109 {
110 this->resume();
111 detail::service_tbb_task_registry_debug_requests();
113 detail::service_tbb_task_registry_debug_requests();
114 }
115
117 void pause() override
118 {
119 std::scoped_lock lock(pause_mutex_);
120 paused_.store(true, std::memory_order_release);
121 }
122
124 void resume() override
125 {
126 std::vector<TaskHandle> drained;
127 {
128 std::scoped_lock lock(pause_mutex_);
129 paused_.store(false, std::memory_order_release);
130 TaskHandle h;
131 while (queue_.try_pop(h))
132 {
133 drained.push_back(h);
134 }
135 }
136
137 for (auto h : drained)
138 {
139 TRACE_MODULE(ASYNC, "scheduling coroutine", h.coroutine());
140 this->dispatch_handle(h);
141 }
142 }
143
144 void help_while_waiting(const WaitPredicate& is_ready) override { this->wait_for(is_ready); }
145
147 void wait_for(const WaitPredicate& is_ready) override { this->wait_for(WaitRequest{.is_ready = is_ready}); }
148
152 void wait_for(WaitRequest const& request) override
153 {
154 if (request.is_ready()) return;
155 if (!request.notify_when_ready)
156 throw async_state_error("TbbScheduler pending wait requires a readiness notification");
157
158 arena_.execute([&] { this->wait_in_arena(request); });
159 }
160
161 private:
162 void execute_batch_impl(LightweightTaskBatch const& batch) override
163 {
164 if (batch.size() == 0) return;
165
166 arena_.execute([&] {
167 // Serial batches must occupy the same arena as coroutine activations.
168 // Running this loop outside execute() could overlap an enqueued task.
169 if (arena_.max_concurrency() == 1)
170 {
171 for (std::size_t index = 0; index < batch.size(); ++index)
172 batch(index);
173 return;
174 }
175
176 if (!batch_options_.maximum_concurrency)
177 {
178 oneapi::tbb::parallel_for(std::size_t{0}, batch.size(), [&](std::size_t index) { batch(index); });
179 return;
180 }
181
182 std::size_t const runner_count =
183 std::min(*batch_options_.maximum_concurrency, static_cast<std::size_t>(arena_.max_concurrency()));
184 if (runner_count >= batch.size())
185 {
186 oneapi::tbb::parallel_for(std::size_t{0}, batch.size(), [&](std::size_t index) { batch(index); });
187 return;
188 }
189
190 std::atomic<std::size_t> next_index{0};
191 oneapi::tbb::parallel_for(
192 std::size_t{0}, runner_count,
193 [&](std::size_t) {
194 while (true)
195 {
196 std::size_t const index = next_index.fetch_add(1, std::memory_order_relaxed);
197 if (index >= batch.size()) return;
198 batch(index);
199 }
200 },
201 oneapi::tbb::simple_partitioner{});
202 });
203 }
204
205 bool accepts_route(TaskRoute route) const noexcept override
206 {
207 return route.domain == TaskDomain::host && !route.cuda_device;
208 }
209
210 bool can_direct_transfer(TaskHandle, TaskHandle) const noexcept override { return true; }
211
213 void reschedule(BasicTask&& t) override { this->enqueue_task(std::move(t)); }
214
215 class SuspendedWait {
216 public:
217 void install(oneapi::tbb::task::suspend_point point) noexcept
218 {
219 oneapi::tbb::task::suspend_point point_to_resume = nullptr;
220 {
221 std::scoped_lock lock(mutex_);
222 point_ = point;
223 if (signaled_ && !resume_issued_)
224 {
225 resume_issued_ = true;
226 point_to_resume = point_;
227 }
228 }
229 if (point_to_resume) oneapi::tbb::task::resume(point_to_resume);
230 }
231
232 void signal() noexcept
233 {
234 oneapi::tbb::task::suspend_point point_to_resume = nullptr;
235 {
236 std::scoped_lock lock(mutex_);
237 signaled_ = true;
238 if (point_ && !resume_issued_)
239 {
240 resume_issued_ = true;
241 point_to_resume = point_;
242 }
243 }
244 if (point_to_resume) oneapi::tbb::task::resume(point_to_resume);
245 }
246
247 private:
248 std::mutex mutex_;
249 oneapi::tbb::task::suspend_point point_{nullptr};
250 bool signaled_{false};
251 bool resume_issued_{false};
252 };
253
254 void wait_in_arena(WaitRequest const& request)
255 {
256 while (!request.is_ready())
257 {
258 detail::service_tbb_task_registry_debug_requests();
259 auto wait = std::make_shared<SuspendedWait>();
260 request.notify_when_ready([weak_wait = std::weak_ptr<SuspendedWait>(wait)]() noexcept {
261 if (auto active = weak_wait.lock()) active->signal();
262 });
263
264 // Registration must also notify if readiness raced with registration.
265 // Recheck before suspending: a writer predicate may acquire its gate here.
266 if (request.is_ready()) return;
267 oneapi::tbb::task::suspend([wait](oneapi::tbb::task::suspend_point point) { wait->install(point); });
268 // Notifications need not reserve a resource. If another writer acquired
269 // it first, recheck and register a fresh notification on the next pass.
270 }
271 }
272
273 protected:
276 {
277 arena_.execute([&] { tg_.wait(); });
278 }
279
282 {
283 TRACE_MODULE(ASYNC, "TBB scheduler enqueuing task", t.coroutine_handle());
284 if (auto h = t.release_handle())
285 {
286 bool paused = paused_.load(std::memory_order_acquire);
287 if (paused)
288 {
289 queue_.push(h);
290 }
291 else
292 {
293 {
294 std::scoped_lock lock(pause_mutex_);
295 if (paused_.load(std::memory_order_acquire))
296 {
297 queue_.push(h);
298 return;
299 }
300 }
301
302 this->dispatch_handle(h);
303 }
304 }
305 }
306
309 {
310 this->dispatch_handle_in_arena(arena_, handle, {.scheduler = "TbbScheduler"}, [] {});
311 }
312
319 template <typename Prepare>
320 void dispatch_handle_in_arena(oneapi::tbb::task_arena& arena, TaskHandle handle,
321 detail::TbbTaskAdmissionContext context, Prepare&& prepare)
322 {
323 detail::enqueue_tbb_task(
324 arena, tg_,
325 [handle, prepare = std::forward<Prepare>(prepare)] {
326 prepare();
327 TRACE_MODULE(ASYNC, "resuming coroutine", handle.coroutine());
329 detail::service_tbb_task_registry_debug_requests();
330 },
331 context);
332 }
333
334 private:
335 oneapi::tbb::task_arena arena_;
336 oneapi::tbb::task_group tg_;
337 TbbSchedulerBatchOptions batch_options_;
338 std::atomic<bool> paused_;
339 std::mutex pause_mutex_;
340 oneapi::tbb::concurrent_queue<TaskHandle> queue_;
341 // FIXME: the concurrent_queue is overkill here, since we don't need to preserve order of tasks
342
343 friend class TbbNumaScheduler;
344};
345
346} // namespace uni20::async
Exception hierarchy for async/dataflow operations.
static bool service_debug_requests()
Processes pending dump requests on the calling thread.
static void record_task_scheduled(std::coroutine_handle<> h)
Records the call stack where a coroutine was submitted to a scheduler.
Canonical host-oriented coroutine task type.
Definition async_task.hpp:196
Move-only promise-neutral ownership claim for a Uni20 coroutine.
Definition async_task.hpp:103
Scheduler interface for initial AsyncTask submission and host-side progress.
Definition scheduler.hpp:89
std::function< bool()> WaitPredicate
Definition scheduler.hpp:127
Borrowed type-erased description of a synchronous lightweight task batch.
Definition scheduler.hpp:28
auto size() const noexcept -> std::size_t
Return the number of independent work items.
Definition scheduler.hpp:45
Non-owning erased identity for one Uni20 coroutine frame.
Definition async_task.hpp:59
static void resume_and_track(TaskHandle handle)
Resume a coroutine handle while recording the running transition.
Definition async_task_promise.hpp:400
NUMA-aware scheduler that balances work across per-node TBB arenas.
Definition tbb_numa_scheduler.hpp:24
Definition tbb_scheduler.hpp:59
void resume() override
Unpause the scheduler, and execute any tasks that have been queued.
Definition tbb_scheduler.hpp:124
void dispatch_handle_in_arena(oneapi::tbb::task_arena &arena, TaskHandle handle, detail::TbbTaskAdmissionContext context, Prepare &&prepare)
Submit one activation to an arena through the shared task group.
Definition tbb_scheduler.hpp:320
void wait_for(const WaitPredicate &is_ready) override
Accept an already-satisfied predicate; pending TBB waits require a WaitRequest notification.
Definition tbb_scheduler.hpp:147
void enqueue_task(BasicTask &&t)
Enqueue one task through the scheduler's domain-routing hook.
Definition tbb_scheduler.hpp:281
TbbScheduler(oneapi::tbb::task_arena::constraints constraints, TbbSchedulerBatchOptions batch_options={})
Construct a TBB scheduler constrained to a specific NUMA node.
Definition tbb_scheduler.hpp:81
void help_while_waiting(const WaitPredicate &is_ready) override
Allow a scheduler to advance queued work while a thread is blocking.
Definition tbb_scheduler.hpp:144
TbbScheduler(int max_concurrency=oneapi::tbb::task_arena::automatic, TbbSchedulerBatchOptions batch_options={})
Construct a TBB scheduler with a given arena concurrency.
Definition tbb_scheduler.hpp:69
void pause() override
Pause the scheduler. Don't execute scheduled tasks, but instead add them to a queue.
Definition tbb_scheduler.hpp:117
void wait_for_submitted_tasks()
Wait for every activation registered with the shared task group.
Definition tbb_scheduler.hpp:275
~TbbScheduler() noexcept override
Definition tbb_scheduler.hpp:89
virtual void dispatch_handle(TaskHandle handle)
Route one runnable task activation to its execution arena.
Definition tbb_scheduler.hpp:308
void run_all()
Block until all tasks scheduled on this scheduler are complete.
Definition tbb_scheduler.hpp:108
void wait_for(WaitRequest const &request) override
Wait for targeted readiness while allowing the arena to execute other work.
Definition tbb_scheduler.hpp:152
void schedule(AsyncTask &&t) override
Schedule a coroutine for initial execution.
Definition tbb_scheduler.hpp:96
Base class for invalid async state transitions or accesses.
Definition async_errors.hpp:20
cuda::ResourceLease< HandleSlot > handle
Definition cublas/execution.cpp:33
Definition qnum.hpp:403
This represents the reverse-mode adjoint for functions of complex variables, assuming a real-valued s...
Definition async/async.hpp:26
Defines internal task routing and the host-oriented scheduler interface.
Readiness predicate and optional targeted wakeup registration for a blocking wait.
Definition scheduler.hpp:133
WaitPredicate is_ready
Predicate that reports whether the requested value is available.
Definition scheduler.hpp:135
WaitWakeupRegistration notify_when_ready
Register a non-throwing wakeup, including when readiness races with registration.
Definition scheduler.hpp:142
Runtime policy for synchronous lightweight batches executed by a TBB scheduler.
Definition tbb_scheduler.hpp:39
std::optional< std::size_t > maximum_concurrency
Maximum number of concurrently executing batch runners; null uses ordinary parallel_for partitioning.
Definition tbb_scheduler.hpp:41
Selects the task-registry implementation for the current build mode.
Internal non-blocking oneTBB arena admission helper.
#define CHECK(cond,...)
Definition trace.hpp:299
#define TRACE_MODULE(m,...)
Definition trace.hpp:149