select.hh
Go to the documentation of this file.
1#pragma once
2
3/// @file
4/// @brief Flat kernel selection: one choose() per op over measured per-device tables.
5/// Spec: docs/design/flat-kernel-selection.md §4-§5. Everything here is generic over a choice
6/// std::variant whose alternatives are family structs (§4.1). @ingroup api_selection
7// State that a test and the library must share (pins, table cache, trace depth) lives in select.cc
8// behind BATCHLAS_API: a header-local static would be one copy per DSO under -fvisibility=hidden.
9
11#include <src/select/vendor.hh>
14#include <batchlas/export.hh>
16
17#include <algorithm>
18#include <array>
19#include <charconv>
20#include <complex>
21#include <cstddef>
22#include <cstdint>
23#include <cstdio>
24#include <memory>
25#include <mutex>
26#include <optional>
27#include <set>
28#include <span>
29#include <stdexcept>
30#include <string>
31#include <string_view>
32#include <type_traits>
33#include <utility>
34#include <variant>
35#include <vector>
36
38/// @addtogroup api_selection
39/// @{
40
41// ---- device facts (§5.1) ---------------------------------------------------------------
42
43/// The device facts a `can_run` predicate and the table lookup read (§5.1).
44struct Device {
45 std::string key; ///< table key: "sm_120", "gfx90a", "intel", "apple_m5_max", "cpu"
46 std::string family; ///< "sm", "gfx", "intel", "apple", "cpu": borrowing stays in a family first
47 int arch_number = 0; ///< 120, 89, 90 (gfx90a), 5 (apple_m5_max), 0 for cpu
48 bool is_gpu = false;
49 bool has_sg32 = false;
50 bool has_vendor = false; ///< the asking op's library group (OpSpec::vendor) is compiled in
51 std::int64_t slm_budget = 0; ///< LOCAL_MEM_SIZE less a 4 KiB reserve, bytes: any work-group size
52 int max_wg = 0; ///< MAX_WORK_GROUP_SIZE
53 std::int64_t slm_per_item = 0; ///< local memory the runtime adds per work-item (Metal: 8)
54 /// Work-groups in SYCL dim 0/1 of a 2-D/3-D launch (CUDA grid z/y); Metal: 2^32 - 1.
55 std::int64_t max_groups_yz = 65535;
56
57 /// slm_budget for a launch of @p wg work-items (>= slm_budget when wg <= max_wg).
58 constexpr std::int64_t slm_budget_for(int wg) const {
59 return wg >= max_wg ? slm_budget : slm_budget + slm_per_item * (max_wg - wg);
60 }
61};
62
63/// Key, family and arch only; the capability fields stay default. Tables and tests use it.
64BATCHLAS_API Device device_from_key(std::string_view key);
65
66/// The table key of an Apple GPU from its device name: "Apple M5 Max" -> "apple_m5_max".
67BATCHLAS_API std::string apple_device_key(std::string_view device_name);
68
69/// The facts of @p dev for backend @p b; memoized per (device, backend, has_vendor).
70BATCHLAS_API const Device& describe(const batchlas::Device& dev, Backend b, bool has_vendor);
71
72/// The facts of @p q's device, with Device::has_vendor answering for the library group @p vendor.
73template <Backend B>
74const Device& device_of(const Queue& q, Lib vendor = Lib::none) {
75 return describe(q.device(), B, has_library<B>(vendor));
76}
77
78/// The dtype key of a table row (the first, exact key of every tuned file): float, double, cfloat or cdouble.
79template <class T>
80constexpr std::string_view dtype_name() {
81 if constexpr (std::is_same_v<T, float>) return "float";
82 else if constexpr (std::is_same_v<T, double>) return "double";
83 else if constexpr (std::is_same_v<T, std::complex<float>>) return "cfloat";
84 else {
85 static_assert(std::is_same_v<T, std::complex<double>>, "unsupported scalar");
86 return "cdouble";
87 }
88}
89
90// ---- families and spelling (§4.1, §5.2) -----------------------------------------------
91
92/// A string literal as a template argument: the name of a NoFields family.
93template <std::size_t N>
94struct FamilyName {
95 char s[N]{};
96 constexpr FamilyName(const char (&a)[N]) { std::copy_n(a, N, s); }
97};
98/// Base of a family with no knobs: `struct Cta : NoFields<"cta"> {};`.
99/// Generic code builds such families as `A{}`.
100template <FamilyName L>
101struct NoFields {
102 static constexpr std::string_view name{L.s, sizeof(L.s) - 1};
103 static constexpr std::array<std::string_view, 0> fields{};
104 std::array<int, 0> values() const { return {}; }
105 bool operator==(const NoFields&) const = default;
106};
107
108/// Every alternative of a field-less choice variant, in declaration (= tie-break) order.
109template <class Choice>
110constexpr auto all_of() {
111 return [&]<std::size_t... I>(std::index_sequence<I...>) {
112 return std::array<Choice, sizeof...(I)>{Choice{std::variant_alternative_t<I, Choice>{}}...};
113 }(std::make_index_sequence<std::variant_size_v<Choice>>{});
114}
115
116/// std::visit over a choice with one lambda per family.
117template <class... F>
118struct overloaded : F... { using F::operator()...; };
119template <class... F>
120overloaded(F...) -> overloaded<F...>;
121
122namespace detail {
123
124template <class Choice, class F>
125void for_each_family(F&& f) {
126 [&]<std::size_t... I>(std::index_sequence<I...>) {
127 (f(std::type_identity<std::variant_alternative_t<I, Choice>>{}), ...);
128 }(std::make_index_sequence<std::variant_size_v<Choice>>{});
129}
130
131inline std::vector<std::string_view> split(std::string_view s, char sep) {
132 std::vector<std::string_view> out;
133 std::size_t start = 0;
134 for (std::size_t i = 0; i <= s.size(); ++i) {
135 if (i == s.size() || s[i] == sep) {
136 out.push_back(s.substr(start, i - start));
137 start = i + 1;
138 }
139 }
140 return out;
141}
142
143inline bool parse_int(std::string_view t, int& out) {
144 if (t.empty()) return false;
145 const auto r = std::from_chars(t.data(), t.data() + t.size(), out);
146 return r.ec == std::errc{} && r.ptr == t.data() + t.size();
147}
148
149} // namespace detail
150
151/// The family name of @p c, e.g. "lpanel" for `lpanel:panel=8`.
152template <class Choice>
153std::string_view family_of(const Choice& c) {
154 return std::visit([](const auto& a) { return std::decay_t<decltype(a)>::name; }, c);
155}
156
157/// Output spelling, always the long form: family[:field=value]...
158template <class Choice>
159std::string to_string(const Choice& c) {
160 return std::visit([](const auto& a) {
161 using A = std::decay_t<decltype(a)>;
162 std::string s(A::name);
163 const auto v = a.values();
164 for (std::size_t i = 0; i < v.size(); ++i) {
165 s += ':';
166 s += A::fields[i];
167 s += '=';
168 s += std::to_string(v[i]);
169 }
170 return s;
171 }, c);
172}
173
174/// Accepts the long form and positional shorthand ("lpanel:8"); exact case, no whitespace.
175/// @return the choice, or nullopt with the reason in `*err` (when non-null).
176template <class Choice>
177std::optional<Choice> parse(std::string_view text, std::string* err = nullptr) {
178 const auto tok = detail::split(text, ':');
179 std::optional<Choice> out;
180 std::string why = "unknown family '" + std::string(tok[0]) + "'";
182 using A = typename decltype(id)::type;
183 if (out || tok[0] != A::name) return;
184 constexpr std::size_t K = A::fields.size();
185 if (tok.size() - 1 != K) {
186 why = std::string(A::name) + " takes " + std::to_string(K) + " field(s), got " +
187 std::to_string(tok.size() - 1);
188 return;
189 }
190 std::array<int, K> vals{};
191 std::array<bool, K> seen{};
192 for (std::size_t i = 1; i < tok.size(); ++i) {
193 std::string_view t = tok[i];
194 std::size_t idx = i - 1;
195 if (const auto eq = t.find('='); eq != std::string_view::npos) {
196 const auto f = std::find(A::fields.begin(), A::fields.end(), t.substr(0, eq));
197 if (f == A::fields.end()) {
198 why = "unknown field '" + std::string(t.substr(0, eq)) + "' of " + std::string(A::name);
199 return;
200 }
201 idx = static_cast<std::size_t>(f - A::fields.begin());
202 t = t.substr(eq + 1);
203 }
204 if (seen[idx]) {
205 why = "field '" + std::string(A::fields[idx]) + "' given twice";
206 return;
207 }
208 if (!detail::parse_int(t, vals[idx])) {
209 why = "'" + std::string(t) + "' is not an integer";
210 return;
211 }
212 seen[idx] = true;
213 }
214 if constexpr (K == 0) out = Choice{A{}};
215 else out = Choice{A::from(vals)};
216 });
217 if (!out && err) *err = "'" + std::string(text) + "': " + why;
218 return out;
219}
220
221// ---- op-supplied rules ------------------------------------------------------------------
222
223/// What an op adds to the generic walk: its last-resort order.
224struct Rules {
225 /// Families in generality order (§5.5); the rest follow in candidate-list order.
226 std::span<const std::string_view> last_resort{};
227};
228
229// ---- keys and tables (§5.4) -------------------------------------------------------------
230
231/// One field of a lookup key, value as text; a Key lists the fields an op's table keys name.
232struct KeyField {
233 std::string name;
234 std::string value;
235 KeyField(std::string_view n, std::string_view v) : name(n), value(v) {}
236 KeyField(std::string_view n, std::int64_t v) : name(n), value(std::to_string(v)) {}
237};
238using Key = std::vector<KeyField>;
239
240/// One entry of a table's `# keys:` line.
241struct TableKey {
242 std::string name;
243 bool log = false; ///< false: ":exact", compared as text
244 double weight = 1.0; ///< ":log:<w>": this key's share of the distance
245};
246
247/// One ranked candidate of a row: its spelling and milliseconds per call for the whole batch.
249 std::string spelling;
250 double ms = 0.0;
251};
252
253/// One measured (or transcribed) shape and every candidate ranked there, fastest first.
254struct TableRow {
255 std::vector<std::string> keys; ///< in Table::keys order
256 std::vector<double> log2_keys; ///< log2 of each :log key, 0 for :exact
257 std::vector<TableEntry> ranked;
258 int line = 0;
259 /// False for a transcribed row ("<spelling> -" entries, every ms 0): ranked, never timed.
260 /// Untimed rows need a `source=transcribed:<sha>` header; such a table may also hold timed rows.
261 bool timed = true;
262};
263
264/// One dtype's table: a `tuned/<op>.<device>.txt` file holds one per dtype.
265struct Table {
266 std::string file; ///< basename, e.g. "potrf.sm_120.txt"
267 std::string op, dtype, device, family;
268 int arch_number = 0;
269 std::string source; ///< the header's source= value, e.g. "transcribed:2b46acab"
270 bool is_override = false; ///< read from BATCHLAS_TUNED_DIR
271 std::vector<TableKey> keys;
272 std::vector<TableRow> rows;
273 // nearest() results by key text, as row indices (copies may share it); set by parse_table.
274 // gemm tables hold thousands of rows and gemm is called per panel, so the scan is memoized.
275 struct Memo;
276 std::shared_ptr<Memo> memo;
277
278 /// Rows matching the longest prefix of the :exact keys (in '# keys:' order; exact keys are
279 /// dropped from the right until some row matches, possibly all of them), then min
280 /// sum w*|log2(row/key)|, ties lexicographic by the :log keys in declared order.
281 /// @throws std::invalid_argument if @p key lacks a table key.
282 BATCHLAS_API const TableRow* nearest(const Key& key) const;
283 const TableRow* nearest_scan(const std::vector<std::string>& kv, const std::vector<double>& kl) const;
284};
285
286/// Parses one single-dtype table: op and device from the header or file name, dtype from the header.
287/// @throws std::runtime_error `"<file>:<line>: <reason>"`.
288BATCHLAS_API Table parse_table(std::string_view text, std::string_view file);
289
290/// Parses one tuned table file, named op.device.txt with the exact key dtype first, into one
291/// Table per dtype without that key.
292/// @throws std::runtime_error as parse_table does.
293BATCHLAS_API std::vector<Table> parse_tables(std::string_view text, std::string_view file);
294
295/// A `tuned/*.txt` file compiled into the library: its basename and text.
297 std::string_view name;
298 std::string_view text;
299};
300BATCHLAS_API std::span<const EmbeddedTable> embedded_tables(); ///< generated from tuned/*.txt
301
302/// §5.5: own key; same family at-or-below by nearest arch, then above; then sm, gfx, intel,
303/// other, cpu. Built-in tables plus BATCHLAS_TUNED_DIR; each file is parsed once per dir (not per dtype).
304/// A CPU device gets only its own table.
305BATCHLAS_API std::vector<const Table*> tables_in_borrow_order(std::string_view op, std::string_view dtype,
306 const Device& d);
307
308namespace detail {
309
310BATCHLAS_API bool trace_enabled() noexcept;
311BATCHLAS_API void note_decision(std::string_view op, std::string spelling, std::string detail, std::string tag);
312BATCHLAS_API void note_borrow(std::string_view op, std::string_view dtype, const Device& d, const Table& t);
313// A class-word pin ("native", "vendor") that nothing in its class can serve: once per op and word.
314BATCHLAS_API void warn_pin_fallback(std::string_view op, std::string_view word, const Device& d);
315// `own_table_exists`: the device has a table but its row had nothing runnable ("fallthrough"),
316// as opposed to an untuned device ("borrowed").
317BATCHLAS_API std::string table_tag(const Table& t, const Device& d, bool own_table_exists);
318BATCHLAS_API std::string format_detail(double ms, const std::string* next, double next_ms);
319// The trace detail for a ranked entry: "transcribed" on an untimed row, else format_detail.
320BATCHLAS_API std::string entry_detail(const TableRow& row, std::size_t i, const TableEntry* next);
321BATCHLAS_API std::optional<std::string> pin_text(std::string_view op, std::string* source);
322BATCHLAS_API void push_pin(std::string_view op, std::string text, bool strict = false);
323BATCHLAS_API bool pin_strict(std::string_view op); // the innermost ScopedPin of op is strict
324BATCHLAS_API void pop_pin(std::string_view op);
325struct NativeFacts;
326// `fields` is what the trace line prints after the dtype; empty means the shape's n and batch.
327BATCHLAS_API bool trace_open(std::string_view op, const std::string& spelling, bool vendor,
328 const coverage::Shape& shape, const NativeFacts& facts, const Key& fields);
329BATCHLAS_API void trace_close();
330
331// A bad spelling in a table is a build defect, so it fails loudly on first use.
332template <class Choice>
333void validate(const Table& t) {
334 static std::mutex mu;
335 static std::set<const Table*> done; // tables are never freed, so pointers stay unique
336 std::lock_guard<std::mutex> lock(mu);
337 if (done.count(&t)) return;
338 for (const auto& row : t.rows)
339 for (const auto& e : row.ranked) {
340 std::string err;
341 if (!parse<Choice>(e.spelling, &err))
342 throw std::runtime_error(t.file + ":" + std::to_string(row.line) + ": " + err);
343 }
344 done.insert(&t);
345}
346
347template <class Choice, std::size_t N>
348bool is_candidate(const Choice& c, const std::array<Choice, N>& candidates) {
349 return std::find(candidates.begin(), candidates.end(), c) != candidates.end();
350}
351
352// The ranked walk of §5.4, then the last resort. nullopt only when native_only.
353template <class Choice, std::size_t N, class CanRun>
354std::optional<Choice> walk(std::string_view op, std::string_view dtype, const Device& d, const Key& key,
355 const std::array<Choice, N>& candidates, CanRun& can_run, const Rules& rules,
356 bool native_only) {
357 auto eligible = [&](const Choice& c) {
358 return is_candidate(c, candidates) && !(native_only && family_of(c) == "vendor") && can_run(c);
359 };
360 const char* pin_note = native_only ? ", pinned native" : "";
361 const auto tables = tables_in_borrow_order(op, dtype, d);
362 const bool own = std::any_of(tables.begin(), tables.end(), [&](const Table* t) { return t->device == d.key; });
363 for (const Table* t : tables) {
365 const TableRow* row = t->nearest(key);
366 if (!row) continue;
367 for (std::size_t i = 0; i < row->ranked.size(); ++i) {
368 const auto c = parse<Choice>(row->ranked[i].spelling);
369 if (!eligible(*c)) continue;
370 // A tuned device whose row has nothing runnable is expected (Upper on Lower-only
371 // rows, vendor-free builds); only an untuned device gets the R8 warning.
372 if (t->device != d.key && !own) note_borrow(op, dtype, d, *t);
373 if (trace_enabled()) {
374 const TableEntry* next = nullptr;
375 for (std::size_t j = i + 1; j < row->ranked.size() && !next; ++j)
376 if (eligible(*parse<Choice>(row->ranked[j].spelling))) next = &row->ranked[j];
377 note_decision(op, to_string(*c), entry_detail(*row, i, next), table_tag(*t, d, own) + pin_note);
378 }
379 return c;
380 }
381 }
382 std::vector<Choice> order;
383 for (std::string_view fam : rules.last_resort)
384 for (const Choice& c : candidates)
385 if (family_of(c) == fam) order.push_back(c);
386 for (const Choice& c : candidates)
387 if (std::find(order.begin(), order.end(), c) == order.end()) order.push_back(c);
388 for (const Choice& c : order) {
389 if (!eligible(c)) continue;
390 if (trace_enabled()) note_decision(op, to_string(c), "", std::string("last resort") + pin_note);
391 return c;
392 }
393 if (native_only) return std::nullopt;
394 throw std::runtime_error(std::string(op) + ": no runnable kernel on " + d.key);
395}
396
397// §5.2 / R6. nullopt means "auto": the caller runs the normal walk. The class words
398// `native` and `vendor` fall back to auto with a warning; every other spelling throws.
399// `strict` (the tuner's arms): a class word that cannot serve the shape throws too, and
400// `vendor` must resolve to the candidate spelled `vendor`.
401template <class Choice, std::size_t N, class CanRun>
402std::optional<Choice> resolve_pin(std::string_view op, std::string_view dtype, const Device& d, const Key& key,
403 const std::array<Choice, N>& candidates, CanRun& can_run, const Rules& rules,
404 const std::string& text, const std::string& source, bool strict = false) {
405 const std::string where = std::string(op) + ": " + source + "=\"" + text + "\"";
406 if (text == "auto") return std::nullopt;
407 const std::string refused = where + " (strict): no " + text + " candidate can run this shape on " + d.key;
408 if (text == "native") {
409 if (auto c = walk(op, dtype, d, key, candidates, can_run, rules, true)) return c;
410 if (strict) throw std::invalid_argument(refused);
411 warn_pin_fallback(op, "native", d);
412 return std::nullopt;
413 }
414 if (text == "vendor") {
415 for (const Choice& k : candidates)
416 if (family_of(k) == "vendor" && (!strict || to_string(k) == text) && can_run(k)) {
417 if (trace_enabled()) note_decision(op, to_string(k), "", "pinned");
418 return k;
419 }
420 if (strict) throw std::invalid_argument(refused);
421 warn_pin_fallback(op, "vendor", d); // a vendor-free build: `vendor` falls back to Auto
422 return std::nullopt;
423 }
424 std::string err;
425 const std::optional<Choice> c = parse<Choice>(text, &err);
426 if (!c) throw std::invalid_argument(where + " is not a valid choice: " + err);
427 if (!is_candidate(*c, candidates))
428 throw std::invalid_argument(where + ": " + to_string(*c) + " is not a compiled " + std::string(op) + " " +
429 std::string(dtype) + " candidate");
430 if (!can_run(*c))
431 throw std::invalid_argument(where + ": " + to_string(*c) + " cannot run this shape on " + d.key);
432 if (trace_enabled()) note_decision(op, to_string(*c), "", "pinned");
433 return c;
434}
435
436} // namespace detail
437
438// ---- pins (§5.2) ------------------------------------------------------------------------
439
440/// Tag for a strict ScopedPin: a class word ("native", "vendor") that cannot serve the shape
441/// throws std::invalid_argument instead of falling back to auto.
442struct StrictPin {};
443
444/// RAII pin for @p op: wins over BATCHLAS_<OP>_ROUTE on this thread; nests, restoring the outer
445/// pin on exit. Takes a choice, or any pin word ("auto", "native", "vendor", a spelling).
446template <class Choice>
448public:
449 ScopedPin(std::string_view op, std::string_view word, StrictPin) : op_(op) {
450 detail::push_pin(op_, std::string(word), true);
451 }
452 ScopedPin(std::string_view op, const Choice& c) : op_(op) { detail::push_pin(op_, to_string(c)); }
453 ScopedPin(std::string_view op, std::string_view word) : op_(op) { detail::push_pin(op_, std::string(word)); }
455 ScopedPin(const ScopedPin&) = delete;
456 ScopedPin& operator=(const ScopedPin&) = delete;
457
458private:
459 std::string op_;
460};
461
462// ---- the selection algorithm (§5.4) -----------------------------------------------------
463
464/// The only selection algorithm: the pin, else the first candidate passing @p can_run in the nearest
465/// row of each table in borrow order, else the first runnable candidate in @p rules' last-resort order.
466/// @throws std::invalid_argument for a bad pin (R6); std::runtime_error when nothing can run.
467template <class Choice, std::size_t N, class CanRun>
468Choice choose(std::string_view op, std::string_view dtype, const Device& d, const Key& key,
469 const std::array<Choice, N>& candidates, CanRun&& can_run, const Rules& rules = {}) {
470 std::string source;
471 if (auto text = detail::pin_text(op, &source))
472 if (auto c = detail::resolve_pin(op, dtype, d, key, candidates, can_run, rules, *text, source,
474 return *c;
475 return *detail::walk(op, dtype, d, key, candidates, can_run, rules, false);
476}
477
478// ---- trace and coverage (§5.5) ----------------------------------------------------------
479
480namespace detail {
481/// The coverage row's native_route_existed / native_route_supported (tri-state, -1 unknown).
483 bool existed = true;
484 int supported = -1;
485};
486} // namespace detail
488
489/// Over the op's candidates: any non-vendor compiled, and any of those passing can_run.
490template <class Choice, std::size_t N, class CanRun>
491NativeFacts native_facts(const std::array<Choice, N>& candidates, CanRun&& can_run) {
492 NativeFacts f{false, 0};
493 for (const Choice& c : candidates)
494 if (family_of(c) != "vendor") {
495 f.existed = true;
496 if (can_run(c)) f.supported = 1;
497 }
498 return f;
499}
500
501/// Prints the last choose() decision for `op` under BATCHLAS_SELECT_TRACE=1, indents nested
502/// scopes, and records the coverage row. The shape is required: with coverage on and trace
503/// off no decision is noted, so scalar, backend and uplo can come from nowhere else.
504/// `fields` are the key fields the line shows (posv: n, nrhs, batch); empty prints n and batch.
506public:
507 template <class Choice>
508 TraceScope(std::string_view op, const Choice& c, const coverage::Shape& shape, NativeFacts facts = {},
509 const Key& fields = {}) {
511 active_ = detail::trace_open(op, to_string(c), family_of(c) == "vendor", shape, facts, fields);
512 }
514 if (active_) detail::trace_close();
515 }
516 TraceScope(const TraceScope&) = delete;
517 TraceScope& operator=(const TraceScope&) = delete;
518
519private:
520 bool active_ = false;
521};
522
523// ---- an op's whole selection path (§4.3) -------------------------------------------------
524
525inline constexpr std::array<std::string_view, 2> kBlockedThenVendor{"blocked", "vendor"}; ///< default last resort
526
527/// What an op's choice.hh declares once: its name, the library its Vendor family calls (one
528/// gate for can_run, the launch arm and the NoRouteError), and its last-resort order.
529struct OpSpec {
531 Lib vendor = Lib::none; ///< Lib::none: the op has no vendor family
533 constexpr std::string_view name() const { return op_name(op); } ///< table, pin and trace name
534};
535
536/// The Vendor arm of an op without its library: record the miss and throw NoRouteError.
537template <Backend B, class T>
538[[noreturn]] void no_vendor(const OpSpec& op) {
540}
541
542/// choose(), with "nothing runnable" in a build without the op's library reported as
543/// NoRouteError plus a coverage `miss` row (the vendor-free burn-down reads them).
544/// can_run(choice, device) is the op's R3 predicate. `<op>_buffer_size` calls this (R5).
545template <Backend B, class T, class Choice, std::size_t N, class CanRun>
546Choice pick(const OpSpec& op, const Device& d, const Key& key, const std::array<Choice, N>& candidates,
547 CanRun&& can_run) {
548 auto ok = [&](const Choice& c) { return can_run(c, d); };
549 try {
550 return choose(op.name(), dtype_name<T>(), d, key, candidates, ok, op.rules);
551 } catch (const std::runtime_error&) {
552 if (op.vendor != Lib::none && !has_library<B>(op.vendor) &&
553 std::none_of(candidates.begin(), candidates.end(), ok))
554 no_vendor<B, T>(op);
555 throw;
556 }
557}
558
559template <Backend B, class T, class Choice, std::size_t N, class CanRun>
560Choice pick(const OpSpec& op, const Queue& q, const Key& key, const std::array<Choice, N>& candidates,
561 CanRun&& can_run) {
562 ::batchlas::detail::require_device_fp64<T>(q, std::string(op.name()).c_str()); // <NETLIB> on Metal
563 return pick<B, T>(op, device_of<B>(q, op.vendor), key, candidates, can_run);
564}
565
566/// A public entry point after validation: pick, open the trace/coverage scope (the shape's
567/// scalar and backend are filled here; `trace_fields` as for TraceScope), then launch(choice)
568/// inside it, so children nest under this line. @return whatever @p launch returns.
569template <Backend B, class T, class Choice, std::size_t N, class CanRun, class Launch>
570decltype(auto) run(const OpSpec& op, Queue& q, const Key& key, const std::array<Choice, N>& candidates,
571 CanRun&& can_run, coverage::Shape shape, const Key& trace_fields, Launch&& launch) {
572 ::batchlas::detail::require_device_fp64<T>(q, std::string(op.name()).c_str()); // <NETLIB> on Metal
573 const Device& d = device_of<B>(q, op.vendor);
574 const Choice c = pick<B, T>(op, d, key, candidates, can_run);
576 shape.backend = B;
577 NativeFacts facts;
578 if (coverage::dynamic_enabled()) facts = native_facts(candidates, [&](const Choice& k) { return can_run(k, d); });
579 TraceScope trace(op.name(), c, shape, facts, trace_fields);
580 return launch(c);
581}
582
583/// Runs f(queue) on q if it is in order, else on an in-order queue that first waits on q's
584/// pending work (the multi-launch drivers need one). q is then ordered after that queue's work,
585/// so q.wait() and q.get_event() cover it.
586template <class F>
587auto on_in_order_queue(Queue& q, F&& f) {
588 if (q.in_order()) return f(q);
589 Queue in_order(q, true);
590 Event dep = q.get_event();
591 in_order.enqueue(dep);
592 auto result = f(in_order);
593 Event done = in_order.get_event();
594 q.enqueue(done);
595 return result;
596}
597
598/// @}
599
600namespace testing {
601// Replace the embedded tables with {file name, text} pairs; caches are dropped.
602BATCHLAS_API void set_builtin_tables(std::vector<std::pair<std::string, std::string>> files);
603BATCHLAS_API void use_embedded_tables();
604BATCHLAS_API void reset_warnings();
605BATCHLAS_API std::size_t parse_count(); ///< table files parsed since load (the loader's cost)
606} // namespace testing
607
608} // namespace batchlas::select
ScopedPin(const ScopedPin &)=delete
ScopedPin & operator=(const ScopedPin &)=delete
ScopedPin(std::string_view op, const Choice &c)
Definition select.hh:452
ScopedPin(std::string_view op, std::string_view word)
Definition select.hh:453
ScopedPin(std::string_view op, std::string_view word, StrictPin)
Definition select.hh:449
Prints the last choose() decision for op under BATCHLAS_SELECT_TRACE=1, indents nested scopes,...
Definition select.hh:505
TraceScope & operator=(const TraceScope &)=delete
TraceScope(const TraceScope &)=delete
TraceScope(std::string_view op, const Choice &c, const coverage::Shape &shape, NativeFacts facts={}, const Key &fields={})
Definition select.hh:508
What this build links and what a run reached, as CSV in BATCHLAS_COVERAGE_OUT.pid; the columns are a ...
Enumerations, scalar traits and scalar concepts shared by every public header.
constexpr std::string_view to_string(MatrixFormat v)
Spelling of v as in source.
Definition enums.hh:84
Backend
Library family an entry point dispatches to, used as a template argument.
Definition enums.hh:111
auto on_in_order_queue(Queue &q, F &&f)
Runs f(queue) on q if it is in order, else on an in-order queue that first waits on q's pending work ...
Definition select.hh:587
const Device & describe(const batchlas::Device &dev, Backend b, bool has_vendor)
The facts of dev for backend b; memoized per (device, backend, has_vendor).
const Device & device_of(const Queue &q, Lib vendor=Lib::none)
The facts of q's device, with Device::has_vendor answering for the library group vendor.
Definition select.hh:74
std::optional< Choice > parse(std::string_view text, std::string *err=nullptr)
Accepts the long form and positional shorthand ("lpanel:8"); exact case, no whitespace.
Definition select.hh:177
Table parse_table(std::string_view text, std::string_view file)
Parses one single-dtype table: op and device from the header or file name, dtype from the header.
Lib
The library group an op's Vendor family calls; an op names exactly one (OpSpec::vendor).
Definition vendor.hh:74
std::string to_string(const Choice &c)
Output spelling, always the long form: family[:field=value]...
Definition select.hh:159
constexpr bool has_library(Lib l)
Is library group l compiled in for backend B? Always false for Lib::none.
Definition vendor.hh:78
constexpr std::array< std::string_view, 2 > kBlockedThenVendor
default last resort
Definition select.hh:525
constexpr const char * library_name(Lib l)
Definition vendor.hh:90
constexpr std::string_view dtype_name()
The dtype key of a table row (the first, exact key of every tuned file): float, double,...
Definition select.hh:80
constexpr auto all_of()
Every alternative of a field-less choice variant, in declaration (= tie-break) order.
Definition select.hh:110
Choice pick(const OpSpec &op, const Device &d, const Key &key, const std::array< Choice, N > &candidates, CanRun &&can_run)
choose(), with "nothing runnable" in a build without the op's library reported as NoRouteError plus a...
Definition select.hh:546
std::span< const EmbeddedTable > embedded_tables()
generated from tuned/*.txt
void no_vendor(const OpSpec &op)
The Vendor arm of an op without its library: record the miss and throw NoRouteError.
Definition select.hh:538
std::string_view family_of(const Choice &c)
The family name of c, e.g. "lpanel" for lpanel:panel=8.
Definition select.hh:153
decltype(auto) run(const OpSpec &op, Queue &q, const Key &key, const std::array< Choice, N > &candidates, CanRun &&can_run, coverage::Shape shape, const Key &trace_fields, Launch &&launch)
A public entry point after validation: pick, open the trace/coverage scope (the shape's scalar and ba...
Definition select.hh:570
Device device_from_key(std::string_view key)
Key, family and arch only; the capability fields stay default. Tables and tests use it.
std::vector< const Table * > tables_in_borrow_order(std::string_view op, std::string_view dtype, const Device &d)
§5.5: own key; same family at-or-below by nearest arch, then above; then sm, gfx, intel,...
std::string apple_device_key(std::string_view device_name)
The table key of an Apple GPU from its device name: "Apple M5 Max" -> "apple_m5_max".
std::vector< KeyField > Key
Definition select.hh:238
void throw_no_vendor_route(Op op, Backend backend, const char *library)
The single funnel for "nothing can serve this call": records the miss, then throws NoRouteError.
Definition vendor.hh:109
bool dynamic_enabled()
Definition coverage.hh:54
NativeFacts native_facts(const std::array< Choice, N > &candidates, CanRun &&can_run)
Over the op's candidates: any non-vendor compiled, and any of those passing can_run.
Definition select.hh:491
Choice choose(std::string_view op, std::string_view dtype, const Device &d, const Key &key, const std::array< Choice, N > &candidates, CanRun &&can_run, const Rules &rules={})
The only selection algorithm: the pin, else the first candidate passing can_run in the nearest row of...
Definition select.hh:468
std::vector< Table > parse_tables(std::string_view text, std::string_view file)
Parses one tuned table file, named op.device.txt with the exact key dtype first, into one Table per d...
void require_device_fp64(const Queue &ctx, const char *op)
Throws throw_no_device_fp64 for a double or complex<double> T on a GPU without fp64.
bool pin_strict(std::string_view op)
void push_pin(std::string_view op, std::string text, bool strict=false)
void note_borrow(std::string_view op, std::string_view dtype, const Device &d, const Table &t)
void warn_pin_fallback(std::string_view op, std::string_view word, const Device &d)
void for_each_family(F &&f)
Definition select.hh:125
bool trace_open(std::string_view op, const std::string &spelling, bool vendor, const coverage::Shape &shape, const NativeFacts &facts, const Key &fields)
std::optional< std::string > pin_text(std::string_view op, std::string *source)
std::string format_detail(double ms, const std::string *next, double next_ms)
bool parse_int(std::string_view t, int &out)
Definition select.hh:143
bool trace_enabled() noexcept
std::vector< std::string_view > split(std::string_view s, char sep)
Definition select.hh:131
void note_decision(std::string_view op, std::string spelling, std::string detail, std::string tag)
std::optional< Choice > resolve_pin(std::string_view op, std::string_view dtype, const Device &d, const Key &key, const std::array< Choice, N > &candidates, CanRun &can_run, const Rules &rules, const std::string &text, const std::string &source, bool strict=false)
Definition select.hh:402
void pop_pin(std::string_view op)
std::optional< Choice > walk(std::string_view op, std::string_view dtype, const Device &d, const Key &key, const std::array< Choice, N > &candidates, CanRun &can_run, const Rules &rules, bool native_only)
Definition select.hh:354
bool is_candidate(const Choice &c, const std::array< Choice, N > &candidates)
Definition select.hh:348
std::string entry_detail(const TableRow &row, std::size_t i, const TableEntry *next)
void validate(const Table &t)
Definition select.hh:333
std::string table_tag(const Table &t, const Device &d, bool own_table_exists)
std::size_t parse_count()
table files parsed since load (the loader's cost)
void set_builtin_tables(std::vector< std::pair< std::string, std::string > > files)
constexpr std::string_view op_name(Op o)
Definition no_route.hh:47
constexpr ScalarKind scalar_kind_of
Definition no_route.hh:31
Backend deduction from the Queue and the USM pointer check.
Link facts for if constexpr gates: is an op's vendor library compiled in for a backend,...
A handle to one SYCL device: its type and its index among devices of that type.
Completion handle of enqueued work, returned by every entry point.
Where BatchLAS work runs: a device, a backend, an ordered SYCL queue and a workspace arena.
Event get_event() const
An Event that completes after everything enqueued on this Queue so far.
void enqueue(Event &event)
Order all later work on this Queue after event, without a host wait.
bool in_order() const
True for an in-order Queue.
Device device() const
The device this Queue runs on.
The device facts a can_run predicate and the table lookup read (§5.1).
Definition select.hh:44
std::int64_t slm_per_item
local memory the runtime adds per work-item (Metal: 8)
Definition select.hh:53
int arch_number
120, 89, 90 (gfx90a), 5 (apple_m5_max), 0 for cpu
Definition select.hh:47
int max_wg
MAX_WORK_GROUP_SIZE.
Definition select.hh:52
constexpr std::int64_t slm_budget_for(int wg) const
slm_budget for a launch of wg work-items (>= slm_budget when wg <= max_wg).
Definition select.hh:58
std::int64_t slm_budget
LOCAL_MEM_SIZE less a 4 KiB reserve, bytes: any work-group size.
Definition select.hh:51
bool has_vendor
the asking op's library group (OpSpec::vendor) is compiled in
Definition select.hh:50
std::string family
"sm", "gfx", "intel", "apple", "cpu": borrowing stays in a family first
Definition select.hh:46
std::int64_t max_groups_yz
Work-groups in SYCL dim 0/1 of a 2-D/3-D launch (CUDA grid z/y); Metal: 2^32 - 1.
Definition select.hh:55
std::string key
table key: "sm_120", "gfx90a", "intel", "apple_m5_max", "cpu"
Definition select.hh:45
A tuned/*.txt file compiled into the library: its basename and text.
Definition select.hh:296
constexpr FamilyName(const char(&a)[N])
Definition select.hh:96
KeyField(std::string_view n, std::string_view v)
Definition select.hh:235
KeyField(std::string_view n, std::int64_t v)
Definition select.hh:236
Base of a family with no knobs: struct Cta : NoFields<"cta"> {};.
Definition select.hh:101
static constexpr std::array< std::string_view, 0 > fields
Definition select.hh:103
std::array< int, 0 > values() const
Definition select.hh:104
bool operator==(const NoFields &) const =default
static constexpr std::string_view name
Definition select.hh:102
What an op's choice.hh declares once: its name, the library its Vendor family calls (one gate for can...
Definition select.hh:529
Lib vendor
Lib::none: the op has no vendor family.
Definition select.hh:531
constexpr std::string_view name() const
table, pin and trace name
Definition select.hh:533
What an op adds to the generic walk: its last-resort order.
Definition select.hh:224
std::span< const std::string_view > last_resort
Families in generality order (§5.5); the rest follow in candidate-list order.
Definition select.hh:226
Tag for a strict ScopedPin: a class word ("native", "vendor") that cannot serve the shape throws std:...
Definition select.hh:442
One ranked candidate of a row: its spelling and milliseconds per call for the whole batch.
Definition select.hh:248
One entry of a table's # keys: line.
Definition select.hh:241
double weight
":log:<w>": this key's share of the distance
Definition select.hh:244
bool log
false: ":exact", compared as text
Definition select.hh:243
One measured (or transcribed) shape and every candidate ranked there, fastest first.
Definition select.hh:254
std::vector< double > log2_keys
log2 of each :log key, 0 for :exact
Definition select.hh:256
std::vector< TableEntry > ranked
Definition select.hh:257
bool timed
False for a transcribed row ("<spelling> -" entries, every ms 0): ranked, never timed.
Definition select.hh:261
std::vector< std::string > keys
in Table::keys order
Definition select.hh:255
One dtype's table: a tuned/<op>.<device>.txt file holds one per dtype.
Definition select.hh:265
std::vector< TableKey > keys
Definition select.hh:271
std::string file
basename, e.g. "potrf.sm_120.txt"
Definition select.hh:266
std::shared_ptr< Memo > memo
Definition select.hh:276
std::vector< TableRow > rows
Definition select.hh:272
std::string source
the header's source= value, e.g. "transcribed:2b46acab"
Definition select.hh:269
bool is_override
read from BATCHLAS_TUNED_DIR
Definition select.hh:270
const TableRow * nearest(const Key &key) const
Rows matching the longest prefix of the :exact keys (in '# keys:' order; exact keys are dropped from ...
const TableRow * nearest_scan(const std::vector< std::string > &kv, const std::vector< double > &kl) const
The coverage row's native_route_existed / native_route_supported (tri-state, -1 unknown).
Definition select.hh:482
std::visit over a choice with one lambda per family.
Definition select.hh:118
batchlas::Device, batchlas::Event and batchlas::Queue: where work runs and how it is ordered.