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.
|
| 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...
|