Backend Dispatch#
The glass::nvidia::gemm / gemv / row_strided_* / gemm_batched_1d
primary templates auto-dispatch at compile time: for shapes where pure-SIMT
wins they fall through to ::glass::*; for shapes where the vendor library
wins they route to cuBLASDx via the DEFINE_NVIDIA_* macros.
This means glass::nvidia::gemm<float, 6, 6, 6>(...) “just works” without any
DEFINE macro — small shapes route to SIMT automatically. Larger shapes such as
glass::nvidia::gemm<float, 32, 32, 32>(...) still require a
DEFINE_NVIDIA_GEMM(32, 32, 32) in scope (placed inside
namespace glass { namespace nvidia { namespace block {), but produce a
clean compile-time message when it is missing.
Note
This cuBLASDx-vs-SIMT decision is one of three distinct dispatch layers.
glass::suggested_backend<> (Tuning for Your Hardware) is a host-side ladder that
advises launch-level packing — which tier (thread / warp / block /
nvidia) to launch, i.e. the shape of <<<grid, block>>>. Newest, and
different from both: glass::dispatch_body() (glass-dispatch.cuh)
picks the in-block body behind the bare glass::op /
glass::nvidia::op face, under a fixed block-scope calling contract —
the launch does not change. The measured in-block body sweep
(bench/tune.py --legs body, Phase 2, 2026-07-30) moved the winning
cells to a warp- or thread-body executed inside the block — an attested,
receipt-gated retune, regenerated per arch like the ladder. A moved cell
matches the block body to tolerance, not bit-exactly; ops with no moved
cell remain exactly glass::block::. Pin glass::block:: where
determinism is load-bearing; see Namespaces, suffixes, and flags and Tuning for Your Hardware.
The dispatch flow#
caller writes: glass::nvidia::gemm<float, M, N, K>(...)
│
▼
should_use_cublasdx<float, M, N, K, SMS>()
│
┌──────────────────────┴──────────────────────┐
▼ ▼
false true
│ │
────────── ▼ ────────── ────────── ▼ ──────────
SIMT fallback: Need a DEFINE_NVIDIA_GEMM*
::glass::gemm<T,M,N,K>(...) to specialize for cuBLASDx;
(no DEFINE needed; no smem) else: static_assert error.
The decision is made at compile time by should_use_cublasdx*<T,M,N,K,SM>()
(see src/nvidia/query.cuh / query_simt.cuh), which consults — in order:
A per-build local override table (when
GLASS_TUNING_TABLE_LOCALis defined).The shipped global table (
src/nvidia/tuning_table.cuh).A fallback static heuristic.
The shipped table currently covers sm_120 (Blackwell-class) for square
shapes from 3×3×3 up to 64×64×64; regenerate for another arch with
bench/autotune.py (a new arch gets its own entries without touching the
existing ones).
The size heuristic#
When no tuning-table entry matches, each API falls back to a per-API heuristic that reflects its arithmetic intensity:
Template |
Default heuristic |
|---|---|
|
|
|
|
|
|
|
delegates to |
|
delegates to |
Restricted to float: the heuristic returns SIMT for non-float types.
Inspecting the decision#
The print_dispatch* host helpers (__host__ __device__, so you can also
drop one into a kernel for runtime diagnostics) report the chosen path:
glass::nvidia::print_dispatch<float, 4, 4, 4>();
// → glass::nvidia::gemm<T,4,4,4,SM=860>: SIMT fallback
glass::nvidia::print_dispatch<float, 32, 32, 32>();
// → glass::nvidia::gemm<T,32,32,32,SM=860>: cuBLASDx (needs DEFINE_NVIDIA_GEMM*)
glass::nvidia::print_dispatch_gemv<float, 64, 64>();
// → glass::nvidia::gemv<T,64,64,SM=860>: cuBLASDx (needs DEFINE_NVIDIA_GEMV*)
Overriding the dispatch#
Goal |
How |
|---|---|
Force cuBLASDx for a shape the heuristic puts in SIMT |
Add |
Force SIMT for a shape the heuristic puts in cuBLASDx |
Call |
Per-host tuning without editing source |
Run |
Different SM in-tree (for a PR) |
|
The shipped values are sensible defaults, but small-GEMM performance is highly SM-dependent. See Tuning for Your Hardware for the full per-host autotuning workflow that measures both paths side by side on your hardware so you can override these defaults.