Skip to content

feat(ck_tile): TE -> Dispatcher GEMM bridge (all layouts, fp16/bf16)#8479

Closed
ozturkosu wants to merge 16 commits into
developfrom
muozturk/gemm-bridge-all-layouts-bf16
Closed

feat(ck_tile): TE -> Dispatcher GEMM bridge (all layouts, fp16/bf16)#8479
ozturkosu wants to merge 16 commits into
developfrom
muozturk/gemm-bridge-all-layouts-bf16

Conversation

@ozturkosu

Copy link
Copy Markdown
Contributor

Summary

This PR routes the Tile Engine (TE) regular-GEMM sweep through the Dispatcher,
making the Dispatcher the single source of truth for codegen → build → runtime
while the Tile Engine keeps only the config search space and the benchmark
loop
. It is the consolidated, single-commit GEMM bridge covering all four
layouts (rcr/rrr/crr/ccr)
and both fp16 and bf16.

It is a clean re-roll of the earlier bridge work (previously split across
#8123 + the stacked key/bf16/layouts/parity/example PRs and consolidated in
#8261). Those branches accumulated unrelated cross-project commits through repeated
develop merges; this branch is a single clean commit off the latest develop
containing only the GEMM-bridge files. It supersedes and replaces #8123 / #8261.

Motivation

The Tile Engine historically owned its own codegen/build/runtime for GEMM
(tile_engine/ops/gemm/gemm_universal/). The consolidation goal is for the
Dispatcher to own all of that — exactly as it already does for FMHA and
Grouped Conv — so there is one kernel-generation/build/runtime path and the
TE shrinks to a config+benchmark frontend. This PR brings regular GEMM in line
with that reference binding.

The binding (mirrors the FMHA/Conv reference, six stages)

  1. Config JSON (TE side) — the sweep search space lives in
    tile_engine/ops/gemm/configs/ (flat op-root layout, matching the
    fmha/ and grouped_conv/ bridges).
  2. Codegen (Dispatcher)dispatcher/codegen/unified_gemm_codegen.py emits
    one fully-typed .hpp per kernel; GemmKernelConfig.name reproduces
    KERNEL_NAME byte-for-byte (the thread tying config → kernel → runtime).
  3. Compile to .so — a single static gemm_ctypes_lib.cpp is force-included
    (-include <kernel.hpp>); one .so per kernel.
  4. Flat extern "C" ABIdispatcher_run_gemm(A, B, C, M, N, K, time_ms) +
    the kernel-name enumeration entry points. Host-pointer memory model (the C
    lib hipMallocs internally) — the FMHA-forward branch of the reference.
  5. Python ctypes wrapperdispatcher/python/gemm_utils.py
    (GemmDispatcherLib + GpuGemmRunner).
  6. TE driver (3 phases)gemm_full_benchmark.py (parallel codegen+build →
    expand_sweep → subprocess-isolated benchmark) + the disposable per-kernel
    worker run_one_gemm_kernel.py.

What's included

Bridge core

  • dispatcher/codegen/unified_gemm_codegen.py — GEMM codegen, byte-exact naming.
  • dispatcher/bindings/ctypes/gemm_ctypes_lib.cpp — flat C ABI, host-pointer model.
  • dispatcher/python/gemm_utils.pyGemmKernelConfig, multi-kernel build
    (setup_multiple_gemm_dispatchers), expand_sweep, one-.so-per-kernel.
  • tile_engine/ops/gemm/gemm_full_benchmark.py + run_one_gemm_kernel.py
    3-phase, multi-GPU, subprocess-isolated driver/worker.

Feature surface (the point of this PR)

  • All four layouts rcr/rrr/crr/ccr (row-major C only — ck_tile rejects
    column-major C at build) with layout-aware host transpose.
  • fp16 + bf16 (bf16 via uint16 byte-encoding; dtype derived from kernel name).
  • Trait-derived registry KernelKey — replaces the earlier hard-coded
    fp16/rcr key so the registry path generalizes across dtype/layout/tile.

Correctness & performance hygiene

  • --verify opt-in fp32 numpy-reference gate (global max|out-ref|/max|ref|),
    verified/max_rel columns in the CSV; a mismatch counts as a failure.
  • Tile Engine AMDGPU -mllvm codegen-flag parity (without these the kernel
    builds with different occupancy and the timing diverges) and
    arch-validated tile filtering against the real pipeline/scheduler.
  • Multi-GPU fan-out across all visible GPUs (--devices, device-pinned
    HIP_VISIBLE_DEVICES workers).

Example & tests

  • dispatcher/examples/gemm/python/12_te_bridge.py — runnable end-to-end example.
  • dispatcher/tests/test_gemm_parity.py, test_gemm_utils.py, and a parity
    regression harness.

Cleanup

  • Removes the legacy standalone gemm_universal build path
    (gemm_universal_instance_builder.py, *_benchmark*.{py,cpp,hpp},
    gemm_universal/CMakeLists.txt) and the old test/ck_tile/gemm_tile_engine/
    harness; promotes the sweep configs to the flat op-root configs/.

Design decisions (consistent with the reference)

  • Host-pointer memory ownership (C lib owns device memory) — matches
    FMHA-forward; the Python runner passes host numpy arrays straight through.
  • One .so per kernel — packaging choice; the multi-kernel name ABI is
    retained (get_kernel_name_at(0) reports the single kernel), so the Python
    enumeration path is unchanged from FMHA/Conv.
  • Flat configs/ at the op root — matches the fmha//grouped_conv/
    convention; the not-yet-bridged variants keep their per-variant configs/
    dirs, selected by --variant.

Validation (gfx942 / MI300X)

  • Bridge build + benchmark + --verify across fp16 and bf16 and all
    four layouts
    , checked against an fp32 numpy reference (A @ B).
  • Name parity holds end-to-end: each .so's reported runtime name equals
    GemmKernelConfig(...).name.
  • bf16 passes under a widened fp16/bf16 tolerance; fp16 within the standard
    max_rel gate.

Test plan

  • gemm_full_benchmark.py --verify over configs/default_ci_config.json for
    fp16 and bf16, each of rcr/rrr/crr/ccr.
  • unified_gemm_codegen.py emits a header whose stem == GemmKernelConfig.name.
  • setup_multiple_gemm_dispatchers builds + links each config against
    gemm_ctypes_lib.cpp.
  • pytest dispatcher/tests/test_gemm_parity.py dispatcher/tests/test_gemm_utils.py.
  • examples/gemm/python/12_te_bridge.py runs end to end.

Notes

Consolidated, single-commit GEMM bridge routing the Tile Engine regular-GEMM
sweep through the Dispatcher (codegen -> build -> runtime), so the Dispatcher is
the single source of truth and the Tile Engine owns only the config search space
and the benchmark loop. Mirrors the FMHA/Conv reference binding end to end.

Scope:
- Regular GEMM bridge: unified_gemm_codegen.py, gemm_ctypes_lib.cpp (flat
  extern "C" ABI, host-pointer model), gemm_utils.py (GemmKernelConfig with
  byte-exact .name, one-.so-per-kernel build), 3-phase TE driver + subprocess
  worker (gemm_full_benchmark.py / run_one_gemm_kernel.py).
- Trait-derived registry KernelKey (replaces the hard-coded fp16/rcr key).
- bf16 support and all four layouts (rcr/rrr/crr/ccr; row-major C only).
- Tile Engine AMDGPU -mllvm codegen-flag parity + arch-validated tile filtering.
- --verify fp32-reference correctness gate; multi-GPU fan-out.
- Runnable example (examples/gemm/python/12_te_bridge.py) and parity/unit tests.
- Removes the legacy standalone gemm_universal build path and the old
  test/ck_tile/gemm_tile_engine harness; promotes sweep configs to the op-root
  flat configs/ directory (fmha/grouped_conv convention).

Validated on gfx942 / MI300X (fp16 + bf16, all four layouts) against an fp32
numpy reference via --verify.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR routes Tile Engine regular-GEMM sweeping/benchmarking through the Dispatcher bridge, making the Dispatcher the single codegen→build→runtime path while TE retains config expansion and the benchmark driver/worker. It also removes the legacy standalone gemm_universal build/test harness and adds parity/unit tests plus an end-to-end example.

Changes:

  • Add the Dispatcher-owned GEMM bridge (dispatcher/python/gemm_utils.py) and TE driver/worker scripts for subprocess-isolated, multi-GPU benchmarking.
  • Extend dispatcher codegen/ctypes runtime to register kernels with trait-derived KernelKey metadata and add benchmark knob parity.
  • Remove the legacy gemm_universal standalone build path and the old gemm_tile_engine test harness; add new bridge-focused tests and examples.

Reviewed changes

Copilot reviewed 33 out of 35 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
projects/composablekernel/tile_engine/ops/gemm/run_one_gemm_kernel.py New per-kernel subprocess worker for loading .so and running/optionally verifying GEMM.
projects/composablekernel/tile_engine/ops/gemm/gemm_full_benchmark.py New 3-phase driver: parallel build + problem load + multi-GPU batched benchmarking.
projects/composablekernel/tile_engine/ops/gemm/README.md Documents the dispatcher bridge workflow, scripts, layouts, and removal of standalone path.
projects/composablekernel/tile_engine/ops/gemm/configs/default_ci_config.json Tweaks CI sweep tile params.
projects/composablekernel/tile_engine/ops/gemm/configs/default_config.json Adds full sweep config for bridged GEMM.
projects/composablekernel/tile_engine/ops/gemm/configs/example_problems.json Adds example GEMM problem shapes for the driver.
projects/composablekernel/tile_engine/ops/gemm/configs/user_provided_config.json Adds a user scratch sweep config template.
projects/composablekernel/tile_engine/ops/gemm/CMakeLists.txt Drops gemm_universal subdir and updates sampling-op lists accordingly.
projects/composablekernel/tile_engine/ops/gemm/gemm_universal/gemm_universal_profiler.hpp Removes legacy standalone GEMM universal profiler.
projects/composablekernel/tile_engine/ops/gemm/gemm_universal/gemm_universal_instance_builder.py Removes legacy standalone GEMM universal instance builder.
projects/composablekernel/tile_engine/ops/gemm/gemm_universal/gemm_universal_benchmark.py Removes legacy standalone GEMM universal benchmark driver.
projects/composablekernel/tile_engine/ops/gemm/gemm_universal/gemm_universal_benchmark.hpp Removes legacy standalone GEMM universal benchmark helpers.
projects/composablekernel/tile_engine/ops/gemm/gemm_universal/gemm_universal_benchmark_single.cpp Removes legacy single-kernel benchmark executable.
projects/composablekernel/tile_engine/ops/gemm/gemm_universal/CMakeLists.txt Removes legacy standalone build integration for universal GEMM.
projects/composablekernel/test/ck_tile/gemm_tile_engine/test_gemm_simple.cpp Removes old tile-engine GEMM gtest harness.
projects/composablekernel/test/ck_tile/gemm_tile_engine/README.md Removes documentation for the deleted GEMM tile-engine tests.
projects/composablekernel/test/ck_tile/gemm_tile_engine/extract_test_params.py Removes helper used by deleted GEMM tile-engine tests.
projects/composablekernel/test/ck_tile/gemm_tile_engine/configs/small_datatype_config.json Removes old test configs (small datatype).
projects/composablekernel/test/ck_tile/gemm_tile_engine/configs/simple_test_config.json Removes old test configs (simple).
projects/composablekernel/test/ck_tile/gemm_tile_engine/configs/quick_coverage_config.json Removes old test configs (quick coverage).
projects/composablekernel/test/ck_tile/gemm_tile_engine/configs/padding_coverage_config.json Removes old test configs (padding coverage).
projects/composablekernel/test/ck_tile/gemm_tile_engine/configs/large_datatype_config.json Removes old test configs (large datatype).
projects/composablekernel/test/ck_tile/gemm_tile_engine/configs/comprehensive_coverage_config.json Removes old test configs (comprehensive coverage).
projects/composablekernel/test/ck_tile/gemm_tile_engine/CMakeLists.txt Removes CMake for the deleted GEMM tile-engine tests.
projects/composablekernel/test/ck_tile/CMakeLists.txt Removes commented-out gemm_tile_engine test subdir stanza.
projects/composablekernel/dispatcher/python/gemm_utils.py New bridge contract + build API + ctypes wrapper + runner + TE sweep expansion.
projects/composablekernel/dispatcher/python/ctypes_utils.py Threads requested GEMM variant into codegen (--variants).
projects/composablekernel/dispatcher/bindings/ctypes/gemm_ctypes_lib.cpp Builds registry KernelKey from codegen-emitted macros; adds indexed kernel-name ABI.
projects/composablekernel/dispatcher/codegen/unified_gemm_codegen.py Emits GEMM_KEY_* macros; fixes arch validation to respect actual pipeline/scheduler.
projects/composablekernel/dispatcher/include/ck_tile/dispatcher/kernel_key.hpp Maps scheduler string "default" to Scheduler::Auto.
projects/composablekernel/dispatcher/include/ck_tile/dispatcher/backends/generated_tile_backend.hpp Adds env-overridable benchmark knobs and updates defaults to match TE.
projects/composablekernel/dispatcher/tests/test_gemm_utils.py New CPU-only tests for bf16 encoding and name parsing/contract.
projects/composablekernel/dispatcher/tests/test_gemm_parity.py New GPU-gated parity suite vs NumPy reference across dtype/layout surface.
projects/composablekernel/dispatcher/examples/gemm/python/12_te_bridge.py New end-to-end bridge example (matrix/layouts, padding behavior, mini-sweep).
projects/composablekernel/dispatcher/parity_diag/regression/ab_same_harness.py Adds diagnostic harness to compare bridge vs old-TE kernel under the same worker.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread projects/composablekernel/tile_engine/ops/gemm/run_one_gemm_kernel.py Outdated
Comment thread projects/composablekernel/tile_engine/ops/gemm/gemm_full_benchmark.py Outdated
Comment thread projects/composablekernel/dispatcher/python/gemm_utils.py
The bridge dispatcher's tile-divisibility gate rejected any problem where
M % TileM != 0 for every layout, returning status -2 ("No suitable kernel")
at runtime even though the .so built fine. This wrongly excluded bf16 rcr/rrr
kernels with a non-power-of-two TileM (e.g. 192) on standard shapes like
1024^3 -- cases Old-TE compiles, runs, and verifies as correct.

Root cause: supports() was layout-blind, while the underlying
ck_tile::GemmKernel::IsSupportedArgument only constrains a dimension when an
operand whose inner axis is that dimension participates without padding:

  RowMajor A -> K, ColMajor A -> M
  RowMajor B -> N, ColMajor B -> K
  RowMajor C -> N, ColMajor C -> M

So for rcr (RowMajor A & C) M is never gated, which is why Old-TE runs M=192
tiles on M-indivisible problems.

Make supports() compute require_m/n/k from the kernel key's A/B/C layouts so
it mirrors IsSupportedArgument exactly (also honoring k_batch in the K grain).
Anything it now lets through is still validated by the kernel's own
IsSupportedArgument inside launch(), so the bridge stays a strict functional
equivalent of Old-TE. Applied to both generated_tile_backend.hpp (the GEMM
.so path) and the sibling tile_backend.hpp.

Validated on gfx942 (MI300X): 85 previously status-2 rcr/rrr bf16 192-tile
.so now run at 1024^3 (Old-TE runs the same, verification correct); the 8
remaining rejects are tile N=192 cases that Old-TE also reports "Arguments
not supported" at N=1024 -- parity preserved in both directions.
@ozturkosu

Copy link
Copy Markdown
Contributor Author

Fix: layout-aware supports() — restores bf16 TileM=192 parity with Old-TE

Pushed 22f0f3ae81 to this branch.

Symptom

bf16 rcr/rrr kernels with a non-power-of-two TileM (e.g. 192) returned status -2 at runtime on standard shapes like 1024³, even though the .so built and loaded fine. Old-TE compiles, runs, and verifies these as correct — so this was a bridge-only parity gap.

Root cause

Status -2 is "No suitable kernel" (gemm_ctypes_lib.cpp:229) — the host-side supports() rejected the kernel before launch, not a HIP launch failure. GeneratedTileKernelInstance::supports() was layout-blind: it required M % TileM == 0 for every layout. The real authority, ck_tile::GemmKernel::IsSupportedArgument, only gates a dimension when an operand whose inner axis is that dimension participates without padding:

layout RowMajor gates ColMajor gates
A K M
B N K
C N M

For rcr/rrr (RowMajor A & C), M is never gated — which is why Old-TE runs TileM=192 on M-indivisible problems.

Fix

supports() now computes require_m/n/k from the kernel key's A/B/C layouts so it mirrors IsSupportedArgument exactly (K grain also honors k_batch). Anything it lets through is still validated by the kernel's own IsSupportedArgument inside launch(), so the bridge stays a strict functional equivalent of Old-TE. Applied to both generated_tile_backend.hpp (the GEMM .so path) and the sibling tile_backend.hpp.

Validation (gfx942 / MI300X)

  • Old-TE baseline, TileM=192 at M=1024³, CPU verify: rcr & rrr × {192x128x64, 192x256x64, 192x64x128, 192x64x64} → all verification result: correct.
  • Bridge after fix: 85/93 rebuilt rcr/rrr TileM=192 .so now run at 1024³ (were status -2). The 8 still rejected are TileN=192 (192x192x64) cases, which Old-TE also rejects with "Arguments not supported" at N=1024 — and both run at N=1152. Parity holds in both directions.
  • crr/ccr at TileM=192 are out of scope: they fail to compile on both engines (ColMajor A → static_assert(X0*Y1==warp_size)), so there is no Old-TE binary or bridge .so for them — not a parity gap.

…oding rcr

dispatcher_initialize() in gemm_ctypes_lib.cpp hardcoded the KernelKey layout to
rcr (RowMajor/ColMajor/RowMajor) for every kernel. Now that supports() is
layout-aware, that wrong key layout makes the dispatcher reject valid problems:
a crr kernel does not gate K (neither A=ColMajor nor B=RowMajor has K as its
inner axis), but with a hardcoded rcr key supports() applies rcr's K-gate and
returns status -2 for TileK=192 problems (e.g. crr 64x64x192 at 1024^3) that
Old-TE compiles, runs, and verifies (~87 TFLOPS).

Derive signature.layout_a/b/c from the force-included kernel's own
ALayout/BLayout/CLayout types via std::is_same_v with tensor_layout::gemm::RowMajor.
The key now matches the kernel, so the layout-aware gate is correct for all four
layouts. Execution was already layout-correct (the kernel uses its own compile-time
layouts); only the host-side selection metadata was wrong.

Validated on gfx942 (MI300X): crr 64x64x192 now runs on the bridge (93 TFLOPS),
restoring parity with Old-TE.
The >=20% bridge-vs-old-TE perf gaps in the parity sweep are a harness
artifact: the sweep timed the bridge in-process but timed old-TE via its
separate standalone benchmark binary, which runs the byte-identical kernel
at a lower sustained SCLK. Measured through one harness the gap is <1%.

ab_same_harness.py removed that artifact but hardcoded the old-TE header dir
to fp16/rcr. Derive it per stem as <base>/<dtype>/<layout> so one run covers
rcr/rrr/ccr/crr and fp16+bf16, add a --stems-file/--csv resume-aware sweep
mode, and use the median (not max) per point.
For a full ~2000-stem sweep on a single GPU: batch all shapes into one worker
call per side (5x fewer process startups), cache the compiled old-TE .so, and
add a parallel --build-only pre-pass so hipcc compilation uses all CPU cores
while GPU measurement stays serial.
@ozturkosu

Copy link
Copy Markdown
Contributor Author

Follow-up: the ">=20% bridge-vs-old-TE perf gaps" are a harness artifact, not a kernel difference

While reviewing the parity sweep allresult_fp16_bf16.csv, 636/4966 rows showed |gap| > 20% between bridge_tflops and old_tflops (up to +316%). Investigated all of them.

Root cause — apples-to-oranges measurement. The sweep timed the bridge in-process (via run_one_gemm_kernel.py) but timed old-TE as its separate standalone benchmark binary (benchmark_gemm_universal_<stem>). The bench knobs already match (the bridge defaults to warmup=50/repeat=100/flush/rotating=1000, benchmarking_=true). The device kernel is byte-identical — generated-header config constants (Tile/Warp/WarpTile/pad/CShuffle/CompV3…) match exactly; the only differences are codegen style and the bridge's TailHandler wrapper, which the old-TE example also uses. The standalone binary simply runs that identical kernel at a lower sustained SCLK + more memory-stall cycles in its own process (rocprof-confirmed earlier; see the note in generated_tile_backend.hpp).

Proof — measure both sides through ONE harness (build the old-TE kernel into a .so, run both via the same worker). Every large gap collapses to <1%:

case old CSV gap same-harness gap
fp16 rcr 128x128x128_2x2x1_32x32x8 @1024³ +49.8% −0.04%
fp16 rcr 192x128x64_1x4x1_16x16x32 @1024³ +44.5% 0.00%
fp16 rrr 256x256x64_1x4x1_16x16x32 @4096³ +181% −0.22%
fp16 ccr 64x64x128_4x1x1_4x64x16 @1024x512x256 +182% −0.03%
fp16 crr 256x256x64_2x2x1_16x16x32 @4096³ +316% −0.09%
bf16 rcr 128x128x128_1x4x1_16x16x16 @1024³ −0.07%

Holds across rcr/rrr/ccr/crr and fp16+bf16; worst residual across spot checks is +2.75% (single 2048³ wave-quant noise sample). No kernel/codegen/config change is needed — the bridge is performance-equivalent to old-TE.

Fix (this PR). Generalized the same-harness A/B tool (ab_same_harness.py) so the old-TE side is derived per stem (<dtype>/<layout>), covering all layouts + bf16 in one run; added a resume-aware --csv sweep mode, batched measurement, and a parallel --build-only pre-compile. A corrected full ~10K-row sweep is being regenerated now and will be posted as the trustworthy parity table.

@ozturkosu

Copy link
Copy Markdown
Contributor Author

Follow-up perf opportunity (not in this PR): gate -enable-post-misched=0 on persistent

While validating bridge↔old-TE parity I confirmed there is no bridge codegen regression — the
apparent ±15–700% gaps were measurement artifacts (stale .so + an unfair A/B harness), addressed
by the harness-fix commit on this branch. Separately I found and prototyped a real performance
win that I've deliberately kept out of this PR:

What: In gemm_utils.py, gate the -mllvm -enable-post-misched=0 codegen flag on config.persistent
— keep it for persistent kernels, drop it for non-persistent ones.

Why: A flag-bisection (same source, 8 variants) shows this is the only perf-relevant flag, and
it does not change register spill — it just yields a worse instruction schedule for
non-persistent compute-bound kernels.

┌─────────────────────────────────┬─────────┬────────────────────┐
│ kernel │ flag ON │ flag OFF │
├─────────────────────────────────┼─────────┼────────────────────┤
│ non-persistent 256×256×64 @4096 │ 188.9 │ 206.2 TFLOPS (+9%) │
├─────────────────────────────────┼─────────┼────────────────────┤
│ persistent 128×128×64 @2048 │ 394.2 │ 372.3 │
└─────────────────────────────────┴─────────┴────────────────────┘

The flag helps persistent kernels (occupancy-bound, +6%) but hurts non-persistent large tiles
(~9–40%). Gating on persistent captures the win without changing persistent behavior.
Correctness preserved (verified=True, max_rel ≈ 3e-4).

Why held back:

  • Validated on a handful of representative stems, not the full kernel set.
  • Needs a broader persistent-kernel no-regression sweep before landing, since the flag was
    originally added for persistent/occupancy-bound kernels.
  • After the change the bridge would be ~9% faster than real old-TE on those tiles (a genuine
    win, not a parity break).

Proposing it as a separate follow-up PR once the broader sweep confirms no persistent
regression.


That wraps it up. Summary of where things stand:

… guard)

