Skip to main content

flydsl-tile-programming

Guided step-by-step wizard for producing a new FlyDSL GPU kernel from a requirement: classify the kernel type, pick a skeleton, fill in compute, add control flow / sync / LDS, then test on GPU. Use when the user wants to WRITE a new kernel, port a Triton kernel to FlyDSL, or learn tile programming by following a procedure. For API/layout-algebra lookups, per-op reference tables, and troubleshooting, use the flydsl-kernel-authoring skill instead.

インストールへ移動

ソース情報

リポジトリ
ROCm/FlyDSL
ソースの最終更新活動
2026年8月24日 06:42
検出された SKILL.md の言語
英語
スター
283
フォーク
120

インストール方法

デフォルトでは、最初にソースを確認する Prompt が選択されています。直接コマンドに切り替えるか、ローカルコピーをダウンロードすることもできます。

ソースファイルを確認

インストールを決める前に、SKILL.md と SkillsMP に表示されている付属ファイルをお読みください。

SKILL.md を表示中

SKILL.md
ソースの指示 · 読み取り専用プレビュー
name
flydsl-tile-programming
description
Guided step-by-step wizard for producing a new FlyDSL GPU kernel from a requirement: classify the kernel type, pick a skeleton, fill in compute, add control flow / sync / LDS, then test on GPU. Use when the user wants to WRITE a new kernel, port a Triton kernel to FlyDSL, or learn tile programming by following a procedure. For API/layout-algebra lookups, per-op reference tables, and troubleshooting, use the flydsl-kernel-authoring skill instead.
allowed-tools
Read Edit Bash Grep Glob Agent
# FlyDSL Tile Programming Guide users through writing GPU kernels using FlyDSL's tile programming model (CuTe-style layout algebra). This skill is a step-by-step wizard that takes a kernel requirement and produces a correct, tested FlyDSL kernel. **Trigger**: User wants to write a new FlyDSL kernel, port a Triton kernel to FlyDSL, or learn tile programming patterns. **Prerequisites**: FlyDSL installed (editable mode via `pip install -e .`). GPU access required for testing. **Scope (read this first)**: This skill is the *procedure* — follow the steps in order to produce a kernel. It is the companion to the **flydsl-kernel-authoring** skill, which is the *reference* (the full layout-algebra API surface, per-op tables, environment variables, and an exhaustive troubleshooting list). When you need to look something up rather than follow a step, go to flydsl-kernel-authoring. This wizard links there instead of duplicating those tables. --- ## Step 1: Classify the Kernel Type Ask the user what kind of kernel they need. Map to one of these patterns: | Pattern | Examples | Key Primitives | |---------|----------|---------------| | **Elementwise** | vecadd, scale, relu, abs | `logical_divide` + `fx.copy` | | **Reduction** | sum, max, softmax, layernorm | `make_buffer_tensor` + warp shuffle + LDS | | **Tiled Copy** | transpose, permute, gather | `zipped_divide` + `TiledCopy` | | **GEMM** | matmul, batched gemm | `TiledMma` + `TiledCopy` + LDS | | **Fused** | fused attention, GEMM+epilogue | Combine GEMM + elementwise | --- ## Step 2: Generate Kernel Skeleton Based on the pattern, generate the appropriate skeleton. Every FlyDSL kernel has two parts: ```python import torch import flydsl.compiler as flyc import flydsl.expr as fx @flyc.kernel def my_kernel(A: fx.Tensor, B: fx.Tensor, ...): tid = fx.thread_idx.x bid = fx.block_idx.x # ... kernel body ... @flyc.jit def my_launch(A: fx.Tensor, B: fx.Tensor, ..., stream: fx.Stream = fx.Stream(None)): my_kernel(A, B, ...).launch( grid=(grid_x, grid_y, grid_z), block=(block_x, 1, 1), stream=stream ) ``` ### Pattern A: Elementwise Kernel The simplest pattern. Each thread processes `VEC_WIDTH` elements independently. **Data flow**: Global -> Register -> Compute -> Register -> Global ```python import torch import flydsl.compiler as flyc import flydsl.expr as fx from flydsl.expr.typing import Vector as Vec BLOCK_DIM = 256 VEC_WIDTH = 4 @flyc.kernel def elementwise_kernel( A: fx.Tensor, Out: fx.Tensor, BLOCK_DIM: fx.Constexpr[int], VEC_WIDTH: fx.Constexpr[int], ): bid = fx.block_idx.x tid = fx.thread_idx.x # === Step 1: Divide global tensor into block-sized tiles === tile_size = BLOCK_DIM * VEC_WIDTH tA = fx.logical_divide(A, fx.make_layout(tile_size, 1)) tOut = fx.logical_divide(Out, fx.make_layout(tile_size, 1)) # === Step 2: Select this block's tile === tA = fx.slice(tA, (None, bid)) tOut = fx.slice(tOut, (None, bid)) # === Step 3: Divide tile for per-thread vectorized access === tA = fx.logical_divide(tA, fx.make_layout(VEC_WIDTH, 1)) tOut = fx.logical_divide(tOut, fx.make_layout(VEC_WIDTH, 1)) # === Step 4: Allocate register and set up copy atom === copy_bits = VEC_WIDTH * 32 copy_atom = fx.make_copy_atom(fx.UniversalCopy(copy_bits), fx.Float32) rA = fx.make_rmem_tensor(VEC_WIDTH, fx.Float32) rOut = fx.make_rmem_tensor(VEC_WIDTH, fx.Float32) # === Step 5: Load -> Compute -> Store === fx.copy(copy_atom, fx.slice(tA, (None, tid)), rA) vA = Vec(fx.memref_load_vec(rA)) # --- YOUR COMPUTE HERE --- vOut = vA * vA # example: square # --- END COMPUTE --- fx.memref_store_vec(vOut, rOut) fx.copy(copy_atom, rOut, fx.slice(tOut, (None, tid))) @flyc.jit def elementwise_launch( A: fx.Tensor, Out: fx.Tensor, N: fx.Int32, stream: fx.Stream = fx.Stream(None), ): tile_size = BLOCK_DIM * VEC_WIDTH grid_x = (N + tile_size - 1) // tile_size elementwise_kernel(A, Out, BLOCK_DIM, VEC_WIDTH).launch( grid=(grid_x, 1, 1), block=(BLOCK_DIM, 1, 1), stream=stream ) # === Test === N = 1024 A = torch.randn(N, dtype=torch.float32, device="cuda") Out = torch.empty(N, dtype=torch.float32, device="cuda") elementwise_launch(A, Out, N, stream=torch.cuda.Stream()) torch.cuda.synchronize() assert torch.allclose(Out, A * A, atol=1e-5) ``` ### Pattern B: Tiled 2D Copy (Transpose, Gather) Uses `zipped_divide` + `TiledCopy` for 2D data movement with explicit thread-value mapping. **Data flow**: Global[M,N] -> Fragment -> Global[M,N] (with layout change) ```python @flyc.kernel def tiled_copy_kernel(A: fx.Tensor, B: fx.Tensor): tid = fx.thread_idx.x bid = fx.block_idx.x block_m, block_n = 8, 24 # make_tile is varargs -- passing a list raises ValueError tile = fx.make_tile( fx.make_layout(block_m, 1), fx.make_layout(block_n, 1), ) # Wrap as buffer tensors (AMD buffer descriptors) A = fx.rocdl.make_buffer_tensor(A) B = fx.rocdl.make_buffer_tensor(B) # Divide into tiles, select block's tile bA = fx.zipped_divide(A, tile) bB = fx.zipped_divide(B, tile) bA = fx.slice(bA, (None, bid)) bB = fx.slice(bB, (None, bid)) # Thread-value layout: how threads cooperate on the tile thr_layout = fx.make_layout((4, 1), (1, 1)) # 4 threads along M val_layout = fx.make_layout((1, 8), (1, 1)) # each loads 8 along N copy_atom = fx.make_copy_atom(fx.rocdl.BufferCopy128b(), fx.Float32) # Build the TV layout with make_layout_tv -- do NOT hand-build it from # raked_product alone: make_layout_tv also derives the TV mapping via # composition(right_inverse(layout_mn), ...). See examples/02-tiledCopy.py. tile_mn, tv_layout = fx.make_layout_tv(thr_layout, val_layout) # Build tiled copy and get thread's partition tiled_copy = fx.make_tiled_copy(copy_atom, tv_layout, tile_mn) thr_copy = tiled_copy.get_slice(tid) src = thr_copy.partition_S(bA) dst = thr_copy.partition_D(bB) frag = fx.make_fragment_like(src) # Copy: global A -> frag -> global B fx.copy(copy_atom, src, frag) fx.copy(copy_atom, frag, dst) ``` ### Pattern C: Tiled MMA (GEMM) Uses `TiledMma` + `TiledCopy` for matrix multiply with AMD MFMA instructions. **Data flow**: Global -> (TiledCopy) -> Fragment A,B -> (MFMA) -> Fragment C -> Global ```python block_m, block_n, block_k = 64, 64, 8 @flyc.kernel def gemm_kernel(A: fx.Tensor, B: fx.Tensor, C: fx.Tensor): tid = fx.thread_idx.x bid = fx.block_idx.x # Define tiles tileA = fx.make_tile(block_m, block_k) tileB = fx.make_tile(block_n, block_k) tileC = fx.make_tile(block_m, block_n) # Wrap as buffer tensors A = fx.rocdl.make_buffer_tensor(A) B = fx.rocdl.make_buffer_tensor(B) C = fx.rocdl.make_buffer_tensor(C) # Divide and select block's tile bA = fx.slice(fx.zipped_divide(A, tileA), (None, bid)) bB = fx.slice(fx.zipped_divide(B, tileB), (None, bid)) bC = fx.slice(fx.zipped_divide(C, tileC), (None, bid)) # === MMA setup === # MFMA(M, N, K, ABType[, AccType]) -- 4th arg is the A/B operand type; # the accumulator defaults to f32. Here operand and acc coincide. mma_atom = fx.make_mma_atom(fx.rocdl.MFMA(16, 16, 4, fx.Float32)) # Tile the MMA atom across threads: 2x2 = 4 MMA atoms per warp tiled_mma = fx.make_tiled_mma( mma_atom, fx.make_layout((2, 2, 1), (1, 2, 0)) # (M_rep, N_rep, K_rep) ) thr_mma = tiled_mma.thr_slice(tid) # === Copy setup (matched to MMA layout) === copy_atom = fx.make_copy_atom(fx.rocdl.BufferCopy32b(), fx.Float32) tiled_copy_A = fx.make_tiled_copy_A(copy_atom, tiled_mma) tiled_copy_B = fx.make_tiled_copy_B(copy_atom, tiled_mma) tiled_copy_C = fx.make_tiled_copy_C(copy_atom, tiled_mma) thr_copy_A = tiled_copy_A.get_slice(tid) thr_copy_B = tiled_copy_B.get_slice(tid) thr_copy_C = tiled_copy_C.get_slice(tid) # === Partition data === # Copy partitions (for data movement) copy_src_A = thr_copy_A.partition_S(bA) copy_src_B = thr_copy_B.partition_S(bB) copy_dst_C = thr_copy_C.partition_S(bC) # MMA partitions (for compute) part_A = thr_mma.partition_A(bA) part_B = thr_mma.partition_B(bB) part_C = thr_mma.partition_C(bC) # === Allocate fragments (registers) === frag_A = thr_mma.make_fragment_A(part_A) frag_B = thr_mma.make_fragment_B(part_B) frag_C = thr_mma.make_fragment_C(part_C) # Retile fragments for copy compatibility copy_frag_A = thr_copy_A.retile(frag_A) copy_frag_B = thr_copy_B.retile(frag_B) copy_frag_C = thr_copy_C.retile(frag_C) # === Execute: Load A,B -> GEMM -> Store C === fx.copy(copy_atom, copy_src_A, copy_frag_A, pred=None) fx.copy(copy_atom, copy_src_B, copy_frag_B, pred=None) fx.gemm(mma_atom, frag_C, frag_A, frag_B, frag_C) fx.copy(copy_atom, copy_frag_C, copy_dst_C, pred=None) ``` ### Pattern D: Raw Buffer Load/Store (Low-level Escape Hatch) Direct AMD buffer intrinsics, bypassing the layout algebra. Reach for this only when the access has no layout form — typically a scalar base with a per-thread element offset. For anything with a tile structure, use Pattern A/B/C, which go through `make_buffer_tensor` + copy atoms and get an OOB-checked V# descriptor built for you. ```python from kernels.common import buffer_ops # moved out of flydsl.expr in #880 @flyc.kernel def buffer_kernel(A: fx.Tensor, B: fx.Tensor, N: fx.Constexpr[int]): tid = fx.thread_idx.x bid = fx.block_idx.x gid = bid * 256 + tid rsrc_a = buffer_ops.create_buffer_resource(A) rsrc_b = buffer_ops.create_buffer_resource(B) # offset is in ELEMENTS (not bytes!) -- buffer_load converts internally # fx.T.* are properties on Types -- reference them, do not call them data = buffer_ops.buffer_load(rsrc_a, gid * 4, vec_width=4, dtype=fx.T.f32) # ... compute on data ... buffer_ops.buffer_store(data, rsrc_b, gid * 4) ``` The element-vs-byte offset is a classic source of bugs; see the **kernel-code-cleanup** skill to migrate an existing kernel onto the layout API. --- ## Step 3: Fill in the Compute Logic Common compute recipes (all work on vectors): ```python from flydsl.expr.typing import Vector as Vec # Scale: C = A * scalar scale = Vec.filled(VEC_WIDTH, 2.0, fx.Float32) vC = Vec(vA) * scale # Add: C = A + B vC = Vec(vA) + Vec(vB) # FMA: D = A * B + C vC = Vec(vA) * Vec(vB) + Vec(vC) # ReLU: C = max(A, 0) zero = Vec.filled(VEC_WIDTH, 0.0, fx.Float32) vC = Vec(vA).maximumf(zero) # Abs: C = |A| v = Vec(vA) neg = -v is_neg = v < zero
GitHubで見る
この SKILL.md は非常に大きいため、SkillsMP では最初のセクションだけを表示しています。 GitHubで見る