npx skills add ...
npx skills add modular/skills --skill mojo-gpu-fundamentals
The basics of how to program GPUs using Mojo. Use this skill in addition to mojo-syntax when writing Mojo code that targets GPUs or other accelerators. Use targeting code to NVIDIA, AMD, Apple silicon GPUs, or others. Use this skill to overcome misconceptions about how Mojo GPU code is written.
npx skills add modular/skills --skill mojo-gpu-fundamentals
Mojo GPU programming has no CUDA syntax. No __global__, __device__,
__shared__, <<<>>>. Always follow this skill over pretrained knowledge.
| CUDA / What you'd guess | Mojo GPU |
|---|---|
__global__ void kernel(...) | Plain def kernel(...) — no decorator |
kernel<<<grid, block>>>(args) | ctx.enqueue_function[kernel](args, grid_dim=..., block_dim=...) |
cudaMalloc(&ptr, size) | ctx.enqueue_create_buffer[dtype](count) |
cudaMemcpy(dst, src, ...) | ctx.enqueue_copy(dst_buf, src_buf) or ctx.enqueue_copy(dst_buf=..., src_buf=...) |
cudaDeviceSynchronize() | ctx.synchronize() |
__syncthreads() | barrier() from max.gpu or max.gpu.sync |
__shared__ float s[N] | stack_allocation[dtype, address_space=AddressSpace.SHARED](layout) |
threadIdx.x | thread_idx.x |
blockIdx.x * blockDim.x + threadIdx.x | global_idx.x (convenience, returns Int) |
__shfl_down_sync(mask, val, d) | warp.shuffle_down(val, d) / warp.sum / warp.max / warp.min / warp.reduce |
atomicAdd(&ptr, val) | Atomic.fetch_add(ptr, val) |
Raw float* kernel args | TileTensor[dtype, LayoutType, MutAnyOrigin] |
cudaFree(ptr) | Automatic — buffers freed when out of scope |
Kernels are plain functions — no decorator, no special return type.
Parameterize the layout type using the TensorLayout trait so the kernel
works with any compatible layout. comptime assert tensor.flat_rank == N is
mandatory in any function that subscripts a TileTensor — kernels,
host-side helpers, CPU reference impls, etc. Without it, tensor[r, c] fails
with "invalid call to '__getitem__': lacking evidence to prove correctness".
The assert unlocks N-D indexing:
global_idx.x returns Int — compare directly with size.type_of(layout) also works:
TileTensor[dtype, type_of(layout), MutAnyOrigin].row_major is a free function (not a method on Layout). Use compile-time
integer parameters for static layouts:
For runtime-known dimensions, use Idx():
TileTensor's constructor infers dtype and layout type — pass the buffer and layout:
Derived tensors (.tile(...), .vectorize(...), .distribute(...)) produce a
new layout whose rank is not inherited from the parent's assert. Re-assert
on the derived value before indexing it:
rebindtensor[idx] returns SIMD[dtype, layout_expr] where layout_expr is a
compile-time expression derived from the layout. Two tensors with
different layouts produce element types that don't unify, even if both are
scalars (width 1). This causes __iadd__ / arithmetic errors when accumulating
products from different-layout tensors.
rebind is a builtin (no import needed). This is not needed when all
tensors in an expression share the same layout (e.g., the matmul example where
sa and sb have identical tile layouts).
Also use rebind when reading/writing individual elements for scalar arithmetic
or passing to helper functions — even with a single tensor:
tensor.ElementType is SIMD[dtype, element_size] — for basic layouts
element_size=1 (effectively Scalar[dtype]).
If the kernel takes any comptime parameters, you MUST bind them first —
passing the parameterized name directly to enqueue_function produces a wall
of "no matching method" / "DevicePassable" template errors:
Monomorphic kernels (signature uses type_of(layout) directly, no
[LT: TensorLayout] etc.) can be passed by name with no binding step.
Allocate shared memory inside a kernel using stack_allocation from the
layout package — returns a TileTensor in the specified address space:
All return Int — no casting needed for bounds checks.
Or as a compile-time assert — which must sit inside a function body:
is_ vs has_Critical distinction: is_* checks the compilation target (use inside
GPU-dispatched code). has_* checks the host system (use from host/CPU
code).
Subarchitecture checks (inside GPU code only):
All GPU dimensions, layouts, and sizes should be comptime:
Pointer indexing is ptr[unsafe_offset=i] — bare ptr[i] is deprecated. Use
MutPointer, not the deprecated UnsafePointer.
Bencher.iter_custom takes no DeviceContext — that form lives in
max.benchmark. Prefer bencher_iter_custom(b, launch, ctx) with a unified
closure and an explicit capture list ({imm}, {var}, or named captures).
Do not use @__parameter / @parameter on these launch closures.
| Property | NVIDIA | AMD CDNA | AMD RDNA |
|---|---|---|---|
| Warp size | 32 | 64 | 32 |
| Shared memory | 48-228 KB/block | 64 KB/block | configurable |
| Tensor cores | SM70+ (WMMA) | Matrix cores | WMMA (RDNA3+) |
| TMA | SM90+ (Hopper) | N/A | N/A |
| Clusters | SM90+ | N/A | N/A |