Kernel selection (developer)

Overview

How an op picks the kernel that runs: kernel families, per-device tuned tables, select::choose, pins, the trace, the coverage instrument and the vendor seam.

This is library-internal code under src/select/ and src/ops/<op>/; none of it is installed. The design, its rules R1-R8 and the record of how it was built are on Flat kernel selection. Every op's families and shipped tables are listed on the generated Kernel selection tables page, which is the authoritative inventory; this group documents the mechanism they share.

One decision per op, in one file

A public entry point decides what runs in src/ops/<op>/<op>.cc and nowhere else. It validates its arguments, builds a table key from the call, and hands the op's candidates and its can_run predicate to select::run(), which picks one choice, opens the trace and coverage scope, and calls the op's launch visitor with it. The op's <op>_buffer_size asks select::pick() the same question with the same key, so the workspace query and the call can never size for different kernels (rule R5).

Families, choices and spellings

A family is one kernel driver, written as a small struct: a static name, a static fields array naming its integer knobs, values(), from() and operator==. A family with no knobs derives from select::NoFields. An op's choice type is a std::variant of its families, so a choice is a value, and it has one spelling everywhere (family[:field=value]..., e.g. lpanel:panel=8): in tables, pins, trace lines, coverage rows and test names (select::to_string(), select::parse()). Anything a driver derives from the shape and the device stays derived inside the driver and is never a field.

The op's choice.hh lists every compiled choice once in candidates<T>(). That list is what the tuner times, what a pin must name and what a table row may contain, and its order is the tie-break order: simpler kernels first and native kernels before vendor, so near-ties go to the native kernel. A field-less choice lists its families in variant order (select::all_of()).

What an op declares once: OpSpec

select::OpSpec holds the op's Op enumerator (its name in tables, pins and coverage), the vendor library group its vendor family calls (select::Lib), and its select::Rules: the last-resort order, by generality, used when no table row names a runnable choice. The default is blocked, then vendor, then the rest in candidate order.

can_run is correctness only

can_run(choice, device) is false exactly when that kernel would throw or give a wrong answer for the call (rule R3); it never means "slower". Speed lives only in the tables (R4). A speed threshold in can_run would make a pinned kernel unreachable and leave a shape with nothing to run in a vendor-free build. Each clause should be the argument check at the top of the driver it guards, and every candidate is pinned in a test on shapes that straddle its limits (R7).

Tables, lookup and borrowing

