Files
TheRock/rocm-libraries/projects/composablekernel/dispatcher/tests/test_streamk_registry.py
T
derek.holloway 7502bf64a4
CodeQL / Analyze (actions) (push) Canceled after 0s
CodeQL / Analyze (python) (push) Canceled after 0s
pre-commit / pre-commit (push) Canceled after 0s
Multi-Arch CI / setup (push) Canceled after 0s
Publish build_manylinux_x86_64 images / publish_build_manylinux_x86_64 (push) Canceled after 0s
Setup (Multi-Arch) / setup (push) Canceled after 0s
Multi-Arch CI / Linux::${{ fromJSON(needs.setup.outputs.linux_build_config || '{}').build_variant_label || 'skip' }} (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish no_rocm_image_ubi10 image / publish_no_rocm_image_ubi10 (push) Canceled after 0s
Multi-Arch CI / Windows::${{ fromJSON(needs.setup.outputs.windows_build_config || '{}').build_variant_label || 'skip' }} (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Multi-Arch CI / Manifest Diff (push) Canceled after 0s
TheRock Manifest Diff Report / Generate Manifest Diff Report (push) Canceled after 0s
Multi-Arch CI / CI Summary (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04 (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04_ocl_rt images / publish_no_rocm_image_ubuntu24_04_ocl_rt (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_rocgdb (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04_rocgdb images / publish_no_rocm_image_ubuntu24_04_rocgdb (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_ocl_rt (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Unit Tests / Unit Tests :: ubuntu-24.04 (push) Canceled after 0s
Unit Tests / Unit Tests :: windows-2022 (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_media (push) Canceled after 0s
Unit Tests / Unit Tests Summary (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_openmpi (push) Canceled after 0s
Flatten subprojects into a single base monorepo
2026-09-02 16:34:47 -07:00

267 lines
10 KiB
Python

#!/usr/bin/env python3
# Copyright (c) Advanced Micro Devices, Inc., or its affiliates.
# SPDX-License-Identifier: MIT
"""
Stream-K deep-core registry test (requires a GPU + hipcc).
Guards the deep-core path that lets Stream-K ride the registry like regular GEMM:
codegen -> generated SK wrapper -> Registry -> Dispatcher::run() (workspace alloc
+ strategy-aware reset) -> generated_tile_backend_streamk -> verify vs reference.
Each reduction strategy (atomic/linear/tree) is a *distinct compiled kernel*
(SkReductionStrategy is a compile-time constexpr), so we generate all three from a
single tile config and build the 04 registry driver once per strategy, force-
including that strategy's header. For each we assert:
* the encode_identifier() suffix matches the strategy (..._streamk[_linear|_tree])
* the Dispatcher selects that kernel by Problem::reduction_strategy
* the result verifies against the reference GEMM
The test SKIPs (exit 77) when no GPU or no hipcc is available, so it is safe in
CPU-only CI; it only runs the heavy build+launch where a GPU is present.
Usage:
python3 test_streamk_registry.py
python3 test_streamk_registry.py --arch gfx942 --m 3840 --n 4096 --k 2048
"""
import argparse
import json
import os
import re
import shutil
import subprocess
import sys
import tempfile
from pathlib import Path
DISPATCHER_DIR = Path(__file__).resolve().parent.parent
CK_DIR = DISPATCHER_DIR.parent
CODEGEN = DISPATCHER_DIR / "codegen" / "unified_gemm_codegen.py"
DRIVER = DISPATCHER_DIR / "examples" / "gemm" / "cpp" / "04_streamk_registry_driver.cpp"
REGISTRY_SRC = DISPATCHER_DIR / "src" / "registry.cpp"
DISPATCHER_SRC = DISPATCHER_DIR / "src" / "dispatcher.cpp"
SKIP = 77 # ctest SKIP_RETURN_CODE
# One tile config, all three reduction strategies.
TILE = "128x128x64_2x2x1_32x32x16"
TILE_CONFIG_JSON = json.dumps(
{
"tile_config": {
"tile_m": [128], "tile_n": [128], "tile_k": [64],
"warp_m": [2], "warp_n": [2], "warp_k": [1],
"warp_tile_m": [32], "warp_tile_n": [32], "warp_tile_k": [16],
"block_size": [256],
},
"trait_config": {
"pipeline": ["compv3"], "epilogue": ["cshuffle"], "scheduler": ["intrawave"],
"pad_m": [False], "pad_n": [False], "pad_k": [False], "persistent": [False],
},
"streamk_config": {"reduction_strategy": ["atomic", "linear", "tree"]},
}
)
# strategy -> (header variant suffix, expected encode_identifier suffix)
STRATEGIES = {
"atomic": ("streamk", "_streamk"),
"linear": ("streamk_linear", "_streamk_linear"),
"tree": ("streamk_tree", "_streamk_tree"),
}
# Datatypes the Stream-K dispatcher codegen supports end-to-end. fp8/bf8 inputs
# accumulate in fp32 and write an fp16 C tensor (get_output_dtype), matching
# Tile Engine; the registry identifier keys on the input dtype (dtype_a), so the
# expected encode_identifier prefix is "{dtype}_{layout}" for each.
DATATYPES = ["fp16", "bf16", "fp8", "bf8"]
# Layouts Tile Engine builds Stream-K for (all keep C row-major, which the atomic
# C-reset relies on). Full coverage = DATATYPES x LAYOUTS x STRATEGIES.
LAYOUTS = ["rcr", "rrr", "ccr", "crr"]
def detect_arch(fallback=None):
# Resolve the gfx target without shelling out to rocminfo. Preference order:
# the arch the build already configured with (passed via --arch from
# CMakeLists.txt) is handled by the caller; here we fall back to the standard
# ROCm environment variables and then the amdgpu-arch / offload-arch LLVM
# tools, which query the driver directly and ship with the ROCm/LLVM toolchain.
for env in ("PYTORCH_ROCM_ARCH", "HCC_AMDGPU_TARGET", "AMDGPU_TARGETS", "GPU_TARGETS"):
val = os.environ.get(env)
if val:
return re.split(r"[;,]", val)[0].strip()
for tool in ("amdgpu-arch", "offload-arch"):
exe = shutil.which(tool)
if exe:
try:
out = run([exe], timeout=30).stdout
m = re.search(r"\bgfx[0-9a-f]+\b", out)
if m:
return m.group(0)
except Exception:
pass
return fallback
def run(cmd, **kw):
return subprocess.run(cmd, capture_output=True, text=True, **kw)
def main():
ap = argparse.ArgumentParser()
ap.add_argument("--arch", default=None)
ap.add_argument("--m", type=int, default=3840)
ap.add_argument("--n", type=int, default=4096)
ap.add_argument("--k", type=int, default=2048)
ap.add_argument(
"--datatypes", default=",".join(DATATYPES),
help="Comma-separated datatypes to test (default: all TE-equivalent).",
)
ap.add_argument(
"--layouts", default=",".join(LAYOUTS),
help="Comma-separated layouts to test (default: all TE-equivalent).",
)
args = ap.parse_args()
datatypes = [d.strip() for d in args.datatypes.split(",") if d.strip()]
layouts = [l.strip() for l in args.layouts.split(",") if l.strip()]
hipcc = shutil.which("hipcc")
if not hipcc:
print("SKIP: hipcc not found")
return SKIP
arch = args.arch or detect_arch()
if not arch:
print("SKIP: no GPU / could not detect gfx arch")
return SKIP
print(f"Stream-K registry test on {arch} @ {args.m}x{args.n}x{args.k}")
inc = ["-I", str(CK_DIR / "include"), "-I", str(DISPATCHER_DIR / "include")]
with tempfile.TemporaryDirectory(prefix="sk_reg_test_") as td:
# Build the dtype-independent core objects once (no force-include).
reg_o, disp_o = Path(td) / "registry.o", Path(td) / "dispatcher.o"
for src, obj in ((REGISTRY_SRC, reg_o), (DISPATCHER_SRC, disp_o)):
c = run(
[hipcc, "-std=c++17", f"--offload-arch={arch}", "-O3", *inc,
"-c", str(src), "-o", str(obj)],
timeout=900,
)
if c.returncode != 0:
print(f"FAIL: compiling {src.name}\n" + c.stderr[-2000:])
return 1
failures = []
for dtype in datatypes:
for layout in layouts:
failures += run_for_combo(
dtype, layout, td, arch, args, hipcc, inc, reg_o, disp_o
)
if failures:
print("\nSTREAM-K REGISTRY TEST FAILED:")
for f in failures:
print(" - " + f)
return 1
print(
"All Stream-K combos registered, dispatched, and verified "
f"(datatypes: {', '.join(datatypes)} | layouts: {', '.join(layouts)})."
)
return 0
def run_for_combo(dtype, layout, td, arch, args, hipcc, inc, reg_o, disp_o):
"""Generate + build + run all reduction strategies for one (dtype, layout).
Returns a list of failure strings (empty on success)."""
failures = []
# Verify each built kernel against the CLI shape AND a small-M/N, large-K
# shape. The latter maximizes the Stream-K split factor, which is exactly
# where the split-K-aware verification tolerance matters: a plain single-pass
# tolerance spuriously FAILs correct atomic results on this shape. The driver
# binary is shape-independent, so this only adds runs, not rebuilds.
shapes = [(args.m, args.n, args.k), (128, 128, 16384)]
gen = Path(td) / f"gen_{dtype}_{layout}"
# 1) generate all three strategy headers from one tile config
g = run(
[
sys.executable, str(CODEGEN),
"--datatype", dtype, "--layout", layout,
"--gpu-target", arch, "--variants", "stream_k",
"--tile-config-json", TILE_CONFIG_JSON,
"--output-dir", str(gen),
],
timeout=600,
)
if g.returncode != 0:
return [f"{dtype}/{layout}: codegen failed\n" + g.stderr[-2000:]]
for strat, (variant, want_suffix) in STRATEGIES.items():
tag = f"{dtype}/{layout}/{strat}"
header = gen / (
f"gemm_{dtype}_{layout}_compv3_cshuffle_intrawave_"
f"False_False_False_False_{TILE}_{variant}.hpp"
)
if not header.exists():
failures.append(f"{tag}: generated header missing ({header.name})")
continue
stem = f"{dtype}_{layout}_{variant}"
drv_o, exe = Path(td) / f"d_{stem}.o", Path(td) / f"skreg_{stem}"
c = run(
[hipcc, "-std=c++17", f"--offload-arch={arch}", "-O3",
"-DCK_TILE_SINGLE_KERNEL_INCLUDE", f'-DGFX_ARCH="{arch}"',
*inc, "-I", str(gen), "-include", str(header),
"-c", str(DRIVER), "-o", str(drv_o)],
timeout=900,
)
if c.returncode != 0:
failures.append(f"{tag}: driver compile failed\n{c.stderr[-1500:]}")
continue
l = run(
[hipcc, f"--offload-arch={arch}", str(drv_o), str(disp_o),
str(reg_o), "-o", str(exe)],
timeout=300,
)
if l.returncode != 0:
failures.append(f"{tag}: link failed\n{l.stderr[-1500:]}")
continue
for (sm, sn, sk) in shapes:
r = run(
[str(exe), "--m", str(sm), "--n", str(sn),
"--k", str(sk), "--strategy", strat, "--validate", "1"],
timeout=300,
)
out = r.stdout
ok_verify = "Verification: PASS" in out
# Guard the identifier parse: a crashed/silent driver prints no
# "identifier=" token, so split(...)[1] would raise IndexError and
# abort the run instead of recording a clean failure.
ok_suffix = False
if f"identifier={dtype}_{layout}" in out and "identifier=" in out:
token = out.split("identifier=", 1)[1].split()[0]
ok_suffix = want_suffix in token
if r.returncode != 0 or not ok_verify or not ok_suffix:
failures.append(
f"{tag} @ {sm}x{sn}x{sk}: rc={r.returncode} verify={ok_verify} "
f"suffix_ok={ok_suffix}\n{out[-800:]}{r.stderr[-400:]}"
)
else:
tflops = next(
(ln for ln in out.splitlines() if "TFlops" in ln), ""
).strip()
print(
f" PASS {dtype:5s} {layout:4s} {strat:6s} {sm}x{sn}x{sk} "
f"-> {want_suffix} | {tflops}"
)
return failures
if __name__ == "__main__":
sys.exit(main())