Files
TheRock/rocm-libraries/projects/composablekernel/dispatcher/examples/fmha/python/08_heuristics.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

349 lines
9.8 KiB
Python

#!/usr/bin/env python3
# Copyright (c) Advanced Micro Devices, Inc., or its affiliates.
# SPDX-License-Identifier: MIT
"""
Example 08: Kernel Selection Heuristics
Demonstrates how to build multiple FMHA kernels with different tile
sizes and select the best kernel for a given problem. Shows that
smaller tiles tend to be better for short sequences while larger tiles
are better for long sequences.
Usage:
python3 08_heuristics.py
python3 08_heuristics.py --help
python3 08_heuristics.py --arch gfx950
"""
import sys
import argparse
from pathlib import Path
from dataclasses import dataclass
from typing import List
sys.path.insert(0, str(Path(__file__).parent.parent.parent.parent / "python"))
import numpy as np
from fmha_utils import (
FmhaKernelConfig,
FmhaProblem,
FmhaRegistry,
detect_gpu_arch,
)
@dataclass
class TileProfile:
"""A kernel profile tagged with a human-readable label."""
label: str
config: FmhaKernelConfig
category: str # "small", "medium", "large"
def build_tile_profiles(arch: str) -> List[TileProfile]:
"""Create kernel configs with varying tile sizes."""
return [
TileProfile(
label="small_64x64",
category="small",
config=FmhaKernelConfig(
family="fwd",
data_type="fp16",
hdim_q=128,
hdim_v=128,
pipeline="qr_async",
# Stage 0 (Q*K^T): seqlen_q x seqlen_k x hdim_q
tile_m0=64,
tile_n0=64,
tile_k0=32,
# Stage 1 (Attn*V): hdim_v x seqlen_k x alignment
tile_n1=128,
tile_k1=32,
tile_k0max=128,
# Wave config per stage
wave_m0=4,
wave_n0=1,
wave_k0=1,
wave_m1=4,
wave_n1=1,
wave_k1=1,
# Warp tile per stage
warp_m0=16,
warp_n0=16,
warp_k0=16,
warp_m1=16,
warp_n1=16,
warp_k1=16,
gfx_arch=arch,
),
),
TileProfile(
label="medium_128x128",
category="medium",
config=FmhaKernelConfig(
family="fwd",
data_type="fp16",
hdim_q=128,
hdim_v=128,
pipeline="qr_async",
tile_m0=128,
tile_n0=128,
tile_k0=32,
tile_n1=128,
tile_k1=32,
tile_k0max=128,
wave_m0=4,
wave_n0=1,
wave_k0=1,
wave_m1=4,
wave_n1=1,
wave_k1=1,
warp_m0=32,
warp_n0=32,
warp_k0=16,
warp_m1=32,
warp_n1=32,
warp_k1=16,
gfx_arch=arch,
),
),
TileProfile(
label="large_128x256",
category="large",
config=FmhaKernelConfig(
family="fwd",
data_type="fp16",
hdim_q=128,
hdim_v=128,
pipeline="qr_async",
tile_m0=128,
tile_n0=256,
tile_k0=32,
tile_n1=128,
tile_k1=32,
tile_k0max=128,
wave_m0=4,
wave_n0=1,
wave_k0=1,
wave_m1=4,
wave_n1=1,
wave_k1=1,
warp_m0=32,
warp_n0=32,
warp_k0=16,
warp_m1=32,
warp_n1=32,
warp_k1=16,
gfx_arch=arch,
),
),
TileProfile(
label="medium_qr_128x128",
category="medium",
config=FmhaKernelConfig(
family="fwd",
data_type="fp16",
hdim_q=128,
hdim_v=128,
pipeline="qr",
tile_m0=128,
tile_n0=128,
tile_k0=32,
tile_n1=128,
tile_k1=32,
tile_k0max=128,
wave_m0=4,
wave_n0=1,
wave_k0=1,
wave_m1=4,
wave_n1=1,
wave_k1=1,
warp_m0=32,
warp_n0=32,
warp_k0=16,
warp_m1=32,
warp_n1=32,
warp_k1=16,
pad_s=False,
pad_sk=False,
pad_d=True,
pad_dv=True,
gfx_arch=arch,
),
),
]
def select_kernel_heuristic(seqlen: int, profiles: List[TileProfile]) -> TileProfile:
"""Simple heuristic: pick tile size category based on sequence length."""
if seqlen <= 64:
target = "small"
elif seqlen <= 256:
target = "medium"
else:
target = "large"
candidates = [p for p in profiles if p.category == target]
if not candidates:
candidates = profiles
return candidates[0]
def main():
parser = argparse.ArgumentParser(
description="FMHA Heuristics - kernel selection by problem size",
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog="""
Examples:
python3 08_heuristics.py
python3 08_heuristics.py --arch gfx950
""",
)
parser.add_argument("--arch", default=detect_gpu_arch())
args = parser.parse_args()
print("=" * 70)
print("Example 08: Kernel Selection Heuristics")
print("=" * 70)
# Step 1: Build kernel pool
print("\nStep 1: Build Kernel Pool")
profiles = build_tile_profiles(args.arch)
reg = FmhaRegistry("heuristic_pool")
for p in profiles:
reg.register_kernel(p.config)
print(f" Profiles: {len(profiles)}")
for i, p in enumerate(profiles, 1):
tile_str = f"{p.config.tile[0]}x{p.config.tile[1]}"
print(
f" [{i}] {p.label:<25} tile={tile_str:<10} pipeline={p.config.pipeline}"
)
print("\n Building kernels ...")
build_results = reg.build(verbose=False)
built = sum(1 for r in build_results if r.success)
print(f" Built: {built}/{len(profiles)}")
for i, r in enumerate(build_results):
tag = "OK" if r.success else f"FAIL: {r.error[:40]}"
print(f" [{i + 1}] {profiles[i].label}: {tag}")
if built == 0:
print(" No kernels built -- aborting")
return 1
# Step 2: Run each kernel on multiple sequence lengths
print("\n" + "=" * 70)
print("Step 2: Benchmark Across Sequence Lengths")
print("=" * 70)
test_seqlens = [32, 64, 128, 256, 512]
header = f" {'SeqLen':>7}"
for p in profiles:
header += f" | {p.label:>18}"
header += " | {'Best':>18}"
print(f"\n {'SeqLen':>7}", end="")
for p in profiles:
print(f" | {p.label:>18}", end="")
print(f" | {'Best':>18}")
print(" " + "-" * (10 + 21 * len(profiles) + 22))
for seqlen in test_seqlens:
prob = FmhaProblem(
batch=2,
nhead_q=8,
nhead_k=8,
seqlen_q=seqlen,
seqlen_k=seqlen,
hdim_q=128,
hdim_v=128,
)
np.random.seed(42)
Q = (np.random.randn(*prob.q_shape()) * 0.5).astype(np.float16)
K = (np.random.randn(*prob.k_shape()) * 0.5).astype(np.float16)
V = (np.random.randn(*prob.v_shape()) * 0.5).astype(np.float16)
row = f" {seqlen:>7}"
best_tflops = 0.0
best_label = "---"
for j, (p, r) in enumerate(zip(profiles, build_results)):
if not r.success or r.runner is None:
row += f" | {'N/A':>18}"
continue
res = r.runner.run(Q, K, V, prob)
if res.success:
cell = f"{res.tflops:.2f} TFLOPS"
row += f" | {cell:>18}"
if res.tflops > best_tflops:
best_tflops = res.tflops
best_label = p.label
else:
row += f" | {'ERR':>18}"
row += f" | {best_label:>18}"
print(row)
# Step 3: Demonstrate heuristic selection
print("\n" + "=" * 70)
print("Step 3: Heuristic Selection Demo")
print("=" * 70)
print(f"\n {'SeqLen':>7} {'Selected':>25} {'TFLOPS':>10} {'Status':<6}")
print(" " + "-" * 55)
for seqlen in test_seqlens:
selected = select_kernel_heuristic(seqlen, profiles)
idx = profiles.index(selected)
r = build_results[idx]
if not r.success or r.runner is None:
print(f" {seqlen:>7} {selected.label:>25} {'---':>10} {'SKIP':<6}")
continue
prob = FmhaProblem(
batch=2,
nhead_q=8,
nhead_k=8,
seqlen_q=seqlen,
seqlen_k=seqlen,
hdim_q=128,
hdim_v=128,
)
np.random.seed(42)
Q = (np.random.randn(*prob.q_shape()) * 0.5).astype(np.float16)
K = (np.random.randn(*prob.k_shape()) * 0.5).astype(np.float16)
V = (np.random.randn(*prob.v_shape()) * 0.5).astype(np.float16)
res = r.runner.run(Q, K, V, prob)
if res.success:
print(f" {seqlen:>7} {selected.label:>25} {res.tflops:>10.2f} {'OK':<6}")
else:
print(f" {seqlen:>7} {selected.label:>25} {'---':>10} {'FAIL':<6}")
# Cleanup
for r in build_results:
if r.runner:
r.runner.cleanup()
print("\n" + "=" * 70)
print("Heuristic Insight:")
print(" - Small tiles: low overhead for short sequences")
print(" - Large tiles: high throughput for long sequences")
print(" - Pipeline choice also matters (qr vs qr_async)")
print("=" * 70)
return 0
if __name__ == "__main__":
sys.exit(main())