One plain-text file per (op, device), tuned/<op>.<device>.txt, one row set per dtype, embedded at build time and parsed once; a directory named by BATCHLAS_TUNED_DIR replaces a built-in table of the same name. The header names the keys (# keys: uplo:exact n:log:3 batch:log); each row is one measured shape followed by every candidate timed there, fastest first. Inventory and provenance: Tuned selection tables.

  • Nearest row (select::Table::nearest()): the rows matching the longest prefix of the :exact keys, then the smallest weighted \( \sum_i w_i\,|\log_2(\mathrm{row}_i/\mathrm{key}_i)| \) over the :log keys.
  • Borrow order (select::tables_in_borrow_order()): the device's own table; then tables of the same family, nearest architecture at or below first; then other families. A device with no table borrows and warns once per op (R8). A CPU device never borrows a GPU table.
  • The walk: in each table in borrow order, the first entry of the nearest row that is a candidate and passes can_run wins. If none does anywhere, the last-resort order decides; if nothing can run at all, choose() throws.

Pins

BATCHLAS_<OP>_ROUTE is consulted on every call, from the settings snapshot (a raw ::setenv is not seen until detail::reload_settings(); ScopedEnvVar triggers one). The value is trimmed and lower-cased, then means auto, native (the best runnable non-vendor choice), vendor, or a spelling. A spelling that does not parse, is not a compiled candidate, or fails can_run throws std::invalid_argument (R6); native and vendor fall back to the automatic choice with a one-time warning when nothing in their class can run. Tests pin with the RAII select::ScopedPin, which wins over the environment on its thread.

Trace and coverage

BATCHLAS_SELECT_TRACE=1 prints one stderr line per decision, indented under its parent op, with a tag naming the table used ([sm_120], a borrowed table, override, fallthrough, last resort, pinned). BATCHLAS_COVERAGE_OUT=<path> makes batchlas::coverage write one CSV per process: linked rows (what this build contains), reached rows (what calls actually ran, with the choice spelling) and miss rows (calls nothing could serve). A linked row is never evidence that a kernel runs. Instrument details and defects: the "Dispatch: the coverage instrument" section of Dispatch and coverage.

The vendor seam

select::has_library<B>(lib) is a compile-time answer to "is this op's vendor library in this build for backend B", and it sets select::Device::has_vendor, which is all a vendor family's can_run reads. The op's vendor arm is an if constexpr on it, so with the library absent the vendor call is not compiled at all and select::no_vendor() records a coverage miss and throws NoRouteError instead. That is what keeps -DBATCHLAS_ENABLE_VENDOR_BLAS=OFF an ordinary build; see Vendor independence.

Adding an op

Write src/ops/<op>/choice.hh (families, candidates<T>(), OpSpec, key names, tuner grid) and src/ops/<op>/<op>.cc (key, can_run, launch, workspace, the public entry point and its *_buffer_size, the instantiations); add tables for every dtype on every shipped device; give the tuner a spec in tools/tune/<op>_spec.cc; pin every candidate in a test. The step-by-step version is in Adding entry points to BatchLAS. An op directory without a choice.hh still selects by hand; the generated Kernel selection tables page lists exactly the ops that have one.

Functions

◆ record_choice()

void batchlas::coverage::record_choice ( Op op,
ScalarKind scalar,
Backend backend,
const Shape & shape,
const char * origin,
const char * spelling,
bool native_route_existed,
int native_route_supported )

A reached row: origin "native"/"vendor" and the choice spelling.

native_route_supported is 1, 0, or -1 when the call site cannot tell.

◆ record_miss()

void batchlas::coverage::record_miss ( Op op,
ScalarKind scalar,
Backend backend,
const char * library )

◆ static_table()

std::string batchlas::coverage::static_table ( )

◆ dynamic_enabled()

bool batchlas::coverage::dynamic_enabled ( )
inline

Definition at line 54 of file coverage.hh.

◆ device_from_key()

Device batchlas::select::device_from_key ( std::string_view key)

Key, family and arch only; the capability fields stay default. Tables and tests use it.

◆ apple_device_key()

std::string batchlas::select::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".

◆ describe()

const Device & batchlas::select::describe ( const batchlas::Device & dev,
Backend b,
bool has_vendor )

The facts of dev for backend b; memoized per (device, backend, has_vendor).

◆ device_of()

template<Backend B>
const Device & batchlas::select::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 at line 74 of file select.hh.

◆ dtype_name()

template<class T>
std::string_view batchlas::select::dtype_name ( )
constexpr

The dtype key of a table row (the first, exact key of every tuned file): float, double, cfloat or cdouble.

Definition at line 80 of file select.hh.

◆ all_of()

template<class Choice>
auto batchlas::select::all_of ( )
constexpr

Every alternative of a field-less choice variant, in declaration (= tie-break) order.

Definition at line 110 of file select.hh.

◆ overloaded()

template<class... F>
batchlas::select::overloaded ( F... ) -> overloaded< F... >

◆ family_of()

template<class Choice>
std::string_view batchlas::select::family_of ( const Choice & c)

The family name of c, e.g. "lpanel" for lpanel:panel=8.

Definition at line 153 of file select.hh.

◆ to_string()

template<class Choice>
std::string batchlas::select::to_string ( const Choice & c)

Output spelling, always the long form: family[:field=value]...

Definition at line 159 of file select.hh.

◆ parse()

template<class Choice>
std::optional< Choice > batchlas::select::parse ( std::string_view text,
std::string * err = nullptr )

Accepts the long form and positional shorthand ("lpanel:8"); exact case, no whitespace.

Returns
the choice, or nullopt with the reason in *err (when non-null).

Definition at line 177 of file select.hh.

◆ parse_table()

Table batchlas::select::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.

Exceptions
std::runtime_error"<file>:<line>: <reason>".

◆ parse_tables()

std::vector< Table > batchlas::select::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 dtype without that key.

Exceptions
std::runtime_erroras parse_table does.

◆ embedded_tables()

std::span< const EmbeddedTable > batchlas::select::embedded_tables ( )

generated from tuned/*.txt

◆ tables_in_borrow_order()

std::vector< const Table * > batchlas::select::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, other, cpu.

Built-in tables plus BATCHLAS_TUNED_DIR; each file is parsed once per dir (not per dtype). A CPU device gets only its own table.

◆ choose()

template<class Choice, std::size_t N, class CanRun>
Choice batchlas::select::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 each table in borrow order, else the first runnable candidate in rules' last-resort order.

Exceptions
std::invalid_argumentfor a bad pin (R6); std::runtime_error when nothing can run.

Definition at line 468 of file select.hh.

◆ native_facts()

template<class Choice, std::size_t N, class CanRun>
NativeFacts batchlas::select::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 at line 491 of file select.hh.

◆ no_vendor()

template<Backend B, class T>
void batchlas::select::no_vendor ( const OpSpec & op)

The Vendor arm of an op without its library: record the miss and throw NoRouteError.

Definition at line 538 of file select.hh.

◆ pick() [1/2]

template<Backend B, class T, class Choice, std::size_t N, class CanRun>
Choice batchlas::select::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 coverage miss row (the vendor-free burn-down reads them).

can_run(choice, device) is the op's R3 predicate. <op>_buffer_size calls this (R5).

Definition at line 546 of file select.hh.

◆ pick() [2/2]

template<Backend B, class T, class Choice, std::size_t N, class CanRun>
Choice batchlas::select::pick ( const OpSpec & op,
const Queue & q,
const Key & key,
const std::array< Choice, N > & candidates,
CanRun && can_run )

Definition at line 560 of file select.hh.

◆ run()

template<Backend B, class T, class Choice, std::size_t N, class CanRun, class Launch>
decltype(auto) batchlas::select::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 backend are filled here; trace_fields as for TraceScope), then launch(choice) inside it, so children nest under this line.

Returns
whatever launch returns.

Definition at line 570 of file select.hh.

◆ on_in_order_queue()

template<class F>
auto batchlas::select::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 (the multi-launch drivers need one).

q is then ordered after that queue's work, so q.wait() and q.get_event() cover it.

Definition at line 587 of file select.hh.

◆ has_library()

template<Backend B>
bool batchlas::select::has_library ( Lib l)
constexpr

Is library group l compiled in for backend B? Always false for Lib::none.

Definition at line 78 of file vendor.hh.

◆ library_name()

template<Backend B>
const char * batchlas::select::library_name ( Lib l)
constexpr

Definition at line 90 of file vendor.hh.

◆ throw_no_vendor_route()

template<typename T>
void batchlas::select::throw_no_vendor_route ( Op op,
Backend backend,
const char * library )
inline

The single funnel for "nothing can serve this call": records the miss, then throws NoRouteError.

Definition at line 109 of file vendor.hh.

Types

◆ Key

using batchlas::select::Key = std::vector<KeyField>

Definition at line 238 of file select.hh.

Enumerations

◆ Lib

enum class batchlas::select::Lib : std::uint8_t
strong

The library group an op's Vendor family calls; an op names exactly one (OpSpec::vendor).

Enumerator
none 
level3 
factorization 
solver 
sparse 

Definition at line 74 of file vendor.hh.

Variables

◆ g_dynamic_enabled

bool batchlas::coverage::g_dynamic_enabled
extern

Set once from $BATCHLAS_COVERAGE_OUT; tests toggle it across the DSO boundary (a runtime bool in one TU).

◆ kBlockedThenVendor

std::array<std::string_view, 2> batchlas::select::kBlockedThenVendor {"blocked", "vendor"}
inlineconstexpr

default last resort

Definition at line 525 of file select.hh.

◆ kHasNetlib

bool batchlas::select::kHasNetlib = 1 && 1
inlineconstexpr

Definition at line 21 of file vendor.hh.

◆ kHasCudaVendor

bool batchlas::select::kHasCudaVendor
inlineconstexpr
Initial value:
=
1 && 1 && 1

Definition at line 22 of file vendor.hh.

◆ native_gpu_backend

template<Backend B>
bool batchlas::select::native_gpu_backend = B == Backend::CUDA || B == Backend::SYCL
inlineconstexpr

Backends with wired native GPU kernels; ROCm and the host keep their vendor routes.

Definition at line 27 of file vendor.hh.

◆ level3_vendor_available

template<Backend B>
bool batchlas::select::level3_vendor_available
inlineconstexpr
Initial value:
=
B == Backend::ROCM ? bool(BATCHLAS_HAS_ROCBLAS) :
B == Backend::NETLIB ? kHasNetlib : false
Backend
Library family an entry point dispatches to, used as a template argument.
Definition enums.hh:111
@ NETLIB
Host CBLAS / LAPACKE; the fallback for any device.
Definition enums.hh:119
@ ROCM
AMD GPU: rocBLAS / rocSOLVER / rocSPARSE and the native SYCL kernels.
Definition enums.hh:114
@ CUDA
NVIDIA GPU: cuBLAS / cuSOLVER / cuSPARSE and the native SYCL kernels.
Definition enums.hh:113
constexpr bool kHasNetlib
Definition vendor.hh:21
constexpr bool kHasCudaVendor
Definition vendor.hh:22

Definition at line 30 of file vendor.hh.

◆ factorization_vendor_available

template<Backend B>
bool batchlas::select::factorization_vendor_available
inlineconstexpr
Initial value:
=
B == Backend::ROCM ? bool(BATCHLAS_HAS_ROCSOLVER) :
B == Backend::NETLIB ? kHasNetlib : false

geqrf/orgqr/getrf/getrs/getri/ormqr.

On NVIDIA the group spans cuBLAS (getrf, getri) and cuSOLVER (geqrf, orgqr, ormqr, getrs).

Definition at line 38 of file vendor.hh.

◆ solver_vendor_available

template<Backend B>
bool batchlas::select::solver_vendor_available
inlineconstexpr
Initial value:
=
B == Backend::ROCM ? bool(BATCHLAS_HAS_ROCSOLVER) :
B == Backend::NETLIB ? kHasNetlib : false

Definition at line 44 of file vendor.hh.

◆ sparse_vendor_available

template<Backend B>
bool batchlas::select::sparse_vendor_available
inlineconstexpr
Initial value:
=
B == Backend::ROCM ? bool(BATCHLAS_HAS_ROCSPARSE) :
B == Backend::NETLIB ? kHasNetlib : false

Definition at line 50 of file vendor.hh.

◆ kLevel3Library

template<Backend B>
const char* batchlas::select::kLevel3Library
inlineconstexpr
Initial value:
=
B == Backend::CUDA ? "cuBLAS" : B == Backend::ROCM ? "rocBLAS"
: B == Backend::SYCL ? "a vendor GPU library" : "netlib CBLAS/LAPACKE"
@ SYCL
Native SYCL kernels on a GPU with no vendor library (AdaptiveCpp Metal); float and complex<float> onl...
Definition enums.hh:117

The library name a diagnostic quotes when the answer is no.

Definition at line 57 of file vendor.hh.

◆ kFactorizationLibrary

template<Backend B>
const char* batchlas::select::kFactorizationLibrary
inlineconstexpr
Initial value:
=
B == Backend::CUDA ? "cuBLAS and cuSOLVER" : B == Backend::ROCM ? "rocSOLVER"
: B == Backend::SYCL ? "a vendor GPU library" : "netlib CBLAS/LAPACKE"

Definition at line 61 of file vendor.hh.

◆ kSolverLibrary

template<Backend B>
const char* batchlas::select::kSolverLibrary
inlineconstexpr
Initial value:
=
B == Backend::CUDA ? "cuSOLVER" : B == Backend::ROCM ? "rocSOLVER"
: B == Backend::SYCL ? "a vendor GPU library" : "netlib CBLAS/LAPACKE"

Definition at line 65 of file vendor.hh.

◆ kSparseLibrary

template<Backend B>
const char* batchlas::select::kSparseLibrary
inlineconstexpr
Initial value:
=
B == Backend::CUDA ? "cuSPARSE" : B == Backend::ROCM ? "rocSPARSE"
: B == Backend::SYCL ? "a vendor GPU library" : "netlib CBLAS/LAPACKE"

Definition at line 69 of file vendor.hh.

◆ level3_tile_substitution_enabled

template<Backend B, typename T>
bool batchlas::select::level3_tile_substitution_enabled
inlineconstexpr
Initial value:
=
native_gpu_backend<B> && (std::is_same_v<T, float> || (B == Backend::CUDA && bool(1)))
constexpr bool native_gpu_backend
Backends with wired native GPU kernels; ROCm and the host keep their vendor routes.
Definition vendor.hh:27

Definition at line 104 of file vendor.hh.

Topics

 Per-op choices
 The vocabulary header of every op under src/ops/<op>/: its families, candidates<T>(), OpSpec, table keys and tuner grid.

Files

file  src/select/coverage.hh
 What this build links and what a run reached, as CSV in BATCHLAS_COVERAGE_OUT.pid; the columns are a contract with scripts/, tools/tune and benchviz.
file  src/select/select.hh
 Flat kernel selection: one choose() per op over measured per-device tables.
file  src/select/vendor.hh
 Link facts for if constexpr gates: is an op's vendor library compiled in for a backend, and is the native level-3 tile kernel linked.

Namespaces

namespace  batchlas::select::detail

Classes

struct  batchlas::coverage::Shape
struct  batchlas::select::Device
 The device facts a can_run predicate and the table lookup read (§5.1). More...
struct  batchlas::select::FamilyName< N >
 A string literal as a template argument: the name of a NoFields family. More...
struct  batchlas::select::NoFields< L >
 Base of a family with no knobs: struct Cta : NoFields<"cta"> {};. More...
struct  batchlas::select::overloaded< F >
 std::visit over a choice with one lambda per family. More...
struct  batchlas::select::Rules
 What an op adds to the generic walk: its last-resort order. More...
struct  batchlas::select::KeyField
 One field of a lookup key, value as text; a Key lists the fields an op's table keys name. More...
struct  batchlas::select::TableKey
 One entry of a table's # keys: line. More...
struct  batchlas::select::TableEntry
 One ranked candidate of a row: its spelling and milliseconds per call for the whole batch. More...
struct  batchlas::select::TableRow
 One measured (or transcribed) shape and every candidate ranked there, fastest first. More...
struct  batchlas::select::Table
 One dtype's table: a tuned/<op>.<device>.txt file holds one per dtype. More...
struct  batchlas::select::EmbeddedTable
 A tuned/*.txt file compiled into the library: its basename and text. More...
struct  batchlas::select::StrictPin
 Tag for a strict ScopedPin: a class word ("native", "vendor") that cannot serve the shape throws std::invalid_argument instead of falling back to auto. More...
class  batchlas::select::ScopedPin< Choice >
 RAII pin for op: wins over BATCHLAS_<OP>_ROUTE on this thread; nests, restoring the outer pin on exit. More...
struct  batchlas::select::detail::NativeFacts
 The coverage row's native_route_existed / native_route_supported (tri-state, -1 unknown). More...
class  batchlas::select::TraceScope
 Prints the last choose() decision for op under BATCHLAS_SELECT_TRACE=1, indents nested scopes, and records the coverage row. More...
struct  batchlas::select::OpSpec
 What an op's choice.hh declares once: its name, the library its Vendor family calls (one gate for can_run, the launch arm and the NoRouteError), and its last-resort order. More...