FlyDSL (Flexible Layout Python DSL) is a Python DSL and MLIR compiler stack for authoring high-performance AMD GPU kernels with explicit layout algebra, tiling, copy atoms, and MMA atoms. The stack targets ROCm/HIP through the Fly and FlyROCDL dialects, lowering to ROCDL/HSACO.
These guidelines reduce common LLM coding mistakes. They bias toward caution over speed; use judgment for trivial tasks.
- Do not assume silently. State assumptions when they affect the implementation.
- If multiple interpretations exist, present them instead of picking one without explanation.
- If a simpler approach exists, say so and push back when the requested path seems overbuilt.
- If something is unclear, stop, name the confusion, and ask.
- Write the minimum code that solves the requested problem.
- Do not add features, abstractions, configurability, or fallback behavior that was not requested.
- Avoid abstractions for single-use code. If a solution grows far larger than necessary, simplify it.
- Prefer invariants and direct control flow over speculative defensive code.
- Touch only what the task requires. Do not refactor or reformat adjacent code opportunistically.
- Match existing style even when a different style might be preferable.
- If unrelated dead code or cleanup is noticed, mention it rather than deleting it.
- Remove imports, variables, or helpers made unused by your own changes, but do not remove pre-existing dead code unless asked.
- Every changed line should trace directly to the user's request.
- Turn tasks into verifiable goals before implementation.
- For bug fixes, reproduce or identify the failing behavior, then verify the fix.
- For refactors, preserve behavior and run focused before/after checks when practical.
- For multi-step tasks, state a brief plan with the verification for each step.
- Keep looping until the stated success criteria are met or a real blocker is surfaced.
FlyDSL/
├── .claude/skills/ # Project-local Claude Code skills (kernel authoring, profiling, build); git-tracked
├── python/
│ ├── flydsl/ # Python DSL core
│ │ ├── expr/ # DSL expression API; direct children are TARGET-NEUTRAL (typing, primitive, gpu, derived, struct, numeric, math, vector, arith, meta, extern; + utils/)
│ │ │ └── rocdl/ # Target-specific ROCDL package (cdna4, cluster, inline_asm, tdm_ops, universal); lazy-loaded via __init__'s _LAZY_MODULES
│ │ ├── compiler/ # @flyc.kernel / @flyc.jit, AST rewriting, JIT cache, backends
│ │ ├── runtime/ # Device runtime and GPU arch detection
│ │ ├── utils/ # EnvManager, SmemAllocator (legacy), logger
│ │ │ # newer kernels use SharedAllocator in expr/gpu.py
│ │ └── autotune.py # Autotuner (@autotune, Config)
│ └── mlir_flydsl/ # MLIR Python binding package source
├── include/flydsl/ # C++ TableGen headers for Fly / FlyROCDL dialects and passes
├── lib/ # C++ dialect implementation, conversions, runtime wrappers, Python bindings
│ └── Dialect/FlyROCDL/{CDNA3,CDNA4,GFX11,GFX1250}/ # Per-subtarget atom lowering: MmaAtom (MFMA on CDNA3/4, WMMA on GFX11/1250) + CopyAtom (Buffer/LDS, CDNA3/4 only)
├── tools/ # fly-opt
├── kernels/ # Production kernels, importable as kernels.*
├── tests/
│ ├── kernels/ # GPU correctness / benchmark harnesses
│ ├── unit/ # Python compiler, runtime, and layout tests
│ ├── system/ # Cross-cutting compile/system tests
│ ├── mlir/ # FileCheck tests driven by scripts/run_tests.sh
│ └── python/examples/ # AOT compile/cache pytest tests (aot_example.py)
├── examples/ # 01-vectorAdd, 02-tiledCopy, 03-tiledMma, 04-preshuffle_gemm
├── scripts/ # build, test, benchmark, wheel, debug helper scripts
├── docs/ # Sphinx documentation source
├── thirdparty/ # Vendored dlpack and tvm-ffi
└── build-fly/ # Generated build output; do not edit
| Topic | Doc | Notes |
|---|---|---|
| Architecture & compiler pipeline | docs/architecture_guide.md |
Project structure, AST tracing, MLIR pass pipeline, JIT/runtime |
| Layout algebra | docs/layout_system_guide.md |
Shape/Stride/Layout/Coord APIs, products, divides, coordinate mapping |
| CuTe layout reference | docs/cute_layout_algebra_guide.md |
Mathematical background and FlyDSL mapping of CuTe concepts |
| Kernel authoring | docs/kernel_authoring_guide.md |
@flyc.kernel, @flyc.jit, launch config, LDS, tiled copy/MMA |
| Pre-built kernels | docs/prebuilt_kernels_guide.md |
Norm, Softmax, GEMM, MoE, attention, dtype/config notes |
| External bitcode integration | docs/extern_integration_guide.md |
ffi + link_extern: plug pre-compiled LLVM bitcode into the JIT pipeline (python/flydsl/expr/extern.py, compiler/extern_link.py) |
| Testing & benchmarking | docs/testing_benchmarking_guide.md |
Test categories, benchmark harness, performance comparisons |
| Test tiering and env vars | tests/README.md |
L0/L1a/L1b/L2 markers, FileCheck flow, canonical env variable names |
Public docs are deployed from .github/workflows/docs.yml to
https://rocm.github.io/FlyDSL.
bash scripts/build_llvm.sh -j64 # Build LLVM/MLIR once
bash scripts/build.sh -j64 # Build FlyDSL C++ + Python bindings
pip install -e . # Editable Python install
# If not relying on editable install paths:
export PYTHONPATH="${PWD}/build-fly/python_packages:${PWD}:${PYTHONPATH}"
export LD_LIBRARY_PATH="${PWD}/build-fly/python_packages/flydsl/_mlir/_mlir_libs:${LD_LIBRARY_PATH}"
bash scripts/run_tests.sh # Pytest + examples + MLIR FileCheck
RUN_TESTS_FULL=1 bash scripts/run_tests.sh # Include large_shape tests; this is the CI invocation (flydsl.yaml, test-whl.yaml)
bash scripts/run_benchmark.sh # Performance benchmarks (all ops)
bash scripts/run_benchmark.sh --only softmax,moe # subset; --list to enumerate ops
bash scripts/run_benchmark.sh --output_csv /tmp/bench.csv # emit CSV for diffing
python3 scripts/compare_benchmark.py base.csv cur.csv # ratio report vs a baselineUseful direct commands:
python3 -m pytest tests/kernels/ tests/unit/ tests/system/ tests/python/examples/ -m "not large_shape" -v
python3 -m pytest tests/kernels/test_pa.py -v
FLYDSL_DUMP_IR=1 FLYDSL_RUNTIME_ENABLE_CACHE=0 python3 -m pytest tests/kernels/test_pa.py -vscripts/run_tests.sh auto-selects the GPU with the most free VRAM when
HIP_VISIBLE_DEVICES is unset and sets FLYDSL_RUN_QUANT=1.
Reproduce the CI Python style gate locally before pushing:
bash scripts/check_python_style.sh # black + ruff over committed diff vs origin/main
bash scripts/check_python_style.sh --fix # auto-format the changed files
bash scripts/check_python_style.sh --fix --include-local # also staged/untrackedThis wraps .github/scripts/check_python_style.sh, the same checker run by the
Check Python Code Style job in .github/workflows/pre-checks.yaml.
Use names from python/flydsl/utils/env.py; do not introduce alternate spellings.
| Purpose | Variable |
|---|---|
| Compile backend | FLYDSL_COMPILE_BACKEND (default rocm) |
| Override compile arch | ARCH |
| Compile without execution | COMPILE_ONLY |
| JIT cache directory | FLYDSL_RUNTIME_CACHE_DIR |
| Enable/disable JIT disk cache | FLYDSL_RUNTIME_ENABLE_CACHE (0 / false disables disk cache; in-memory cache remains) |
| AOT-cache-only execution | FLYDSL_RUNTIME_RUN_ONLY (1 skips JIT; loads disk cache only, raises on cache miss; incompatible with FLYDSL_DUMP_IR=1) |
| External LLVM/MLIR codegen | FLYDSL_COMPILE_LLVM_DIR (install prefix; enables external-binary final codegen, part of the JIT cache key) |
| IR dumps | FLYDSL_DUMP_IR, FLYDSL_DUMP_DIR |
| Runtime kind | FLYDSL_RUNTIME_KIND |
| GPU arch hints | FLYDSL_GPU_ARCH, HSA_OVERRIDE_GFX_VERSION |
| Debug info / pass diagnostics | FLYDSL_DEBUG_ENABLE_DEBUG_INFO, FLYDSL_DEBUG_PRINT_AFTER_ALL, FLYDSL_DEBUG_AST_DIFF |
The JIT disk cache normally invalidates on kernel source and closure changes. Disable it when debugging stale artifacts, changing C++ passes, or changing helper code that is not part of the traced closure.
- Python: black line length 120; ruff checks
E,W,F, andI. Config lives inpyproject.toml. - Imports: isort treats
flydslas first-party. - C++: LLVM style,
ColumnLimit: 100in.clang-format; C++17 via top-levelCMakeLists.txt. - CI style gate:
.github/workflows/pre-checks.yamlruns a Python check (black + ruff,.github/scripts/check_python_style.sh) and a C++ check (clang-format-18,.github/scripts/check_cpp_style.sh) on every PR. Reproduce the Python gate locally withscripts/check_python_style.sh. - Generated output: never edit
build-fly/python_packages/, generated_mlirbindings, or other build outputs directly. - Third-party code: avoid touching
thirdparty/unless the task explicitly requires it.
| Arch | Chips | Wave size | MMA path | Notes |
|---|---|---|---|---|
gfx942 |
MI300X / MI308X | 64 | MFMA | CDNA3 baseline; preshuffle GEMM, PA decode, CDNA BufferCopy |
gfx950 / gfx95* |
MI350 / MI355X | 64 | MFMA | CDNA4 path; FP4, MFMA scale, wider LDS copy paths, 160KB LDS |
gfx11* |
RDNA3 / RDNA3.5 (Strix Halo, e.g. gfx1151) | 32 | WMMA | No MFMA; f16/bf16 (and i8/i4) WMMA GEMM; legacy v16-operand WMMA ABI; no native FP8 (kernels fail-fast); kernels/rdna3_f16_gemm.py. is_rdna_arch() returns True. |
gfx120* |
RDNA4 (gfx1201 = Radeon AI PRO R9700) | 32 | WMMA | RDNA path, wave32; new v8-operand WMMA ABI; native FP8. is_rdna_arch() returns True. |
gfx1250 |
MI450 | 32 | WMMA / TDM | FP8/FP4 GEMM, MoE, async/TDM copy helpers, 320KB LDS. NOTE: is_rdna_arch('gfx1250') returns False and get_warp_size returns 64 — the gfx1250 kernels hardcode WAVE_SIZE = 32 themselves. |
Use from flydsl.runtime.device import get_rocm_arch, is_rdna_arch rather than
hard-coding behavior when possible. is_rdna_arch()
(python/flydsl/runtime/device.py) is the single source of truth for CDNA vs
RDNA and is wave32-true only for gfx10*/gfx11*/gfx120* prefixes; it does
not match gfx1250. Shared wave-size logic is get_warp_size(arch) in
kernels/kernels_common.py (32 if is_rdna_arch(arch) else 64), so it returns
64 for gfx1250 — gfx1250 kernels set wave32 explicitly.
tests/kernels/test_rdna_gemm.py shows the gfx11* (v16 ABI) vs gfx120* (v8 ABI)
kernel-selection pattern.
This is routing guidance, not a complete kernel inventory. Search the current kernels/ tree before edits; keep user-facing catalogs in docs/prebuilt_kernels_guide.md.
- Attention: start paged-decode changes in
kernels/pa_decode_fp8.pyandtests/kernels/test_pa.py; sliding-window is a backend mode imported bypa_decode_fp8.py, not a separate public entry point. - GEMM / MoE: choose by architecture and dtype first (CDNA MFMA, RDNA wave32 WMMA, gfx1250 TDM/WMMA/MX-scale).
tests/kernels/test_rdna_gemm.pyshows gfx11* vs gfx120* dispatch; reuse topical*_common.py/*_utils.pyhelpers. - Other kernels: keep regression tests close to touched
kernels/modules; preserve host-shim vs device-kernel boundaries for multi-GPU communication (custom_all_reduce.pywraps the lower-level implementation). - New families: add focused
tests/kernels/test_*.pycoverage, updatedocs/prebuilt_kernels_guide.mdfor public APIs, and only add durable routing rules here when they prevent likely agent mistakes.
- Prefer the layout API for new kernels:
fx.rocdl.make_buffer_tensor()plus logical layout operations andfx.copy_atom_call. Rawbuffer_ops.create_buffer_resource()/ manual byte offsets are legacy. - Use
@flyc.kernelfor device kernels and@flyc.jitfor launch wrappers; kernel modules are normally imported fromkernels.*. - Use
range_constexprfor compile-time unrolled Python loops. Userange(start, stop, step, init=[...])forscf.forloops with loop-carried values. - Keep
scf.forstate explicit and compact. ClearSmemPtr._view_cache = Noneafter exitingscf.forwhen shared-memory views are recreated, to avoid MLIR dominance issues. - Allocate shared memory with
SharedAllocator(flydsl.expr.gpu, reached asfx.SharedAllocator) over a@fx.structstorage layout for new kernels. The typicalstatic=True(default) mode emits a per-leaf static LDS global and the compiler sizes it, solaunch(smem=...)is left unset. Only thestatic=False(dynamic) mode makes the launch wrapper auto-infer smem fromSharedAllocator.allocated_byteswhensmem=None; an explicitsmemmust be >= that size. The legacyutils.smem_allocator.SmemAllocator/SmemPtrpath remains for un-migrated kernels (PR #506 added SharedAllocator; PR #541 migrated norm/softmax/fp8-gemm). - Do not define a value only inside an
if/elsebranch and use it after the branch. Hoist the value or return a single explicit merged value. - Nested helpers inside
@flyc.kernel/@flyc.jitmay read captured values, but should not mutate captured outer variables. Pass values explicitly and return updated state. - Avoid early
returnand branch-localreturn/yieldin traced functions. Keep a single explicit exit path so MLIR result types stay well-defined. - Prefer arch-specific helper modules and constants over inline scattered
gfx*conditionals. - Helper placement. Do not scatter small helpers across unrelated modules and do not duplicate an existing one; search for and reuse an existing helper first. Shared kernel helpers belong in
kernels/kernels_common.py(wave size viaget_warp_size,dtype_to_elem_type,validate_moe_dtypes, the_if_thenSCF context manager, LLVM-ptr/stream helpers); domain-specific shared helpers go in the existing topical modules (kernels/moe_common.py,layout_utils.py,pipeline_utils.py,fp8_gemm_utils.py,dpp_utils.py,mfma_epilogues.py,mfma_preshuffle_pipeline.py). DSL-level numeric/arith and type helpers belong inpython/flydsl/expr/utils/arith.py/python/flydsl/expr/numeric.py; compiler/runtime-wide utilities (env, logger, smem allocator) inpython/flydsl/utils/. (PR #388 extracted shared_if_then/validate_moe_dtypesintokernels_common.py; PR #448 removed redundant numeric wrappers in favor of existingfx.*type methods.) expr/is target-neutral. The direct child modules ofpython/flydsl/expr/(typing,primitive,gpu,derived,struct,arith,math,vector,numeric,meta,extern,utils/) must stay backend-agnostic: they may not import ROCDL/HIP bindings (flydsl._mlir.dialects.rocdl,_mlirDialectsFlyROCDL,fly_rocdl).import flydsl.exprmust succeed without the FlyROCDL bindings;tests/unit/test_expr_optional_rocdl.pyenforces this in CI. New target-specific (ROCDL/HIP, MFMA/WMMA, buffer/TDM/cluster) expr code goes in theexpr/rocdl/package (cdna4,cluster,inline_asm,tdm_ops,universal), never in a new top-levelexpr/*.py. The target-specific modulesbuffer_ops,rocdl, andtdm_opsare lazy-loaded fromexpr/__init__.pyvia__getattr__(_LAZY_MODULES); add new backend modules to that lazy map rather than eager-importing them (PR #521).expr/rocdlis a package.expr/rocdl/(__init__.py+cluster.py,tdm_ops.py,cdna4.py,universal.py,inline_asm.py) holds all target-specific ROCDL/MFMA/WMMA/buffer/TDM/cluster code.from flydsl.expr import rocdlandflydsl.expr.rocdlbind toexpr/rocdl/__init__.py. Import submodules explicitly, e.g.from flydsl.expr.rocdl import cluster;flydsl.expr.tdm_opsis a lazy alias forflydsl.expr.rocdl.tdm_ops(seeexpr/__init__.py_LAZY_MODULES).
tests/kernels/*.pyare generallyl2_device+rocm_lowerand require GPU execution.tests/unit/*mixes backend-agnostic, compile-tier, and device-tier tests; check markers before broad edits.tests/mlir/**/*.mlirare FileCheck tests run byscripts/run_tests.sh, not pytest.tests/arch_compat.pyis the source of truth for examples/tests that are RDNA-compatible versus CDNA-only.- Pytest markers are registered in
tests/pytest.ini:large_shape, the tier markers (l0_backend_agnostic,l1a_compile_no_target_dialect,l1b_target_dialect,l2_device,rocm_lower), plusmulti_gpu(multi-GPU tests; auto-skipped when GPU count is insufficient) andbenchmark(long-running perf tests). - Multi-GPU coverage (
tests/kernels/test_flydsl_shmem.py,tests/kernels/test_allreduce.py) runs underpytest -m multi_gpu. The shmem regression skips below 2 GPUs; allreduce has a 4-GPU accuracy case that skips below 4 GPUs and 8-GPU accuracy/benchmark cases that skip below 8 GPUs. It is gated in CI to a label-triggered job (PR labelmulti-gpuor manual dispatch) on the 8-GPU runner matrix (linux-flydsl-mi325-8,linux-flydsl-mi355-8); it does not run in the defaultscripts/run_tests.shflow. - For paged-attention changes, start with
tests/kernels/test_pa.py; reference semantics live inreference_masked_attention()andtorch_mha_extend().