The bridge-vs-old-TE A/B reported phantom regressions from two MEASUREMENT
bugs, not real codegen gaps:

- ab_same_harness.py built the old-TE side WITHOUT the TE codegen flags the
  bridge (and real old-TE's own CMake) use, so -enable-post-misched defaulted
  back on and old-TE ran ~10-40% faster -> the bridge looked regressed when it
  is at parity. Now both sides build with identical flags.

- ab_efficient_sweep.py measured whatever libgemm_<stem>.so existed with no
  freshness check, so 3-day-old binaries built from an obsolete codegen showed
  up as -78%/+703% gaps. Added a guard: skip any .so older than its generated
  header (treated as missing) instead of reporting a phantom gap.

With both fixes the 41 former >15% outlier stems measure within +/-10%
(median +0.01%); no bridge codegen regression exists.

Note: a separate, deliberately UNCOMMITTED perf change in gemm_utils.py (gate
-enable-post-misched=0 on persistent) gives non-persistent large tiles ~9-40%;
held back pending a broader persistent-kernel no-regression sweep.
ozturkosu and others added 2 commits June 23, 2026 08:56
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
@ozturkosu

Copy link
Copy Markdown
Contributor Author

Codegen validity gate: reject invalid non-power-of-2-repeat tiles (commit a7e01c4)

Follow-up to the 192-tile correctness finding. The CShuffle epilogue stores the accumulator through LDS in power-of-two MRepeat/NRepeat chunks (MRepeat = tile_m / (wave_m * warp_tile_m), likewise N). A tile whose per-wave repeat is not a power of two — or whose tile dim isn't divisible by wave * warp_tile — is mis-stored and returns numerically wrong results at runtime, yet still passes the ctypes validator and the epilogue static_asserts, so it compiles and silently produces garbage.

Verified on MI350: tile_m=192 (MRepeat=3) and tile_n=192 (e.g. 64x192x64_1x4x1, 192 ∤ 4·32) are incorrect on both the bridge and Tile Engine (fp32 ref max_rel ≈ 1.2–1.4), at every shape incl. 192-divisible ones; a control 256-tile verifies cleanly (max_rel ≈ 4e-4).

This commit adds the gate to both tile-expansion paths so invalid tiles are dropped rather than emitted/run:

  • unified_gemm_codegen.py::_get_tile_configs (codegen CLI path)
  • gemm_utils.py::expand_sweep (bridge .so build path — this path previously only ran validate_kernel_config, which doesn't catch it)

tile_k is intentionally unaffected (the K reduction has no CShuffle store constraint). Verified the gate removes every 192-tile_m/tile_n config while leaving all power-of-two tiles (64/128/256) intact.

(The parity harness was also updated locally to run with fp32 verification on, so a numerically-wrong kernel is recorded as INVALID_failed_verify instead of being timed as a perf gap.)

ozturkosu added a commit that referenced this pull request Jun 30, 2026
Re-stacks the grouped_gemm bridge on top of the all-layouts GEMM bridge
(muozturk/gemm-bridge-all-layouts-bf16, #8479) instead of the now-closed
#8123 base. Squashes the prior grouped-branch history (was 5e665a3..
d075b90 on the old base) into the net grouped delta replayed onto #8479.

Contents:
- codegen: grouped variant + launch generator (arch_filter.py,
  unified_gemm_codegen.py), plus the standalone 02_grouped_gemm_driver.cpp
  and README sections (absorbed from the closed #8075).
- bridge: multi-problem registry-bypass ctypes ABI
  (grouped_gemm_ctypes_lib.cpp), GpuGroupedGemmRunner + dtype/layout codecs
  (gemm_utils.py), TE driver/worker harness, GROUPED_GEMM_BRIDGE.md.

Kept both #8479's GEMM_KEY_* registry descriptors and grouped's
ALayout/BLayout/CLayout single-include exports. Coverage: {rcr,rrr,ccr,crr}
x {fp16,bf16,fp8,bf8}; validated at Old-TE parity on MI300X/gfx942.
ozturkosu added a commit that referenced this pull request Jun 30, 2026
Grouped GEMM variant of the bridge, stacked on the fp8/bf8/int8 bridge
(#8887, itself on #8479). The grouped kernel is multi-problem, so it uses a
dedicated registry-bypass ctypes ABI; TE only generates configs and benchmarks.

- codegen: grouped variant + launch generator (arch_filter.py,
  unified_gemm_codegen.py), standalone 02_grouped_gemm_driver.cpp, README.
- bridge: grouped_gemm_ctypes_lib.cpp (multi-problem ABI), GpuGroupedGemmRunner
  + dtype/layout codecs (gemm_utils.py), TE driver/worker harness,
  GROUPED_GEMM_BRIDGE.md.

Coverage: {rcr,rrr,ccr,crr} x {fp16,bf16,fp8,bf8}; validated at Old-TE parity
on MI300X/gfx942 (64/64 correct, 64/64 within +/-15%).
Copilot AI requested a review from a team as a code owner June 30, 2026 20:59
@therock-pr-bot

therock-pr-bot Bot commented Jul 1, 2026

Copy link
Copy Markdown

❌ PR Check — Action Required

Check Status Details
🌿 Branch Name ❌ Fail Branch name does not match allowed patterns.
Branch: muozturk/gemm-bridge-all-layouts-bf16
Allowed patterns:
- ^users\/[A-Za-z0-9][A-Za-z0-9\-]*\/.+
- ^shared\/.+
- ^[A-Za-z0-9][A-Za-z0-9\-_]*$
- ^dependabot\/.+
- ^revert-[0-9]+-.+
📝 PR Title/Description ❌ Fail Error: Title does not follow Conventional Commits style.
Expected: start with a valid type (feat, fix, docs, …).
Desired format: type(optional-scope): short description
Forbidden Files ✅ Pass
🧪 Unit Test ✅ Pass
🔎 pre-commit ✅ Pass
🚫 Draft PR 🔜 To Be Enabled
🚩 Feature Flag 🔜 To Be Enabled
📊 Code Coverage 🔜 To Be Enabled

⚠️ 2 policy check(s) failed. Please address the issues above before this PR can be Reviewed.

🚫 Please fix the failed policies

  • ❌ Branch Name
  • ❌ PR Title/Description

The Not ready to Review label was added to this PR. Once all policies pass, the label is removed automatically.

📖 Need help? See the Policy FAQ for details on every check and how to fix failures.

@therock-pr-bot

therock-pr-bot Bot commented Jul 1, 2026

Copy link
Copy Markdown

🚫 Please fix the failed policies before requesting reviews.

The following policy checks failed:

  • ❌ Branch Name
  • ❌ PR Title/Description

The Not ready to Review label has been added to this PR.
Once all policies pass, the label will be removed automatically.

@ozturkosu ozturkosu closed this Jul 1, 2026
@ozturkosu ozturkosu deleted the muozturk/gemm-bridge-all-layouts-bf16 branch July 1, 2026 05:06
@ozturkosu ozturkosu changed the title [CK_TILE] TE -> Dispatcher GEMM bridge (all layouts + fp16/bf16) feat(ck_tile): TE -> Dispatcher GEMM bridge (all layouts, fp16/bf16) Jul 1, 2026
@ozturkosu ozturkosu restored the muozturk/gemm-bridge-all-layouts-bf16 branch July 1, 2026 05:08
@ozturkosu ozturkosu reopened this Jul 1, 2026
@ozturkosu ozturkosu closed this Jul 1, 2026
@ozturkosu ozturkosu deleted the muozturk/gemm-bridge-all-layouts-bf16 branch July 1, 2026 05:09
@ozturkosu ozturkosu restored the muozturk/gemm-bridge-all-layouts-bf16 branch July 1, 2026 05:09
@ozturkosu ozturkosu reopened this Jul 1, 2026
@ozturkosu

Copy link
Copy Markdown
Contributor Author

Superseded by #8997, which is the same commit (2361d88) on a policy-compliant branch (users/muozturk/ck-tile/gemm-bridge-all-layout-bf16-fp16). Closing this in favor of #8997 — please continue review there.

@ozturkosu ozturkosu closed this Jul 1, 2026
ozturkosu added a commit that referenced this pull request Jul 7, 2026
…8997)

> Re-opened from #8479 with a compliant branch name
(users/muozturk/ck-tile/gemm-bridge-all-layout-bf16-fp16). Supersedes
#8479.

## Summary

This PR routes the **Tile Engine (TE) regular-GEMM sweep through the
Dispatcher**,
making the Dispatcher the single source of truth for **codegen → build →
runtime**
while the Tile Engine keeps only the **config search space** and the
**benchmark
loop**. It is the consolidated, **single-commit** GEMM bridge covering
**all four
layouts (`rcr`/`rrr`/`crr`/`ccr`)** and **both `fp16` and `bf16`**.

It is a clean re-roll of the earlier bridge work (previously split
across
#8123 + the stacked key/bf16/layouts/parity/example PRs and consolidated
in
#8261). Those branches accumulated unrelated cross-project commits
through repeated
`develop` merges; **this branch is a single clean commit off the latest
`develop`**
containing only the GEMM-bridge files. It supersedes and replaces #8123
/ #8261.

## Motivation

The Tile Engine historically owned its own codegen/build/runtime for
GEMM
(`tile_engine/ops/gemm/gemm_universal/`). The consolidation goal is for
the
**Dispatcher** to own all of that — exactly as it already does for
**FMHA** and
**Grouped Conv** — so there is one kernel-generation/build/runtime path
and the
TE shrinks to a config+benchmark frontend. This PR brings regular GEMM
in line
with that reference binding.

## The binding (mirrors the FMHA/Conv reference, six stages)

1. **Config JSON (TE side)** — the sweep search space lives in
   `tile_engine/ops/gemm/configs/` (flat op-root layout, matching the
   `fmha/` and `grouped_conv/` bridges).
2. **Codegen (Dispatcher)** —
`dispatcher/codegen/unified_gemm_codegen.py` emits
   one fully-typed `.hpp` per kernel; `GemmKernelConfig.name` reproduces
`KERNEL_NAME` **byte-for-byte** (the thread tying config → kernel →
runtime).
3. **Compile to `.so`** — a single static `gemm_ctypes_lib.cpp` is
force-included
   (`-include <kernel.hpp>`); one `.so` per kernel.
4. **Flat `extern "C"` ABI** — `dispatcher_run_gemm(A, B, C, M, N, K,
time_ms)` +
the kernel-name enumeration entry points. **Host-pointer** memory model
(the C
lib `hipMalloc`s internally) — the FMHA-forward branch of the reference.
5. **Python ctypes wrapper** — `dispatcher/python/gemm_utils.py`
   (`GemmDispatcherLib` + `GpuGemmRunner`).
6. **TE driver (3 phases)** — `gemm_full_benchmark.py` (parallel
codegen+build →
`expand_sweep` → subprocess-isolated benchmark) + the disposable
per-kernel
   worker `run_one_gemm_kernel.py`.

## What's included

**Bridge core**
- `dispatcher/codegen/unified_gemm_codegen.py` — GEMM codegen,
byte-exact naming.
- `dispatcher/bindings/ctypes/gemm_ctypes_lib.cpp` — flat C ABI,
host-pointer model.
- `dispatcher/python/gemm_utils.py` — `GemmKernelConfig`, multi-kernel
build
(`setup_multiple_gemm_dispatchers`), `expand_sweep`,
one-`.so`-per-kernel.
- `tile_engine/ops/gemm/gemm_full_benchmark.py` +
`run_one_gemm_kernel.py` —
  3-phase, multi-GPU, subprocess-isolated driver/worker.

**Feature surface (the point of this PR)**
- **All four layouts** `rcr`/`rrr`/`crr`/`ccr` (row-major C only —
ck_tile rejects
  column-major C at build) with layout-aware host transpose.
- **`fp16` + `bf16`** (bf16 via uint16 byte-encoding; dtype derived from
kernel name).
- **Trait-derived registry `KernelKey`** — replaces the earlier
hard-coded
fp16/rcr key so the registry path generalizes across dtype/layout/tile.

**Correctness & performance hygiene**
- **`--verify`** opt-in fp32 numpy-reference gate (global
`max|out-ref|/max|ref|`),
`verified`/`max_rel` columns in the CSV; a mismatch counts as a failure.
- **Tile Engine AMDGPU `-mllvm` codegen-flag parity** (without these the
kernel
  builds with different occupancy and the timing diverges) and
  **arch-validated tile filtering** against the real pipeline/scheduler.
- **Multi-GPU** fan-out across all visible GPUs (`--devices`,
device-pinned
  `HIP_VISIBLE_DEVICES` workers).

**Example & tests**
- `dispatcher/examples/gemm/python/12_te_bridge.py` — runnable
end-to-end example.
- `dispatcher/tests/test_gemm_parity.py`, `test_gemm_utils.py`, and a
parity
  regression harness.

**Cleanup**
- Removes the legacy standalone `gemm_universal` build path
  (`gemm_universal_instance_builder.py`, `*_benchmark*.{py,cpp,hpp}`,
`gemm_universal/CMakeLists.txt`) and the old
`test/ck_tile/gemm_tile_engine/`
  harness; promotes the sweep configs to the flat op-root `configs/`.

## Design decisions (consistent with the reference)

- **Host-pointer memory ownership** (C lib owns device memory) — matches
FMHA-forward; the Python runner passes host numpy arrays straight
through.
- **One `.so` per kernel** — packaging choice; the multi-kernel name ABI
is
retained (`get_kernel_name_at(0)` reports the single kernel), so the
Python
  enumeration path is unchanged from FMHA/Conv.
- **Flat `configs/`** at the op root — matches the
`fmha/`/`grouped_conv/`
convention; the not-yet-bridged variants keep their per-variant
`configs/`
  dirs, selected by `--variant`.

## Validation (gfx942 / MI300X)

- Bridge build + benchmark + `--verify` across **`fp16` and `bf16`** and
**all
  four layouts**, checked against an fp32 numpy reference (`A @ B`).
- **Name parity** holds end-to-end: each `.so`'s reported runtime name
equals
  `GemmKernelConfig(...).name`.
- bf16 passes under a widened fp16/bf16 tolerance; fp16 within the
standard
  `max_rel` gate.

## Test plan

- [ ] `gemm_full_benchmark.py --verify` over
`configs/default_ci_config.json` for
      `fp16` and `bf16`, each of `rcr`/`rrr`/`crr`/`ccr`.
- [ ] `unified_gemm_codegen.py` emits a header whose stem ==
`GemmKernelConfig.name`.
- [ ] `setup_multiple_gemm_dispatchers` builds + links each config
against
      `gemm_ctypes_lib.cpp`.
- [ ] `pytest dispatcher/tests/test_gemm_parity.py
dispatcher/tests/test_gemm_utils.py`.
- [ ] `examples/gemm/python/12_te_bridge.py` runs end to end.

## Notes

- Single clean commit off the latest `develop`; the diff is **35 files,
all under
`projects/composablekernel/`** (dispatcher + tile_engine/ops/gemm +
test/ck_tile).
- **Supersedes #8123 and #8261**, which will be closed.
- Stream-K (#8136) and grouped GEMM are separate bridge efforts, not in
this PR.

---------

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: Muhammed Ozturk <muozturk@ctr2-alola-ctrl-01.amd.com>
ozturkosu added a commit that referenced this pull request Jul 9, 2026
…outs) (#8998)

## Summary

Extends the Tile Engine ↔ Dispatcher GEMM **bridge** to the remaining
data types TE's plain GEMM has MFMA warp tiles for, beyond the fp16/bf16
surface of #8479:

- **fp8** (E4M3) and **bf8** (E5M2) → fp16 output, fp32 accumulate
- **int8** → int32 output and accumulate (gfx942)

All four A/B layout combinations per dtype (row-major C only, matching
#8479). `fp32`/`fp64` are intentionally **excluded** — they appear in
TE's dtype-string map but have no MFMA warp tiles in
`GEMM_WARP_TILE_SUPPORTED_COMBINATIONS`, so no kernel can be
generated/run.

**Depends on the fp16/bf16 bridge in #8997**
(`users/muozturk/ck-tile/gemm-bridge-all-layout-bf16-fp16`), which
carries the bridge infrastructure and is not yet merged. This PR targets
`develop`, so until #8997 merges its diff also includes the base bridge
changes; please merge #8997 first.

## Changes

- **Codegen** (`codegen_common.py`, `unified_gemm_codegen.py`): add
`int32` to the dtype maps; `get_output_dtype` int8→int32; new
`get_acc_dtype` (int8→int32, else fp32); derive
`AccDataType`/`CDataType`, the `GEMM_KEY_DTYPE_{C,ACC}` macros, and the
registry `dtype_c`/`dtype_acc` from the dtype instead of hard-coding
`float`/`fp32`.
- **Host harness** (`gemm_utils.py`): fp8/bf8 **FNUZ** (gfx942) uint8
codecs — exact decode (matches device `fp8_t`/`bf8_t`),
nearest-representable saturating encode (same pattern as the existing
bf16 helper); `GpuGemmRunner.run` encodes A/B and sizes the C buffer per
dtype; `expand_sweep` sets `dtype_c`/`dtype_acc`.
- **Tests**: `test_gemm_utils.py` adds CPU-only fp8/bf8 codec +
output-dtype tests (all green); `test_gemm_parity.py` adds fp8/bf8/int8
cases with dtype-aware inputs/references/tolerances (int8 is bit-exact),
GPU-gated like the existing cases.

## Verification done

- `test_gemm_utils.py` + `test_codegen_common.py`: **54 passed** (CPU).
- Codegen smoke: fp8/int8/fp16 each generate 1 kernel + 1 wrapper, 0
failed; emitted `ADataType/CDataType/AccDataType` and `GEMM_KEY_*`
macros are correct (int8→int32_t acc/C; fp8→fp16_t C).
- `test_gemm_parity.py` collects 60 cases and skips cleanly without a
GPU.
- The 16 unrelated failures in `test_examples_integration` /
`test_grouped_conv_codegen` / `test_library_caching` are
**pre-existing** (verified identical on the base branch; they require a
built dispatcher `.a` / GPU).

## Test plan

- [x] Merge #8997 (fp16/bf16 bridge), then this reduces to just the
fp8/bf8/int8 delta on `develop`.
- [x] On an MI300X (gfx942) node: run `python3
tests/test_gemm_parity.py` and confirm fp8/bf8/int8 parity; tune the
fp8/bf8 tolerances if needed (current values are first-cut headroom).
- [x] FNUZ vs OCP: the fp8/bf8 host codec targets the gfx942 FNUZ
format; validate / extend for gfx950 (OCP) before enabling there.

---------

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Muhammed Ozturk <muozturk@ctr2-alola-ctrl-01.amd.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants