tvm_tirx.backend.cuda.lang.ClusterPersistentScheduler2D#
- class tvm_tirx.backend.cuda.lang.ClusterPersistentScheduler2D(*args, **kwargs)#
Bases:
BaseTileSchedulerTile scheduler for cluster-based persistent kernels.
Distributes a 2D tile grid across persistent clusters using group-major ordering for L2 cache locality. Each cluster starts at its cluster_id and strides by num_clusters to process tiles.
Tile Ordering (group-major for L2 locality):
Tiles are grouped into “L2 groups” of l2_group_size rows
Within a group, tiles are visited in column-major order within the group
Groups are processed in row-major order
Example with 4x4 tiles, l2_group_size=2:
Group 0 (rows 0-1): 0 2 4 6 1 3 5 7 Group 1 (rows 2-3): 8 10 12 14 9 11 13 15
Serpentine Mode (serpentine=True):
Uses CUTLASS-style 2D block swizzle with serpentine traversal
Grid is divided into swizzle_size x swizzle_size blocks
Within each block, tiles are visited in row-major order
Blocks are traversed in serpentine order (even block-rows forward, odd backward)
This provides better L2 locality by reusing both A and B tiles
Example with 4x4 tiles, swizzle_size=2, serpentine=True:
Block layout: Block(0,0) Block(0,1) Block(1,0) Block(1,1) Tile numbering with serpentine: n=0 n=1 n=2 n=3 m=0 0 1 14 15 m=1 2 3 12 13 m=2 4 5 10 11 m=3 6 7 8 9 Traversal: Block(0,0) -> Block(1,0) -> Block(1,1) -> Block(0,1) (serpentine: down in col 0, then up in col 1)
- Parameters:
prefix (str) – Prefix for TIR variable names
num_m_tiles (int | T.ExprLike) – Total number of tiles in M dimension (can be runtime expression)
num_n_tiles (int) – Total number of tiles in N dimension
num_clusters (int) – Number of persistent clusters (determines stride)
l2_group_size (int) – Number of M-tile rows per L2 locality group (default: 8) When serpentine=True, this is used as swizzle_size for 2D blocks
cluster_m (int) – Cluster dimension in M for hierarchical scheduling (default: 1)
cluster_n (int) – Cluster dimension in N for hierarchical scheduling (default: 1)
serpentine (bool) – If True, use CUTLASS-style 2D block swizzle with serpentine traversal (default: False)
- Variables:
m_idx (T.local_scalar) – Current M tile index (output)
n_idx (T.local_scalar) – Current N tile index (output)
work_idx (T.local_scalar) – Global work item index for this cluster
tile_count (T.local_scalar) – Number of tiles processed by this cluster so far
Usage
scheduler = ClusterPersistentScheduler2D( "sched", num_m_tiles=M_TILES, num_n_tiles=N_TILES, num_clusters=NUM_CLUSTERS, l2_group_size=8 ) scheduler.init(cluster_id) # cluster_id = cta_idx // CLUSTER_SIZE while scheduler.valid(): m = T.meta_var(scheduler.m_idx) # current M tile n = T.meta_var(scheduler.n_idx) # current N tile # ... process tile (m, n) ... scheduler.next_tile()
Examples
Example 1: Basic persistent kernel
num_m_tiles=4, num_n_tiles=4, num_clusters=3, l2_group_size=2 cluster_m=1, cluster_n=1 (default, no tile subdivision) Group-major tile numbering (l2_group_size=2): n=0 n=1 n=2 n=3 m=0 0 2 4 6 ┐ L2 group 0 m=1 1 3 5 7 ┘ m=2 8 10 12 14 ┐ L2 group 1 m=3 9 11 13 15 ┘ Work distribution (cluster starts at cluster_id, strides by num_clusters=3): cluster 0: work_idx 0,3,6,9,12,15 -> tiles 0,3,6,9,12,15 cluster 1: work_idx 1,4,7,10,13 -> tiles 1,4,7,10,13 cluster 2: work_idx 2,5,8,11,14 -> tiles 2,5,8,11,14 Tile grid (which cluster handles each tile): n=0 n=1 n=2 n=3 m=0 C0 C2 C1 C0 ┐ L2 group 0 m=1 C1 C0 C2 C1 ┘ m=2 C2 C1 C0 C2 ┐ L2 group 1 m=3 C0 C2 C1 C0 ┘ Tile sequence per cluster (in execution order): cluster 0: (0,0)->(1,1)->(0,3)->(2,0)->(2,3)->(3,3) cluster 1: (1,0)->(0,2)->(1,3)->(2,1)->(3,2) cluster 2: (0,1)->(1,2)->(2,0)->(3,1)->(2,3)Example 2: 2SM GEMM (typical B200 config)
M=1024, N=512, CTA_M=128, MMA_N=128, CLUSTER_M=2, CLUSTER_N=1 => M_TILES=8, N_TILES=4 => CLUSTER_M_TILES=4, CLUSTER_N_TILES=4 (scheduler at cluster granularity) Scheduler params: num_m_tiles=4, num_n_tiles=4, num_clusters=74, l2_group_size=8 cluster_m=1, cluster_n=1 Key: Scheduler outputs CLUSTER-level tiles. All CTAs in same cluster get SAME (m_idx, n_idx) from scheduler. CTAs differentiate via cluster_rank (computed OUTSIDE scheduler): cluster_rank = cta_idx % CLUSTER_SIZE cb_m = cluster_rank % CLUSTER_M # 0 or 1 for 2SM cb_n = cluster_rank // CLUSTER_M # 0 for 2SM Final CTA tile: cta_m = m_idx * CLUSTER_M + cb_m cta_n = n_idx * CLUSTER_N + cb_n Example: cluster 5 gets scheduler tile (1,2) CTA rank=0 (cb_m=0): actual tile (2,2) CTA rank=1 (cb_m=1): actual tile (3,2)- update_current_m_n_idx(work_idx)#
Convert global work index to (m_idx, n_idx) tile coordinates.
- init(cluster_id)#
Initialize scheduler for a given cluster.
- Parameters:
cluster_id (int) – The cluster’s index (typically cta_idx // CLUSTER_SIZE)
- next_tile()#
Advance to the next tile for this cluster.
- next_tile_stride(stride)#
Advance by a custom stride (for non-standard scheduling).
- Parameters:
stride (int)
- valid()#
Check if this cluster has more tiles to process.