Skip to content

Commit 9faa8de

Browse files
ozturkosuCopilotMuhammed Ozturk
authored
feat(ck-tile): add grouped GEMM variant to TE to dispatcher bridge (#9000)
> Re-opened from #8130 with a policy-compliant branch name (`users/muozturk/ck-tile/dispatcher-te-bridge-grouped-gemm`). Supersedes #8130. ## What this PR does Routes the **grouped_gemm** variant through the Tile Engine (TE) → Dispatcher **bridge**: TE only generates configs and benchmarks; the Dispatcher owns codegen, build, and runtime. This is the grouped counterpart of the regular-GEMM bridge (#8123/#8479), the fp8/bf8/int8 bridge (#8887), and the Stream-K bridge (#8136). **This PR now also contains the grouped Dispatcher codegen** that previously lived in #8075 — that PR has been **closed in favor of this one** to keep the grouped codegen in a single place (it was otherwise duplicated across both). ## Why grouped needs special handling Grouped GEMM is **multi-problem**: one launch runs a *list* of `(M, N, K)` sub-problems with arrays of A/B/C device pointers. 1. The single-problem run path (`g_dispatcher->run` / `GemmHostArgs`) cannot express a list of problems. 2. The generated registry wrapper (`generated_tile_backend.hpp::run()`) hard-codes the single-problem launch and won't compile against a grouped `SelectedKernel`. So the grouped path **bypasses the registry**: a dedicated ctypes lib calls the generated `SelectedKernel::launch(descs, stream)` directly and reports the name from the compile-time `KERNEL_NAME` macro. ## Changes **Codegen (absorbed from #8075)** - `codegen/arch_filter.py` — `GEMM_GROUPED` operator tile constraints. - `codegen/unified_gemm_codegen.py` — `GemmVariant.GROUPED`, the grouped launch generator (DeviceMem internal workspace via `MakeKargs`, persistent/non-persistent grid), `grouped` in `--variants`. - `examples/gemm/cpp/02_grouped_gemm_driver.cpp` — standalone, layout/dtype-generic grouped driver with per-group reference verification. - `codegen/README.md` + `examples/gemm/cpp/README.md` — grouped sections. **Bridge** - `bindings/ctypes/grouped_gemm_ctypes_lib.cpp` — multi-problem, registry-bypass C ABI; per-group device alloc/copy; strides derived from the compile-time `ALayout/BLayout/CLayout`; warmup/repeat timing matched to Old-TE (`CK_TILE_BENCH_WARMUP/REPEAT`). - `python/gemm_utils.py` — `GroupedGemmProblem`/`GroupedGemmResult`, `GpuGroupedGemmRunner`, `run_grouped`, fp16/bf16/fp8(E4M3 FNUZ)/bf8(E5M2 FNUZ) codecs, output-dtype-aware C buffer. - `tile_engine/ops/gemm/grouped_gemm_full_benchmark.py` + `run_one_grouped_gemm_kernel.py` — TE driver + worker for the parity sweep. - `bindings/ctypes/GROUPED_GEMM_BRIDGE.md` — design README. ## Coverage (= Old-TE grouped runnable set on develop) | Layout \ Dtype | fp16 | bf16 | fp8 (E4M3) | bf8 (E5M2) | |---|---|---|---|---| | rcr / rrr / ccr / crr | ✓ | ✓ | ✓ | ✓ | C is always row-major. `int8` (rejected by the TE grouped builder) and `fp32`/`fp64` (no MFMA warp tiles) are excluded on both sides. ## Parity vs Old-TE (MI300X / gfx942) Apples-to-apples (same warmup=50/repeat=100 both sides, A/B interleaved, single GPU, both engines rebuilt fresh, stale-`.so` guard, matched compile flags): - **Correctness: 64/64 PASS.** - **Performance: 64/64 within ±15%.** - The 5 small-shape (1024³ fp8/bf8) rows that initially read >15% were proven by `rocprof` to be a **measurement-harness artifact** (Old-TE's JSON `latency(ms)` rounded to 2 decimals → 30–50% TFLOPS swing on ~0.02 ms kernels), **not** a kernel/codegen difference — bridge and Old-TE launch byte-identical kernels (same grid/VGPR/SGPR, duration ≤3.22%); full-precision re-measure collapses all 5 to <3%. ## Notes - Targets `develop`. Depends on #8997 (fp16/bf16 bridge) and #8998 (fp8/bf8/int8 bridge) merging to `develop` first; until then this PR's diff also shows their content, after which it reduces to the grouped-only files. - Supersedes #8075 (closed). --------- 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> Co-authored-by: Muhammed Emin Ozturk <3836908+ozturkosu@users.noreply.github.com>
1 parent 1c6b7f7 commit 9faa8de

12 files changed

Lines changed: 1654 additions & 22 deletions

File tree

projects/composablekernel/dispatcher/bindings/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ This directory contains language bindings for the CK Tile Dispatcher.
88
bindings/
99
|---- ctypes/ # Python ctypes bindings (C API)
1010
| |---- gemm_ctypes_lib.cpp # GEMM dispatcher C API
11+
| |---- grouped_gemm_ctypes_lib.cpp # Grouped (multi-problem) GEMM bridge C API -- see GROUPED_GEMM_BRIDGE.md
1112
| |---- conv_ctypes_lib.cpp # Grouped conv dispatcher C API (fwd + bwd_data)
1213
| |---- conv_bwdw_ctypes_lib.cpp # Grouped conv backward weight C API (separate library)
1314
| |---- fmha_ctypes_lib.cpp # FMHA dispatcher C API (fwd + bwd)
Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
<!--
2+
Copyright (c) Advanced Micro Devices, Inc., or its affiliates.
3+
SPDX-License-Identifier: MIT
4+
-->
5+
6+
# Grouped GEMM: Tile Engine -> Dispatcher Bridge
7+
8+
This document describes the **grouped_gemm** variant of the Tile Engine (TE) ->
9+
Dispatcher bridge (PR #8130). It is the grouped counterpart of the regular-GEMM
10+
bridge (#8123/#8479), the fp8/bf8/int8 bridge (#8887), and the Stream-K bridge
11+
(#8136).
12+
13+
## What the bridge is
14+
15+
In the bridge model the **Dispatcher is the single source of truth** for
16+
codegen, build, and runtime; **Tile Engine only generates configs and
17+
benchmarks** them. TE no longer carries its own kernel-instance build path —
18+
it shells out to the dispatcher codegen and runs the resulting kernel.
19+
20+
For most variants the dispatcher runs a kernel through its registry/backend.
21+
Grouped GEMM cannot use that path (see below), so the grouped bridge takes the
22+
same approach as Stream-K: a dedicated ctypes library that **bypasses the
23+
registry** and calls the generated `SelectedKernel::launch(...)` directly.
24+
25+
## Why grouped needs special handling
26+
27+
Grouped GEMM is **multi-problem**: a single launch runs a *list* of `(M, N, K)`
28+
sub-problems, each with its own A/B/C device pointers. Two consequences:
29+
30+
1. The single-problem run path (`g_dispatcher->run` / `GemmHostArgs`) cannot
31+
express a list of problems.
32+
2. The generated registry wrapper (`generated_tile_backend.hpp::run()`)
33+
hard-codes the single-problem `SelectedKernel::launch(GemmHostArgs, ...)`
34+
signature and will not compile against a grouped `SelectedKernel`.
35+
36+
So the grouped kernel header exposes a different launch signature
37+
38+
```cpp
39+
static float launch(const std::vector<ck_tile::GroupedGemmHostArgs<>>& descs,
40+
const stream_config& stream);
41+
```
42+
43+
and the grouped ctypes lib force-includes one generated kernel header
44+
(`-include ..._grouped.hpp` with `CK_TILE_SINGLE_KERNEL_INCLUDE`), calls that
45+
`launch` directly, and reports the kernel name from the compile-time
46+
`KERNEL_NAME` macro.
47+
48+
## Components
49+
50+
| Layer | File | Role |
51+
|---|---|---|
52+
| Codegen | `dispatcher/codegen/unified_gemm_codegen.py` | `GemmVariant.GROUPED`; `_launch_function_grouped` (DeviceMem internal workspace, `MakeKargs`, persistent/non-persistent grid). Kept in lockstep with PR #8075. |
53+
| Codegen | `dispatcher/codegen/arch_filter.py` | `GEMM_GROUPED` operator tile constraints. |
54+
| C API | `dispatcher/bindings/ctypes/grouped_gemm_ctypes_lib.cpp` | Multi-problem ABI; per-group device alloc/copy; layout-derived strides; warmup/repeat timing. |
55+
| Python | `dispatcher/python/gemm_utils.py` | `GroupedGemmProblem` / `GroupedGemmResult`, `GpuGroupedGemmRunner`, `run_grouped`, `build_grouped`, dtype/layout codecs. |
56+
| Python | `dispatcher/python/ctypes_utils.py` | Threads the `grouped` variant into the codegen `--variants` flag. |
57+
| TE driver | `tile_engine/ops/gemm/grouped_gemm_full_benchmark.py` | Generates configs, builds `.so`s in parallel, benchmarks in disposable workers. |
58+
| TE worker | `tile_engine/ops/gemm/run_one_grouped_gemm_kernel.py` | Runs one grouped kernel; dtype/layout-aware operand generation. |
59+
60+
## C ABI
61+
62+
```c
63+
int dispatcher_init(void); // lightweight no-op (no registry)
64+
int dispatcher_run_grouped_gemm(
65+
int group_count,
66+
const int64_t* Ms, // [group_count]
67+
const int64_t* Ns, // [group_count]
68+
const int64_t* Ks, // [group_count]
69+
const void** A_ptrs, // host A buffers, one per group
70+
const void** B_ptrs, // host B buffers, one per group
71+
void** C_ptrs, // host C out buffers, one per group
72+
float* time_ms); // out: average kernel time
73+
// returns 0 ok, -1 HIP/throw, -2 arguments unsupported by the kernel
74+
```
75+
76+
The lib `hipMalloc`s A/B/C per group, copies A and B host->device, memsets C,
77+
builds `std::vector<ck_tile::GroupedGemmHostArgs<>>` with **strides derived from
78+
the compile-time `ALayout`/`BLayout`/`CLayout`** of the `-include`d header
79+
(`std::is_same_v<…, RowMajor>`), launches once, then copies each C back. The ABI
80+
is `void*` + element-size, so it is dtype-agnostic; the Python runner owns the
81+
numpy codecs.
82+
83+
## Coverage
84+
85+
The bridge runnable set is exactly the Old-TE grouped_gemm runnable set on
86+
`develop` — no more, no less:
87+
88+
| Layout \ Dtype | fp16 | bf16 | fp8 (E4M3) | bf8 (E5M2) |
89+
|---|---|---|---|---|
90+
| rcr |||||
91+
| rrr |||||
92+
| ccr |||||
93+
| crr |||||
94+
95+
- **Matrix C is always row-major** (grouped builder constraint), so the layout
96+
string varies A/B only.
97+
- **Excluded:** `int8` (rejected by the TE grouped builder), `fp32`/`fp64`
98+
(no MFMA warp tiles). These are excluded on both sides.
99+
- fp8/bf8 use the **FNUZ** encoding on gfx942 (matches the regular #8887 path);
100+
the Python codecs require `ml_dtypes`.
101+
102+
## Building and running
103+
104+
Generate + build one grouped `.so` and run the A/B parity sweep vs Old-TE:
105+
106+
```bash
107+
# Codegen smoke (no GPU): one variant/dtype/layout
108+
python3 dispatcher/codegen/unified_gemm_codegen.py \
109+
--output-dir /tmp/grp --datatype bf16 --layout ccr \
110+
--variants grouped --config dispatcher/codegen/default_config.json
111+
112+
# Full TE-driven parity sweep (build + benchmark)
113+
python3 tile_engine/ops/gemm/grouped_gemm_full_benchmark.py <config.json> \
114+
--arch gfx942 --dtype fp16 --layout rcr --csv grouped_results.csv
115+
```
116+
117+
Timing knobs `CK_TILE_BENCH_WARMUP` (default 50) and `CK_TILE_BENCH_REPEAT`
118+
(default 100) are honored by **both** the grouped ctypes lib and the registry
119+
backend, so bridge-vs-Old-TE A/B comparisons stay matched. For fair parity keep
120+
`flush_cache=false`, `rotating_count=1`, run on a single GPU, and re-measure any
121+
`|gap|>15%` outlier standalone.

0 commit comments

Comments
 (0)