From Python CuTeDSL to cute-rs#
If you have used NVIDIA’s Python CuTeDSL, the concepts in cute-rs will feel immediately familiar: layouts, composed swizzles, zipped_divide, TMA copy atoms, and tiled MMA. Both compile down to the exact same CUTLASS cute and cute_nvgpu MLIR operations.
The key difference is where the static configuration lives and when the IR is constructed:
Python CuTeDSL traces Python objects wrapping live MLIR values at runtime; Rust cute-rs encodes layouts, swizzles, and MMA plans in Rust types and translates them ahead of time.#
Why the Kernel Signatures Look Different#
In Python CuTeDSL, @cute.jit traces a Python function at runtime to build an MLIR module. Because Python objects like cute.Tensor, cute.CopyAtom, cute.TiledMma, and cute.ComposedLayout directly wrap MLIR values, the host script constructs all of them and passes them as parameters into @cute.kernel:
# Python CuTeDSL: 18 parameters wrapping MLIR types during @cute.jit tracing
@cute.kernel
def kernel(
self,
tma_atom_a: cute.CopyAtom, mA_mkl: cute.Tensor,
tma_atom_b: cute.CopyAtom, mB_nkl: cute.Tensor,
tma_atom_sfa: cute.CopyAtom, mSFA_mkl: cute.Tensor,
tma_atom_sfb: cute.CopyAtom, mSFB_nkl: cute.Tensor,
tma_atom_c: cute.CopyAtom, mC_mnl: cute.Tensor,
tiled_mma: cute.TiledMma, cta_layout_mnk: cute.Layout,
a_smem_layout: cute.ComposedLayout, b_smem_layout: cute.ComposedLayout,
sfa_smem_layout: cute.Layout, sfb_smem_layout: cute.Layout,
epi_smem_layout: cute.ComposedLayout, tile_sched_params: PersistentTileSchedulerParams,
): ...
In Rust cute-rs, there is no runtime tracer: rustc compiles the kernel ahead of time. Nothing is hidden:
Kernel parameters carry only the hardware launch ABI (for example, 128-byte hardware TMA descriptors passed by value via
#[grid_constant], device pointers, and scalar dimensions).Layouts, swizzles, operand roles, and MMA configurations live in Rust types (
ASmem,Sm100TiledMma<...>,MainloopPipeline) with zero runtime byte overhead.
// Rust cute-rs: static configuration lives in types; arguments carry only runtime TMA descriptors
type ASmem = ComposedLayout<Swizzle<3, 3, 3>, 0, RowMajor<128, 64>>;
type B0Smem = ComposedLayout<Swizzle<3, 3, 3>, 0, RowMajor<96, 64>>;
type B1Smem = ComposedLayout<Swizzle<3, 3, 3>, 0, RowMajor<80, 64>>;
type CSmem = ComposedLayout<Swizzle<2, 3, 3>, 0, RowMajor<128, 32>>;
type TiledMma = Sm100TiledMma<ASmem, B0Smem, B1Smem, 192, 160>;
type MainloopPipeline = PipelineTmaUmma<5, 77_824>;
#[kernel]
pub fn fp16_gemm_256x352(
#[grid_constant] a: &TensorMap<f16, ASmem>,
#[grid_constant] b0: &TensorMap<f16, B0Smem>,
#[grid_constant] b1: &TensorMap<f16, B1Smem>,
#[grid_constant] c: &TensorMap<f16, CSmem>,
bias: *const f16, m: u64, n: u64, k: u64, has_bias: u64,
) { ... }
How mir-importer Translates Rust Types to CuTe IR#
When mir-importer translates a cute-rs call, it matches the callee’s canonical rustc DefId, structurally walks its monomorphized generic type arguments (static_config.rs), runs cute-layout static validation proofs, and emits a dialect-cute operation combining those static attributes with the call’s runtime SSA operands:
Compile-time Rust types supply the static IR attributes (shape, stride, swizzle, operand role, MMA partitions), while kernel values supply the runtime SSA operands.#
Side-by-Side Concept Map#
Concept |
Python CuTeDSL |
Rust cute-rs |
|---|---|---|
Layout definition |
|
|
Swizzled SMEM layout |
|
|
Tensor view |
|
|
Tiling |
|
|
Register fragment |
|
no function: the tile or MMA atom type fixes the fragment. |
TMA descriptor |
|
|
TMA encoding (host) |
|
|
TMA copy |
|
|
Shared tensor view |
|
|
TMA pipeline |
|
|
TMEM allocator |
|
|
Pipeline state |
|
|
Pipeline producer step |
|
|
Persistent scheduler |
|
|
Tiled MMA |
|
|
Tiled copy |
|
|
Register vectors |
|
|
Architecture primitives |
|
|
Where cute-rs Deviates, and Why#
The names follow CuTeDSL 4.7. Where the Rust form differs on purpose, the difference and its reason are listed here, so a CuTeDSL author is not surprised.
Concept |
CuTeDSL 4.7 |
Rust cute-rs |
Why |
|---|---|---|---|
Constructors and plans |
|
|
the compiler reads the plan from the generic arguments, so there is nothing for a |
Pipeline producer step |
|
|
electing the lane is a convergence obligation the compiler does not prove, so it stays visible in kernel code; the pipeline value stores pointers only |
Scheduler |
|
|
the grid is one dimensional and has no params object; the bounds check stays outside the coordinate arithmetic; the step is always one grid of blocks; the coordinate is a Rust tuple |
Pipeline state |
|
|
a |
TMA names |
|
|
the descriptor is CUDA’s |
Clusters |
|
the same two names with the same meanings |
the compiler’s own |
Named barriers |
|
|
both values are const generics, so the id and the thread count (a multiple of 32, at most 1024) are checked at compile time; the split arrive and wait methods are not bound on the type |
Sync-restricted fence |
|
|
the instruction is chosen when the kernel compiles; the release form waits for its intrinsic to be bound |
Tiled copy |
|
|
the thread and value layouts live in |
Register vectors |
|
|
the operators are Rust’s own: floating-point division matches the DSL, integer division and the sign of a negated zero differ |
ctm-rs keeps PTX instruction names (cp_async_bulk_tensor_shared_cta_global, mbarrier_arrive_expect_tx, fence_sync_restrict_acquire); the cute.arch concept names live in cute_rs::arch as forwarders over them. Dialect op, type and attribute names are compiler-internal and are not part of this surface.