Uni20
Asynchronous tensor and runtime library.
Loading...
Searching...
No Matches
dispatch.hpp
Go to the documentation of this file.
1#pragma once
2
14
15#include <array>
16#include <concepts>
17#include <optional>
18#include <span>
19#include <string>
20#include <string_view>
21#include <tuple>
22#include <type_traits>
23#include <utility>
24#include <vector>
25
26namespace uni20::linalg
27{
28
29namespace detail
30{
31template <class T> using kernel_type_probe_arg_t = std::remove_reference_t<T>&;
32
33template <class T> struct is_kernel_acceptance : std::false_type
34{};
35
36template <KernelTypeAcceptance Acceptance> struct is_kernel_acceptance<KernelAcceptance<Acceptance>> : std::true_type
37{};
38
39template <class Backend, class Op, class... Args> consteval bool backend_has_try_kernel()
40{
41 return requires {
42 {
43 try_kernel(std::declval<Backend const&>(), std::declval<Op const&>(),
44 std::declval<kernel_type_probe_arg_t<Args>>()...)
45 } -> std::same_as<KernelAttempt>;
46 };
47}
48
54template <class Backend, class Op, class... Args> consteval KernelTypeAcceptance backend_type_acceptance()
55{
56 using backend_type = std::remove_cvref_t<Backend>;
57 using op_type = std::remove_cvref_t<Op>;
58 if constexpr (requires {
59 kernel_accepts_types(std::declval<backend_type const&>(), std::declval<op_type const&>(),
60 std::declval<kernel_type_probe_arg_t<Args>>()...);
61 })
62 {
63 using result_type = std::remove_cvref_t<decltype(kernel_accepts_types(
64 std::declval<backend_type const&>(), std::declval<op_type const&>(),
65 std::declval<kernel_type_probe_arg_t<Args>>()...))>;
66 if constexpr (!is_kernel_acceptance<result_type>::value)
67 {
68 static_assert(is_kernel_acceptance<result_type>::value,
69 "kernel_accepts_types must return kernel_types_no, kernel_types_maybe, or kernel_types_yes");
71 }
72 else
73 {
74 constexpr auto acceptance = result_type::value;
75 if constexpr (acceptance == KernelTypeAcceptance::no)
76 {
78 }
79 else
80 {
81 static_assert(backend_has_try_kernel<backend_type, op_type, Args...>(),
82 "kernel_accepts_types accepted these types, but try_kernel is not available");
83 return acceptance;
84 }
85 }
86 }
88}
89} // namespace detail
90
91namespace detail
92{
93template <class BackendSelector>
94using normalized_backend_selector_t =
95 std::remove_cvref_t<decltype(normalize_backend_selector(std::declval<BackendSelector>()))>;
96
97template <class Backend, class BackendList> struct PrependBackend;
98
99template <class Backend, class... Backends> struct PrependBackend<Backend, backend_list<Backends...>>
100{
101 using type = backend_list<Backend, Backends...>;
102};
103
104template <class BackendList, class Op, class... Args> struct KernelTypeCandidates;
105
106template <class Op, class... Args> struct KernelTypeCandidates<backend_list<>, Op, Args...>
107{
108 using type = backend_list<>;
109};
110
111template <class Backend, class... Backends, class Op, class... Args>
112struct KernelTypeCandidates<backend_list<Backend, Backends...>, Op, Args...>
113{
114 private:
115 using tail = typename KernelTypeCandidates<backend_list<Backends...>, Op, Args...>::type;
116
117 public:
118 using type = std::conditional_t<backend_type_acceptance<Backend, Op, Args...>() == KernelTypeAcceptance::no, tail,
119 typename PrependBackend<Backend, tail>::type>;
120};
121
122template <class Op, class... Args, class Backend> constexpr auto candidate_backend_tuple(Backend&& backend)
123{
124 using backend_type = std::remove_cvref_t<Backend>;
125 if constexpr (backend_type_acceptance<backend_type, Op, Args...>() == KernelTypeAcceptance::no)
126 {
127 return std::tuple<>{};
128 }
129 else
130 {
131 return std::tuple<backend_type>{std::forward<Backend>(backend)};
132 }
133}
134
135template <class Tuple, std::size_t... Index>
136constexpr auto backend_list_from_tuple(Tuple&& tuple, std::index_sequence<Index...>)
137{
138 using tuple_type = std::remove_cvref_t<Tuple>;
139 return backend_list<std::tuple_element_t<Index, tuple_type>...>{std::get<Index>(std::forward<Tuple>(tuple))...};
140}
141
142template <class Op, class... Args, class... Backends>
143consteval KernelTypeAcceptance probe_backend_list_types(std::type_identity<backend_list<Backends...>>)
144{
145 constexpr bool any_yes = ((backend_type_acceptance<Backends, Op, Args...>() == KernelTypeAcceptance::yes) || ...);
146 if constexpr (any_yes)
147 {
149 }
150
151 constexpr bool any_maybe = ((backend_type_acceptance<Backends, Op, Args...>() == KernelTypeAcceptance::maybe) || ...);
152 if constexpr (any_maybe)
153 {
155 }
156
158}
159} // namespace detail
160
165template <class BackendSelector, class Op, class... Args>
167 typename detail::KernelTypeCandidates<detail::normalized_backend_selector_t<BackendSelector>,
168 std::remove_cvref_t<Op>, Args...>::type;
169
174template <class BackendSelector, class Op, class... Args>
175constexpr auto kernel_type_candidates(BackendSelector&& selector, Op const&, Args&&...)
176{
177 using op_type = std::remove_cvref_t<Op>;
178 auto backends = normalize_backend_selector(std::forward<BackendSelector>(selector));
179 auto candidates = std::apply(
180 []<class... Backends>(Backends&&... backend) {
181 return std::tuple_cat(detail::candidate_backend_tuple<op_type, Args...>(std::forward<Backends>(backend))...);
182 },
183 std::move(backends.entries));
184 auto result = detail::backend_list_from_tuple(std::move(candidates),
185 std::make_index_sequence<std::tuple_size_v<decltype(candidates)>>{});
186 static_assert(std::same_as<decltype(result), kernel_type_candidates_t<BackendSelector, Op, Args...>>);
187 return result;
188}
189
194template <class BackendSelector, class Op, class... Args> consteval KernelTypeAcceptance probe_dispatch_kernel_types()
195{
196 using backends_type = detail::normalized_backend_selector_t<BackendSelector>;
197 static_assert(is_backend_list_v<backends_type>, "backend selectors must normalize to backend_list");
198 return detail::probe_backend_list_types<std::remove_cvref_t<Op>, Args...>(std::type_identity<backends_type>{});
199}
200
205template <class BackendSelector, class Op, class... Args>
206constexpr KernelTypeAcceptance probe_dispatch_kernel(BackendSelector&&, Op const&, Args&&...)
207{
208 return probe_dispatch_kernel_types<BackendSelector, Op, Args...>();
209}
210
211namespace detail
212{
213template <class BackendList, class Op, class... Args>
214concept KernelDispatchTypesAccepted =
215 probe_backend_list_types<std::remove_cvref_t<Op>, Args...>(std::type_identity<BackendList>{}) !=
217
218template <class Entity>
219concept NamedKernelDispatchEntity = requires {
220 { std::remove_cvref_t<Entity>::name } -> std::convertible_to<std::string_view>;
221};
222
223template <class... Backends, class Op, class... Args>
224std::vector<KernelBackendAttempt>
225make_kernel_backend_attempts(backend_list<Backends...> const&,
226 std::span<std::optional<KernelAttempt> const> runtime_results, Op const&, Args&&...)
227{
228 static_assert(NamedKernelDispatchEntity<Op>, "kernel operation tags must define a static name");
229 static_assert((NamedKernelDispatchEntity<Backends> && ...), "kernel backends must define a static name");
230 CHECK(runtime_results.empty() || runtime_results.size() == sizeof...(Backends));
231
232 std::vector<KernelBackendAttempt> attempts;
233 attempts.reserve(sizeof...(Backends));
234 std::size_t index = 0;
235 auto append_attempt = [&]<class Backend>() {
236 constexpr auto acceptance = backend_type_acceptance<Backend, Op, Args...>();
237 attempts.push_back(KernelBackendAttempt{
238 .backend = std::string(std::remove_cvref_t<Backend>::name),
239 .type_acceptance = acceptance,
240 .runtime_result = runtime_results.empty() ? std::nullopt : runtime_results[index],
241 });
242 ++index;
243 };
244 (append_attempt.template operator()<Backends>(), ...);
245 return attempts;
246}
247
248template <class... Backends, class Op, class... Args>
249dispatch_diagnostics::event
250make_kernel_dispatch_diagnostic(backend_list<Backends...> const& backends,
251 std::span<std::optional<KernelAttempt> const> runtime_results, Op const& op,
252 Args&&... args)
253{
254 return dispatch_diagnostics::event{
255 .operation = std::string(std::remove_cvref_t<Op>::name),
256 .backend_attempts = make_kernel_backend_attempts(backends, runtime_results, op, args...),
257 };
258}
259
260template <KernelDispatchFailure Failure, class... Backends, class Op, class... Args>
261KernelDispatchError make_kernel_dispatch_error(backend_list<Backends...> const& backends,
262 std::span<std::optional<KernelAttempt> const> runtime_results,
263 Op const& op, Args&&... args)
264{
265 auto diagnostic = make_kernel_dispatch_diagnostic(backends, runtime_results, op, args...);
266 return KernelDispatchError(std::move(diagnostic.operation), Failure, std::move(diagnostic.backend_attempts));
267}
268
269template <std::size_t Index, class... Backends, class Op, class RecordAttempt, class... Args>
270bool try_dispatch_kernel_at(backend_list<Backends...> const& backends, Op const& op, RecordAttempt& record_attempt,
271 Args&&... args)
272{
273 if constexpr (Index == sizeof...(Backends))
274 {
275 return false;
276 }
277 else
278 {
279 using backend_type = std::tuple_element_t<Index, std::tuple<Backends...>>;
280 constexpr auto acceptance = backend_type_acceptance<backend_type, Op, Args...>();
281
282 if constexpr (acceptance == KernelTypeAcceptance::yes)
283 {
284 KernelAttempt const attempt = try_kernel(std::get<Index>(backends.entries), op, args...);
285 record_attempt(Index, attempt);
287 return true;
288 }
289 else if constexpr (acceptance == KernelTypeAcceptance::maybe)
290 {
291 KernelAttempt const attempt = try_kernel(std::get<Index>(backends.entries), op, args...);
292 record_attempt(Index, attempt);
293 if (kernel_attempt_succeeded(attempt))
294 {
295 return true;
296 }
297 }
298
299 return try_dispatch_kernel_at<Index + 1>(backends, op, record_attempt, args...);
300 }
301}
302
303template <class... Backends, class Op, class... Args>
304void dispatch_backend_list(backend_list<Backends...> const& backends, Op const& op, Args&&... args)
305{
306 bool const diagnostics_enabled = dispatch_diagnostics::enabled();
307 std::array<std::optional<KernelAttempt>, sizeof...(Backends)> runtime_results{};
308 auto record_attempt = [&](std::size_t index, KernelAttempt attempt) { runtime_results[index] = attempt; };
309 bool const succeeded = try_dispatch_kernel_at<0>(backends, op, record_attempt, args...);
310 auto const results = std::span<std::optional<KernelAttempt> const>(runtime_results);
311
312 if (succeeded)
313 {
314 if (diagnostics_enabled)
315 {
316 auto diagnostic = make_kernel_dispatch_diagnostic(backends, results, op, args...);
317 dispatch_diagnostics::detail::emit(diagnostic);
318 }
319 return;
320 }
321
322 if (diagnostics_enabled)
323 {
324 auto diagnostic = make_kernel_dispatch_diagnostic(backends, results, op, args...);
325 dispatch_diagnostics::detail::emit(diagnostic);
326 trace::raise(KernelDispatchError(std::move(diagnostic.operation), KernelDispatchFailure::all_candidates_declined,
327 std::move(diagnostic.backend_attempts)));
328 }
329
331 make_kernel_dispatch_error<KernelDispatchFailure::all_candidates_declined>(backends, results, op, args...));
332}
333} // namespace detail
334
346template <class BackendSelector, class Op, class... Args>
347 requires detail::KernelDispatchTypesAccepted<detail::normalized_backend_selector_t<BackendSelector>, Op, Args...>
348bool try_dispatch_kernel(BackendSelector&& selector, Op op, Args&&... args)
349{
350 auto backends = normalize_backend_selector(std::forward<BackendSelector>(selector));
352 {
353 constexpr auto backend_count = std::tuple_size_v<decltype(backends.entries)>;
354 std::array<std::optional<KernelAttempt>, backend_count> runtime_results{};
355 auto record_attempt = [&](std::size_t index, KernelAttempt attempt) { runtime_results[index] = attempt; };
356 bool const succeeded = detail::try_dispatch_kernel_at<0>(backends, op, record_attempt, args...);
357 auto diagnostic = detail::make_kernel_dispatch_diagnostic(
358 backends, std::span<std::optional<KernelAttempt> const>(runtime_results), op, args...);
359 dispatch_diagnostics::detail::emit(diagnostic);
360 return succeeded;
361 }
362
363 auto ignore_attempt = [](std::size_t, KernelAttempt) {};
364 return detail::try_dispatch_kernel_at<0>(backends, op, ignore_attempt, args...);
365}
366
372template <class BackendSelector, class Op, class... Args>
373 requires detail::KernelDispatchTypesAccepted<detail::normalized_backend_selector_t<BackendSelector>, Op, Args...>
374void dispatch_kernel(BackendSelector&& selector, Op op, Args&&... args)
375{
376 auto backends = normalize_backend_selector(std::forward<BackendSelector>(selector));
377 detail::dispatch_backend_list(backends, op, std::forward<Args>(args)...);
378}
379
383template <class BackendSelector, class Op, class... Args>
384void dynamic_dispatch_kernel(BackendSelector&& selector, Op op, Args&&... args)
385{
386 using backends_type = detail::normalized_backend_selector_t<BackendSelector>;
387 auto backends = normalize_backend_selector(std::forward<BackendSelector>(selector));
388 if constexpr (!detail::KernelDispatchTypesAccepted<backends_type, Op, Args...>)
389 {
391 {
392 auto diagnostic = detail::make_kernel_dispatch_diagnostic(
393 backends, std::span<std::optional<KernelAttempt> const>{}, op, args...);
394 dispatch_diagnostics::detail::emit(diagnostic);
396 std::move(diagnostic.backend_attempts)));
397 }
398 trace::raise(detail::make_kernel_dispatch_error<KernelDispatchFailure::no_eligible_backend>(
399 backends, std::span<std::optional<KernelAttempt> const>{}, op, args...));
400 }
401 else
402 {
403 detail::dispatch_backend_list(backends, op, std::forward<Args>(args)...);
404 }
405}
406
407} // namespace uni20::linalg
Ordered backend selector values shared by tensor storage and linalg dispatch.
Exception carrying structured information about a failed backend walk.
Definition dispatch_error.hpp:93
Opt-in structured diagnostics for kernel-dispatch walks.
Structured kernel-dispatch failure data.
Presentation adapter for structured kernel-dispatch failures.
void raise(Exception &&exception, std::source_location where=std::source_location::current())
Raise a concrete exception through Uni20's configured error boundary.
Definition trace_impl.hpp:2184
bool enabled() noexcept
Return whether runtime kernel-dispatch diagnostics are enabled.
Definition dispatch_diagnostics.hpp:60
Definition linalg/async.hpp:28
constexpr bool kernel_attempt_succeeded(KernelAttempt attempt) noexcept
Return whether a backend performed the operation.
Definition kernel_attempt.hpp:40
KernelAttempt try_kernel(BlasBackend, gemm_op const &, C &c, S const &alpha, A &a, B &b, S const &beta)
Pack logical host values and execute GEMM through value-preserving provider conversion.
Definition linalg/backends/blas/binary80.hpp:30
constexpr KernelTypeAcceptance probe_dispatch_kernel(BackendSelector &&, Op const &, Args &&...)
Probe whether a backend selector can dispatch an operation for the argument types.
Definition dispatch.hpp:206
backend_list(Backends...) -> backend_list< std::remove_cvref_t< Backends >... >
KernelAttempt
Result of one runtime kernel backend attempt.
Definition kernel_attempt.hpp:27
KernelDispatchFailure
Classify why an ordered kernel-dispatch walk failed.
Definition dispatch_error.hpp:44
constexpr auto normalize_backend_selector(backend_list< Backends... > selector) -> backend_list< Backends... >
Return an explicit backend list unchanged.
Definition backend_selector.hpp:259
typename detail::KernelTypeCandidates< detail::normalized_backend_selector_t< BackendSelector >, std::remove_cvref_t< Op >, Args... >::type kernel_type_candidates_t
Ordered backend-list type whose entries accept the operation argument types.
Definition dispatch.hpp:168
std::integral_constant< KernelTypeAcceptance, Value > KernelAcceptance
Type-level result returned by kernel_accepts_types customizations.
Definition dispatch_error.hpp:31
constexpr auto kernel_type_candidates(BackendSelector &&selector, Op const &, Args &&...)
Filter a backend selector to candidates accepting the call's argument types.
Definition dispatch.hpp:175
consteval KernelTypeAcceptance probe_dispatch_kernel_types()
Probe whether a backend selector type can dispatch operation argument types.
Definition dispatch.hpp:194
bool try_dispatch_kernel(BackendSelector &&selector, Op op, Args &&... args)
Normalize a selector and try each eligible backend until one performs the operation.
Definition dispatch.hpp:348
KernelTypeAcceptance
Type-level backend eligibility for an operation and argument set.
Definition dispatch_error.hpp:24
void dispatch_kernel(BackendSelector &&selector, Op op, Args &&... args)
Normalize a selector and dispatch or report that every eligible backend declined.
Definition dispatch.hpp:374
consteval auto kernel_accepts_types(BlasBackend, gemm_op const &, C &, S const &, A &, B &, S const &)
Accept host-readable native binary80 operands, including transforming accessors.
Definition linalg/backends/blas/binary80.hpp:19
void dynamic_dispatch_kernel(BackendSelector &&selector, Op op, Args &&... args)
Dynamically dispatch an operation, raising even type-level rejection at runtime.
Definition dispatch.hpp:384
std::string operation
Definition dispatch_diagnostics.hpp:25
#define CHECK(cond,...)
Definition trace.hpp:299