Uni20
Asynchronous tensor and runtime library.
Loading...
Searching...
No Matches
tbb_numa_scheduler.hpp
Go to the documentation of this file.
1#pragma once
4
5#include "scheduler.hpp"
6#include "tbb_scheduler.hpp"
7
9
10#include <atomic>
11#include <fmt/core.h>
12#include <memory>
13#include <mutex>
14#include <optional>
15#include <unordered_map>
16#include <vector>
17
18#include <oneapi/tbb/info.h>
19
20namespace uni20::async
21{
22
24class TbbNumaScheduler final : public IAsyncScheduler {
25 public:
27 TbbNumaScheduler() : TbbNumaScheduler(oneapi::tbb::info::numa_nodes()) {}
28
31 explicit TbbNumaScheduler(std::vector<int> nodes) : numa_nodes_(std::move(nodes))
32 {
33 if (numa_nodes_.empty())
34 {
35 numa_nodes_.push_back(-1);
36 }
37
38 arenas_.reserve(numa_nodes_.size());
39 scheduled_counts_.assign(numa_nodes_.size(), 0);
40
41 for (std::size_t i = 0; i < numa_nodes_.size(); ++i)
42 {
43 auto node = numa_nodes_[i];
44 oneapi::tbb::task_arena::constraints constraints;
45 constraints.set_numa_id(node);
46 auto scheduler = std::make_unique<TbbScheduler>(constraints);
47 node_to_index_.emplace(node, arenas_.size());
48 arenas_.push_back(Arena{node, std::move(scheduler)});
49 }
50
51 fmt::print("[uni20] TbbNumaScheduler: initialized {} NUMA nodes\n", arenas_.size());
52 }
53
55 void schedule(AsyncTask&& task) override
56 {
57 TaskRegistry::record_task_scheduled(task.coroutine_handle());
58 int target = task.preferred_numa_node().value_or(this->select_next_numa_node());
59 this->schedule_on_node(std::move(task), target);
60 }
61
65 void schedule(AsyncTask&& task, int numa_node)
66 {
67 TaskRegistry::record_task_scheduled(task.coroutine_handle());
68 this->schedule_on_node(std::move(task), numa_node);
69 }
70
72 void pause() override
73 {
74 for (auto& arena : arenas_)
75 {
76 arena.scheduler->pause();
77 }
78 }
79
81 void resume() override
82 {
83 for (auto& arena : arenas_)
84 {
85 arena.scheduler->resume();
86 }
87 }
88
90 void run_all()
91 {
92 for (auto& arena : arenas_)
93 {
94 arena.scheduler->run_all();
95 }
96 }
97
100 [[nodiscard]] const std::vector<int>& numa_nodes() const noexcept { return numa_nodes_; }
101
105 [[nodiscard]] std::size_t scheduled_count_for(int numa_node) const noexcept
106 {
107 CHECK(!arenas_.empty(), "TbbNumaScheduler must retain at least one arena");
108 auto idx = this->index_for_node(numa_node);
109 std::lock_guard<std::mutex> lock(counts_mutex_);
110 return scheduled_counts_[idx];
111 }
112
113 private:
114 void execute_batch_impl(LightweightTaskBatch const& batch) override
115 {
116 CHECK(!arenas_.empty(), "TbbNumaScheduler must retain at least one arena");
117 arenas_[this->index_for_node(this->select_next_numa_node())].scheduler->execute_batch(
118 batch.size(), [&batch](std::size_t index) { batch(index); });
119 }
120
121 bool accepts_route(TaskRoute route) const noexcept override
122 {
123 return route.domain == TaskDomain::host && !route.cuda_device;
124 }
125
126 // TbbNumaScheduler itself is a router; direct execution belongs to one of
127 // its child arena schedulers.
128 bool can_direct_transfer(TaskHandle, TaskHandle) const noexcept override { return false; }
129
131 void reschedule(BasicTask&& task) override
132 {
133 if (auto preferred = task.preferred_numa_node())
134 {
135 this->reschedule_on_node(std::move(task), *preferred);
136 }
137 else
138 {
139 this->reschedule_on_node(std::move(task), this->select_next_numa_node());
140 }
141 }
142
143 struct Arena
144 {
145 int numa_node;
146 std::unique_ptr<TbbScheduler> scheduler;
147 };
148
149 [[nodiscard]] std::size_t index_for_node(int numa_node) const noexcept
150 {
151 if (auto it = node_to_index_.find(numa_node); it != node_to_index_.end())
152 {
153 return it->second;
154 }
155 return 0;
156 }
157
158 [[nodiscard]] int select_next_numa_node() noexcept
159 {
160 CHECK(!arenas_.empty(), "TbbNumaScheduler must retain at least one arena");
161 auto index = next_index_.fetch_add(1, std::memory_order_relaxed) % arenas_.size();
162 return arenas_[index].numa_node;
163 }
164
165 void schedule_on_node(AsyncTask&& task, int numa_node)
166 {
167 CHECK(!arenas_.empty(), "TbbNumaScheduler must retain at least one arena");
168 auto index = this->index_for_node(numa_node);
169 auto& arena = arenas_[index];
170 auto actual_node = arena.numa_node;
171 task.set_preferred_numa_node(actual_node);
172 {
173 std::lock_guard<std::mutex> lock(counts_mutex_);
174 ++scheduled_counts_[index];
175 }
176 arena.scheduler->schedule(std::move(task));
177 }
178
179 void reschedule_on_node(BasicTask&& task, int numa_node)
180 {
181 CHECK(!arenas_.empty(), "TbbNumaScheduler must retain at least one arena");
182 auto index = this->index_for_node(numa_node);
183 auto& arena = arenas_[index];
184 auto actual_node = arena.numa_node;
185 task.set_preferred_numa_node(actual_node);
186 {
187 std::lock_guard<std::mutex> lock(counts_mutex_);
188 ++scheduled_counts_[index];
189 }
190 arena.scheduler->reschedule(std::move(task));
191 }
192
193 std::vector<int> numa_nodes_;
194 std::vector<Arena> arenas_;
195 std::unordered_map<int, std::size_t> node_to_index_;
196 std::vector<std::size_t> scheduled_counts_;
197 mutable std::mutex counts_mutex_;
198 std::atomic<std::size_t> next_index_{0};
199};
200
201} // namespace uni20::async
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
Scheduler interface for initial AsyncTask submission and host-side progress.
Definition scheduler.hpp:89
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
NUMA-aware scheduler that balances work across per-node TBB arenas.
Definition tbb_numa_scheduler.hpp:24
void run_all()
Drain all arenas by waiting for completion of pending work.
Definition tbb_numa_scheduler.hpp:90
TbbNumaScheduler()
Construct a scheduler that reflects the system's visible NUMA nodes.
Definition tbb_numa_scheduler.hpp:27
const std::vector< int > & numa_nodes() const noexcept
Access the NUMA nodes managed by this scheduler.
Definition tbb_numa_scheduler.hpp:100
void resume() override
Resume all managed arenas.
Definition tbb_numa_scheduler.hpp:81
TbbNumaScheduler(std::vector< int > nodes)
Construct a scheduler with an explicit set of NUMA nodes.
Definition tbb_numa_scheduler.hpp:31
void schedule(AsyncTask &&task, int numa_node)
Schedule a coroutine on a specific NUMA node.
Definition tbb_numa_scheduler.hpp:65
void pause() override
Pause all managed arenas.
Definition tbb_numa_scheduler.hpp:72
std::size_t scheduled_count_for(int numa_node) const noexcept
Query how many tasks have been dispatched to a NUMA node.
Definition tbb_numa_scheduler.hpp:105
void schedule(AsyncTask &&task) override
Schedule a coroutine, choosing an arena based on NUMA preference.
Definition tbb_numa_scheduler.hpp:55
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.
Scheduler implementation using oneAPI oneTBB task_arena + task_group.
#define CHECK(cond,...)
Definition trace.hpp:299