Files
TheRock/rocm-libraries/projects/hipblaslt/tensilelite/Tensile/Tests/unit/test_graTileAssignment.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

298 lines
13 KiB
Python

#!/usr/bin/env python3
################################################################################
# GPU functional test for graTileAssignment with parameterized tile configs
#
# Usage:
# pytest test_graTileAssignment.py -v -s
################################################################################
import math
import os
import struct
import sys
import tempfile
import pytest
from types import SimpleNamespace
from gpu_test_helpers import (
HAS_GFX950,
GFX_TARGET,
TileConfig,
BPE, LOAD_WIDTH, WAVESIZE, NUM_THREADS, NUM_WAVES,
generate_gra_asm,
export_register,
compute_expected_subtile,
print_offset_grid,
)
# ---- Reference implementations ----
def compute_expected_offset(thread_id, cfg, tileInfo):
"""Python reference implementation matching graTileAssignment / _grComputeOffset.
Mirrors the kernel's exact logic:
1. colId from Serial, swizzled via DPP quad_perm + rotation (always on)
2. _grComputeRowOffset for wave partitioning
3. _grComputeOffset: (rowId + rowOffset) * stride * bpe + colId_bytes
"""
stride = cfg.stride_a if tileInfo.tc == 'A' else cfg.stride_b
bpe = BPE
depthUBytes = cfg.depth_u * bpe
blockSize = depthUBytes // LOAD_WIDTH
numRowsPerLDSBanks = (64 * 4) // depthUBytes # ldsRowBankSize / depthUBytes
waveId = thread_id // WAVESIZE
laneId = thread_id % WAVESIZE
# --- colId computation (common for A and B) ---
colId = thread_id & (blockSize - 1)
# Swizzling is always on in the kernel (hardcodes True)
# Step 1: DPP quad_perm[1,0,3,2] applied only when ldsRowId is even
# Kernel uses laneId (not Serial) for ldsRowId computation
rowInWave = laneId >> (blockSize.bit_length() - 1)
ldsRowId = rowInWave >> (numRowsPerLDSBanks.bit_length() - 1)
if ldsRowId % 2 == 0:
# quad_perm[1,0,3,2] swaps pairs within each quad
if colId % 2 == 0:
colId = colId + 1
else:
colId = colId - 1
# Step 2: Rotation with wave-specific component
rotation = (ldsRowId // 2) * 2
rotationOffset = blockSize - rotation
if tileInfo.loadRatioGR != 0.5:
# Wave-specific rotation: subtract (waveId & 1) << log2(2*numRowsPerLDSBanks)
waveRotation = (waveId & 1) << ((2 * numRowsPerLDSBanks).bit_length() - 1)
colId = (colId + rotationOffset - waveRotation) & (blockSize - 1)
else:
colId = (colId + rotationOffset) & (blockSize - 1)
# Scale colId by loadWidth
colId_bytes = colId * LOAD_WIDTH
# --- _grComputeRowOffset ---
numRowsPerWave = WAVESIZE // blockSize
partitionOffset = tileInfo.mmaTileShape[0] * tileInfo.localSubtileGrid[0]
if tileInfo.loadRatioGR == 1.0:
localRow = waveId & 1
partitionRow = waveId >> 1
elif tileInfo.loadRatioGR == 0.5:
localRow = 0
partitionRow = waveId
elif tileInfo.loadRatioGR == 2.0:
localRow = waveId
partitionRow = 0
else:
raise NotImplementedError(f"Unsupported loadRatioGR: {tileInfo.loadRatioGR}")
localRow = localRow << (numRowsPerWave.bit_length() - 1)
partitionRow = partitionOffset * partitionRow
waveRowOffset = localRow + partitionRow
# --- _grComputeOffset ---
rowId = laneId >> (blockSize.bit_length() - 1)
totalRow = rowId + waveRowOffset
base = totalRow * stride * bpe + colId_bytes
# Second GR offset if numGRPerSubtile > 1
# Kernel advances row by ceil(subtileSize * loadRatioGR) and rotates colId by +4
if tileInfo.numGRPerSubtile == 1:
return [base]
subtileSize = tileInfo.subtileShape[0] * tileInfo.mmaTileShape[0]
rowAdvance = math.ceil(subtileSize * tileInfo.loadRatioGR)
totalRow2 = totalRow + rowAdvance
colId2 = (colId + 4) & (blockSize - 1)
colId2_bytes = colId2 * LOAD_WIDTH
offset2 = totalRow2 * stride * bpe + colId2_bytes
return [base, offset2]
# Tile configs to test
TILE_CONFIGS = [
# 2x2 configs
TileConfig(mt_a=256, mt_b=256, depth_u=64, stride_a=512, stride_b=512, use_swizzling=False),
TileConfig(mt_a=256, mt_b=256, depth_u=64, stride_a=4096, stride_b=1024, use_swizzling=True),
TileConfig(mt_a=96, mt_b=256, depth_u=64, stride_a=1024, stride_b=256, use_swizzling=True),
# # 1x4 configs
TileConfig(mt_a=80, mt_b=64, depth_u=64, stride_a=64, stride_b=256, use_swizzling=True),
TileConfig(mt_a=80, mt_b=64, depth_u=64, stride_a=64, stride_b=64, use_swizzling=True),
# # 4x1 configs
TileConfig(mt_a=64, mt_b=48, depth_u=64, stride_a=64, stride_b=256, use_swizzling=True),
# # mt0<32 (read size)
TileConfig(mt_a=16, mt_b=64, depth_u=64, stride_a=64, stride_b=64, use_swizzling=True),
]
# ---- Pytest tests ----
@pytest.mark.skipif(not HAS_GFX950, reason=f"GPU tests require gfx950, found {GFX_TARGET}")
class TestGraTileAssignmentGPU:
@pytest.fixture(params=TILE_CONFIGS, ids=lambda c: c.label)
def gra_env(self, request, tmp_path):
"""Generate graTileAssignment asm once per tile config."""
cfg = request.param
gra_asm, writer, tileInfoA, tileInfoB, kernel = generate_gra_asm(cfg)
return SimpleNamespace(
cfg=cfg,
gra_asm=gra_asm,
writer=writer,
tileInfoA=tileInfoA,
tileInfoB=tileInfoB,
kernel=kernel,
tmp_path=tmp_path,
)
def test_offset_a(self, gra_env):
"""Validate all sharedVgprGROffset vgprs for matrix A across all threads."""
cfg = gra_env.cfg
for idx, reg in enumerate(gra_env.tileInfoA.sharedVgprGROffset):
results = export_register(gra_env.writer, gra_env.gra_asm, reg, False,
cfg, gra_env.tmp_path, f"offsetA_v{reg}_{cfg.label}")
for tid in range(NUM_THREADS):
expected = compute_expected_offset(tid, cfg, gra_env.tileInfoA)
assert results[tid] == expected[idx], \
f"[{cfg.label}] A offset[{idx}] v{reg} mismatch at tid={tid}: got {results[tid]}, expected {expected[idx]}"
def test_offset_b(self, gra_env):
"""Validate all sharedVgprGROffset vgprs for matrix B across all threads."""
cfg = gra_env.cfg
for idx, reg in enumerate(gra_env.tileInfoB.sharedVgprGROffset):
results = export_register(gra_env.writer, gra_env.gra_asm, reg, False,
cfg, gra_env.tmp_path, f"offsetB_v{reg}_{cfg.label}")
for tid in range(NUM_THREADS):
expected = compute_expected_offset(tid, cfg, gra_env.tileInfoB)
assert results[tid] == expected[idx], \
f"[{cfg.label}] B offset[{idx}] v{reg} mismatch at tid={tid}: got {results[tid]}, expected {expected[idx]}"
def _test_subtile_registers(self, gra_env, tc):
"""Validate localSubtilesRegister values for matrix tc."""
cfg = gra_env.cfg
tileInfo = gra_env.tileInfoA if tc == 'A' else gra_env.tileInfoB
stride = cfg.stride_a if tc == 'A' else cfg.stride_b
seen = set()
for st in tileInfo.localSubtiles:
regId = st.regListId
if regId in seen:
continue
seen.add(regId)
for reg in tileInfo.localSubtilesRegister[regId]:
results = export_register(gra_env.writer, gra_env.gra_asm, reg, st.useSgpr,
cfg, gra_env.tmp_path, f"subtile{tc}_s{reg}_{cfg.label}")
expected = compute_expected_subtile(regId, stride, tileInfo, BPE)
actual = results[0]
assert actual == expected, \
f"[{cfg.label}] {tc} subtile s{reg} (regId={regId}): " \
f"got {actual}, expected {expected}"
def test_subtile_registers_a(self, gra_env):
"""Validate localSubtilesRegister values for matrix A."""
self._test_subtile_registers(gra_env, 'A')
def test_subtile_registers_b(self, gra_env):
"""Validate localSubtilesRegister values for matrix B."""
self._test_subtile_registers(gra_env, 'B')
if __name__ == "__main__":
"""Run standalone without pytest."""
import argparse
parser = argparse.ArgumentParser(description="GPU test for graTileAssignment")
parser.add_argument("--grid", action="store_true",
help="Display offsets as 2D grid (waves x lanes) for A and B")
parser.add_argument("--debug", action="store_true",
help="Display expected matrix in grid mode (implies --grid)")
args = parser.parse_args()
if args.debug:
args.grid = True
for cfg in TILE_CONFIGS:
print(f"\n{'='*60}")
print(f" Tile Config: {cfg.label}")
print(f"{'='*60}")
gra_asm, writer, tileInfoA, tileInfoB, kernel = generate_gra_asm(cfg)
with tempfile.TemporaryDirectory() as tmp_dir:
tmp_path = type('P', (), {'__truediv__': lambda s, n: os.path.join(tmp_dir, n)})()
# Print the generated assembly for inspection
print("\n--- Generated Assembly (graTileAssignment section) ---")
in_gra = False
for line in gra_asm.split('\n'):
if 'GR Offset' in line or in_gra:
in_gra = True
print(line)
print("--- End ---\n")
# Test all sharedVgprGROffset vgprs for both matrices
for tc, tileInfo, stride, mt in [("A", tileInfoA, cfg.stride_a, cfg.mt_a),
("B", tileInfoB, cfg.stride_b, cfg.mt_b)]:
for idx, reg in enumerate(tileInfo.sharedVgprGROffset):
results = export_register(writer, gra_asm, reg, False, cfg, tmp_path,
f"offset{tc}_v{reg}_{cfg.label}")
if args.grid:
print_offset_grid(f"Matrix {tc} GPU offset[{idx}] v{reg} ({cfg.label})",
results, WAVESIZE, NUM_WAVES)
if args.debug:
expected = [compute_expected_offset(tid, cfg,
tileInfo)[idx]
for tid in range(NUM_THREADS)]
print_offset_grid(f"Matrix {tc} EXPECTED offset[{idx}] ({cfg.label})",
expected, WAVESIZE, NUM_WAVES)
mismatches = sum(1 for t in range(NUM_THREADS) if results[t] != expected[t])
if mismatches:
print(f"\n--- Matrix {tc} offset[{idx}] DIFF ({mismatches} mismatches) ---")
for w in range(NUM_WAVES):
print(f" w{w}: ", end="")
for lane in range(WAVESIZE):
tid = w * WAVESIZE + lane
if results[tid] != expected[tid]:
print(f" t{tid}:{results[tid]}!={expected[tid]}", end="")
print()
else:
print(f"\n Matrix {tc} offset[{idx}]: all match.")
errors = 0
for tid in range(NUM_THREADS):
exp = compute_expected_offset(tid, cfg, tileInfo)[idx]
if results[tid] != exp:
errors += 1
if not args.grid:
print(f" FAIL {tc} offset[{idx}] v{reg} tid={tid}: got {results[tid]}, expected {exp}")
elif not args.grid and tid < 64:
print(f" OK {tc} offset[{idx}] v{reg} tid={tid}: {results[tid]}")
print(f" Matrix {tc} offset[{idx}] v{reg}: {NUM_THREADS} threads, {errors} errors")
# Subtile registers
for tc, tileInfo, stride in [("A", tileInfoA, cfg.stride_a),
("B", tileInfoB, cfg.stride_b)]:
seen = set()
for st in tileInfo.localSubtiles:
regId = st.regListId
if regId in seen:
continue
seen.add(regId)
for reg in tileInfo.localSubtilesRegister[regId]:
print("Regl",reg)
results = export_register(writer, gra_asm, reg, st.useSgpr, cfg,
tmp_path, f"subtile{tc}_s{reg}_{cfg.label}")
expected = compute_expected_subtile(regId, stride, tileInfo, BPE)
actual = results[0]
status = "OK" if actual == expected else "FAIL"
print(f" Subtile {tc} s{reg} (regId={regId}): {actual} (expected {expected}) {status}")