cuTile Python DSL Reference
Execution Model
- Tiles are chunks of data loaded from HBM into SMEM (shared memory)
- Operations work on tiles, not individual elements
- TMA (Tensor Memory Accelerator) handles async HBM-SMEM transfers (
allow_tma=True)
- Grid of thread blocks: use
ct.bid(0/1/2) for block indices
- Compiler generates optimized MLIR → PTX/SASS
Basically cuTile allows to divide data into tiles, and define which blocks (mapped with ct.bid(0/1/2)) will process which tiles. The scheduler/compiler will do the mapping to the actual hardware resources.
Scheduling and Clustering Boundaries
The execution model is intentionally block-level, not thread-level. The official cuTile execution docs state that tile programs describe block-level parallelism, that threads cannot be explicitly identified or manipulated, and that explicit synchronization or communication within a block is not permitted.
Practical consequence: cuTile lets you choose block work assignment and compiler hints, but it does not expose explicit intra-CTA scheduling primitives. There is no public API for thread, warp, or warpgroup identity; no user-visible producer/consumer warp specialization; no explicit barrier choreography; and no public pipeline controls comparable to handwritten CUDA or CUTLASS/CuTe kernels.
For clustering, num_ctas is real, but it is only a cluster-size entry hint. The compiler lowers it to num_cta_in_cga, yet cuTile does not expose a cluster-local programming model such as cluster rank, cluster barrier, DSM primitives, or cluster memory scope. Treat num_ctas as a placement/codegen hint, not as an API for explicit cluster-cooperative algorithms.
This limitation matters most for kernels that are structurally forced into one CTA per SM by register pressure or shared-memory footprint. In that regime, high-end kernels often recover utilization through explicit warpgroup scheduling, fine-grained copy/compute pipelining, or cluster-cooperative work distribution. cuTile cannot express those controls directly, so if profiling shows that one-block-per-SM residency is the root bottleneck and the missing win requires explicit scheduling, a lower-level DSL or CUDA path is usually the correct next step.
Core API Table
| API |
Purpose |
Key params |
@ct.kernel |
Kernel decorator |
num_ctas, occupancy, opt_level |
ct.launch(stream, grid, kernel, args) |
Launch kernel |
grid is 1-3D tuple |
ct.bid(axis) |
Block index |
0, 1, 2 |
ct.num_blocks(axis) |
Grid size on axis |
0, 1, 2 |
ct.cdiv(a, b) |
Ceil division |
— |
ct.load(arr, index, shape, ...) |
HBM→SMEM tile load |
latency, allow_tma, order |
ct.store(arr, index, tile, ...) |
SMEM→HBM tile store |
latency, allow_tma |
ct.mma(A, B, C) |
Tensor Core MMA: C += A @ B |
accumulator C |
ct.matmul(A, B) |
Matrix multiply |
— |
ct.zeros(shape, dtype) |
Zero-filled tile |
— |
ct.full(shape, val, dtype) |
Value-filled tile |
— |
ct.ones(shape, dtype) |
Ones tile |
— |
ct.arange(N, dtype) |
Range tile |
— |
ct.max(tile, axis) |
Max reduction |
keepdims |
ct.min(tile, axis) |
Min reduction |
keepdims |
ct.sum(tile, axis) |
Sum reduction |
keepdims |
ct.exp2(x) |
Element-wise 2^x |
— |
ct.log2(x) |
Element-wise log2 |
— |
ct.exp(x) |
Element-wise exp |
— |
ct.pow(base, exp) |
Element-wise power |
— |
ct.reshape(tile, shape) |
Tile reshape |
— |
ct.cast(tile, dtype) |
Type cast |
— |
ct.maximum(a, b) |
Element-wise max |
— |
tile + tile |
Element-wise add |
— |
tile * tile |
Element-wise mul |
— |
tile[:, None] |
Broadcasting |
— |
Key Constraints
- No
ct.softmax() — implement manually with online running statistics
ct.mma requires compatible shapes for tensor cores (M/N/K multiples of 16/8)
- Latency hints are suggestions — validated with profiling, not guaranteed
ct.load order param — axis permutation for transposed loads (e.g., 'F', (0, 2, 1))
- ConstInt params — tile sizes must be compile-time constants (passed as
ConstInt)
- Grid up to 3D —
(x, y, z) dimensions
- No explicit intra-CTA control — no thread IDs, warp IDs, warpgroup IDs, or in-block barrier choreography
- No public cluster programming model —
num_ctas selects CGA size, but there is no exposed cluster rank/barrier/DSM API
Compiler Hints
@ct.kernel(num_ctas=2, occupancy=4)
def my_kernel(...):
| Hint |
Values |
Purpose |
num_ctas |
1, 2, 4, 8, 16 |
CTAs per thread block cluster (Hopper+ CGA). Use as a cluster-size hint only; cuTile does not expose explicit cluster-local coordination APIs |
occupancy |
1-32 |
Steers register/resource allocation. Validate with profiling |
opt_level |
0-3 |
Optimization level (default: 3) |
Persistent Kernel Grid Formula
When combining persistent kernels with CGA clusters, account for both:
# num_ctas affects available cluster count (not specific to persistent kernels)
grid_size = min(NUM_SMS // num_ctas, num_tiles) * occupancy
@ct.kernel(num_ctas=num_ctas_val)
def persistent_kernel(...):
bid = ct.bid(0)
num_programs = ct.num_blocks(0)
for tile_id in range(bid, num_tiles, num_programs):
# Process tile
Common Patterns
Online Softmax (FlashAttention-style)
m_i = ct.full((Bm,), -math.inf, dtype=ct.float32) # running max
l_i = ct.full((Bm,), 1.0, dtype=ct.float32) # running sum
acc = ct.zeros((Bm, D), dtype=ct.float32) # accumulator
for kv_idx in range(0, T, Bn):
q = ct.load(Q, ...)
k = ct.load(K, ...)
qk = ct.mma(q, k, ct.zeros((Bm, Bn), dtype=ct.float32))
# Add positional scores if needed
m_ij = ct.max(qk, axis=-1)
m_new = ct.maximum(m_i, m_ij)
alpha = ct.exp2(m_i - m_new)
p = ct.exp2(qk - m_new[:, None])
l_ij = ct.sum(p, axis=-1)
l_i = l_i * alpha + l_ij
acc = acc * alpha[:, None]
v = ct.load(V, ...)
acc = ct.mma(p, v, acc)
m_i = m_new
# Normalize
acc = acc / l_i[:, None]
Head Grouping (Bh > 1)
# Load query for Bh heads at once
q = ct.load(Qc, shape=(1, k, Bh, Bm), ...)
q = ct.reshape(q, (k, Bh * Bm))
# Z tile shared across all Bh heads (loaded ONCE per block)
z = ct.load(Z, shape=(1, Bn, k), ...)
qk = ct.mma(q, z, acc) # (Bh*Bm, Bn) — all heads at once
Split-KV Block Assignment
split_idx = ct.bid(0) # which split (latent or decompressed)
path_id = ct.bid(1) # batch * head_groups * query_tiles
is_latent = split_idx < C_o
if is_latent:
start = split_idx * L_o
# process latent segment
else:
start = (split_idx - C_o) * L_n
# process decompressed segment
Official Documentation Index
Load these files on demand when you need detailed API information:
| Need detail on... |
Read this file |
ct.load params, order, latency |
docs/cutile-dsl/generated/cuda.tile.load.md |
ct.store details |
docs/cutile-dsl/generated/cuda.tile.store.md |
ct.mma shape requirements |
docs/cutile-dsl/generated/cuda.tile.mma.md |
ct.gather / ct.scatter |
docs/cutile-dsl/generated/cuda.tile.gather.md |
| Performance hints |
docs/cutile-dsl/performance.md |
| Execution model |
docs/cutile-dsl/execution.md |
| Full API index |
docs/cutile-dsl/index.md |
Example Kernels
Study these for complete implementation patterns:
| Kernel |
Path |
Pattern |
| FlashAttention |
src/mla_var3/kernel/cutile/mla/flash_attention/flash_attention/flash_attention.py |
Single kernel, online softmax |
| FlashMLA |
src/mla_var3/kernel/cutile/mla/flash_mla/flash_mla/flash_mla.py |
Single kernel, head grouping, latent space |
| MLA-var6+ v2 |
src/mla_var3/kernel/cutile/mla/mla_var6_plus/mla_var6_plus_v2/ |
Pipeline, split-KV, block specialization |
| TileGym ops |
third_party/tilegym/src/tilegym/ops/cutile/ |
Matmuls, attention variants, activations |
Detailed References
- API deep-dives: See references/api-index.md for organized links to official cuTile documentation sections
- Scheduling boundary evidence: See
docs/cutile-dsl/execution.md and docs/cutile-dsl/performance.md for the public model, and docs/kernels/flash-mla.md for the concrete FlashMLA consequence when the kernel is pinned to one CTA per SM
1---2name: cutile-dsl-ref3description: cuTile Python DSL Reference4---56# cuTile Python DSL Reference78## Execution Model910- **Tiles** are chunks of data loaded from HBM into SMEM (shared memory)11- Operations work on tiles, not individual elements12- **TMA** (Tensor Memory Accelerator) handles async HBM-SMEM transfers (`allow_tma=True`)13- Grid of thread blocks: use `ct.bid(0/1/2)` for block indices14- Compiler generates optimized MLIR → PTX/SASS1516Basically cuTile allows to divide data into tiles, and define which blocks (mapped with ct.bid(0/1/2)) will process which tiles. The scheduler/compiler will do the mapping to the actual hardware resources.1718### Scheduling and Clustering Boundaries1920The execution model is intentionally block-level, not thread-level. The official cuTile execution docs state that tile programs describe block-level parallelism, that threads cannot be explicitly identified or manipulated, and that explicit synchronization or communication within a block is not permitted.2122Practical consequence: cuTile lets you choose block work assignment and compiler hints, but it does not expose explicit intra-CTA scheduling primitives. There is no public API for thread, warp, or warpgroup identity; no user-visible producer/consumer warp specialization; no explicit barrier choreography; and no public pipeline controls comparable to handwritten CUDA or CUTLASS/CuTe kernels.2324For clustering, `num_ctas` is real, but it is only a cluster-size entry hint. The compiler lowers it to `num_cta_in_cga`, yet cuTile does not expose a cluster-local programming model such as cluster rank, cluster barrier, DSM primitives, or cluster memory scope. Treat `num_ctas` as a placement/codegen hint, not as an API for explicit cluster-cooperative algorithms.2526This limitation matters most for kernels that are structurally forced into one CTA per SM by register pressure or shared-memory footprint. In that regime, high-end kernels often recover utilization through explicit warpgroup scheduling, fine-grained copy/compute pipelining, or cluster-cooperative work distribution. cuTile cannot express those controls directly, so if profiling shows that one-block-per-SM residency is the root bottleneck and the missing win requires explicit scheduling, a lower-level DSL or CUDA path is usually the correct next step.2728## Core API Table2930| API | Purpose | Key params |31|-----|---------|-----------|32| `@ct.kernel` | Kernel decorator | `num_ctas`, `occupancy`, `opt_level` |33| `ct.launch(stream, grid, kernel, args)` | Launch kernel | grid is 1-3D tuple |34| `ct.bid(axis)` | Block index | 0, 1, 2 |35| `ct.num_blocks(axis)` | Grid size on axis | 0, 1, 2 |36| `ct.cdiv(a, b)` | Ceil division | — |37| `ct.load(arr, index, shape, ...)` | HBM→SMEM tile load | `latency`, `allow_tma`, `order` |38| `ct.store(arr, index, tile, ...)` | SMEM→HBM tile store | `latency`, `allow_tma` |39| `ct.mma(A, B, C)` | Tensor Core MMA: C += A @ B | accumulator C |40| `ct.matmul(A, B)` | Matrix multiply | — |41| `ct.zeros(shape, dtype)` | Zero-filled tile | — |42| `ct.full(shape, val, dtype)` | Value-filled tile | — |43| `ct.ones(shape, dtype)` | Ones tile | — |44| `ct.arange(N, dtype)` | Range tile | — |45| `ct.max(tile, axis)` | Max reduction | `keepdims` |46| `ct.min(tile, axis)` | Min reduction | `keepdims` |47| `ct.sum(tile, axis)` | Sum reduction | `keepdims` |48| `ct.exp2(x)` | Element-wise 2^x | — |49| `ct.log2(x)` | Element-wise log2 | — |50| `ct.exp(x)` | Element-wise exp | — |51| `ct.pow(base, exp)` | Element-wise power | — |52| `ct.reshape(tile, shape)` | Tile reshape | — |53| `ct.cast(tile, dtype)` | Type cast | — |54| `ct.maximum(a, b)` | Element-wise max | — |55| `tile + tile` | Element-wise add | — |56| `tile * tile` | Element-wise mul | — |57| `tile[:, None]` | Broadcasting | — |5859## Key Constraints60611. **No `ct.softmax()`** — implement manually with online running statistics622. **`ct.mma` requires compatible shapes** for tensor cores (M/N/K multiples of 16/8)633. **Latency hints are suggestions** — validated with profiling, not guaranteed644. **`ct.load` order param** — axis permutation for transposed loads (e.g., `'F'`, `(0, 2, 1)`)655. **ConstInt params** — tile sizes must be compile-time constants (passed as `ConstInt`)666. **Grid up to 3D** — `(x, y, z)` dimensions677. **No explicit intra-CTA control** — no thread IDs, warp IDs, warpgroup IDs, or in-block barrier choreography688. **No public cluster programming model** — `num_ctas` selects CGA size, but there is no exposed cluster rank/barrier/DSM API6970## Compiler Hints7172```python73@ct.kernel(num_ctas=2, occupancy=4)74def my_kernel(...):75```7677| Hint | Values | Purpose |78|------|--------|---------|79| `num_ctas` | 1, 2, 4, 8, 16 | CTAs per thread block cluster (Hopper+ CGA). Use as a cluster-size hint only; cuTile does not expose explicit cluster-local coordination APIs |80| `occupancy` | 1-32 | Steers register/resource allocation. Validate with profiling |81| `opt_level` | 0-3 | Optimization level (default: 3) |8283## Persistent Kernel Grid Formula8485When combining persistent kernels with CGA clusters, account for both:8687```python88# num_ctas affects available cluster count (not specific to persistent kernels)89grid_size = min(NUM_SMS // num_ctas, num_tiles) * occupancy9091@ct.kernel(num_ctas=num_ctas_val)92def persistent_kernel(...):93 bid = ct.bid(0)94 num_programs = ct.num_blocks(0)95 for tile_id in range(bid, num_tiles, num_programs):96 # Process tile97```9899## Common Patterns100101### Online Softmax (FlashAttention-style)102103```python104m_i = ct.full((Bm,), -math.inf, dtype=ct.float32) # running max105l_i = ct.full((Bm,), 1.0, dtype=ct.float32) # running sum106acc = ct.zeros((Bm, D), dtype=ct.float32) # accumulator107108for kv_idx in range(0, T, Bn):109 q = ct.load(Q, ...)110 k = ct.load(K, ...)111 qk = ct.mma(q, k, ct.zeros((Bm, Bn), dtype=ct.float32))112 # Add positional scores if needed113 m_ij = ct.max(qk, axis=-1)114 m_new = ct.maximum(m_i, m_ij)115 alpha = ct.exp2(m_i - m_new)116 p = ct.exp2(qk - m_new[:, None])117 l_ij = ct.sum(p, axis=-1)118 l_i = l_i * alpha + l_ij119 acc = acc * alpha[:, None]120 v = ct.load(V, ...)121 acc = ct.mma(p, v, acc)122 m_i = m_new123124# Normalize125acc = acc / l_i[:, None]126```127128### Head Grouping (Bh > 1)129130```python131# Load query for Bh heads at once132q = ct.load(Qc, shape=(1, k, Bh, Bm), ...)133q = ct.reshape(q, (k, Bh * Bm))134# Z tile shared across all Bh heads (loaded ONCE per block)135z = ct.load(Z, shape=(1, Bn, k), ...)136qk = ct.mma(q, z, acc) # (Bh*Bm, Bn) — all heads at once137```138139### Split-KV Block Assignment140141```python142split_idx = ct.bid(0) # which split (latent or decompressed)143path_id = ct.bid(1) # batch * head_groups * query_tiles144is_latent = split_idx < C_o145if is_latent:146 start = split_idx * L_o147 # process latent segment148else:149 start = (split_idx - C_o) * L_n150 # process decompressed segment151```152153## Official Documentation Index154155Load these files on demand when you need detailed API information:156157| Need detail on... | Read this file |158|-------------------|----------------|159| `ct.load` params, order, latency | `docs/cutile-dsl/generated/cuda.tile.load.md` |160| `ct.store` details | `docs/cutile-dsl/generated/cuda.tile.store.md` |161| `ct.mma` shape requirements | `docs/cutile-dsl/generated/cuda.tile.mma.md` |162| `ct.gather` / `ct.scatter` | `docs/cutile-dsl/generated/cuda.tile.gather.md` |163| Performance hints | `docs/cutile-dsl/performance.md` |164| Execution model | `docs/cutile-dsl/execution.md` |165| Full API index | `docs/cutile-dsl/index.md` |166167## Example Kernels168169Study these for complete implementation patterns:170171| Kernel | Path | Pattern |172|--------|------|---------|173| FlashAttention | `src/mla_var3/kernel/cutile/mla/flash_attention/flash_attention/flash_attention.py` | Single kernel, online softmax |174| FlashMLA | `src/mla_var3/kernel/cutile/mla/flash_mla/flash_mla/flash_mla.py` | Single kernel, head grouping, latent space |175| MLA-var6+ v2 | `src/mla_var3/kernel/cutile/mla/mla_var6_plus/mla_var6_plus_v2/` | Pipeline, split-KV, block specialization |176| TileGym ops | `third_party/tilegym/src/tilegym/ops/cutile/` | Matmuls, attention variants, activations |177178## Detailed References179180- **API deep-dives**: See [references/api-index.md](references/api-index.md) for organized links to official cuTile documentation sections181- **Scheduling boundary evidence**: See `docs/cutile-dsl/execution.md` and `docs/cutile-dsl/performance.md` for the public model, and `docs/kernels/flash-mla.md` for the concrete FlashMLA consequence when the kernel is pinned to one CTA per SM