[CK_TILE] Add bf16 support to GEMM TE -> Dispatcher bridge#8190
[CK_TILE] Add bf16 support to GEMM TE -> Dispatcher bridge#8190ozturkosu wants to merge 6 commits into
Conversation
Closes the regular-path dtype-coverage gap (fp16-only) identified when measuring the GEMM bridge against the FMHA reference (PR #5260). The C ABI is already dtype-agnostic (sizes buffers from sizeof(ADataType)) and the codegen already emits bf16 kernels; the only fp16 hard-coding lived in the Python host buffers. - gemm_utils.py: GpuGemmRunner.run() is now dtype-aware. It detects the kernel's real dtype from the compiled kernel name and encodes the host buffers to match. bf16 has no native numpy dtype, so it is carried as a uint16 bit pattern (sizeof(bf16_t) == 2): _fp32_to_bf16_u16 encodes fp32 -> bf16 with round-to-nearest-even, _bf16_u16_to_fp32 decodes the output back to fp32. fp16 path is unchanged. - run_one_gemm_kernel.py: worker now generates fp32 source data so the runner owns all dtype encoding (no double rounding). Stacked on muozturk/dispatcher-gemm-registry-key (PR #8187): bf16 needs the trait-derived KernelKey so the registry reports the correct dtype. Validated on gfx942 (MI300X): 3 bf16 kernels x 2 problems = 6/6 OK, 46-219 TFLOPS; numeric check vs CPU reference max_rel_err 7.7e-3 (within bf16 tolerance). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Adds bfloat16 (bf16) support to the CK Tile GEMM Tile-Engine → Dispatcher bridge by making the Python runner dtype-aware and ensuring host buffers match the compiled kernel’s dtype.
Changes:
- Generate fp32 source matrices in the GEMM worker so dtype encoding is handled centrally in the runner.
- Add dependency-free bf16 host-buffer encoding/decoding in
GpuGemmRunner.run()using uint16 bit patterns. - Infer dtype from the compiled kernel name (
gemm_<dtype>_...) to prevent runner/kernel mismatches.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
| projects/composablekernel/tile_engine/ops/gemm/run_one_gemm_kernel.py | Switches worker input generation to fp32 so the runner performs dtype-specific encoding (fp16/bf16). |
| projects/composablekernel/dispatcher/python/gemm_utils.py | Adds bf16 bit-pattern encode/decode helpers and makes GpuGemmRunner.run() select host buffer dtypes based on the compiled kernel name. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| A_h = _fp32_to_bf16_u16(A) | ||
| B_h = _fp32_to_bf16_u16(np.ascontiguousarray(B.T)) | ||
| C_h = np.zeros((M, N), dtype=np.uint16) |
| def _fp32_to_bf16_u16(x: np.ndarray) -> np.ndarray: | ||
| """Encode fp32 -> bfloat16 bit pattern in a uint16 array (round-to-nearest-even). | ||
|
|
||
| numpy has no native bf16, but the C ABI only cares about the 2-byte memory | ||
| layout (sizeof(bf16_t) == 2 == sizeof(uint16)). Truncating the low 16 bits of | ||
| the fp32 representation with round-to-nearest-even matches ck_tile's bf16. | ||
| """ | ||
| u32 = np.ascontiguousarray(x, dtype=np.float32).view(np.uint32) | ||
| # round-to-nearest-even: add (lsb-of-kept-bits + 0x7FFF) before truncating | ||
| rounding = ((u32 >> 16) & 1) + np.uint32(0x7FFF) | ||
| return ((u32 + rounding) >> 16).astype(np.uint16) | ||
|
|
||
|
|
||
| def _bf16_u16_to_fp32(u16: np.ndarray) -> np.ndarray: | ||
| """Decode a uint16 bf16 bit pattern back to fp32 (low 16 mantissa bits zero).""" | ||
| return (u16.astype(np.uint32) << 16).view(np.float32) |
…spatcher-gemm-bf16
Addresses the Copilot review requests on the bf16 (#8190) and layout (#8191) changes: the bit-level bf16 round-to-nearest-even encoder and the dtype/layout name parsers had no coverage. Adds dispatcher/tests/test_gemm_utils.py (10 CPU-only tests): bf16 exact round-trip, <=2^-8 relative error, tie-to-even, Inf/NaN, uint16 size; dtype/layout parsing with fallbacks; and a GemmKernelConfig.name -> parser round-trip locking the config/codegen/runtime naming contract. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
bf16 RNE encoder now has unit-test coverageAddressing the review request to lock in the bit-level |
…spatcher-gemm-bf16
…spatcher-gemm-bf16
…spatcher-gemm-bf16
This branch's runner encodes bf16 from the kernel name, so add bf16 to SUPPORTED_DTYPES. Layout stays rcr-only here. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Addresses the three review items on the TE->Dispatcher GEMM bridge driver, scoped to this foundation PR's fp16/rcr surface (bf16/layouts follow in the #8190/#8191 stack): 1. Example configs to sweep - gemm_full_benchmark.py defaults to the selected variant's configs/default_ci_config.json (small CI sweep) when no config is passed, and to configs/example_problems.json when --problems is omitted; configs/default_config.json remains the full sweep. - New gemm_universal/configs/example_problems.json (square / rectangular / large M,N,K). Nightly-test JSON drops into the same configs/ dir -- no driver change needed. 2. Multi-GPU launch in parallel (supersedes grouped_conv's serial-GPU design) - Phase 3 fans the (kernel x problem) work across every visible GPU: one worker thread per device pulls batches from a shared queue and spawns a disposable subprocess pinned with HIP_VISIBLE_DEVICES, so an N-GPU box runs ~Nx faster while keeping per-batch fault isolation. - Devices auto-detected (HIP_VISIBLE_DEVICES, then rocm-smi/amd-smi); override with --devices (count, explicit ids, or all). 3. Variant organization + README + deprecation note - --variant selects the per-variant configs/ directory. - New README "Dispatcher Bridge Workflow" section: scripts, per-variant config layout, run examples, multi-GPU explanation, supported surface (fp16/rcr here), and a deprecation note for the legacy *_instance_builder.py generators. Driver --dtype/--layout choices stay fp16/rcr to match this PR's dispatcher host path; run_one_gemm_kernel.py (fp16 host gen) is unchanged.
|
Closing this draft PR as its changes are fully superseded by #8479 ([CK_TILE] TE -> Dispatcher GEMM bridge — all layouts + fp16/bf16). PR #8479 is the consolidated, single-commit re-roll of the entire GEMM bridge work that was previously split across the stacked draft series (#8187 → #8190 → #8191 → #8193). Every file touched by this PR is included in #8479 at the same or an updated path. Please track further work in #8479. |
Summary
Closes the regular-path dtype-coverage gap (fp16/rcr only) identified when
measuring our GEMM Tile-Engine → Dispatcher bridge against the FMHA reference
(PR #5260). FMHA shipped fp16 and bf16; our regular path was fp16-only.
This PR adds bf16, bringing the regular path to dtype parity for the two
core floating-point types.
Draft, stacked on
muozturk/dispatcher-gemm-registry-key(PR #8187).bf16 depends on the trait-derived
KernelKeyfrom #8187: without it a bf16kernel would be registered under the old hard-coded fp16 key and the
registry would misreport its dtype.
Why this is small
The bridge was already 90% dtype-agnostic:
gemm_ctypes_lib.cpp) sizes every device buffer fromsizeof(ADataType)/sizeof(BDataType)/sizeof(CDataType), which come fromthe force-included kernel header — so it already handles bf16 byte layout.
unified_gemm_codegen.pyalready emits bf16 kernels (gemm_bf16_rcr_...).expand_sweepalready threads the--dtypeargument through to the configs.The only fp16 hard-coding was in the Python host buffers in the runner.
The fix
dispatcher/python/gemm_utils.py—GpuGemmRunner.run()is nowdtype-aware. It detects the kernel's real dtype from the compiled kernel
name (
gemm_<dtype>_...) and encodes the host buffers to match. numpy hasno native bf16, so bf16 is carried as a uint16 bit pattern
(
sizeof(bf16_t) == 2 == sizeof(uint16)):_fp32_to_bf16_u16— fp32 → bf16 with round-to-nearest-even_bf16_u16_to_fp32— decodes the kernel's output back to fp32The fp16 path is unchanged.
tile_engine/ops/gemm/run_one_gemm_kernel.py— the worker now generatesfp32 source data so the runner owns all dtype encoding (avoids
double-rounding for bf16).
Design decisions
ml_dtypes/bfloat16as a new dependency. The C ABI only cares about the2-byte memory layout, so a correct bit pattern is sufficient and keeps the
bridge dependency-light.
the runner cannot disagree with the kernel it actually loaded. Single source
of truth = the compiled
.so's reported name.Validation (gfx942 / MI300X)
3 bf16 kernels × 2 problems = 6/6 OK:
Numeric correctness vs CPU reference (bf16-emulated inputs, fp32 accumulate):
max_rel_err 7.7e-3, mean 2.8e-3 — within bf16 precision. Output buffers
fully populated (
non_zero == M*N).Remaining (tracked, not in this PR)
rcr(rrr/rcc/…): the same dtype-agnostic mechanism applies;needs a layout-aware host-buffer transpose, follow-up.
Next