Files
TheRock/rocm-libraries/shared/origami/python/tests/test_selector.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

748 lines
21 KiB
Python

# Copyright Advanced Micro Devices, Inc., or its affiliates.
# SPDX-License-Identifier: MIT
"""Tests for OrigamiMatmulSelector interface."""
import pytest
import math
import origami
# Skip entire module if torch is not available (selector requires torch)
torch = pytest.importorskip("torch", reason="torch is required for OrigamiMatmulSelector tests.")
from origami.selector import OrigamiMatmulSelector
class MockConfig:
"""Mock Triton config for testing."""
def __init__(self, block_m, block_n, block_k, waves_per_eu):
self.kwargs = {
'BLOCK_M': block_m,
'BLOCK_N': block_n,
'BLOCK_K': block_k,
'waves_per_eu': waves_per_eu
}
def create_mock_config_gen():
"""Create a simple mock config generator for testing."""
return [
MockConfig(128, 128, 32, 1),
MockConfig(64, 64, 64, 2),
MockConfig(256, 128, 64, 1),
]
@pytest.fixture
def rocm_device():
"""Fixture to check for ROCm device availability."""
if not torch.cuda.is_available():
pytest.skip("CUDA not available")
try:
# Try to get hardware info to check if ROCm is available
origami.get_hardware_for_device(0)
return torch.device("cuda:0")
except RuntimeError:
pytest.skip("No ROCm-capable device detected")
@pytest.mark.integration
def test_selector_initialization_basic(rocm_device):
"""Test basic initialization of OrigamiMatmulSelector."""
config_gen = create_mock_config_gen()
selector = OrigamiMatmulSelector(
config_gen=config_gen,
m=2048,
n=2048,
k=2048,
a_dtype=torch.float16,
b_dtype=torch.float16,
out_dtype=torch.float16,
device=rocm_device
)
assert selector is not None
assert selector._m == 2048
assert selector._n == 2048
assert selector._k == 2048
@pytest.mark.integration
def test_selector_dtype_conversion(rocm_device):
"""Test dtype to string conversion."""
config_gen = create_mock_config_gen()
selector = OrigamiMatmulSelector(
config_gen=config_gen,
m=1024,
n=1024,
k=1024,
a_dtype=torch.float32,
b_dtype=torch.float32,
out_dtype=torch.float32,
device=rocm_device
)
assert selector._a_dtype_str == "f32"
assert selector._b_dtype_str == "f32"
assert selector._out_dtype_str == "f32"
@pytest.mark.integration
@pytest.mark.parametrize("dtype,expected_str", [
(torch.float32, "f32"),
(torch.float16, "f16"),
(torch.bfloat16, "bf16"),
(torch.int8, "i8"),
])
def test_selector_various_dtypes(rocm_device, dtype, expected_str):
"""Test selector with various data types."""
config_gen = create_mock_config_gen()
selector = OrigamiMatmulSelector(
config_gen=config_gen,
m=512,
n=512,
k=512,
a_dtype=dtype,
b_dtype=dtype,
out_dtype=dtype,
device=rocm_device
)
assert selector._a_dtype_str == expected_str
assert selector._b_dtype_str == expected_str
assert selector._out_dtype_str == expected_str
@pytest.mark.integration
def test_selector_macrotile_properties(rocm_device):
"""Test macrotile size properties."""
config_gen = create_mock_config_gen()
selector = OrigamiMatmulSelector(
config_gen=config_gen,
m=4096,
n=4096,
k=4096,
a_dtype=torch.float16,
b_dtype=torch.float16,
out_dtype=torch.float16,
device=rocm_device
)
# Check that macrotile properties return valid values
assert selector.macrotile_m > 0
assert selector.macrotile_n > 0
assert selector.macrotile_k > 0
# Macrotile sizes should be reasonable (not too large)
assert selector.macrotile_m <= 16384
assert selector.macrotile_n <= 16384
assert selector.macrotile_k <= 16384
@pytest.mark.integration
def test_selector_wgm_property(rocm_device):
"""Test wgm (workgroup mapping) property."""
config_gen = create_mock_config_gen()
selector = OrigamiMatmulSelector(
config_gen=config_gen,
m=2048,
n=2048,
k=2048,
a_dtype=torch.float16,
b_dtype=torch.float16,
out_dtype=torch.float16,
device=rocm_device
)
assert selector.wgm > 0
@pytest.mark.integration
def test_selector_number_of_cus_property(rocm_device):
"""Test number_of_cus property."""
config_gen = create_mock_config_gen()
selector = OrigamiMatmulSelector(
config_gen=config_gen,
m=2048,
n=2048,
k=2048,
a_dtype=torch.float16,
b_dtype=torch.float16,
out_dtype=torch.float16,
device=rocm_device
)
assert selector.number_of_cus > 0
@pytest.mark.integration
def test_selector_occupancy_property(rocm_device):
"""Test occupancy property."""
config_gen = create_mock_config_gen()
selector = OrigamiMatmulSelector(
config_gen=config_gen,
m=2048,
n=2048,
k=2048,
a_dtype=torch.float16,
b_dtype=torch.float16,
out_dtype=torch.float16,
device=rocm_device
)
# Occupancy should be a reasonable value
assert selector.occupancy > 0
assert selector.occupancy <= 64 # max waves per eu is typically 2 or less for efficient schedules.
@pytest.mark.integration
def test_selector_even_k_property(rocm_device):
"""Test even_k property."""
config_gen = create_mock_config_gen()
# Test with K that should be evenly divisible
selector = OrigamiMatmulSelector(
config_gen=config_gen,
m=2048,
n=2048,
k=2048,
a_dtype=torch.float16,
b_dtype=torch.float16,
out_dtype=torch.float16,
device=rocm_device
)
# even_k is a boolean
assert isinstance(selector.even_k, bool)
@pytest.mark.integration
def test_selector_streamk_disabled(rocm_device):
"""Test selector with StreamK disabled (data-parallel grid)."""
config_gen = create_mock_config_gen()
m = n = k = 2048
selector = OrigamiMatmulSelector(
config_gen=config_gen,
m=m,
n=n,
k=k,
a_dtype=torch.float16,
b_dtype=torch.float16,
out_dtype=torch.float16,
device=rocm_device,
streamk=False
)
assert selector.grid_size > 0
# Data-parallel grid: tiles in M and N based on selected macrotiles
mt_m = selector.macrotile_m
mt_n = selector.macrotile_n
assert mt_m > 0 and mt_n > 0
expected_grid = math.ceil(m / mt_m) * math.ceil(n / mt_n)
assert selector.grid_size == expected_grid
@pytest.mark.integration
def test_selector_streamk_enabled(rocm_device):
"""Test selector with StreamK enabled."""
config_gen = create_mock_config_gen()
selector = OrigamiMatmulSelector(
config_gen=config_gen,
m=2048,
n=2048,
k=2048,
a_dtype=torch.float16,
b_dtype=torch.float16,
out_dtype=torch.float16,
device=rocm_device,
streamk=True
)
# With StreamK enabled, grid is selected analytically
assert selector.grid_size > 0
@pytest.mark.integration
def test_selector_batch_size(rocm_device):
"""Test selector with custom batch size."""
config_gen = create_mock_config_gen()
selector = OrigamiMatmulSelector(
config_gen=config_gen,
m=1024,
n=1024,
k=1024,
a_dtype=torch.float16,
b_dtype=torch.float16,
out_dtype=torch.float16,
device=rocm_device,
batch=4
)
assert selector._batch == 4
assert selector._problem.batch == 4
@pytest.mark.integration
def test_selector_mx_block_size(rocm_device):
"""Test selector with MX block size parameter."""
config_gen = create_mock_config_gen()
selector = OrigamiMatmulSelector(
config_gen=config_gen,
m=1024,
n=1024,
k=1024,
a_dtype=torch.float16,
b_dtype=torch.float16,
out_dtype=torch.float16,
device=rocm_device,
mx_block_size=32
)
assert selector._mx_block_size == 32
@pytest.mark.integration
def test_selector_mixed_precision(rocm_device):
"""Test selector with mixed precision (FP8 input, BF16 output)."""
# Check if FP8 types are available
if not hasattr(torch, 'float8_e4m3fn'):
pytest.skip("FP8 types not available in this PyTorch version")
config_gen = create_mock_config_gen()
selector = OrigamiMatmulSelector(
config_gen=config_gen,
m=2048,
n=2048,
k=2048,
a_dtype=torch.float8_e4m3fn,
b_dtype=torch.float8_e4m3fn,
out_dtype=torch.bfloat16,
device=rocm_device
)
assert selector._a_dtype_str == "f8"
assert selector._b_dtype_str == "f8"
assert selector._out_dtype_str == "bf16"
# MI dtype should be based on input types (FP8)
assert selector.mi_dtype == "f8"
@pytest.mark.integration
def test_selector_small_problem_size(rocm_device):
"""Test selector with small problem sizes."""
config_gen = create_mock_config_gen()
selector = OrigamiMatmulSelector(
config_gen=config_gen,
m=128,
n=128,
k=128,
a_dtype=torch.float16,
b_dtype=torch.float16,
out_dtype=torch.float16,
device=rocm_device
)
assert selector.macrotile_m > 0
assert selector.macrotile_n > 0
assert selector.macrotile_k > 0
@pytest.mark.integration
def test_selector_large_problem_size(rocm_device):
"""Test selector with large problem sizes."""
config_gen = create_mock_config_gen()
selector = OrigamiMatmulSelector(
config_gen=config_gen,
m=16384,
n=16384,
k=16384,
a_dtype=torch.float16,
b_dtype=torch.float16,
out_dtype=torch.float16,
device=rocm_device
)
assert selector.macrotile_m > 0
assert selector.macrotile_n > 0
assert selector.macrotile_k > 0
@pytest.mark.integration
def test_selector_rectangular_matrices(rocm_device):
"""Test selector with non-square matrices."""
config_gen = create_mock_config_gen()
# Test tall-skinny matrix
selector = OrigamiMatmulSelector(
config_gen=config_gen,
m=8192,
n=512,
k=2048,
a_dtype=torch.float16,
b_dtype=torch.float16,
out_dtype=torch.float16,
device=rocm_device
)
assert selector.macrotile_m > 0
assert selector.macrotile_n > 0
assert selector.macrotile_k > 0
@pytest.mark.integration
def test_selector_hardware_info(rocm_device):
"""Test that selector correctly retrieves hardware info."""
config_gen = create_mock_config_gen()
selector = OrigamiMatmulSelector(
config_gen=config_gen,
m=2048,
n=2048,
k=2048,
a_dtype=torch.float16,
b_dtype=torch.float16,
out_dtype=torch.float16,
device=rocm_device
)
# Check that hardware info was retrieved
assert selector._hardware is not None
assert selector._mx_block_size == 0 # Default value
assert selector.number_of_cus > 0
# N_CU should be a known AMD GPU value
# Expand this list as we add more GPUs to the test suite.
assert selector.number_of_cus in [104, 228, 256, 304]
@pytest.mark.integration
def test_selector_config_generation(rocm_device):
"""Test that configs are properly generated from config_gen."""
config_gen = create_mock_config_gen()
selector = OrigamiMatmulSelector(
config_gen=config_gen,
m=2048,
n=2048,
k=2048,
a_dtype=torch.float16,
b_dtype=torch.float16,
out_dtype=torch.float16,
device=rocm_device
)
# Should have generated configs from the input generator
assert len(selector._configs) == len(list(config_gen))
assert all(isinstance(cfg, origami.config_t) for cfg in selector._configs)
@pytest.mark.integration
def test_selector_problem_creation(rocm_device):
"""Test that problem_t is properly created."""
config_gen = create_mock_config_gen()
selector = OrigamiMatmulSelector(
config_gen=config_gen,
m=2048,
n=2048,
k=2048,
a_dtype=torch.float16,
b_dtype=torch.float16,
out_dtype=torch.float16,
device=rocm_device
)
# Check that problem was created with correct dimensions
assert selector._problem is not None
assert selector._problem.size.m == 2048
assert selector._problem.size.n == 2048
assert selector._problem.size.k == 2048
assert selector._problem.batch == 1
#####
# Tests for _check_transpose_type functionality
#####
@pytest.mark.integration
def test_selector_transpose_no_strides_default(rocm_device):
"""Test that default transpose types are used when no strides are provided.
When a_stride and b_stride are None, _check_transpose_type should return
the default values specified in _make_problem:
- A matrix: default=T (transposed)
- B matrix: default=N (non-transposed)
"""
config_gen = create_mock_config_gen()
selector = OrigamiMatmulSelector(
config_gen=config_gen,
m=2048,
n=2048,
k=2048,
a_dtype=torch.float16,
b_dtype=torch.float16,
out_dtype=torch.float16,
device=rocm_device
# No a_stride or b_stride provided
)
# Default for A is T (transposed), default for B is N (non-transposed)
assert selector._problem.a_transpose == origami.transpose_t.T
assert selector._problem.b_transpose == origami.transpose_t.N
@pytest.mark.integration
def test_selector_transpose_row_major(rocm_device):
"""Test row-major (non-transposed) stride detection.
A row-major matrix has stride_n=1 (contiguous in the last dimension).
For a matrix of shape (m, k), row-major strides are (k, 1).
Using non-square dimensions to catch potential m/n/k swapping errors:
- A has shape (m, k) = (2048, 512), strides (512, 1)
- B has shape (k, n) = (512, 1024), strides (1024, 1)
"""
config_gen = create_mock_config_gen()
m, n, k = 2048, 1024, 512
selector = OrigamiMatmulSelector(
config_gen=config_gen,
m=m,
n=n,
k=k,
a_dtype=torch.float16,
b_dtype=torch.float16,
out_dtype=torch.float16,
device=rocm_device,
a_stride=(k, 1), # Row-major for A (shape m x k) = (512, 1)
b_stride=(n, 1), # Row-major for B (shape k x n) = (1024, 1)
)
# Row-major should be detected as non-transposed (N)
assert selector._problem.a_transpose == origami.transpose_t.N
assert selector._problem.b_transpose == origami.transpose_t.N
@pytest.mark.integration
def test_selector_transpose_column_major(rocm_device):
"""Test column-major (transposed) stride detection.
A column-major matrix has stride_m=1 (contiguous in the first dimension).
For a matrix of shape (m, k), column-major strides are (1, m).
Using non-square dimensions to catch potential m/n/k swapping errors:
- A has shape (m, k) = (2048, 512), strides (1, 2048)
- B has shape (k, n) = (512, 1024), strides (1, 512)
"""
config_gen = create_mock_config_gen()
m, n, k = 2048, 1024, 512
selector = OrigamiMatmulSelector(
config_gen=config_gen,
m=m,
n=n,
k=k,
a_dtype=torch.float16,
b_dtype=torch.float16,
out_dtype=torch.float16,
device=rocm_device,
a_stride=(1, m), # Column-major for A (shape m x k) = (1, 2048)
b_stride=(1, k), # Column-major for B (shape k x n) = (1, 512)
)
# Column-major should be detected as transposed (T)
assert selector._problem.a_transpose == origami.transpose_t.T
assert selector._problem.b_transpose == origami.transpose_t.T
@pytest.mark.integration
def test_selector_transpose_degenerate_row_vector(rocm_device):
"""Test transpose detection for degenerate row vector (m=1).
When m=1, the matrix is a row vector. With stride_n=1, it should be
detected as non-transposed (N).
"""
config_gen = create_mock_config_gen()
m, n, k = 1, 2048, 2048 # m=1 makes A a row vector
selector = OrigamiMatmulSelector(
config_gen=config_gen,
m=m,
n=n,
k=k,
a_dtype=torch.float16,
b_dtype=torch.float16,
out_dtype=torch.float16,
device=rocm_device,
a_stride=(k, 1), # Row vector with stride_n=1
)
# Degenerate case with stride_n=1 should be N
assert selector._problem.a_transpose == origami.transpose_t.N
@pytest.mark.integration
def test_selector_transpose_degenerate_column_vector(rocm_device):
"""Test transpose detection for degenerate column vector (n=1 or k=1).
When the second dimension is 1, the matrix is a column vector.
With stride_m=1, it should be detected as transposed (T).
"""
config_gen = create_mock_config_gen()
m, n, k = 2048, 2048, 1 # k=1 makes A a column vector (m x 1)
selector = OrigamiMatmulSelector(
config_gen=config_gen,
m=m,
n=n,
k=k,
a_dtype=torch.float16,
b_dtype=torch.float16,
out_dtype=torch.float16,
device=rocm_device,
a_stride=(1, m), # Column vector with stride_m=1
)
# Degenerate case with stride_m=1 should be T
assert selector._problem.a_transpose == origami.transpose_t.T
@pytest.mark.integration
def test_selector_transpose_non_dense_vector_error(rocm_device):
"""Test that non-dense vector strides raise ValueError.
For a degenerate matrix (m=1 or n=1), if neither stride is 1,
it indicates a non-dense (strided) vector which is unsupported.
"""
config_gen = create_mock_config_gen()
m, n, k = 1, 2048, 2048 # m=1 makes A a row vector
with pytest.raises(ValueError, match="non-dense vector"):
OrigamiMatmulSelector(
config_gen=config_gen,
m=m,
n=n,
k=k,
a_dtype=torch.float16,
b_dtype=torch.float16,
out_dtype=torch.float16,
device=rocm_device,
a_stride=(2, 2), # Neither stride is 1 - non-dense
)
@pytest.mark.integration
def test_selector_transpose_unsupported_layout_error(rocm_device):
"""Test that unsupported stride layouts raise ValueError.
For a non-degenerate matrix, if neither stride is 1, it indicates
a non-contiguous memory layout which is unsupported.
Using non-square dimensions (m=2048, n=1024, k=512).
"""
config_gen = create_mock_config_gen()
m, n, k = 2048, 1024, 512
with pytest.raises(ValueError, match="unsupported layout"):
OrigamiMatmulSelector(
config_gen=config_gen,
m=m,
n=n,
k=k,
a_dtype=torch.float16,
b_dtype=torch.float16,
out_dtype=torch.float16,
device=rocm_device,
a_stride=(3, 2), # Neither stride is 1 - unsupported
)
@pytest.mark.integration
def test_selector_transpose_high_order_strides(rocm_device):
"""Test transpose detection with high-order tensor strides (batch dimension).
When strides have more than 2 values (e.g., for batched tensors),
_check_transpose_type should use only the last 2 stride values.
Using non-square dimensions (m=2048, n=1024, k=512):
- A has shape (batch, m, k) = (4, 2048, 512), strides (m*k, k, 1) = (1048576, 512, 1)
- B has shape (batch, k, n) = (4, 512, 1024), strides (k*n, n, 1) = (524288, 1024, 1)
"""
config_gen = create_mock_config_gen()
m, n, k = 2048, 1024, 512
batch = 4
selector = OrigamiMatmulSelector(
config_gen=config_gen,
m=m,
n=n,
k=k,
a_dtype=torch.float16,
b_dtype=torch.float16,
out_dtype=torch.float16,
device=rocm_device,
# 3D strides: (batch_stride, row_stride, col_stride)
# Last two values (k, 1) indicate row-major
a_stride=(m * k, k, 1), # (1048576, 512, 1)
b_stride=(k * n, n, 1), # (524288, 1024, 1)
batch=batch,
)
# Should detect row-major from last 2 stride values
assert selector._problem.a_transpose == origami.transpose_t.N
assert selector._problem.b_transpose == origami.transpose_t.N
@pytest.mark.integration
def test_selector_transpose_mixed_layouts(rocm_device):
"""Test with different transpose types for A and B matrices.
A real use case where A is column-major (transposed) and B is row-major
(non-transposed), which is common in neural network operations.
Using non-square dimensions (m=2048, n=1024, k=512):
- A has shape (m, k) = (2048, 512), strides (1, 2048) for column-major
- B has shape (k, n) = (512, 1024), strides (1024, 1) for row-major
"""
config_gen = create_mock_config_gen()
m, n, k = 2048, 1024, 512
selector = OrigamiMatmulSelector(
config_gen=config_gen,
m=m,
n=n,
k=k,
a_dtype=torch.float16,
b_dtype=torch.float16,
out_dtype=torch.float16,
device=rocm_device,
a_stride=(1, m), # Column-major for A = (1, 2048)
b_stride=(n, 1), # Row-major for B = (1024, 1)
)
assert selector._problem.a_transpose == origami.transpose_t.T
assert selector._problem.b_transpose == origami.transpose_t.N