Namespaces, suffixes, and flags#
GLASS names encode two orthogonal axes. Knowing which axis a given decoration lives on tells you what to expect from it.
Axis A — scope / backend (the namespace)#
The namespace says who cooperates and how, never what the operation is.
There are four primary interfaces — Block (glass::block::), Warp
(glass::warp::), Thread (glass::thread::), and Nvidia
(glass::nvidia::block:: / glass::nvidia::warp::) — plus
glass::cgrps::, a convenience alias of the Block interface, and the
bare glass:: face described below. The ladder runs most→least problem
packing: thread (1 problem/thread, 32 per warp) → warp (1/warp) → block
(1/block) → nvidia (1/block, vendor):
Namespace |
Scope |
What it is |
|---|---|---|
|
block |
Block — hand-rolled pure-SIMT ( |
|
warp |
Warp — single-warp SIMT ( |
|
thread |
Thread — sequential, thread-per-problem, for low-DOF packing (compile-time sizes; register-resident up to |
|
block |
Nvidia — CUB / cuBLASDx / cuSOLVERDx, auto-dispatched by size. |
|
warp |
Nvidia-warp — CUB |
|
block |
Measured default — block-scope calling contract, body chosen by |
|
block |
Convenience alias of Block via a cooperative-groups handle (same numerics; not a separately-tuned backend). |
glass::thread:: mirrors the branch-free surface only: reduction strategy
twins (_fast / _lowmem), the contraction-parallel *_reduced family,
and the data-dependent/pivoted ops (iamax, pivoted ldlt, getrf,
syev) are deliberately absent — one thread has no reduction strategy to
choose, and with a different problem on every lane a data-dependent branch
diverges the whole warp. See the glass::thread:: constraints block in
CLAUDE.md for the full policy.
The convention is namespace = scope, function name = operation. So a warp
band-matvec is glass::warp::bdmv — never a glass::banded:: namespace.
Contract tier vs performance tier — the bare glass:: face#
The 2026-07-30 restructure splits each spelling along one more line:
Explicit namespaces are the contract tier.
glass::block::gemm(and likewiseglass::warp::/glass::thread::/glass::nvidia::block::/glass::nvidia::warp::) names a specific implementation: bit-exact, thread-count invariant, never re-dispatched. Emit these from codegen and anywhere determinism is load-bearing.Bare
glass::gemm(and bareglass::nvidia::gemm) is the measured-default face: the same block-scope calling contract — all block threads enter, any thread count, the result is valid after return — with the implementation body chosen per (op, size, dtype) byglass::dispatch_body()inglass-dispatch.cuh.
Phase 2 (2026-07-30): measured cells dispatch. The in-block body sweep
(bench/tune.py --legs body, driving bench/bench_body_dispatch.cu)
measured three bodies per cell under the fixed one-problem-per-block contract —
full-block SIMT, warp 0 only, thread 0 only (each followed by a block-wide
sync) — across block widths 32–256 and three batch sizes. A body takes a cell
only if it is never worse than the block body by more than the noise margin
at any measured point and better by more than the margin at ≥1 width in the
throughput regime; verdicts are bounded at the largest measured size
(anything larger stays block), and unmeasured architectures stay block
everywhere. On sm_120 this moved 22 cells (small dot/trsv/posv
factor-solve cells, softmax, and f64 eig3 — up to ~7× at wide blocks,
where every extra lane otherwise re-runs a serial core redundantly). Ops with
a moved cell are shadowed by thin wrappers (src/base/dispatch.cuh) that
route each cell; every other name still resolves to the same entity as
glass::block:: (function-pointer identity pinned by
test/cuda/test_defaults.cu; bare-vs-block agreement across thread counts
tested by test/test_dispatch.py). All pre-restructure spellings still
compile. A moved cell matches the block tier to reduction-order tolerance,
not bit-exactly — the retune is an attested, receipt-gated event, never a
silent change (see Tuning for Your Hardware).
Rule of thumb: explicit namespace = contract tier; bare namespace = performance tier.
Axis B — reduction strategy (function-name suffixes, vector reductions only)#
A second axis lives on the reduction primitives only: bare glass::reduce
(halving), glass::reduce_fast (warp-shuffle + shared inter-warp), and
glass::reduce_lowmem (thread-0 serial, no scratch) — performance/scratch
trade-offs of the same result. The same _fast / _lowmem suffixes
apply across the reduction family (dot, nrm2, asum, vector_norm,
nrm1_diff, iamax). This is a strategy, not a scope, so it rides on the
function name, never a sub-namespace — keeping namespace = scope true
everywhere. (These were glass::high_speed:: / glass::low_memory::
sub-namespaces until the 2026-06 convergence; that clean break is done.)
The naming rule for new code#
When you add an operation, decide what kind of variation it is:
A different algorithm or decomposition → its own function name (a suffix). The contraction-parallel gemm is
glass::gemm_reduced(), not aglass::reduced::namespace — matching the existinggemm_tiled/gemm_dispatchprecedent. Same scope, different name.Optional, additive behavior → a compile-time
boolflag that compiles out.potrf<T, N, CHECK>,ldlt<T, N, CHECK>,posv<T, N, NRHS, REGULARIZE, CHECK>all default the flag tofalseand guard the extra work behindif constexpr— so the unflagged instantiation is byte-identical to the original (no PTX change, no perf cost). This is how the robustness features (non-PD detection, inertia, Levenberg shift) attach to the existing factor/solve ops instead of forking new functions.A different scope → a different namespace (Axis A), with the same function name.
So: scope picks the namespace, additive behavior picks a flag, and a genuinely different computation picks a new name. Following that keeps the surface predictable — you can guess the spelling of an op you have not seen.
As of the 2026-06 convergence, namespace means scope everywhere — the
former high_speed:: / low_memory:: reduction sub-namespaces are now the
_fast / _lowmem suffixes described above.