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
279 lines
9.5 KiB
Python
279 lines
9.5 KiB
Python
# Copyright Advanced Micro Devices, Inc., or its affiliates.
|
|
# SPDX-License-Identifier: MIT
|
|
|
|
"""Unit tests for zigzag WMMA tile ordering in emit_mfma."""
|
|
|
|
import re
|
|
import pytest
|
|
from types import SimpleNamespace
|
|
from unittest.mock import MagicMock
|
|
|
|
from Tensile.Components.Subtile.LogicalScheduler import (
|
|
MFMATileRange, ReadGranularity, SchedulerConfig, MFMAPlacement,
|
|
)
|
|
from Tensile.Components.Subtile.InstructionEmitter import _zigzag_order
|
|
from Tensile.Components.Subtile.InstructionEmitter import InstructionEmitter
|
|
from Tensile.Components.Subtile.Kernel import TileInfo, AB_B16, CD_F32
|
|
|
|
|
|
# ── Helpers ─────────────────────────────────────────────────────
|
|
|
|
_COMMENT_RE = re.compile(r"MFMA C\[(\d+),(\d+)\]")
|
|
|
|
|
|
def _extract_ab_pairs(instructions):
|
|
"""Return [(a, b), ...] from MFMA instruction comments."""
|
|
pairs = []
|
|
for inst in instructions:
|
|
m = _COMMENT_RE.search(str(inst))
|
|
if m:
|
|
pairs.append((int(m.group(1)), int(m.group(2))))
|
|
return pairs
|
|
|
|
|
|
def _mock_dtype(num_bytes=2):
|
|
mock = MagicMock()
|
|
mock.numBytes.return_value = num_bytes
|
|
mock.numRegisters.return_value = num_bytes / 4
|
|
mock.isBFloat16.return_value = (num_bytes == 2)
|
|
mock.is8bitFloat.return_value = (num_bytes == 1)
|
|
return mock
|
|
|
|
|
|
def _make_kernel(numM, numN):
|
|
"""Minimal kernel dict for emit_mfma."""
|
|
dtype = _mock_dtype(2)
|
|
return {
|
|
"MacroTile0": numM * 16,
|
|
"MacroTile1": numN * 16,
|
|
"MacroTileA": numM * 16,
|
|
"MacroTileB": numN * 16,
|
|
"DepthU": 64,
|
|
"_DepthUA": 64,
|
|
"_DepthUB": 64,
|
|
"MatrixInstM": 16,
|
|
"MatrixInstN": 16,
|
|
"MatrixInstK": 32,
|
|
"MatrixInstB": 1,
|
|
"MIInputPerThreadA": 8,
|
|
"MIInputPerThreadB": 8,
|
|
"MIWaveGroup": [1, 1],
|
|
"WavefrontSize": 32,
|
|
"SourceSwap": False,
|
|
"MIArchVgpr": True,
|
|
"ProblemType": {
|
|
"DataTypeA": dtype,
|
|
"DataTypeB": dtype,
|
|
"ComputeDataType": _mock_dtype(4),
|
|
"MXBlockA": 0,
|
|
"MXBlockB": 0,
|
|
},
|
|
}
|
|
|
|
|
|
def _make_emitter(numM, numN):
|
|
"""Build a minimal InstructionEmitter that can call emit_mfma."""
|
|
from rocisa.register import RegisterPool
|
|
from rocisa.enum import RegisterType
|
|
|
|
kernel = _make_kernel(numM, numN)
|
|
|
|
writer = SimpleNamespace()
|
|
writer.vgprPool = RegisterPool(0, RegisterType.Vgpr,
|
|
defaultPreventOverflow=False, printRP=False)
|
|
writer.sgprPool = RegisterPool(0, RegisterType.Sgpr,
|
|
defaultPreventOverflow=False, printRP=False)
|
|
writer.agprPool = RegisterPool(0, RegisterType.Accvgpr,
|
|
defaultPreventOverflow=False, printRP=False)
|
|
writer.states = SimpleNamespace(
|
|
archCaps={"LDSBankCount": 64, "LDSBankWidth": 4},
|
|
asmCaps={"HasWMMA_V3": True},
|
|
regCaps={"MaxSgpr": 106, "MaxVgpr": 256, "PhysicalMaxVgpr": 512},
|
|
subtileLdsSwizzle=False,
|
|
)
|
|
|
|
tileInfoA = TileInfo(AB_B16, 'A', writer, kernel)
|
|
tileInfoB = TileInfo(AB_B16, 'B', writer, kernel)
|
|
dtileInfo = TileInfo(CD_F32, 'D', writer, kernel)
|
|
|
|
# Allocate VGPR tiles for A, B, D
|
|
tileInfoA.allocVgprTileRegisters_legacy(writer, kernel)
|
|
tileInfoB.allocVgprTileRegisters_legacy(writer, kernel)
|
|
dtileInfo.allocVgprTileRegisters_legacy(writer, kernel)
|
|
|
|
config = SchedulerConfig(
|
|
numMFMATilesM=numM,
|
|
numMFMATilesN=numN,
|
|
numSubIterK=2,
|
|
lrA=ReadGranularity(mn=1, k=1),
|
|
lrB=ReadGranularity(mn=1, k=1),
|
|
grA=ReadGranularity(mn=1, k=2),
|
|
grB=ReadGranularity(mn=1, k=2),
|
|
)
|
|
|
|
emitter = InstructionEmitter(
|
|
writer, kernel, config,
|
|
tileInfoA, tileInfoB, dtileInfo,
|
|
tileInfoA.vgprTiles, tileInfoB.vgprTiles,
|
|
)
|
|
return emitter
|
|
|
|
|
|
def _make_placement(numM, numN, subIterK=0):
|
|
"""Create an MFMAPlacement spanning all M and N tiles at one subIterK."""
|
|
placement = MFMAPlacement(
|
|
subIterK=subIterK,
|
|
tileA=MFMATileRange(subIterK, subIterK + 1, 0, numM),
|
|
tileB=MFMATileRange(subIterK, subIterK + 1, 0, numN),
|
|
)
|
|
# Identity tile map: tileId -> vgprTileId (one unroll iter)
|
|
placement.vgpr_tile_maps = {
|
|
'A': [{i: i for i in range(numM)}],
|
|
'B': [{i: i for i in range(numN)}],
|
|
}
|
|
return placement
|
|
|
|
|
|
@pytest.fixture(scope="module", autouse=True)
|
|
def _init_rocisa():
|
|
from gpu_test_helpers import init_rocisa
|
|
init_rocisa(target="gfx1250", wavesize=32)
|
|
|
|
|
|
# ── Tests ───────────────────────────────────────────────────────
|
|
|
|
class TestZigzagOrder:
|
|
"""Verify the _zigzag_order helper produces correct zigzag-matrix traversal."""
|
|
|
|
def test_1x1(self):
|
|
assert _zigzag_order(1, 1) == [(0, 0)]
|
|
|
|
def test_2x2(self):
|
|
assert _zigzag_order(2, 2) == [(0, 0), (0, 1), (1, 1), (1, 0)]
|
|
|
|
def test_3x3(self):
|
|
expected = [
|
|
(0, 0), (0, 1), (0, 2),
|
|
(1, 2), (1, 1), (1, 0),
|
|
(2, 0), (2, 1), (2, 2),
|
|
]
|
|
assert _zigzag_order(3, 3) == expected
|
|
|
|
def test_4x4(self):
|
|
result = _zigzag_order(4, 4)
|
|
assert len(result) == 16
|
|
assert len(set(result)) == 16 # all unique
|
|
# every consecutive pair shares exactly one coordinate
|
|
for i in range(1, len(result)):
|
|
r0, c0 = result[i - 1]
|
|
r1, c1 = result[i]
|
|
assert (r0 == r1) != (c0 == c1), (
|
|
f"Step {i}: ({r0},{c0})->({r1},{c1}) must share exactly one coord"
|
|
)
|
|
|
|
def test_2x4_rectangular(self):
|
|
result = _zigzag_order(2, 4)
|
|
assert len(result) == 8
|
|
assert len(set(result)) == 8
|
|
# first row left-to-right, then down, then bottom row right-to-left, then up
|
|
assert result == [
|
|
(0, 0), (0, 1), (0, 2), (0, 3),
|
|
(1, 3), (1, 2), (1, 1), (1, 0),
|
|
]
|
|
|
|
def test_4x2_rectangular(self):
|
|
result = _zigzag_order(4, 2)
|
|
assert len(result) == 8
|
|
assert len(set(result)) == 8
|
|
assert result == [
|
|
(0, 0), (0, 1),
|
|
(1, 1), (1, 0),
|
|
(2, 0), (2, 1),
|
|
(3, 1), (3, 0),
|
|
]
|
|
|
|
def test_1xN_single_row(self):
|
|
assert _zigzag_order(1, 5) == [(0, c) for c in range(5)]
|
|
|
|
def test_Nx1_single_col(self):
|
|
assert _zigzag_order(5, 1) == [(r, 0) for r in range(5)]
|
|
|
|
|
|
class TestZigzagEmitMfma:
|
|
"""Verify emit_mfma produces zigzag-ordered (a,b) pairs when enabled."""
|
|
|
|
def test_longer_a_sweeps_along_a(self):
|
|
"""When A > B, the contiguous zig sweeps along A (longer dim)."""
|
|
numM, numN = 4, 3
|
|
emitter = _make_emitter(numM, numN)
|
|
placement = _make_placement(numM, numN)
|
|
insts = emitter.emit_mfma(placement, unroll_iter=0)
|
|
pairs = _extract_ab_pairs(insts)
|
|
|
|
# Sweep along A: outer=B(3), inner=A(4)
|
|
expected = [(c, r) for r, c in _zigzag_order(numN, numM)]
|
|
assert pairs == expected
|
|
|
|
def test_longer_b_sweeps_along_b(self):
|
|
"""When B > A, the contiguous zig sweeps along B (longer dim)."""
|
|
numM, numN = 3, 4
|
|
emitter = _make_emitter(numM, numN)
|
|
placement = _make_placement(numM, numN)
|
|
insts = emitter.emit_mfma(placement, unroll_iter=0)
|
|
pairs = _extract_ab_pairs(insts)
|
|
|
|
# Sweep along B: outer=A(3), inner=B(4)
|
|
expected = [(r, c) for r, c in _zigzag_order(numM, numN)]
|
|
assert pairs == expected
|
|
|
|
def test_every_pair_shares_one_operand(self):
|
|
"""Every consecutive WMMA pair must share exactly one operand (A or B)."""
|
|
numM, numN = 4, 4
|
|
emitter = _make_emitter(numM, numN)
|
|
placement = _make_placement(numM, numN)
|
|
insts = emitter.emit_mfma(placement, unroll_iter=0)
|
|
pairs = _extract_ab_pairs(insts)
|
|
|
|
for i in range(1, len(pairs)):
|
|
prev_a, prev_b = pairs[i - 1]
|
|
cur_a, cur_b = pairs[i]
|
|
shared_a = (prev_a == cur_a)
|
|
shared_b = (prev_b == cur_b)
|
|
assert shared_a != shared_b, (
|
|
f"Step {i}: ({prev_a},{prev_b})->({cur_a},{cur_b}) "
|
|
f"must share exactly one operand"
|
|
)
|
|
|
|
def test_single_b_tile_unaffected(self):
|
|
"""With only 1 B tile, zigzag falls back to raster."""
|
|
numM, numN = 4, 1
|
|
emitter = _make_emitter(numM, numN)
|
|
placement = _make_placement(numM, numN)
|
|
insts = emitter.emit_mfma(placement, unroll_iter=0)
|
|
pairs = _extract_ab_pairs(insts)
|
|
|
|
expected = [(a, 0) for a in range(numM)]
|
|
assert pairs == expected
|
|
|
|
def test_single_a_tile_unaffected(self):
|
|
"""With only 1 A tile, zigzag falls back to raster."""
|
|
numM, numN = 1, 4
|
|
emitter = _make_emitter(numM, numN)
|
|
placement = _make_placement(numM, numN)
|
|
insts = emitter.emit_mfma(placement, unroll_iter=0)
|
|
pairs = _extract_ab_pairs(insts)
|
|
|
|
expected = [(0, b) for b in range(numN)]
|
|
assert pairs == expected
|
|
|
|
def test_2x2_zigzag(self):
|
|
"""Minimal 2x2 case: zigzag snake (A==B, sweeps along A)."""
|
|
numM, numN = 2, 2
|
|
emitter = _make_emitter(numM, numN)
|
|
placement = _make_placement(numM, numN)
|
|
insts = emitter.emit_mfma(placement, unroll_iter=0)
|
|
pairs = _extract_ab_pairs(insts)
|
|
|
|
# A>=B so sweep along A: [(0,0),(1,0),(1,1),(0,1)]
|
|
assert pairs == [(0, 0), (1, 0), (1, 1), (0, 1)]
|