The bridge runs CK-Tile microscaling GEMM with FP8 (OCP E4M3) or packed FP4
(E2M1) inputs, one E8M0 scale per 32 K elements, FP32 accumulation, and FP16
output. Layout is RCR: A is stored as [M, K], B as [N, K], and C as [M, N].
FP4 stores the even K element in the low nibble and the odd K element in the high
nibble, so its input storage is [M, K/2] and [N, K/2] bytes.
The bridge exposes all five pipeline choices in the native MX GEMM selector:
| Target | Pipeline | Native implementation | Epilogue |
|---|---|---|---|
| gfx950 | comp_async |
GemmPipelineAgBgCrCompAsync |
CShuffle |
| gfx950 | comp_async_eight_waves |
GemmPipelineAgBgCrCompAsyncEightWaves |
CShuffle |
| gfx950 | weight_preshuffle |
MXGemmPreshufflePipelineAGmemBGmemCRegV1 |
CShuffle |
| gfx1250 | comp_tdm |
GemmPipelineAgBgCrCompTDMV1 |
TDM |
| gfx1250 | comp_tdm_v2 |
GemmPipelineAgBgCrCompTDMV2 |
TDM |
| gfx1250 | comp_async |
GemmPipelineAgBgCrCompAsync |
CShuffle |
| gfx1250 | comp_async_eight_waves |
GemmPipelineAgBgCrCompAsyncEightWaves |
CShuffle |
| gfx1250 | weight_preshuffle |
MXGemmPreshufflePipelineAGmemBGmemCRegV1 |
CShuffle |
These are the five choices in the native MxGemmPipelineType selector.
CK-Tile also has a separate MXFlatmmKernel family, including
MXFlatmmPipelineAGmemBGmemCRegV1 and
WeightPreshufflePipelineAGmemBGmemCRegTDM. Those MXFlatMM paths are not
currently exposed by the old Tile Engine or this bridge.
All use intrawave scheduling. The default warp tile is 16 × 16 × 128;
gfx1250 TDM V1/V2 also expose 32 × 32 × 128 with either input type and
32 × 16 × 128 for FP4. Set warp_tile_m=32 and warp_tile_n=32 or 16
in MxGemmKernelConfig, or in a custom Tile Engine JSON configuration.
gfx950 and the three CShuffle pipelines retain the 16 × 16 × 128 warp tile.
The default pipeline remains
comp_async on gfx950 and comp_tdm on gfx1250. Select another pipeline with
default_fp8_config("gfx950", pipeline="weight_preshuffle"),
default_fp4_config("gfx950", pipeline="comp_async_eight_waves"), or
default_fp8_config("gfx1250", pipeline="comp_async"). These helpers choose
compatible block tiles and warp counts for the selected pipeline.
Eight-wave async requires 4 × 2 × 1 warps and M/N block tiles divisible by
128, with a valid CShuffle distribution. Weight preshuffle requires
1 × 4 × 1 warps and block tiles divisible by 32 × 128 × 256; FP4 also
requires block N divisible by 512. The host reshuffles B for weight preshuffle,
so callers supply the same packed RCR input buffers for every pipeline.
On gfx1250, weight preshuffle requires problem N divisible by 16; the host
rejects other N sizes before reshuffling. Async and TDM require 2 × 2 × 1
warps on gfx1250.
The generator and Python configuration validation apply the same architecture,
tile-distribution, and LDS limits.
The gfx1250 path uses WMMA without cluster launch and supports revision 0. M and N may include partial tiles subject to the weight-preshuffle N alignment; the host pads their scale buffers before reshuffling. K must be divisible by both 128 and the selected block tile K. Split-K, persistent execution, and K padding are not supported by this bridge's gfx1250 path.
The FP8 32 × 32 warp tile is implemented by four 16 × 16 × 128 WMMA operations. The dedicated FP4 32 × 32 specialization uses two scaled 32 × 16 × 128 FP4 operations. A 32 × 32 warp tile uses 32 FP32 accumulators per lane, compared with eight for 16 × 16. Total register use depends on the block shape and compiler; a larger warp tile does not guarantee higher throughput.
FP4 32 × 16 × 128 is an opt-in configuration to request the native
v_wmma_scale_f32_32x16x128_f4 path. Tile Engine and the bridge do not
distinguish gfx1250 A0/B0 revisions; instruction support is left to the native
pipeline. The current C++ 32 × 16 trait has only the unscaled operation, and
both TDM V1/V2 reject MX builds with a missing scaled wmma_intrinsic
overload. Exposing the configuration does not imply a successful device
build. It is excluded from the default and CI sweeps.
All exposed warp tiles retain E8M0 scales per 32 K elements.
The dispatcher generator reuses MxGemmKernelBuilder from Tile Engine. Both
entry points use the same generated kernel, while their host code chooses the
architecture's native scale reshuffling helper. Select one GPU architecture per
Tile Engine build.
From projects/composablekernel, with NumPy installed and a matching ROCm
compiler/runtime available:
export CK_TILE_HIPCC=/opt/rocm/bin/hipcc
export PATH=/opt/rocm/bin:$PATH
export LD_LIBRARY_PATH=/opt/rocm/lib:$LD_LIBRARY_PATH
export CK_TILE_BENCH_WARMUP=1
export CK_TILE_BENCH_REPEAT=2
PYTHONPATH=dispatcher/python python3 - <<'PYCODE'
from pathlib import Path
import numpy as np
from mx_gemm_utils import (
GpuMxGemmRunner, MxGemmProblem, default_fp4_config, default_fp8_config,
setup_multiple_mx_gemm_dispatchers,
)
for make_config in (default_fp8_config, default_fp4_config):
config = make_config("gfx1250", pipeline="comp_tdm_v2")
library = setup_multiple_mx_gemm_dispatchers(
[config], output_dir=Path("build/mx_bridge"), gfx_arch="gfx1250",
parallel=False,
)[0]
assert library is not None, "Kernel compilation failed"
runner = GpuMxGemmRunner(library, dtype=config.datatype, arch="gfx1250")
problem = MxGemmProblem(129, 257, 384)
a_ref, b_ref, a, b, sa, sb = runner.make_inputs(problem, scale=1.0, seed=5)
rng = np.random.default_rng(19)
sa[:] = rng.integers(124, 130, size=sa.shape, dtype=np.uint8)
sb[:] = rng.integers(124, 130, size=sb.shape, dtype=np.uint8)
result = runner.run(problem, a, b, sa, sb)
reference = runner.reference(a_ref, b_ref, sa, sb, problem).astype(np.float32)
got = np.asarray(result.C, dtype=np.float32)
denominator = np.abs(reference) + max(np.abs(reference).max() * 1e-2, 1e-6)
error = np.max(np.abs(got - reference) / denominator)
assert np.isfinite(got).all() and error <= 5e-2
print(config.datatype, "PASS", "max_rel=", error, "time_ms=", result.time_ms)
PYCODEtile_engine/ops/gemm/mx_gemm_full_benchmark.py sweeps a Tile Engine MX JSON
configuration through the bridge, like gemm_full_benchmark.py does for regular
GEMM. expand_mx_sweep() takes the kernel list from the Old-TE
MxGemmKernelBuilder, so the bridge runs exactly the kernels an Old-TE build of
the same JSON, dtype and --max-instances produces.
The driver runs in three phases:
- Phase 1 expands the sweep JSON into kernel configurations and compiles each
one into its own
.soin parallel. No GPU is used. - Phase 2 loads the problem shapes.
- Phase 3 benchmarks every kernel on every problem across all visible GPUs.
Each batch of kernels runs in a disposable worker process
(
run_one_mx_gemm_kernel.py), so a GPU fault loses only that batch.
A gfx950 or gfx1250 GPU, ROCm with hipcc, and Python 3 with NumPy. MX GEMM does
not need a CMake build: the driver generates and compiles the kernels itself.
Run one architecture per sweep; it is detected with rocminfo unless --arch
is given.
export CK_TILE_HIPCC=/opt/rocm/bin/hipcc
export PATH=/opt/rocm/bin:$PATH
cd projects/composablekernel/tile_engine/ops/gemm# fp4, bridge_ci_config_<arch>.json (every MX pipeline of the arch),
# example problems, all visible GPUs
python3 mx_gemm_full_benchmark.py --verify
# fp8
python3 mx_gemm_full_benchmark.py --dtype fp8 --verifyThe driver reads the same JSON format as the Old-TE MX CMake build. A bare file
name is looked up in tile_engine/ops/gemm/mx_gemm/configs/; any other path is
used as given.
| File | Content |
|---|---|
bridge_ci_config_gfx950.json |
Default on gfx950. Small sweep covering all three gfx950 pipelines. |
bridge_ci_config_gfx1250.json |
Default on gfx1250. Small sweep covering all five gfx1250 pipelines. |
default_ci_config.json |
Old-TE CI sweep: comp_async, with and without persistence. |
default_config.json |
Old-TE full gfx950 sweep (194 FP4 / 150 FP8 kernels). |
default_config_gfx1250.json |
Old-TE full gfx1250 sweep (64 kernels per input type). |
user_provided_config.json |
Template for a custom sweep. |
Each field takes either {"values": [...]} or {"min": a, "max": b, "step": c}.
An FP4 sweep for gfx950:
{
"tile_config": {
"tile_m": {"values": [128, 256]},
"tile_n": {"min": 128, "max": 512, "step": 128},
"tile_k": {"values": [256]},
"warp_m": {"values": [1, 2, 4]},
"warp_n": {"values": [2, 4]},
"warp_k": {"values": [1]},
"warp_tile_m": {"values": [16]},
"warp_tile_n": {"values": [16]},
"warp_tile_k": {"values": [128]}
},
"trait_config": {
"pipeline": {"values": ["comp_async", "comp_async_eight_waves", "weight_preshuffle"]},
"scheduler": {"values": ["intrawave"]},
"epilogue": {"values": ["cshuffle"]},
"pad_m": {"values": [false]},
"pad_n": {"values": [false]},
"pad_k": {"values": [false]},
"persistent": {"values": [false]}
},
"k_block_per_cu": 1
}The driver forms every combination of these values and drops the ones a pipeline does not support, using the same validation as the Old-TE build and the bridge generator:
| Pipeline | Architectures | Constraints |
|---|---|---|
comp_async |
gfx950, gfx1250 | 2 × 2 × 1 warps on gfx1250 |
comp_async_eight_waves |
gfx950, gfx1250 | 4 × 2 × 1 warps; M and N block tiles divisible by 128 |
weight_preshuffle |
gfx950, gfx1250 | 1 × 4 × 1 warps; block tiles divisible by 32 × 128 × 256; FP4 also needs block N divisible by 512 |
comp_tdm, comp_tdm_v2 |
gfx1250 | 2 × 2 × 1 warps; tdm epilogue |
gfx950 uses the 16 × 16 × 128 warp tile. gfx1250 does not support pad_k or
persistent. Phase 1 prints Expanded configs: N, the number of kernels left
after validation. To list the kernels without compiling:
python3 -c "
import sys; sys.path.insert(0, '../../../dispatcher/python')
from mx_gemm_utils import expand_mx_sweep
for c in expand_mx_sweep('my_config.json', 'gfx950', 'fp4'): print(c.name)"The default problem set is mx_gemm/configs/example_problems.json. Pass another
file with --problems:
{
"problems": [
{"M": 1024, "N": 1024, "K": 1024},
{"M": 512, "N": 7168, "K": 2048}
]
}K must be a multiple of the block tile K, and of 128 on gfx1250. On gfx1250,
M/N tails are supported even with pad_m=pad_n=false; weight preshuffle also
requires N divisible by 16. gfx950 retains the bridge's scale-packing and native
kernel alignment checks. A kernel that rejects a problem shape is
reported as SKIPPED (unsupported problem); it is not a failure and writes no
CSV row.
| Option | Default | Meaning |
|---|---|---|
configs (positional) |
bridge_ci_config_<arch>.json |
One or more sweep JSON files. |
--dtype fp4|fp8 |
fp4 |
MXFP4 (E2M1) or MXFP8 (OCP E4M3) inputs. |
--arch |
detected | gfx950 or gfx1250. |
--problems |
example_problems.json |
Problem shapes. |
--csv |
mx_gemm_results.csv |
Results file. |
--verify |
off | Compare each result with the numpy microscaled reference, using random E8M0 scales between 2^-3 and 2^2. A mismatch is a failure. |
--verify-tol |
2e-2 |
Tolerance for max|out - ref| / max|ref|. |
--devices |
all visible | A count (4) or a list of IDs (0,2,5; 5, selects only GPU 5). |
--workers |
8 | Parallel kernel builds. |
--output-dir |
new temporary directory | Build directory for the generated headers and .so files. Reusing it skips kernels that are already built. |
--compile-only |
off | Stop after Phase 1. |
--max-instances, --seed |
off | Sample at most N kernels per configuration with the Old-TE sampler (MX_GEMM_MAX_INSTANCES). |
--max-kernels |
0 (all) | Keep only the first N kernels. |
--batch-size |
20 | Kernels per worker process. |
--kernel-timeout |
60 | Timeout per kernel, in seconds. |
CK_TILE_BENCH_WARMUP (default 50) and CK_TILE_BENCH_REPEAT (default 100) set
the warmup and timed iterations. The cache is not flushed between iterations;
use the same settings on both sides when comparing with Old-TE results.
# 1. Build once into a reusable directory
python3 mx_gemm_full_benchmark.py my_config.json --dtype fp4 \
--output-dir ~/mx_build --workers 48 --compile-only
# 2. Check correctness on a few shapes
python3 mx_gemm_full_benchmark.py my_config.json --dtype fp4 \
--output-dir ~/mx_build --verify --problems my_shapes.json --csv check.csv
# 3. Measure performance; --verify is slow for large shapes
python3 mx_gemm_full_benchmark.py my_config.json --dtype fp4 \
--output-dir ~/mx_build --problems my_shapes.json --csv perf.csvEach console line shows the GPU, kernel name, latency in ms, TFLOPS and a status:
| Status | Meaning |
|---|---|
OK |
The kernel ran (no --verify). |
ZERO |
The output was all zeros. |
VERIFY |
The output matched the reference. |
MISMATCH |
The output did not match the reference. |
FAILED |
The kernel returned an error. |
MISSING |
The worker process crashed before reporting. |
SKIPPED |
The kernel does not support the problem shape. |
The CSV has the columns kernel, problem_idx, M, N, K, device, latency_ms, tflops, non_zero, max_rel, verified. The driver exits with 1 if any
measurement or kernel build failed. To list the fastest kernel per shape:
python3 -c "
import pandas as pd
d = pd.read_csv('perf.csv')
best = d.loc[d.groupby(['M', 'N', 'K']).tflops.idxmax()]
print(best[['M', 'N', 'K', 'kernel', 'latency_ms', 'tflops']].to_string(index=False))"CPU tests cover architecture selection and target suffixes, invalid configurations, LDS boundaries, scale/packing codecs, standalone Tile Engine entry points, the CI configuration, and exact generated-header parity with Tile Engine:
python3 -m unittest discover -s dispatcher/tests -p test_mx_gemm_bridge.py -vThe CMake tests check MX target selection and effective compiler definitions. They require CMake and a host C++ compiler, and skip when either is unavailable; no GPU or HIP compiler is needed:
python3 -m unittest discover -s dispatcher/tests -p test_mx_gemm_cmake.py -vBoth suites are registered with CTest when dispatcher tests are enabled:
ctest --test-dir build -R '^dispatcher_test_mx_gemm_(bridge|cmake)$' --output-on-failureThe GPU suite builds all 16 gfx1250 TDM CI configurations, four larger TDM configurations whose LDS allocation exceeds 64 KiB, six configurations covering async, eight-wave async, and weight preshuffle in FP4 and FP8, and four TDM configurations with the 32 × 32 × 128 warp tile: 30 configurations total. It tests partial tiles and one through five and eight K-loop iterations with two seeds, varies scales across rows and K blocks, and repeats eight-wave launches to catch buffer-reuse races. It checks K-tail/split-K rejection and weight-preshuffle N alignment. On gfx950 it tests all three pipelines with FP4 and FP8 over four block-relative shapes and two seeds:
CK_TILE_BENCH_WARMUP=1 CK_TILE_BENCH_REPEAT=2 \
python3 -m unittest discover -s dispatcher/tests -p test_mx_gemm_gpu_correctness.py -vThe native C++ regression uses CK-Tile directly:
cmake -S . -B build/mx_native \
-DBUILD_DEV=ON -DCMAKE_BUILD_TYPE=Release -DGPU_TARGETS=gfx1250 \
-DCMAKE_CXX_COMPILER=/opt/rocm/llvm/bin/clang++ \
-DCMAKE_HIP_COMPILER=/opt/rocm/llvm/bin/clang++ -DCMAKE_PREFIX_PATH=/opt/rocm
cmake --build build/mx_native --target test_ck_tile_mx_gemm_e8m0_gfx1250 --parallel 2
ctest --test-dir build/mx_native -R test_ck_tile_mx_gemm_e8m0_gfx1250 --output-on-failure
cmake --build build/mx_native --target test_ck_tile_mx_gemm_splitk_support --parallel 2
ctest --test-dir build/mx_native -R test_ck_tile_mx_gemm_splitk_support --output-on-failureFor Tile Engine, enable BUILD_CK_TILE_ENGINE, use GPU_TARGETS=gfx1250, and
build benchmark_mx_gemm_all. The MX operation selects
default_config_gfx1250.json automatically unless a custom MX config is
provided. It uses both comp_tdm and comp_tdm_v2 with the TDM epilogue,
intrawave scheduling, 2 x 2 x 1 warps,
a 16 x 16 x 128 warp tile, and no padding or persistent execution. Block M/N
range from 64 to 256 in steps of 64; block K is 128 or 256. All 32 block shapes
fit gfx1250's 320 KiB LDS capacity with both FP4 and FP8, including TDM descriptor
padding and both staging buffers: 64 kernels per pipeline, for 128 kernels total.
Both the bridge and Tile Engine use this architecture-aware capacity check.
This default set contains TDM kernels. The three CShuffle pipelines
are available through explicit configuration or the Python helpers above.
For the smaller 16-kernel CI set covering both TDM pipelines, pass
-DMX_GEMM_CONFIG_FILE=default_ci_config_gfx1250.json to CMake. The
MX_GEMM_CONFIG_FILE environment variable takes precedence over the CMake
option. gfx950 selects default_config.json, which covers all three gfx950
pipelines with CShuffle. Each configuration file contains pipelines supported
by its target architecture.
Benchmark executables accept -m=, -n=, -k=, -verify=1, -init=0,
-warmup=1, and -repeat=2 for a short correctness run with random inputs.