CuTe
A header-only CUDA C++ template library inside CUTLASS for describing and manipulating tensors of data and threads through composable Layouts.
CuTe (CUDA Templates) is a header-only CUDA C++ library within CUTLASS for describing and manipulating tensors of data and threads.
It is built on C++ templates, C++'s implementation of parametric polymorphism (often called generics in other languages): a polymorphic function is written once but works on inputs of different types. Don't confuse CuTe with CuTe DSL, which exposes CuTe/CUTLASS through a Python Domain-Specific Language (DSL).
Layouts and Tensors#
CuTe's type system centers on two types:
- A
Layoutdescribes a regular pattern of access to a tensor. It is built fromShapeandStridetuples, which describe memory extents and how to traverse them. - A
Tensorcombines aLayoutwith a pointer to memory.
Critically, Layouts are composable. They form a category with a rich algebra, so they offer both expressiveness and structure.
Correctness and performance from the type system#
CuTe encodes key program metadata (memory organization, strided accesses, tiling) in types. That lets the compiler check many aspects of correctness and preserve invariants while it optimizes. The result is very high-level metaprogramming of kernels without giving up performance; for example, one template can compile into highly optimized kernels for several Streaming Multiprocessor architectures.
Key idea Layouts are resolved at compile time, so memory accesses carry zero extra runtime overhead, overhead that could otherwise kill performance in memory-bound workloads.
See NVIDIA's CuTe documentation for more.
Example: matrix transpose#
This transpose kernel shows CuTe's core features: templating, shapes, layouts and tensors. It is based on the initial "naive" implementation from this Colfax International article. You can run it on an H100 via this Modal Notebook.
// one CuTe trick: transpose a row-major matrix just using Layouts
template <typename T>
__global__ void transpose_kernel(const T* __restrict__ d_S,
T* __restrict__ d_D,
int M, int N)
{
// define the Shape of tiles worked on by thread blocks
using b = Int<32>;
auto block_shape = make_shape(b{}, b{});
// define the Shape of input/output Tensors
auto tensor_shape = make_shape(M, N);
// define the Layout of the input and output Tensors in global memory
auto gmemLayoutS = make_layout(tensor_shape, GenRowMajor{}); // input: row-major
auto gmemLayoutDT = make_layout(tensor_shape, GenColMajor{}); // output: col-major
// construct the Tensors
auto tensor_S = make_tensor(make_gmem_ptr(d_S), gmemLayoutS);
auto tensor_DT = make_tensor(make_gmem_ptr(d_D), gmemLayoutDT);
// define a tile-ing of the Tensors (as a "Tensor of Tensors")
auto tiled_tensor_S = tiled_divide(tensor_S, block_shape);
auto tiled_tensor_DT = tiled_divide(tensor_DT, block_shape);
// pull out the tiles this thread block will be working on
auto tile_S = tiled_tensor_S (make_coord(_, _), blockIdx.x, blockIdx.y);
auto tile_DT = tiled_tensor_DT(make_coord(_, _), blockIdx.x, blockIdx.y);
// create a Layout for threads in the thread block
auto thr_layout = make_layout(
make_shape(Int<8>{}, Int<32>{}),
GenRowMajor{}
);
// pull out the tile this thread will work on
auto thr_tile_S = local_partition(tile_S, thr_layout, threadIdx.x);
auto thr_tile_DT = local_partition(tile_DT, thr_layout, threadIdx.x);
// define a "Tensor" in register memory
auto rmem = make_tensor_like<T>(thr_tile_S);
// copy tile into registers
copy(thr_tile_S, rmem);
// copy tile out of registers as though it were column-major
copy(rmem, thr_tile_DT);
}
The transpose happens purely through layouts: the input is read with a row-major Layout and written back with a column-major one.
Related#
- CUTLASS: the library CuTe sits at the core of
- CuTe DSL: CuTe from Python
- Memory hierarchy: the memories CuTe Tensors point into