Using the NVIDIA Backend#
The glass::nvidia::block:: interface routes to NVIDIA’s device-side
libraries — CUB (L1), cuBLASDx (L2/L3 GEMM/GEMV/batched), and cuSOLVERDx
(LAPACK) — while preserving the same one-block __device__ calling
convention. These wrappers require compile-time matrix sizes. (Bare
glass::nvidia:: spellings are the measured-default face and currently
resolve to the same entities — see Namespaces, suffixes, and flags. There is
also glass::nvidia::warp:: — CUB WarpReduce reduce / dot /
nrm2, one FULL 32-lane warp per problem, per-warp scratch via
warp_reduce_scratch_bytes<T>(), TRAILING_SYNC emitting
__syncwarp().)
Make sure MathDx is installed and MATHDX_ROOT is set first — see
Installation.
Compiling against MathDx#
cuBLASDx is header-only; cuSOLVERDx links a precompiled device fatbin. The flags differ by which level you use.
cuBLASDx (GEMM / GEMV / batched):
nvcc -std=c++17 -arch=sm_86 \
-I /path/to/GLASS \
-I $MATHDX_ROOT/include \
-I $MATHDX_ROOT/external/cutlass/include \
--expt-relaxed-constexpr \
-Xptxas -O1 \
-DGLASS_BENCH_CUBLASDX \
my_kernel.cu -o my_kernel
--expt-relaxed-constexpris required for cuBLASDx headers.-Xptxas -O1works around the CUDA 12.9 cuBLASDx miscompilation (and doubles as anti-DSE).-DGLASS_BENCH_CUBLASDXforce-enables the wrappers when the TU hasn’t pre-included the cuBLASDx header. (GLASS otherwise auto-detects viaGLASS_HAVE_CUBLASDXfrom include order.)
cuSOLVERDx (LAPACK) additionally needs the device-link flags:
nvcc -std=c++17 -arch=sm_86 \
-I /path/to/GLASS -I $MATHDX_ROOT/include \
--expt-relaxed-constexpr -Xptxas -O1 \
-rdc=true -dlto \
-L $MATHDX_ROOT/lib \
-lcusolverdx -lcublas -lcusolver -lcudart \
my_kernel.cu -o my_kernel
SMS defaults to 860 and can be overridden with -DSMS=XXX so the
dispatch heuristic and cuBLASDx code-gen target your arch.
Calling glass::nvidia::block:: — default form#
In the default form cuBLASDx picks the thread count; you query the required
shared memory and thread count (both constexpr) on the host and launch with
the exact thread count — a mismatch deadlocks.
#include "glass-nvidia.cuh"
constexpr auto smem = glass::nvidia::block::gemm_scratch_bytes<float, 6, 6, 6>();
constexpr auto threads = glass::nvidia::block::gemm_threads<float, 6, 6, 6>();
__global__ void k(float* A, float* B, float* C) {
extern __shared__ __align__(16) char smem_buf[];
glass::nvidia::block::gemm<float, 6, 6, 6>(1.f, A, B, 0.f, C, smem_buf);
}
// Launch with the EXACT thread count cuBLASDx wants.
k<<<1, threads, smem>>>(dA, dB, dC);
Caller-pinned BlockDim<TC>#
To launch with a thread count your surrounding kernel needs (e.g. GRiD’s
352-thread launches), pin BlockDim<TC> with a DEFINE macro. Extra threads go
idle inside the GEMM:
namespace glass { namespace nvidia { namespace block {
DEFINE_NVIDIA_GEMM_BLOCKDIM(6, 6, 6, 352) // pin BlockDim<352,1,1>
}}}
__global__ void k(float* A, float* B, float* C) {
extern __shared__ __align__(16) char smem_buf[];
glass::nvidia::block::gemm<float, 6, 6, 6, 352>(1.f, A, B, 0.f, C, smem_buf);
}
constexpr auto smem = glass::nvidia::block::gemm_scratch_bytes<float, 6, 6, 6, 352>();
k<<<1, 352, smem>>>(dA, dB, dC);
Query the smallest valid TC for a (T, M, N, K, SM) tuple:
constexpr uint32_t MIN = glass::nvidia::block::gemm_min_block_threads<float, 6, 6, 6>();
static_assert(glass::nvidia::block::gemm_block_threads_valid<float, 6, 6, 6, 352>(),
"352 threads should be enough for 6x6x6 on this SM");
Tip
Compile without -DNDEBUG and the wrappers assert(blockDim >=
GEMM::block_dim) inside every run() — a misconfigured launch fails with
a clean assertion instead of a silent deadlock. The asserts compile out under
-DNDEBUG.
Layout / transpose#
glass::nvidia::block::gemm accepts layout LA, LB, LC template
parameters (mirroring cuBLASDx’s Arrangement<>) so you can express transpose
/ row-major storage without falling back to SIMT:
namespace glass { namespace nvidia { namespace block {
// A · Bᵀ (B row-major); alias for LB=row_major
DEFINE_NVIDIA_GEMM_BLOCKDIM_TRANSB(6, 6, 6, 352)
// Or fully explicit (LA=row, LB=col, LC=col):
DEFINE_NVIDIA_GEMM_BLOCKDIM_LAYOUT(6, 6, 6, 352, 1, 0, 0)
}}}
Layout arguments are integer literals: 0 = col_major, 1 =
row_major.
Adding a custom size#
Pre-instantiated square GEMM/GEMV sizes are 4, 6, 8, 12, 14, 24, 64. For any
other size, place the DEFINE macro inside namespace glass::nvidia::block in
your .cu file (the specializations must live where the primary templates
do — the block namespace):
#include "glass-nvidia.cuh"
namespace glass { namespace nvidia { namespace block {
DEFINE_NVIDIA_GEMM(16, 16, 16)
DEFINE_NVIDIA_GEMV(16, 16)
DEFINE_NVIDIA_GEMM_BLOCKDIM(16, 16, 16, 256)
DEFINE_NVIDIA_POSV_BLOCKDIM(16, 4, 256) // SPD solve, 4 RHS
}}}
Every wrapper exposes the same macro family — substitute GEMM / GEMV /
GEMM_BATCHED / CHOL / TRSM / POSV / POTRS / GETRF /
GETRS / GESV / GEQRF / GELS and append _BLOCKDIM /
_LAYOUT / _SM / _BLOCKDIM_SM etc. as needed.
Linear solvers (cuSOLVERDx)#
#include "glass-nvidia.cuh"
namespace glass { namespace nvidia { namespace block {
DEFINE_NVIDIA_POSV_BLOCKDIM(7, 1, 256) // 7×7 SPD, 1 RHS, BlockDim<256>
}}}
__global__ void k(float* A, float* b) {
extern __shared__ __align__(16) char smem_buf[];
// Solves A·x = b in place: A := L (lower Cholesky), b := x
glass::nvidia::block::posv<float, 7, 1, 256>(A, b, smem_buf);
}
constexpr auto smem = glass::nvidia::block::posv_scratch_bytes<float, 7, 1, 256>();
k<<<1, 256, smem>>>(dA, db);
Available cuSOLVERDx wrappers: potrf, trsm, posv, potrs,
getrf_no_pivot, getrs_no_pivot, gesv_no_pivot, geqrf, gels.
All follow the same DEFINE_NVIDIA_<NAME> macro pattern and are not
pre-instantiated — call the macro per size you need.
See Backend Dispatch for how the auto-dispatch decides between cuBLASDx and SIMT, and Batched-1D GEMM APIs for the 1D-launch batched GEMM APIs.