Files
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

615 lines
28 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env python3
# Copyright (c) Advanced Micro Devices, Inc., or its affiliates.
# SPDX-License-Identifier: MIT
"""
Unit tests for codegen/tile_math.py.
The reference lookup tables TILE_TO_WAVE_WARP and _TILE_WTILK_TO_VECS are
used as ground-truth oracles. The mathematical functions in tile_math.py
must generate at least the set of configurations present in those tables
(no false negatives).
Run:
python3 -m pytest dispatcher/tests/test_tile_math.py -v
or:
cd projects/composablekernel/dispatcher
python3 -m pytest tests/test_tile_math.py -v
"""
import sys
import unittest
from pathlib import Path
from typing import Dict, List, Tuple
SCRIPT_DIR = Path(__file__).parent.resolve()
DISPATCHER_DIR = SCRIPT_DIR.parent
sys.path.insert(0, str(DISPATCHER_DIR / "codegen"))
from arch_specs_generated import WARP_SUPPORTED_COMBINATIONS, ELEMENT_SIZE_MAP # noqa: E402
from grouped_conv.tile_math import ( # noqa: E402
get_valid_wave_warp_pairs,
get_valid_vec_sizes,
get_vec_sizes_for_wave_warp,
dtype_keys_for_warp_tile_k,
WARP_SIZE,
)
# =============================================================================
# Reference oracle tables (ground truth extracted from CK profiler configs)
#
# They live here as static test fixtures: the math functions in tile_math.py must generate
# at least every entry present in these tables (zero false negatives).
# =============================================================================
TILE_TO_WAVE_WARP: Dict[Tuple[int, int, int], List[Tuple[Tuple, Tuple]]] = {
(16, 16, 32): [((1, 1, 1), (16, 16, 8)), ((1, 1, 1), (16, 16, 16))],
(16, 16, 64): [((1, 1, 1), (16, 16, 8)), ((1, 1, 1), (16, 16, 16))],
(16, 16, 128): [((1, 1, 1), (16, 16, 8)), ((1, 1, 1), (16, 16, 16))],
(16, 32, 64): [((1, 2, 1), (16, 16, 8)), ((1, 2, 1), (16, 16, 16))],
(16, 64, 32): [((1, 1, 1), (16, 16, 8)), ((1, 1, 1), (16, 16, 16))],
(16, 64, 64): [((1, 2, 1), (16, 16, 8)), ((1, 2, 1), (16, 16, 16))],
(16, 128, 32): [((1, 1, 1), (16, 16, 16))],
(16, 128, 64): [((1, 2, 1), (16, 16, 8)), ((1, 2, 1), (16, 16, 16))],
(16, 256, 32): [((1, 1, 1), (16, 16, 16))],
(16, 256, 64): [((1, 4, 1), (16, 16, 16))],
(32, 16, 64): [((2, 1, 1), (16, 16, 8)), ((2, 1, 1), (16, 16, 16))],
(32, 32, 32): [((1, 1, 1), (32, 32, 8))],
(32, 64, 16): [((1, 1, 1), (32, 32, 4))],
(32, 64, 32): [((1, 1, 1), (32, 32, 8))],
(32, 64, 64): [((1, 2, 1), (32, 32, 8))],
(32, 128, 16): [((1, 2, 1), (32, 32, 4))],
(32, 128, 32): [((1, 1, 1), (32, 32, 8)), ((1, 2, 1), (32, 32, 8))],
(32, 128, 64): [((1, 2, 1), (32, 32, 8))],
(32, 256, 64): [((1, 4, 1), (32, 32, 8))],
(64, 16, 16): [((1, 1, 1), (16, 16, 4)), ((1, 1, 1), (16, 16, 16)),
((4, 1, 1), (16, 16, 4)), ((4, 1, 1), (16, 16, 16))],
(64, 16, 32): [((1, 1, 1), (16, 16, 8)), ((1, 1, 1), (16, 16, 16)),
((4, 1, 1), (16, 16, 8)), ((4, 1, 1), (16, 16, 16))],
(64, 16, 64): [((2, 1, 1), (16, 16, 8)), ((2, 1, 1), (16, 16, 16)),
((4, 1, 1), (16, 16, 16))],
(64, 32, 16): [((1, 1, 1), (32, 32, 4))],
(64, 32, 32): [((1, 1, 1), (32, 32, 8))],
(64, 32, 64): [((2, 1, 1), (32, 32, 8))],
(64, 64, 8): [((2, 1, 1), (32, 32, 8))],
(64, 64, 16): [((1, 1, 1), (32, 32, 4))],
(64, 64, 32): [((1, 1, 1), (32, 32, 8)), ((2, 2, 1), (16, 16, 8)),
((2, 2, 1), (16, 16, 16))],
(64, 64, 64): [((2, 2, 1), (16, 16, 16)), ((2, 2, 1), (32, 32, 8))],
(64, 128, 16): [((1, 2, 1), (32, 32, 4)), ((2, 2, 1), (32, 32, 4))],
(64, 128, 32): [((1, 2, 1), (32, 32, 8)), ((2, 2, 1), (32, 32, 8))],
(64, 128, 64): [((2, 2, 1), (32, 32, 8))],
(128, 16, 64): [((2, 1, 1), (16, 16, 8)), ((2, 1, 1), (16, 16, 16))],
(128, 32, 16): [((2, 1, 1), (32, 32, 4)), ((4, 1, 1), (32, 32, 4)),
((4, 1, 1), (32, 32, 8))],
(128, 32, 32): [((1, 1, 1), (32, 32, 8)), ((2, 1, 1), (32, 32, 8)),
((2, 1, 2), (32, 32, 8)), ((4, 1, 1), (32, 32, 8))],
(128, 32, 64): [((2, 1, 1), (32, 32, 8)), ((4, 1, 1), (32, 32, 16))],
(128, 64, 8): [((2, 1, 1), (32, 32, 8)), ((2, 2, 1), (32, 32, 8))],
(128, 64, 16): [((2, 1, 1), (32, 32, 4)), ((2, 2, 1), (32, 32, 4)),
((2, 2, 1), (32, 32, 8))],
(128, 64, 32): [((2, 1, 1), (32, 32, 8)), ((2, 2, 1), (32, 32, 8))],
(128, 64, 64): [((2, 2, 1), (32, 32, 8))],
(128, 128, 16):[((1, 2, 1), (32, 32, 4)), ((2, 2, 1), (32, 32, 4))],
(128, 128, 32):[((1, 2, 1), (32, 32, 8)), ((2, 2, 1), (32, 32, 8))],
(128, 128, 64):[((2, 2, 1), (32, 32, 8))],
(128, 192, 16):[((2, 2, 1), (32, 32, 4))],
(128, 256, 16):[((2, 2, 1), (32, 32, 4))],
(128, 256, 32):[((2, 2, 1), (32, 32, 8))],
(224, 256, 64):[((2, 2, 1), (16, 16, 16))],
(256, 16, 64): [((4, 1, 1), (16, 16, 16))],
(256, 32, 64): [((4, 1, 1), (32, 32, 8))],
(256, 64, 8): [((2, 2, 1), (32, 32, 8))],
(256, 128, 16):[((2, 2, 1), (32, 32, 4))],
(256, 128, 32):[((2, 2, 1), (32, 32, 8))],
(256, 224, 64):[((2, 2, 1), (16, 16, 16))],
(256, 256, 32):[((2, 2, 1), (16, 16, 16)), ((2, 2, 1), (32, 32, 8))],
}
_TILE_WTILK_TO_VECS: Dict[Tuple[int, int, int, int], List[Tuple[int, int, int]]] = {
(16, 16, 32, 8): [(1, 1, 2)],
(16, 16, 32, 16): [(1, 1, 1), (1, 1, 2)],
(16, 16, 64, 8): [(4, 4, 4)],
(16, 16, 64, 16): [(1, 1, 1), (1, 4, 4), (4, 1, 1), (4, 4, 4), (8, 8, 4)],
(16, 16, 128, 8): [(4, 4, 4)],
(16, 16, 128, 16): [(8, 8, 4)],
(16, 32, 64, 8): [(4, 4, 4)],
(16, 32, 64, 16): [(1, 1, 1), (1, 2, 4), (1, 4, 4), (2, 1, 1), (2, 2, 4), (2, 4, 4), (8, 8, 4)],
(16, 64, 32, 8): [(1, 4, 4), (4, 1, 1), (4, 4, 4)],
(16, 64, 32, 16): [(1, 8, 4), (8, 1, 1), (8, 8, 4)],
(16, 64, 64, 8): [(4, 4, 4)],
(16, 64, 64, 16): [(1, 1, 1), (1, 8, 4), (2, 1, 1), (2, 8, 4), (8, 8, 4)],
(16, 128, 32, 16): [(4, 4, 1)],
(16, 128, 64, 8): [(4, 4, 4)],
(16, 128, 64, 16): [(1, 8, 4), (2, 8, 4), (8, 8, 4)],
(16, 256, 32, 16): [(8, 8, 1)],
(16, 256, 64, 16): [(1, 8, 4), (2, 8, 4), (8, 8, 4)],
(32, 16, 64, 8): [(4, 4, 2)],
(32, 16, 64, 16): [(1, 1, 1), (1, 2, 2), (2, 1, 1), (2, 2, 2), (4, 1, 1), (4, 2, 2), (8, 8, 2)],
(32, 32, 32, 8): [(2, 2, 1), (2, 2, 2)],
(32, 64, 16, 4): [(1, 4, 4), (4, 4, 4)],
(32, 64, 32, 8): [(1, 1, 8), (2, 2, 1), (2, 8, 8), (4, 4, 1), (4, 4, 2), (4, 4, 4), (8, 8, 8)],
(32, 64, 64, 8): [(4, 4, 8), (8, 8, 8)],
(32, 128, 16, 4): [(4, 4, 4)],
(32, 128, 32, 8): [(1, 1, 8), (4, 4, 4), (8, 8, 1), (8, 8, 2), (8, 8, 8)],
(32, 128, 64, 8): [(4, 4, 8), (8, 8, 8)],
(32, 256, 64, 8): [(8, 8, 8)],
(64, 16, 16, 4): [(4, 1, 1)],
(64, 16, 16, 16): [(4, 1, 1)],
(64, 16, 32, 8): [(1, 4, 4), (4, 1, 1), (4, 4, 4), (8, 1, 1), (8, 2, 2)],
(64, 16, 32, 16): [(1, 8, 4), (8, 1, 1), (8, 2, 2), (8, 8, 4)],
(64, 16, 64, 8): [(4, 4, 2)],
(64, 16, 64, 16): [(1, 1, 1), (1, 2, 2), (8, 1, 1), (8, 2, 2), (8, 8, 2), (16, 1, 1), (16, 2, 2)],
(64, 32, 16, 4): [(4, 4, 1), (4, 4, 4)],
(64, 32, 32, 8): [(1, 1, 8), (4, 4, 1), (4, 4, 2), (4, 4, 4), (8, 8, 1), (8, 8, 8)],
(64, 32, 64, 8): [(4, 4, 4), (8, 8, 4)],
(64, 64, 8, 8): [(1, 1, 8)],
(64, 64, 16, 4): [(1, 1, 1), (4, 4, 4)],
(64, 64, 32, 8): [(1, 1, 1), (1, 1, 8), (1, 2, 1), (2, 1, 2), (2, 2, 2), (4, 4, 4), (8, 8, 8)],
(64, 64, 32, 16): [(1, 2, 1), (2, 1, 2), (4, 4, 4), (8, 8, 8)],
(64, 64, 64, 8): [(1, 1, 1), (1, 4, 4), (2, 2, 2), (4, 1, 1), (4, 4, 4), (8, 8, 4), (8, 8, 8)],
(64, 64, 64, 16): [(2, 2, 4), (4, 1, 1), (8, 8, 2), (8, 8, 8)],
(64, 128, 16, 4): [(4, 4, 4)],
(64, 128, 32, 8): [(1, 1, 8), (1, 4, 4), (1, 8, 8), (4, 4, 4), (8, 8, 8)],
(64, 128, 64, 8): [(8, 8, 8)],
(128, 16, 64, 8): [(4, 4, 2)],
(128, 16, 64, 16): [(8, 1, 1), (8, 2, 2), (8, 8, 2)],
(128, 32, 16, 4): [(4, 1, 1), (4, 2, 2), (4, 4, 4)],
(128, 32, 16, 8): [(4, 1, 1), (4, 2, 2)],
(128, 32, 32, 8): [(1, 1, 8), (4, 1, 1), (4, 4, 4), (8, 1, 1), (8, 2, 2), (8, 8, 1), (8, 8, 2), (8, 8, 8)],
(128, 32, 64, 8): [(4, 4, 4), (8, 8, 4)],
(128, 32, 64, 16): [(16, 1, 1), (16, 2, 2), (16, 8, 8)],
(128, 64, 8, 8): [(1, 1, 8)],
(128, 64, 16, 4): [(4, 4, 4)],
(128, 64, 16, 8): [(1, 1, 8)],
(128, 64, 32, 8): [(1, 1, 8), (4, 4, 4), (8, 8, 8)],
(128, 64, 64, 8): [(8, 8, 8)],
(128, 128, 16, 4): [(1, 1, 4), (4, 4, 4)],
(128, 128, 32, 8): [(1, 1, 8), (4, 4, 4), (4, 4, 8), (8, 8, 8)],
(128, 128, 64, 8): [(4, 4, 4), (4, 4, 8), (8, 8, 4), (8, 8, 8)],
(128, 192, 16, 4): [(4, 4, 4)],
(128, 256, 16, 4): [(4, 4, 4)],
(128, 256, 32, 8): [(1, 1, 8), (4, 4, 4), (8, 4, 8), (8, 8, 8)],
(224, 256, 64, 16):[(8, 8, 8)],
(256, 16, 64, 16): [(8, 1, 1), (8, 2, 2), (8, 8, 2)],
(256, 32, 64, 8): [(8, 8, 4), (8, 8, 8)],
(256, 64, 8, 8): [(1, 1, 8)],
(256, 128, 16, 4): [(4, 4, 4)],
(256, 128, 32, 8): [(1, 1, 8), (2, 2, 2), (4, 4, 4), (8, 8, 8)],
(256, 224, 64, 16):[(8, 8, 8)],
(256, 256, 32, 8): [(4, 4, 4), (8, 8, 4), (8, 8, 8)],
(256, 256, 32, 16):[(8, 8, 8)],
}
# =============================================================================
# TestGetValidWaveWarpPairs
# =============================================================================
class TestGetValidWaveWarpPairs(unittest.TestCase):
"""Tests for get_valid_wave_warp_pairs()."""
# --- Spot checks ---
def test_spot_check_128_64_32(self):
"""Reference pairs for (128,64,32) must all be in the math result."""
tile = (128, 64, 32)
ref_pairs = TILE_TO_WAVE_WARP[tile]
math_pairs = set(get_valid_wave_warp_pairs(*tile, "bf16_bf16_fp32"))
for pair in ref_pairs:
self.assertIn(
pair, math_pairs,
f"Missing reference pair {pair} for tile {tile}",
)
def test_spot_check_64_64_32(self):
"""Reference pairs for (64,64,32) must all be in the union of dtype results."""
tile = (64, 64, 32)
ref_pairs = TILE_TO_WAVE_WARP[tile]
# Union across dtype_keys — reference table is dtype-agnostic
math_pairs = set(get_valid_wave_warp_pairs(*tile, "bf16_bf16_fp32"))
math_pairs |= set(get_valid_wave_warp_pairs(*tile, "fp32_fp32_fp32"))
math_pairs |= set(get_valid_wave_warp_pairs(*tile, "fp16_fp16_fp32"))
for pair in ref_pairs:
self.assertIn(
pair, math_pairs,
f"Missing reference pair {pair} for tile {tile}",
)
def test_spot_check_128_32_32_fp32(self):
"""Reference pairs for (128,32,32) include fp32 warp tiles."""
tile = (128, 32, 32)
ref_pairs = TILE_TO_WAVE_WARP[tile]
# fp32 uses warp_tile_k in {4, 8}; bf16 uses {8, 16}
# Try both dtype_keys and require the union covers all reference pairs
math_pairs = set(get_valid_wave_warp_pairs(*tile, "bf16_bf16_fp32"))
math_pairs |= set(get_valid_wave_warp_pairs(*tile, "fp32_fp32_fp32"))
for pair in ref_pairs:
self.assertIn(
pair, math_pairs,
f"Missing reference pair {pair} for tile {tile}",
)
# --- Full coverage of reference table ---
def test_coverage_all_reference_tiles(self):
"""Every (tile, pair) in TILE_TO_WAVE_WARP must be generated by the math.
The math is called with both bf16_bf16_fp32 and fp32_fp32_fp32 dtype_keys
and the union must contain every reference pair. This is the zero-false-
negatives requirement.
"""
DTYPE_KEYS = ["bf16_bf16_fp32", "fp32_fp32_fp32", "fp16_fp16_fp32"]
missing = []
for tile, ref_pairs in TILE_TO_WAVE_WARP.items():
# Union across dtype_keys (reference table is dtype-agnostic)
math_pairs: set = set()
for dk in DTYPE_KEYS:
math_pairs |= set(get_valid_wave_warp_pairs(*tile, dk))
for pair in ref_pairs:
if pair not in math_pairs:
missing.append((tile, pair))
if missing:
lines = [f" tile={t} pair={p}" for t, p in missing]
self.fail(
f"{len(missing)} reference pairs not generated by math:\n"
+ "\n".join(lines)
)
# --- Structural validity of math output ---
def test_no_invalid_wave_combos(self):
"""Every wave combo returned must be in WARP_SUPPORTED_COMBINATIONS for gfx942."""
valid_waves = {tuple(c) for c in WARP_SUPPORTED_COMBINATIONS["gfx942"]}
tile = (128, 64, 32)
for wave, _warp in get_valid_wave_warp_pairs(*tile, "bf16_bf16_fp32"):
self.assertIn(
wave, valid_waves,
f"Wave combo {wave} not in gfx942 supported combinations",
)
def test_no_invalid_wave_combos_all_tiles(self):
"""All tiles in reference table: every returned wave must be arch-valid."""
valid_waves = {tuple(c) for c in WARP_SUPPORTED_COMBINATIONS["gfx942"]}
for tile in TILE_TO_WAVE_WARP:
for wave, _warp in get_valid_wave_warp_pairs(*tile, "bf16_bf16_fp32"):
self.assertIn(
wave, valid_waves,
f"tile={tile}: wave {wave} not in gfx942 supported combinations",
)
def test_tile_divisibility_enforced(self):
"""A tile not divisible by any warp_tile_m must return no pairs."""
# (13, 17, 32) — prime dimensions, won't divide any MFMA shape
pairs = get_valid_wave_warp_pairs(13, 17, 32, "bf16_bf16_fp32")
self.assertEqual(pairs, [], "Expected no pairs for prime-dimension tile")
def test_warp_tile_divides_tile_m_and_n(self):
"""For every returned pair, warp_tile_m | tile_m and warp_tile_n | tile_n."""
for tile in TILE_TO_WAVE_WARP:
tm, tn, tk = tile
for _wave, (wt_m, wt_n, wt_k) in get_valid_wave_warp_pairs(*tile, "bf16_bf16_fp32"):
self.assertEqual(tm % wt_m, 0, f"tile_m={tm} not divisible by warp_m={wt_m}")
self.assertEqual(tn % wt_n, 0, f"tile_n={tn} not divisible by warp_n={wt_n}")
# --- Special cases ---
def test_wave_k2_special_case(self):
"""(128,32,32) must produce the ((2,1,2), (32,32,8)) pair (wave_k=2)."""
tile = (128, 32, 32)
math_pairs = set(get_valid_wave_warp_pairs(*tile, "bf16_bf16_fp32"))
expected = ((2, 1, 2), (32, 32, 8))
self.assertIn(
expected, math_pairs,
f"Expected wave_k=2 pair {expected} not found for tile {tile}",
)
def test_pipeline_async_constraint(self):
"""pipeline='basic_async_v1' must only return pairs with wave_n==2 and warp_tile_n==16."""
tile = (64, 64, 32)
pairs = get_valid_wave_warp_pairs(*tile, "bf16_bf16_fp32", pipeline="basic_async_v1")
for wave, (wt_m, wt_n, wt_k) in pairs:
_wm, wn, _wk = wave
self.assertEqual(wn, 2, f"async pipeline: expected wave_n=2, got {wn}")
self.assertEqual(wt_n, 16, f"async pipeline: expected warp_tile_n=16, got {wt_n}")
def test_no_duplicates(self):
"""The returned list must not contain duplicate pairs."""
for tile in list(TILE_TO_WAVE_WARP.keys()):
pairs = get_valid_wave_warp_pairs(*tile, "bf16_bf16_fp32")
self.assertEqual(
len(pairs), len(set(pairs)),
f"tile={tile}: duplicate pairs in result",
)
def test_gfx950_returns_superset_of_gfx942(self):
"""gfx950 supports more wave combos, so it must return >= as many pairs as gfx942."""
tile = (128, 64, 32)
pairs_942 = set(get_valid_wave_warp_pairs(*tile, "bf16_bf16_fp32", arch="gfx942"))
pairs_950 = set(get_valid_wave_warp_pairs(*tile, "bf16_bf16_fp32", arch="gfx950"))
self.assertTrue(
pairs_942.issubset(pairs_950),
"gfx942 pairs should be a subset of gfx950 pairs",
)
# =============================================================================
# TestGetValidVecSizes
# =============================================================================
class TestGetValidVecSizes(unittest.TestCase):
"""Tests for get_valid_vec_sizes()."""
# --- Spot checks ---
def test_spot_check_128_64_32_wave221_wt_32328(self):
"""Reference vecs for (128,64,32) wave=(2,2,1) warp=(32,32,8) bf16."""
vecs = get_valid_vec_sizes(128, 64, 32, 2, 2, 1, 32, 32, 8, "bf16_bf16_fp32")
# Reference: [(1,1,8), (4,4,4), (8,8,8)] from _TILE_WTILK_TO_VECS[(128,64,32,8)]
ref = _TILE_WTILK_TO_VECS[(128, 64, 32, 8)]
math_set = set(vecs)
for v in ref:
self.assertIn(v, math_set, f"Reference vec {v} missing for (128,64,32) wave=(2,2,1)")
def test_spot_check_64_64_32_wave111_wt_32328(self):
"""Reference vecs for (64,64,32) wave=(1,1,1) warp=(32,32,8) bf16."""
vecs = get_valid_vec_sizes(64, 64, 32, 1, 1, 1, 32, 32, 8, "bf16_bf16_fp32")
ref = _TILE_WTILK_TO_VECS[(64, 64, 32, 8)]
math_set = set(vecs)
for v in ref:
self.assertIn(v, math_set, f"Reference vec {v} missing for (64,64,32) wave=(1,1,1)")
# --- Structural validity ---
def test_pixel_budget_respected(self):
"""vec_a/vec_b must be compatible with their pixel budget.
Compatible means the vector divides the per-thread pixel budget OR is an
exact multiple of it (the wide load decomposes into v/pixels sub-loads).
"""
configs = [
(128, 64, 32, 2, 2, 1, 32, 32, 8, "bf16_bf16_fp32"),
(64, 64, 32, 1, 1, 1, 32, 32, 8, "bf16_bf16_fp32"),
(256, 128, 32, 2, 2, 1, 32, 32, 8, "bf16_bf16_fp32"),
]
for cfg in configs:
tm, tn, tk, wm, wn, wk, wt_m, wt_n, wt_k, dk = cfg
block_size = WARP_SIZE * wm * wn * wk
pixels_a = tm * tk // block_size
pixels_b = tn * tk // block_size
for va, vb, vc in get_valid_vec_sizes(*cfg):
self.assertTrue(pixels_a % va == 0 or va % pixels_a == 0,
f"cfg={cfg}: pixels_a={pixels_a} incompatible with vec_a={va}")
self.assertTrue(pixels_b % vb == 0 or vb % pixels_b == 0,
f"cfg={cfg}: pixels_b={pixels_b} incompatible with vec_b={vb}")
def test_fp32_vec_c_8_admitted(self):
"""fp32 tiles admit vec_c=8 (relaxed 32-byte ceiling). Category (A)."""
vecs = get_valid_vec_sizes(128, 128, 32, 2, 2, 1, 32, 32, 8, "fp32_fp32_fp32")
self.assertIn((4, 4, 8), set(vecs),
"fp32 (128,128,32) should admit vec triple (4,4,8)")
def test_bf16_asymmetric_small_pixel_vec8(self):
"""Asymmetric small-pixel tile admits a vec=8 triple. Category (B)."""
# (16,256,64) wave=(1,4,1): pixels_a = 16*64/256 = 4 < 8, but 8 % 4 == 0.
vecs = get_valid_vec_sizes(16, 256, 64, 1, 4, 1, 16, 16, 16, "bf16_bf16_fp32")
self.assertTrue(any(8 in (va, vb) for va, vb, vc in vecs),
"bf16 (16,256,64) wave=(1,4,1) should admit a vec=8 triple")
def test_bf16_vec_c_capped_at_16(self):
"""bf16 vec_c never exceeds 16 (LDS 256-bit ceiling still enforced)."""
for va, vb, vc in get_valid_vec_sizes(128, 128, 32, 2, 2, 1, 32, 32, 8, "bf16_bf16_fp32"):
self.assertLessEqual(vc, 16, f"bf16: vec_c={vc} > 16")
def test_lds_validity_bf16(self):
"""All returned vecs must satisfy the power-of-2 bit-width constraint for bf16.
Standard LDS allows 8–128 bits; some bwd_data global loads allow 256 bits.
The constraint is: bits = vec * sizeof * 8 must be a power of 2 up to 256.
"""
sizeof_bf16 = 2.0
for va, vb, vc in get_valid_vec_sizes(128, 64, 32, 2, 2, 1, 32, 32, 8, "bf16_bf16_fp32"):
for v in (va, vb, vc):
bits = int(v * sizeof_bf16 * 8)
self.assertGreater(bits, 0, f"vec={v}: bits must be > 0")
self.assertLessEqual(bits, 256, f"vec={v}: bits={bits} > 256")
self.assertEqual(bits & (bits - 1), 0, f"vec={v}: bits={bits} not power of 2")
def test_dtype_max_vec_fp32(self):
"""For fp32, vec_a and vec_b must be <= 8 (32 bytes / 4 bytes, allowing 2×16-byte loads)."""
for va, vb, vc in get_valid_vec_sizes(128, 64, 32, 2, 2, 1, 32, 32, 4, "fp32_fp32_fp32"):
self.assertLessEqual(va, 8, f"fp32: vec_a={va} > 8")
self.assertLessEqual(vb, 8, f"fp32: vec_b={vb} > 8")
def test_dtype_max_vec_bf16(self):
"""For bf16, vec_a and vec_b must be <= 16 (32 bytes / 2 bytes per element)."""
for va, vb, vc in get_valid_vec_sizes(128, 64, 32, 2, 2, 1, 32, 32, 8, "bf16_bf16_fp32"):
self.assertLessEqual(va, 16, f"bf16: vec_a={va} > 16")
self.assertLessEqual(vb, 16, f"bf16: vec_b={vb} > 16")
def test_vec_c_divisibility(self):
"""tile_n must be divisible by vec_c (output is vectorized along N)."""
tile_n = 64
wave_n = 2
warp_tile_n = 16
for va, vb, vc in get_valid_vec_sizes(
128, tile_n, 32, 2, wave_n, 1, 16, warp_tile_n, 16, "bf16_bf16_fp32"
):
self.assertEqual(
tile_n % vc, 0,
f"tile_n={tile_n} not divisible by vec_c={vc}",
)
def test_empty_result_for_non_divisible_block(self):
"""If tile_m * tile_k is not divisible by block_size, return []."""
# tile_m=13 (prime) → pixels_a won't be integer → expect empty
result = get_valid_vec_sizes(13, 16, 32, 1, 1, 1, 16, 16, 16, "bf16_bf16_fp32")
self.assertEqual(result, [])
# =============================================================================
# TestGetVecSizesForWaveWarp (wrapper)
# =============================================================================
class TestGetVecSizesForWaveWarp(unittest.TestCase):
"""Tests for the get_vec_sizes_for_wave_warp() convenience wrapper."""
def test_spot_check_128_64_32_wt8(self):
"""Reference vecs for (128,64,32,8) in _TILE_WTILK_TO_VECS must all appear."""
ref = _TILE_WTILK_TO_VECS[(128, 64, 32, 8)]
math_set = set(get_vec_sizes_for_wave_warp(128, 64, 32, 8, "bf16_bf16_fp32"))
for v in ref:
self.assertIn(v, math_set,
f"Reference vec {v} missing from get_vec_sizes_for_wave_warp(128,64,32,8)")
def test_spot_check_64_64_32_wt8(self):
"""Reference vecs for (64,64,32,8) in _TILE_WTILK_TO_VECS must all appear."""
ref = _TILE_WTILK_TO_VECS[(64, 64, 32, 8)]
math_set = set(get_vec_sizes_for_wave_warp(64, 64, 32, 8, "bf16_bf16_fp32"))
for v in ref:
self.assertIn(v, math_set,
f"Reference vec {v} missing from get_vec_sizes_for_wave_warp(64,64,32,8)")
def test_result_is_sorted(self):
"""Returned list must be in sorted order."""
result = get_vec_sizes_for_wave_warp(128, 64, 32, 8, "bf16_bf16_fp32")
self.assertEqual(result, sorted(result))
def test_no_duplicates(self):
"""Returned list must not contain duplicates."""
result = get_vec_sizes_for_wave_warp(128, 64, 32, 8, "bf16_bf16_fp32")
self.assertEqual(len(result), len(set(result)))
# =============================================================================
# TestMathVsReferenceStatistics — coverage tests (the key correctness tests)
# =============================================================================
# Map warp_tile_k → dtype_keys to try when verifying _TILE_WTILK_TO_VECS
# Derived from warp_gemm_dispatcher.hpp; we try all plausible dtypes per warp_tile_k.
_DTYPE_KEYS_ALL = ["bf16_bf16_fp32", "fp32_fp32_fp32", "fp16_fp16_fp32", "fp8_fp8_fp32"]
class TestMathVsReferenceStatistics(unittest.TestCase):
"""Comprehensive coverage: math must generate >= all reference entries."""
def test_wave_warp_no_false_negatives(self):
"""TILE_TO_WAVE_WARP: every reference pair must be in the math output.
The math is queried with multiple dtype_keys and the union must cover
every reference pair. This guarantees zero false negatives.
"""
missing = []
for tile, ref_pairs in TILE_TO_WAVE_WARP.items():
math_pairs: set = set()
for dk in _DTYPE_KEYS_ALL:
math_pairs |= set(get_valid_wave_warp_pairs(*tile, dk))
for pair in ref_pairs:
if pair not in math_pairs:
missing.append((tile, pair))
if missing:
lines = [f" tile={t} pair={p}" for t, p in missing[:20]]
self.fail(
f"{len(missing)} reference wave/warp pairs not generated by math "
f"(showing first 20):\n" + "\n".join(lines)
)
def test_vec_no_false_negatives(self):
"""_TILE_WTILK_TO_VECS: every reference vec must be in the math output.
For each (tile_m, tile_n, tile_k, warp_tile_k) key, the math is queried
with all plausible dtype_keys (inferred from warp_tile_k) and the union
must contain every reference vec triple.
"""
missing = []
for (tm, tn, tk, wtk), ref_vecs in _TILE_WTILK_TO_VECS.items():
dtype_keys = dtype_keys_for_warp_tile_k(wtk)
if not dtype_keys:
dtype_keys = _DTYPE_KEYS_ALL # fallback: try all
math_vecs: set = set()
for dk in dtype_keys:
math_vecs |= set(get_vec_sizes_for_wave_warp(tm, tn, tk, wtk, dk))
for v in ref_vecs:
if v not in math_vecs:
missing.append(((tm, tn, tk, wtk), v))
if missing:
lines = [f" key={k} vec={v}" for k, v in missing[:30]]
self.fail(
f"{len(missing)} reference vec triples not generated by math "
f"(showing first 30):\n" + "\n".join(lines)
)
def test_extra_wave_warp_pairs_are_structurally_valid(self):
"""Pairs the math generates but reference doesn't have must still be valid.
Extra pairs are not failures — they represent valid configs not yet in
the JSON profiler files. But they must satisfy structural constraints.
"""
valid_waves = {tuple(c) for c in WARP_SUPPORTED_COMBINATIONS["gfx942"]}
invalid_extras = []
for tile in TILE_TO_WAVE_WARP:
ref_set = set(TILE_TO_WAVE_WARP[tile])
math_pairs = get_valid_wave_warp_pairs(*tile, "bf16_bf16_fp32")
extras = [p for p in math_pairs if p not in ref_set]
for wave, (wt_m, wt_n, wt_k) in extras:
tm, tn, _tk = tile
# Wave must be arch-valid
if wave not in valid_waves:
invalid_extras.append((tile, wave, "not in arch wave combos"))
# Tile must be divisible by warp tile
elif tm % wt_m != 0 or tn % wt_n != 0:
invalid_extras.append((tile, (wt_m, wt_n, wt_k), "divisibility violated"))
if invalid_extras:
lines = [f" {e}" for e in invalid_extras[:10]]
self.fail(
f"{len(invalid_extras)} extra pairs are structurally invalid:\n"
+ "\n".join(lines)
)
def test_coverage_rate_wave_warp(self):
"""Log coverage statistics for TILE_TO_WAVE_WARP (informational)."""
total = 0
covered = 0
for tile, ref_pairs in TILE_TO_WAVE_WARP.items():
math_pairs: set = set()
for dk in _DTYPE_KEYS_ALL:
math_pairs |= set(get_valid_wave_warp_pairs(*tile, dk))
for pair in ref_pairs:
total += 1
if pair in math_pairs:
covered += 1
rate = covered / total * 100 if total else 0
print(f"\n[wave/warp coverage] {covered}/{total} = {rate:.1f}%")
self.assertEqual(covered, total, f"Coverage {rate:.1f}% < 100%")
def test_coverage_rate_vec(self):
"""Log coverage statistics for _TILE_WTILK_TO_VECS (informational)."""
total = 0
covered = 0
for (tm, tn, tk, wtk), ref_vecs in _TILE_WTILK_TO_VECS.items():
dtype_keys = dtype_keys_for_warp_tile_k(wtk) or _DTYPE_KEYS_ALL
math_vecs: set = set()
for dk in dtype_keys:
math_vecs |= set(get_vec_sizes_for_wave_warp(tm, tn, tk, wtk, dk))
for v in ref_vecs:
total += 1
if v in math_vecs:
covered += 1
rate = covered / total * 100 if total else 0
print(f"\n[vec coverage] {covered}/{total} = {rate:.1f}%")
self.assertEqual(covered, total, f"Vec coverage {rate:.1f}% < 100%")
if __name__ == "__main__":
unittest.main(verbosity=2)