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

321 lines
9.7 KiB
Python

# Copyright Advanced Micro Devices, Inc., or its affiliates.
# SPDX-License-Identifier: MIT
"""Tests for hardware_t constructor with architecture constants."""
import pytest
import origami
from dataclasses import dataclass
from typing import List
@dataclass(frozen=True)
class MI:
m: int
n: int
k: int
@dataclass(frozen=True)
class Tile:
MT_M: int
MT_N: int
MT_K: int
bytes_ab: int
def enumerate_tiles_half_lds(
mi: MI,
*,
datatype_size: int, # bytes/element (fp16/bf16=2)
lds_bytes: int, # total LDS bytes available
) -> List[Tile]:
"""
Generate tiles by incrementing each tile dim in MI-sized steps:
MT_M in {MI_M, 2*MI_M, 3*MI_M, ...}
MT_N in {MI_N, 2*MI_N, 3*MI_N, ...}
MT_K in {MI_K, 2*MI_K, 3*MI_K, ...}
Keep tiles where:
datatype_size * (MT_M*MT_K + MT_N*MT_K) <= lds_bytes/2
"""
if min(mi.m, mi.n, mi.k) <= 0:
raise ValueError("MI dims must be positive.")
if datatype_size <= 0:
raise ValueError("datatype_size must be positive.")
if lds_bytes <= 0:
raise ValueError("lds_bytes must be positive.")
budget = lds_bytes
out: List[Tile] = []
# MT_K increments
MT_K = mi.k
while True:
# bytes_ab = dtype * MT_K * (MT_M + MT_N)
# For a fixed MT_K, minimum MT_M+MT_N is (MI_M + MI_N)
min_bytes_for_this_K = datatype_size * MT_K * (mi.m + mi.n)
if min_bytes_for_this_K > budget:
break
# MT_M increments
MT_M = mi.m
while True:
# For fixed (MT_K, MT_M), minimum MT_N is MI_N
min_bytes_for_this_M = datatype_size * MT_K * (MT_M + mi.n)
if min_bytes_for_this_M > budget:
break
# MT_N increments
MT_N = mi.n
while True:
bytes_ab = datatype_size * MT_K * (MT_M + MT_N)
if bytes_ab > budget:
break
out.append(Tile(MT_M=MT_M, MT_N=MT_N, MT_K=MT_K, bytes_ab=bytes_ab))
MT_N += mi.n
MT_M += mi.m
MT_K += mi.k
out.sort(key=lambda t: (t.MT_K, t.MT_M, t.MT_N))
return out
@pytest.mark.integration
def test_hardware_for_arch_gfx950():
"""Test creating hardware object for gfx950 using get_hardware_for_arch."""
hardware = origami.get_hardware_for_arch(
arch=origami.architecture_t.gfx950,
N_CU=304,
lds_capacity=64 * 1024,
rf_capacity=512 * 1024,
L2_capacity=32 * 1024 * 1024,
compute_clock_khz=2100000
)
# Verify basic properties
assert hardware.N_CU == 304
assert hardware.lds_capacity == 64 * 1024
assert hardware.rf_capacity == 512 * 1024
assert hardware.L2_capacity == 32 * 1024 * 1024
assert hardware.compute_clock_ghz == pytest.approx(2.1, rel=1e-6)
# Verify architecture-specific constants were applied
assert hardware.NUM_XCD == 8
assert hardware.parallel_mi_cu == 4
# mem1_perf_ratio is calculated based on clock speeds, not a direct constant
assert hardware.mem1_perf_ratio > 0
@pytest.mark.integration
def test_gfx950_pci_chip_id_0x75a8_differs_from_default():
"""gfx950 pci_chip_id 0x75a8 uses alternate mem constants vs omitted / id75a0 path."""
args = {
"arch": origami.architecture_t.gfx950,
"N_CU": 256,
"lds_capacity": 64 * 1024,
"rf_capacity": 512 * 1024,
"L2_capacity": 32 * 1024 * 1024,
"compute_clock_khz": 2_100_000,
}
h_def = origami.get_hardware_for_arch(**args, pci_chip_id=None)
h_v2 = origami.get_hardware_for_arch(**args, pci_chip_id=0x75A8)
assert h_def.mem1_perf_ratio != h_v2.mem1_perf_ratio
assert h_def.mem2_perf_ratio != h_v2.mem2_perf_ratio
assert h_def.mem3_perf_ratio != h_v2.mem3_perf_ratio
assert h_def.mem_bw_per_wg_coefficients != h_v2.mem_bw_per_wg_coefficients
assert h_def.parallel_mi_cu == h_v2.parallel_mi_cu == 4
assert h_def.pci_chip_id is None
assert h_v2.pci_chip_id == 0x75A8
@pytest.mark.integration
def test_hardware_for_arch_gfx942():
"""Test creating hardware object for gfx942 using get_hardware_for_arch."""
hardware = origami.get_hardware_for_arch(
arch=origami.architecture_t.gfx942,
N_CU=228,
lds_capacity=64 * 1024,
rf_capacity=512 * 1024,
L2_capacity=24 * 1024 * 1024,
compute_clock_khz=1700000
)
# Verify basic properties
assert hardware.N_CU == 228
assert hardware.lds_capacity == 64 * 1024
assert hardware.L2_capacity == 24 * 1024 * 1024
assert hardware.compute_clock_ghz == pytest.approx(1.7, rel=1e-6)
# Verify architecture-specific constants
assert hardware.NUM_XCD == 8
assert hardware.parallel_mi_cu == 4
@pytest.mark.integration
def test_hardware_for_arch_gfx90a():
"""Test creating hardware object for gfx90a using get_hardware_for_arch."""
hardware = origami.get_hardware_for_arch(
arch=origami.architecture_t.gfx90a,
N_CU=110,
lds_capacity=64 * 1024,
rf_capacity=512 * 1024,
L2_capacity=8 * 1024 * 1024,
compute_clock_khz=1700000
)
# Verify basic properties
assert hardware.N_CU == 110
assert hardware.lds_capacity == 64 * 1024
# Verify architecture-specific constants
assert hardware.NUM_XCD == 1
assert hardware.parallel_mi_cu == 4
@pytest.mark.integration
def test_hardware_for_arch_gfx1200():
"""Test creating hardware object for gfx1200 using get_hardware_for_arch."""
hardware = origami.get_hardware_for_arch(
arch=origami.architecture_t.gfx1200,
N_CU=32,
lds_capacity=128 * 1024,
rf_capacity=512 * 1024,
L2_capacity=4 * 1024 * 1024,
compute_clock_khz=2700000
)
# Verify basic properties
assert hardware.N_CU == 32
assert hardware.lds_capacity == 128 * 1024
# Verify architecture-specific constants
assert hardware.NUM_XCD == 1
assert hardware.parallel_mi_cu == 2
@pytest.mark.integration
def test_hardware_for_arch_gfx1201():
"""Test creating hardware object for gfx1201 using get_hardware_for_arch."""
hardware = origami.get_hardware_for_arch(
arch=origami.architecture_t.gfx1201,
N_CU=60,
lds_capacity=128 * 1024,
rf_capacity=512 * 1024,
L2_capacity=6 * 1024 * 1024,
compute_clock_khz=2500000
)
# Verify basic properties
assert hardware.N_CU == 60
assert hardware.lds_capacity == 128 * 1024
# Verify architecture-specific constants
assert hardware.NUM_XCD == 1
assert hardware.parallel_mi_cu == 2
@pytest.mark.integration
def test_enumerate_tiles_with_hardware():
"""Test tile enumeration using hardware_t LDS capacity."""
# Create MI350 hardware configuration
hardware = origami.get_hardware_for_arch(
arch=origami.architecture_t.gfx950,
N_CU=304,
lds_capacity=64 * 1024,
rf_capacity=512 * 1024,
L2_capacity=32 * 1024 * 1024,
compute_clock_khz=2100000
)
# MI350 typical bf16/fp16 inst
mi = MI(m=16, n=16, k=32)
tiles = enumerate_tiles_half_lds(
mi,
datatype_size=2,
lds_bytes=hardware.lds_capacity
)
# Verify we got some tiles
assert len(tiles) > 0
# All tiles should fit in half LDS
for tile in tiles:
assert tile.bytes_ab <= hardware.lds_capacity
# Verify tile dimensions are multiples of MI dimensions
assert tile.MT_M % mi.m == 0
assert tile.MT_N % mi.n == 0
assert tile.MT_K % mi.k == 0
@pytest.mark.integration
def test_enumerate_tiles_mi350_example():
"""Test the example from the task description."""
# MI350 typical bf16/fp16 inst
mi = MI(m=16, n=16, k=32)
tiles = enumerate_tiles_half_lds(mi, datatype_size=2, lds_bytes=64 * 1024)
# Verify results
assert len(tiles) > 0
print(f"{len(tiles)} tiles under half LDS ({(64*1024)//2} bytes)")
# Check some properties of the generated tiles
for tile in tiles:
# Verify bytes_ab calculation
expected_bytes = 2 * tile.MT_K * (tile.MT_M + tile.MT_N)
assert tile.bytes_ab == expected_bytes
# Verify it fits in budget
assert tile.bytes_ab <= 64 * 1024
# Verify dimensions are multiples of MI
assert tile.MT_M % 16 == 0
assert tile.MT_N % 16 == 0
assert tile.MT_K % 32 == 0
@pytest.mark.integration
def test_tile_enumeration_empty_cases():
"""Test edge cases for tile enumeration."""
mi = MI(m=16, n=16, k=32)
# Very small LDS should produce few or no tiles
tiles = enumerate_tiles_half_lds(mi, datatype_size=2, lds_bytes=1024)
# With 1024 bytes budget and smallest tile = 2 * 32 * (16 + 16) = 2048 bytes
# We expect 0 tiles
assert len(tiles) == 0
# Invalid inputs should raise errors
with pytest.raises(ValueError):
enumerate_tiles_half_lds(MI(m=0, n=16, k=32), datatype_size=2, lds_bytes=64*1024)
with pytest.raises(ValueError):
enumerate_tiles_half_lds(mi, datatype_size=0, lds_bytes=64*1024)
with pytest.raises(ValueError):
enumerate_tiles_half_lds(mi, datatype_size=2, lds_bytes=0)
@pytest.mark.integration
def test_different_datatypes():
"""Test tile enumeration with different datatype sizes."""
mi = MI(m=16, n=16, k=32)
lds_bytes = 64 * 1024
# FP32 (4 bytes)
tiles_fp32 = enumerate_tiles_half_lds(mi, datatype_size=4, lds_bytes=lds_bytes)
# FP16/BF16 (2 bytes)
tiles_fp16 = enumerate_tiles_half_lds(mi, datatype_size=2, lds_bytes=lds_bytes)
# FP8 (1 byte)
tiles_fp8 = enumerate_tiles_half_lds(mi, datatype_size=1, lds_bytes=lds_bytes)
# More tiles should fit with smaller datatypes
assert len(tiles_fp8) > len(tiles_fp16) > len(tiles_fp32)