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
458 lines
15 KiB
Python
458 lines
15 KiB
Python
################################################################################
|
|
#
|
|
# Copyright (C) 2026 Advanced Micro Devices, Inc. All rights reserved.
|
|
#
|
|
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
# of this software and associated documentation files (the "Software"), to deal
|
|
# in the Software without restriction, including without limitation the rights
|
|
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
# copies of the Software, and to permit persons to whom the Software is
|
|
# furnished to do so, subject to the following conditions:
|
|
#
|
|
# The above copyright notice and this permission notice shall be included in
|
|
# all copies or substantial portions of the Software.
|
|
#
|
|
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
# SOFTWARE.
|
|
#
|
|
################################################################################
|
|
|
|
import importlib.util
|
|
import sys
|
|
import types
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
|
|
|
|
# Load ValidChipId.py via importlib to bypass Tensile/TensileLogic/__init__.py,
|
|
# which transitively imports joblib / heavy build deps via Run.py.
|
|
def _load_validchipid_mod():
|
|
p = Path(__file__).resolve().parents[2] / "TensileLogic" / "ValidChipId.py"
|
|
spec = importlib.util.spec_from_file_location("ValidChipId_under_test", p)
|
|
assert spec and spec.loader
|
|
mod = importlib.util.module_from_spec(spec)
|
|
spec.loader.exec_module(mod)
|
|
return mod
|
|
|
|
|
|
def _install_rocisa_stub(monkeypatch):
|
|
# When the rocisa C-extension is not importable (e.g. CI lint job), install
|
|
# a minimal fixture-scoped stub so this module does not pollute sys.modules
|
|
# for the rest of the pytest session.
|
|
try: # pragma: no cover - environment-dependent
|
|
import rocisa # noqa: F401
|
|
return
|
|
except ImportError: # pragma: no cover
|
|
_rocisa_stub = types.ModuleType("rocisa")
|
|
|
|
class _RocIsaStub: # noqa: D401 - test helper
|
|
@staticmethod
|
|
def getInstance():
|
|
return None
|
|
|
|
_rocisa_stub.rocIsa = _RocIsaStub
|
|
monkeypatch.setitem(sys.modules, "rocisa", _rocisa_stub)
|
|
|
|
|
|
@pytest.fixture
|
|
def validchipid_mod(monkeypatch):
|
|
_install_rocisa_stub(monkeypatch)
|
|
return _load_validchipid_mod()
|
|
|
|
|
|
@pytest.fixture
|
|
def validate_chip_id(validchipid_mod):
|
|
return validchipid_mod._validateChipId
|
|
|
|
|
|
@pytest.fixture
|
|
def fallback_family(validchipid_mod):
|
|
return validchipid_mod._fallbackFamily
|
|
|
|
|
|
|
|
@pytest.fixture
|
|
def arch_mod(monkeypatch):
|
|
# Architectures.py uses package-relative imports, so spec_from_file_location
|
|
# is not viable here. The fixture-scoped rocisa stub is sufficient to import
|
|
# it normally without the C-extension.
|
|
_install_rocisa_stub(monkeypatch)
|
|
from Tensile.Common import Architectures
|
|
|
|
return Architectures
|
|
|
|
|
|
def _writeLogicFile(
|
|
path: Path,
|
|
*,
|
|
gfx: str = "gfx950",
|
|
name: str = "gfx950",
|
|
devices: str = "Device 75a0",
|
|
) -> Path:
|
|
path.parent.mkdir(parents=True, exist_ok=True)
|
|
path.write_text(
|
|
"\n".join(
|
|
[
|
|
"- MinimumRequiredVersion: 4.33.0",
|
|
f"- {name}",
|
|
f"- {gfx}",
|
|
f"- [{devices}]",
|
|
"",
|
|
]
|
|
)
|
|
)
|
|
return path
|
|
|
|
|
|
def _baseGfx950Path(tmp_path: Path) -> Path:
|
|
return tmp_path / "gfx950" / "gfx950" / "Equality" / "logic.yaml"
|
|
|
|
|
|
def _variantGfx950Path(tmp_path: Path, chip_id: str = "75a3") -> Path:
|
|
return tmp_path / "gfx950" / f"gfx950_id{chip_id}" / "Equality" / "logic.yaml"
|
|
|
|
|
|
def _malformedVariantGfx950Path(tmp_path: Path, chip_id: str = "75a3") -> Path:
|
|
return tmp_path / "gfx950" / f"gfx950_{chip_id}" / "Equality" / "logic.yaml"
|
|
|
|
|
|
def test_validateChipIdAcceptsBaseDefaultChipId(tmp_path, validate_chip_id):
|
|
logic_file = _writeLogicFile(_baseGfx950Path(tmp_path), devices="Device 75a0")
|
|
|
|
assert validate_chip_id(logic_file)
|
|
|
|
|
|
def test_validateChipIdAcceptsVariantChipIdWithFallbackFamily(tmp_path, validate_chip_id):
|
|
logic_file = _writeLogicFile(
|
|
_variantGfx950Path(tmp_path, "75a3"),
|
|
devices="Device 75a3, Device 75a2",
|
|
)
|
|
|
|
assert validate_chip_id(logic_file)
|
|
|
|
|
|
def test_validateChipIdRejectsDefaultFallbackChipIdInVariantDirectory(
|
|
tmp_path,
|
|
capsys,
|
|
validate_chip_id,
|
|
):
|
|
logic_file = _writeLogicFile(
|
|
_variantGfx950Path(tmp_path, "75a3"),
|
|
devices="Device 75a3, Device 75a2, Device 75a0",
|
|
)
|
|
|
|
assert not validate_chip_id(logic_file)
|
|
err = capsys.readouterr().err
|
|
assert "may not declare default fallback chip IDs" in err
|
|
|
|
|
|
def test_validateChipIdRejectsEmptyDeviceList(tmp_path, capsys, validate_chip_id):
|
|
# `- [Device]` (no chip after the keyword) is the only form that yields
|
|
# an empty DeviceIds set without raising in the parser. ValidChipId must
|
|
# surface the specific "must declare at least one Device chip ID" branch.
|
|
logic_file = _baseGfx950Path(tmp_path)
|
|
logic_file.parent.mkdir(parents=True, exist_ok=True)
|
|
logic_file.write_text(
|
|
"\n".join(
|
|
[
|
|
"- MinimumRequiredVersion: 4.33.0",
|
|
"- gfx950",
|
|
"- gfx950",
|
|
"- [Device]",
|
|
"",
|
|
]
|
|
)
|
|
)
|
|
|
|
assert not validate_chip_id(logic_file)
|
|
err = capsys.readouterr().err
|
|
assert "must declare at least one Device chip ID" in err
|
|
|
|
|
|
def test_validateChipIdRejectsMissingDeviceLine(tmp_path, capsys, validate_chip_id):
|
|
# File with no Device line raises LogicFileError in the parser, which
|
|
# ValidChipId must surface via the "Chip ID validation failed" path.
|
|
logic_file = _baseGfx950Path(tmp_path)
|
|
logic_file.parent.mkdir(parents=True, exist_ok=True)
|
|
logic_file.write_text(
|
|
"\n".join(
|
|
[
|
|
"- {MinimumRequiredVersion: 4.33.0}",
|
|
"- gfx950",
|
|
"- gfx950",
|
|
"",
|
|
]
|
|
)
|
|
)
|
|
|
|
assert not validate_chip_id(logic_file)
|
|
err = capsys.readouterr().err
|
|
assert "Chip ID validation failed" in err
|
|
|
|
|
|
def test_validateChipIdRejectsMismatchedChipIdReportsPredicateError(
|
|
tmp_path,
|
|
capsys,
|
|
validate_chip_id,
|
|
):
|
|
# 74a0 belongs to gfx942, not gfx950 — _verifyPredicate must reject it.
|
|
logic_file = _writeLogicFile(_baseGfx950Path(tmp_path), devices="Device 74a0")
|
|
|
|
assert not validate_chip_id(logic_file)
|
|
err = capsys.readouterr().err
|
|
assert "not associated with gfx950" in err
|
|
|
|
|
|
def test_validateChipIdRejectsUnsupportedChipIdReportsPredicateError(
|
|
tmp_path,
|
|
capsys,
|
|
validate_chip_id,
|
|
):
|
|
logic_file = _writeLogicFile(_baseGfx950Path(tmp_path), devices="Device ffff")
|
|
|
|
assert not validate_chip_id(logic_file)
|
|
err = capsys.readouterr().err
|
|
assert "device ID not supported" in err
|
|
|
|
|
|
def test_validateChipIdAcceptsSourceChipIdInBaseDirectory(tmp_path, validate_chip_id):
|
|
logic_file = _writeLogicFile(_baseGfx950Path(tmp_path), devices="Device 75a3")
|
|
|
|
assert validate_chip_id(logic_file)
|
|
|
|
|
|
def test_validateChipIdAcceptsMixedSourceAndDefaultChipIdsInPlainArchDirectory(
|
|
tmp_path,
|
|
validate_chip_id,
|
|
):
|
|
logic_file = _writeLogicFile(
|
|
tmp_path / "gfx950" / "GridBased" / "logic.yaml",
|
|
devices="Device 75a3, Device 75a0",
|
|
)
|
|
|
|
assert validate_chip_id(logic_file)
|
|
|
|
|
|
def test_validateChipIdRejectsVariantDirectoryWithoutMatchingChipId(
|
|
tmp_path,
|
|
capsys,
|
|
validate_chip_id,
|
|
):
|
|
logic_file = _writeLogicFile(
|
|
_variantGfx950Path(tmp_path, "75a3"),
|
|
devices="Device 75a8",
|
|
)
|
|
|
|
assert not validate_chip_id(logic_file)
|
|
err = capsys.readouterr().err
|
|
assert "directory must contain id=75a3" in err
|
|
|
|
|
|
def test_validateChipIdRejectsDefaultChipIdInVariantDirectory(
|
|
tmp_path,
|
|
capsys,
|
|
validate_chip_id,
|
|
):
|
|
logic_file = _writeLogicFile(
|
|
_variantGfx950Path(tmp_path, "75a0"),
|
|
devices="Device 75a0",
|
|
)
|
|
|
|
assert not validate_chip_id(logic_file)
|
|
err = capsys.readouterr().err
|
|
assert "non-source chip ID" in err
|
|
|
|
|
|
def test_validateChipIdRejectsFallbackChipIdInMalformedVariantDirectory(
|
|
tmp_path,
|
|
capsys,
|
|
validate_chip_id,
|
|
):
|
|
logic_file = _writeLogicFile(
|
|
_malformedVariantGfx950Path(tmp_path, "75a3"),
|
|
devices="Device 75a0",
|
|
)
|
|
|
|
assert not validate_chip_id(logic_file)
|
|
err = capsys.readouterr().err
|
|
assert "must use gfx950_id<chip> format" in err
|
|
# Offending directory name must be reported so the user can act on it.
|
|
assert "gfx950_75a3" in err
|
|
|
|
|
|
def test_validateChipIdDoesNotRequireChipIdDirectoryForNonGatedArch(tmp_path, validate_chip_id):
|
|
logic_file = _writeLogicFile(
|
|
tmp_path / "aquavanjaram" / "gfx942" / "Equality" / "logic.yaml",
|
|
gfx="gfx942",
|
|
name="aquavanjaram",
|
|
devices="Device 74a0",
|
|
)
|
|
|
|
assert validate_chip_id(logic_file)
|
|
|
|
|
|
def test_validateChipIdIgnoresUnsupportedChipIdForNonGatedArch(tmp_path, validate_chip_id):
|
|
logic_file = _writeLogicFile(
|
|
tmp_path / "aquavanjaram" / "gfx942_20cu" / "GridBased" / "logic.yaml",
|
|
gfx="gfx942",
|
|
name="aquavanjaram",
|
|
devices="Device 0050",
|
|
)
|
|
|
|
assert validate_chip_id(logic_file)
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Path-walk: chip-ID directory nearest the file wins, regardless of unrelated
|
|
# 'gfx950' segments earlier in the path. Regression coverage for the
|
|
# last-match-wins bug in _chipIdDirFromPath.
|
|
# ---------------------------------------------------------------------------
|
|
|
|
def test_validateChipIdResolvesNearestChipIdDirWithEnclosingGfxAncestor(
|
|
tmp_path,
|
|
validate_chip_id,
|
|
):
|
|
# Layout: <tmp>/gfx950/checkout/gfx950/gfx950_id75a3/Equality/logic.yaml
|
|
# The inner 'gfx950' must NOT reset the chip-ID dir state and cause the
|
|
# variant directory to be missed.
|
|
logic_file = _writeLogicFile(
|
|
tmp_path / "gfx950" / "checkout" / "gfx950" / "gfx950_id75a3" / "Equality" / "logic.yaml",
|
|
devices="Device 75a3",
|
|
)
|
|
|
|
assert validate_chip_id(logic_file, logic_relative_path=logic_file.relative_to(tmp_path))
|
|
|
|
|
|
def test_validateChipIdAcceptsUppercaseHexInYaml(tmp_path, validate_chip_id):
|
|
# Regression: uppercase hex in YAML must canonicalize before predicate
|
|
# check; otherwise gets falsely reported as "device ID not supported".
|
|
logic_file = _writeLogicFile(
|
|
_variantGfx950Path(tmp_path, "75a3"),
|
|
devices="Device 75A3",
|
|
)
|
|
|
|
assert validate_chip_id(logic_file)
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# "Test 5" parametrized matrix: every chip-ID-aware arch in the production
|
|
# registries must validate cleanly when placed in a representative directory.
|
|
# Derived directly from GFX_CHIP_IDS / SUPPORTED_CHIP_ID_FALLBACKS so this
|
|
# test stays in sync with the registry.
|
|
# ---------------------------------------------------------------------------
|
|
|
|
def _gated_archs(arch_mod):
|
|
"""Archs currently gated by supportsChipIdPredicate (only gfx950 today)."""
|
|
return [
|
|
gfx
|
|
for gfx in arch_mod.GFX_CHIP_IDS
|
|
if arch_mod.supportsChipIdPredicate(gfx)
|
|
]
|
|
|
|
|
|
def _source_chip_ids(arch_mod, gfx):
|
|
arch_keys = {f"id={cid.lower()}" for cid in arch_mod.GFX_CHIP_IDS[gfx]}
|
|
return [
|
|
cid for cid in arch_mod.GFX_CHIP_IDS[gfx]
|
|
if f"id={cid.lower()}" in arch_mod.SUPPORTED_CHIP_ID_FALLBACKS
|
|
and f"id={cid.lower()}" in arch_keys
|
|
]
|
|
|
|
|
|
def test_validateChipIdAcceptsAllArchChipIdsInBaseDir(
|
|
tmp_path,
|
|
validate_chip_id,
|
|
arch_mod,
|
|
):
|
|
for gfx in _gated_archs(arch_mod):
|
|
for chip_id in arch_mod.GFX_CHIP_IDS[gfx]:
|
|
logic_file = _writeLogicFile(
|
|
tmp_path / gfx / gfx / "Equality" / f"{chip_id}.yaml",
|
|
gfx=gfx,
|
|
name=gfx,
|
|
devices=f"Device {chip_id}",
|
|
)
|
|
|
|
assert validate_chip_id(logic_file), (
|
|
f"chip ID {chip_id} should validate in base {gfx} directory"
|
|
)
|
|
|
|
|
|
def test_validateChipIdAcceptsEverySourceChipIdInItsVariantDir(
|
|
tmp_path,
|
|
validate_chip_id,
|
|
arch_mod,
|
|
):
|
|
for gfx in _gated_archs(arch_mod):
|
|
for chip_id in _source_chip_ids(arch_mod, gfx):
|
|
logic_file = _writeLogicFile(
|
|
tmp_path / gfx / f"{gfx}_id{chip_id}" / "Equality" / "logic.yaml",
|
|
gfx=gfx,
|
|
name=gfx,
|
|
devices=f"Device {chip_id}",
|
|
)
|
|
|
|
assert validate_chip_id(logic_file), (
|
|
f"source chip ID {chip_id} should validate in {gfx}_id{chip_id} directory"
|
|
)
|
|
|
|
|
|
def test_validateChipIdSkipsAllNonGatedArchs(tmp_path, validate_chip_id, arch_mod):
|
|
non_gated_archs = [
|
|
gfx
|
|
for gfx in arch_mod.GFX_CHIP_IDS
|
|
if not arch_mod.supportsChipIdPredicate(gfx)
|
|
]
|
|
assert non_gated_archs
|
|
|
|
for gfx in non_gated_archs:
|
|
# Pick any chip ID for the arch — placement rules don't apply to
|
|
# non-gated archs, so the validator must short-circuit to True.
|
|
chip_id = arch_mod.GFX_CHIP_IDS[gfx][0]
|
|
logic_file = _writeLogicFile(
|
|
tmp_path / gfx / gfx / "Equality" / f"{chip_id}.yaml",
|
|
gfx=gfx,
|
|
name=gfx,
|
|
devices=f"Device {chip_id}",
|
|
)
|
|
|
|
assert validate_chip_id(logic_file)
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Direct unit tests for _fallbackFamily — pin the documented behaviour so
|
|
# changes to SUPPORTED_CHIP_ID_FALLBACKS or the family-expansion algorithm
|
|
# fail loudly here, not silently in placement validation.
|
|
# ---------------------------------------------------------------------------
|
|
|
|
def test_fallbackFamilyForDefaultChipIdIsSingleton(fallback_family):
|
|
# A default chip ID has no entry in SUPPORTED_CHIP_ID_FALLBACKS; its
|
|
# family must be just itself.
|
|
assert fallback_family("id=75a0", "gfx950") == {"id=75a0"}
|
|
|
|
|
|
def test_fallbackFamilyForSourceChipIdIncludesSiblingsAndFallback(
|
|
fallback_family,
|
|
arch_mod,
|
|
):
|
|
# All gfx950 source IDs share id=75a0 as their direct fallback. The
|
|
# family expansion is intentionally permissive so a single variant
|
|
# directory can host logic for siblings sharing a fallback root.
|
|
family = fallback_family("id=75a3", "gfx950")
|
|
assert "id=75a3" in family # the chip itself
|
|
assert "id=75a0" in family # its direct fallback
|
|
# All other source IDs whose direct fallback is also id=75a0 are siblings.
|
|
expected_siblings = {
|
|
src
|
|
for src, fallbacks in arch_mod.SUPPORTED_CHIP_ID_FALLBACKS.items()
|
|
if "id=75a0" in fallbacks
|
|
and src in {f"id={c.lower()}" for c in arch_mod.GFX_CHIP_IDS["gfx950"]}
|
|
}
|
|
assert expected_siblings.issubset(family)
|