This guide covers the FlyDSL test infrastructure, how to run tests and benchmarks, how to write new tests, and how to measure and compare performance.
| Category | Location | Requires GPU | Description |
|---|---|---|---|
| MLIR lit tests | tests/mlir/LayoutAlgebra/, tests/mlir/Conversion/, tests/mlir/Transforms/ |
No | Verify Fly dialect lowering |
| Python tests | tests/python/examples/ |
Varies | Python integration and JIT-cache examples |
| GPU kernel tests | tests/kernels/test_*.py |
Yes | Full compilation → GPU execution |
| Cache pre-warming examples | tests/python/examples/ |
Varies | Populate the normal JIT cache before workload execution |
Run GEMM tests:
bash scripts/run_tests.shRun benchmarks:
bash scripts/run_benchmark.shMLIR-based tests organized by category, verified using the fly-opt tool. These tests confirm that Fly dialect operations lower correctly to standard MLIR dialects without requiring a GPU.
Directories:
| Directory | Tests | Description |
|---|---|---|
LayoutAlgebra/ |
coalesce.mlir, composition.mlir, construction.mlir, coordinate.mlir, divide.mlir, int_tuple.mlir, product.mlir, size_cosize.mlir |
Layout algebra operations |
Conversion/ |
gpu_ops.mlir, memref_alloca.mlir, memref_ops.mlir, mma_atom.mlir, pointer_ops.mlir, type_conversion.mlir |
Dialect conversion passes |
Transforms/ |
canonicalize.mlir, layout_lowering.mlir |
Transformation passes |
Running individually:
# Build fly-opt first if needed
cmake --build build-fly --target fly-opt -j$(nproc)
# Run a single test
build-fly/bin/fly-opt --fly-canonicalize tests/mlir/LayoutAlgebra/construction.mlirEditor support (MLIR LSP): build-fly/bin/flydsl-lsp-server is a thin
MlirLspServerMain wrapper with Fly (+ backend) dialects registered. Point the
editor's MLIR LSP client / mlir-lsp-server path at that binary for .mlir
diagnostics, hover, go-to-definition, and completion. It is not a Python DSL /
kernel language server.
cmake --build build-fly --target flydsl-lsp-server -j$(nproc)Python-based integration tests, including an example that pre-warms the normal JIT cache. It does not create standalone linkable artifacts.
Running:
python tests/python/examples/aot_example.pyFull end-to-end tests that compile FlyDSL kernels, execute them on the GPU, and validate results against a PyTorch reference.
Files:
| Test file | Kernel | Description |
|---|---|---|
test_vec_add.py |
VecAdd | Vector addition (C = A + B) |
test_softmax.py |
Softmax | Row-wise softmax |
test_layernorm.py |
LayerNorm | Layer normalization |
test_rmsnorm.py |
RMSNorm | RMS normalization |
test_preshuffle_gemm.py |
GEMM | Preshuffle MFMA GEMM (fp8/int8/fp16/bf16) |
test_moe_gemm.py |
MoE GEMM | Mixture-of-Experts GEMM |
test_moe_reduce.py |
MoE Reduce | MoE reduction kernel |
test_pa.py |
Paged Attn | Paged attention decode |
test_quant.py |
Quantization | Quantization ops |
test_ref.py |
Reference | Reference implementations |
Running individually:
python tests/kernels/test_softmax.py
python tests/kernels/test_preshuffle_gemm.py --in_dtype fp8 -M 16 -N 5120 -K 8192The script retains its historical filename, but it exercises compile-only JIT cache population:
tests/python/examples/
└── aot_example.py # JIT cache pre-warming for preshuffle GEMM
Runs the full FlyDSL test suite:
bash scripts/run_tests.shFeatures:
- Auto-discovers build directory (
build-fly/) - Auto-selects the GPU with the most free VRAM when
HIP_VISIBLE_DEVICESis unset - Sets up
PYTHONPATHandLD_LIBRARY_PATH, and exportsFLYDSL_RUN_QUANT=1 - Runs
pytestovertests/kernels/,tests/language/,tests/unit/,tests/system/,tests/extension/, andtests/python/examples/ - Runs the standalone
examples/scripts and the MLIR FileCheck tests (tests/mlir/) - By default skips
large_shape-marked tests; setRUN_TESTS_FULL=1to include them.multi_gpuandbenchmarktests remain separate. - Skips the MLIR/FileCheck stage when FileCheck is unavailable unless
FLYDSL_REQUIRE_FILECHECK=1is set - Fail-fast: exits on the first failure
Environment setup:
PYTHONPATH="${BUILD_DIR}/python_packages:${REPO_ROOT}:${PYTHONPATH}"
LD_LIBRARY_PATH="${MLIR_LIBS_DIR}:${LD_LIBRARY_PATH}"Specialized benchmarking harness for performance characterization.
Selected default configurations:
The script is the source of truth for the complete matrix; these excerpts show the configuration format.
# Softmax/RMSNorm: "M,N,dtype"
SOFTMAX_SHAPES='32768,8192,bf16'
RMSNORM_SHAPES='32768,8192,bf16'
# Preshuffle GEMM: "dtype,M,N,K,tile_m,tile_n,tile_k"
GEMM_SHAPES='
fp8,16,40960,5120,16,128,256
fp8,16,77824,5120,16,128,256
fp8,5120,5120,8320,64,256,128
fp8,9728,8192,8320,64,256,128
int8,9728,8192,8320,64,256,128
bf16,5120,5120,8320,64,256,128
'
# FP4 GEMM (gfx950 only): "M,N,K"
GEMM_FP4_SHAPES='8192,8192,8192'Selective execution:
bash scripts/run_benchmark.sh # default: all benchmark groups
bash scripts/run_benchmark.sh softmax # only softmax
bash scripts/run_benchmark.sh gemm moe # GEMM and MoE
bash scripts/run_benchmark.sh --only softmax,rmsnorm
bash scripts/run_benchmark.sh --list # list available opsOutput format: Tabular with TB/s and TFLOPS columns:
op shape dtype TB/s TFLOPS
-------------- ---------------------------------- ---------- ---------- ----------
gemm 16x40960x5120 fp8 1.234 56.789
Logs: Written to ${BENCH_LOG_DIR:-/tmp/flydsl_bench}/
Pytest configuration with MLIR context fixtures for the Fly dialect.
Fixtures:
@pytest.fixture
def ctx():
"""Fresh MLIR context per test with dialects registered."""
# Creates Context, yields object with: ctx.context, ctx.module, ctx.location
@pytest.fixture
def module(ctx):
"""Provides ctx.module."""
@pytest.fixture
def insert_point(ctx):
"""Sets insertion point to module body."""Build discovery: The configuration supports multiple build layouts:
build-fly/python_packages(preferred)build/python_packages/flydsl(fallback)
Session hook: Prevents pytest exit code 5 (no tests collected) from being treated as failure.
Core performance testing utilities (adapted from AIter).
perftest() decorator:
@perftest(num_iters=20, num_warmup=3, testGraph=False, num_rotate_args=0)
def my_kernel_test(Input, Output):
# Kernel invocation
...Features:
- Device memory profiling to determine rotation count
- Torch CUDA event timing
- HIPGraph capture mode (
testGraph=True) - Cache-aware iteration calculation
checkAllclose() function:
checkAllclose(output, reference, rtol=1e-2, atol=1e-2, tol_err_ratio=0.05)Returns a mismatch ratio in [0, 1] (0 = pass).
verify_output() function:
verify_output(c_out, c_ref, atol=1e-2, rtol=1e-2, msg='')High-level validation wrapper around checkAllclose.
Shared benchmark harness for performance comparison.
Key functions:
# Measure device time (torch CUDA events)
gpu_us = bench_gpu_us_torch(fn, warmup=20, iters=200)from tests.utils import pertoken_quant, shuffle_weight
# Per-token quantization (handles NaN/Inf)
quantized, scales = pertoken_quant(tensor, dtype=torch.float8_e4m3fnuz)
# Weight preshuffle for MFMA (layout 16x16)
shuffled = shuffle_weight(weight, layout=(16, 16))# tests/python/test_my_feature.py
import flydsl.expr as fx
from flydsl.expr.typing import T
def test_my_layout_op(ctx, insert_point):
shape = fx.make_shape(4, 8)
stride = fx.make_stride(8, 1)
layout = fx.make_layout(shape, stride)
result = fx.size(layout)
ir_str = str(ctx.module)
assert "fly.make_layout" in ir_str# tests/kernels/test_my_kernel.py
import torch
import flydsl.compiler as flyc
import flydsl.expr as fx
from flydsl.expr import gpu
from tests.test_common import checkAllclose
@flyc.kernel
def my_kernel(A: fx.Tensor, B: fx.Tensor, N: fx.Constexpr[int]):
tid = gpu.thread_idx.x
bid = gpu.block_idx.x
# ... kernel body ...
@flyc.jit
def launch(A: fx.Tensor, B: fx.Tensor, N: fx.Constexpr[int],
stream: fx.Stream = fx.Stream(None)):
my_kernel(A, B, N).launch(grid=(N // 256,), block=(256,), stream=stream)
def test_my_kernel():
N = 1024
A = torch.randn(N, device="cuda", dtype=torch.float32)
B = torch.empty(N, device="cuda", dtype=torch.float32)
launch(A, B, N)
# Reference
ref = A # or some computation
# Validate
err = checkAllclose(B, ref, rtol=1e-2, atol=1e-2)
assert err == 0, f"Mismatch: {err * 100:.2f}%"from tests.kernels.benchmark_common import bench_gpu_us_torch
def benchmark_my_kernel():
# Setup
launch_fn = compile_my_kernel(...)
def run():
launch_fn(input_tensor, output_tensor)
# Measure
gpu_us = bench_gpu_us_torch(run, warmup=20, iters=200)
# Compute metrics
total_bytes = 2 * M * N * elem_size
bandwidth_tbs = total_bytes / (gpu_us * 1e-6) / 1e12
print(f"Time: {gpu_us:.1f} us, Bandwidth: {bandwidth_tbs:.2f} TB/s")The test_preshuffle_gemm.py test supports extensive CLI configuration:
python tests/kernels/test_preshuffle_gemm.py \
--in_dtype fp8 \
-M 16 -N 5120 -K 8192 \
--tile_m 16 --tile_n 128 --tile_k 256 \
--lds_stage 2 \
--num_iters 20 \
--num_warmup 3 \
--no_aiter_bench \
--test_graph # or -tg for HIPGraph mode
# FP4 weight path (gfx950 only)
python tests/kernels/test_preshuffle_gemm.py \
--in_dtype fp4 --wfp4 \
-M 8192 -N 8192 -K 8192| Variable | Used by | Description |
|---|---|---|
ROCDSL_SOFTMAX_SHAPES |
test_softmax.py |
Override softmax test shapes ("M,N,dtype;...") |
ROCDSL_LAYERNORM_SHAPES |
test_layernorm.py |
Override layernorm test shapes |
FLYDSL_DUMP_IR |
Compiler | Dump intermediate IR at each pipeline stage |
FLYDSL_DUMP_DIR |
Compiler | IR dump directory (default: ~/.flydsl/debug) |
FLYDSL_RUNTIME_CACHE_DIR |
Compiler | Cache directory (default: ~/.flydsl/cache) |
RUN_TESTS_FULL |
run_tests.sh |
Set to 1 to include large_shape cases; multi-GPU and benchmark tests remain separate |
BENCH_LOG_DIR |
run_benchmark.sh |
Benchmark log directory (default: /tmp/flydsl_bench) |
FLYDSL_DUMP_IR=1 FLYDSL_DUMP_DIR=./dumps python my_test.pyProduces numbered .mlir files per pipeline stage plus final_isa.s.
bash scripts/dumpir.sh python my_kernel.pyTo try "what if these few instructions were different" without rebuilding the compiler, edit the dumped ISA and run
the unmodified test against it with tools/inject_asm/inject_asm.py:
python3 tools/inject_asm/inject_asm.py prepare ./ws -- python -m pytest tests/kernels/test_my_kernel.py -k one_case
vim ./ws/edited.s
python3 tools/inject_asm/inject_asm.py run ./ws # the edited assembly
python3 tools/inject_asm/inject_asm.py run ./ws --baseline # the unedited dump, under the same conditionsprepare runs the command twice into a fresh workspace: once with FLYDSL_DUMP_IR for the ISA, and once with a
scratch FLYDSL_RUNTIME_CACHE_DIR for the compiled kernel, since a dump run leaves the cache alone. It copies the
dump to original.s (read-only) and edited.s. run assembles edited.s, swaps it into the cache entry's
gpu.binary, and reruns the command with FLYDSL_RUNTIME_RUN_ONLY=1, so a cache miss is an error rather than a
silent recompile. This works because a cache entry stores the compiled module as MLIR text and rebuilds its
ExecutionEngine from it on load.
The command must compile exactly one JIT function in one specialization; narrow it with -k otherwise. Constexpr
specializations of a kernel share its symbol, so the dump keeps only the last one compiled and their cache entries
cannot be told apart. Keep every kernel symbol unchanged in edited.s, since the host launches kernels by name.
FlyDSL assembles with the LLVM it was built with, while the tool defaults to the ROCm clang, which is usually older;
both link with the ROCm ld.lld. prepare checks that original.s reassembles to the compiler's code object byte for
byte, which holds on gfx942 and gfx950. Dumps for gfx11 and later use directives the ROCm clang cannot read, so for
those pass prepare --clang with the clang of the LLVM FlyDSL was built with; run reuses it.
inject_asm.py inject CACHE_DIR FILE.s is the single patching step on its own, for a cache you populated yourself.
--check compares instead of patching and --save-hsaco also writes the code object next to the .s. It refuses
any directory that contains or lies inside the default cache ~/.flydsl/cache, where a patched entry would be served
to every later run.
| File | Description |
|---|---|
scripts/run_tests.sh |
Full test runner (pytest + examples + FileCheck) |
scripts/run_benchmark.sh |
Benchmark harness with configurable shapes |
scripts/dumpir.sh |
IR dump helper script |
tools/inject_asm/inject_asm.py |
Run a test against a hand-edited ISA dump (debug only) |
tests/conftest.py |
Pytest fixtures (MLIR context, module, insert point) |
tests/test_common.py |
perftest(), checkAllclose(), verify_output() |
tests/utils.py |
pertoken_quant(), shuffle_weight() |
tests/kernels/benchmark_common.py |
bench_gpu_us_torch(), benchmark harness |
tests/mlir/LayoutAlgebra/, tests/mlir/Conversion/, tests/mlir/Transforms/ |
MLIR lit tests |
tests/python/examples/ |
Python integration and cache pre-warming examples |
tests/kernels/test_*.py |
GPU kernel test modules |
tests/python/examples/aot_example.py |
Compile-only JIT cache pre-warming example |