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
241 lines
11 KiB
Python
241 lines
11 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.
|
|
#
|
|
################################################################################
|
|
|
|
"""Producer-side tests for per-arch fallback rename and Mapping completeness.
|
|
|
|
Each test is constructed so it fails on the *un*patched producer for the
|
|
specific behavior the patch changes. Discrimination is the point: a test that
|
|
passes on either implementation does not protect against this regression class.
|
|
"""
|
|
|
|
import pytest
|
|
|
|
from Tensile.SolutionLibrary import (
|
|
MasterSolutionLibrary,
|
|
PlaceholderLibrary,
|
|
PredicateLibrary,
|
|
ProblemMapLibrary,
|
|
)
|
|
from Tensile.TensileCreateLibrary.Run import renameFallbacksPerArch
|
|
|
|
|
|
def _placeholder(name: str) -> PlaceholderLibrary:
|
|
return PlaceholderLibrary(name)
|
|
|
|
|
|
def _wrap_in_predicate(child) -> PredicateLibrary:
|
|
"""Wrap a single child in a PredicateLibrary row, mirroring the
|
|
structure produced by the real library builder."""
|
|
pred = PredicateLibrary(tag="Hardware")
|
|
pred.rows = [{"predicate": object(), "library": child}]
|
|
return pred
|
|
|
|
|
|
def _wrap_in_problem_map(child) -> ProblemMapLibrary:
|
|
pmap = ProblemMapLibrary(mappingProperty=object(), mapping={"key": child})
|
|
return pmap
|
|
|
|
|
|
def _make_master_with_fallback(lazyName: str, treeName: str) -> MasterSolutionLibrary:
|
|
"""Build a minimal master library that has a single fallback lazy library
|
|
and a matching PlaceholderLibrary buried inside the master.library tree.
|
|
|
|
Solutions dicts are left empty; the rename code does not need real
|
|
solutions, only attribute shape (.lazyLibraries dict, .library tree).
|
|
"""
|
|
placeholder = _placeholder(treeName)
|
|
tree = _wrap_in_predicate(_wrap_in_problem_map(placeholder))
|
|
master = MasterSolutionLibrary({}, tree)
|
|
# `merge()` would have populated lazyLibraries from the fallback master;
|
|
# we plant the entry directly to avoid needing real solution objects.
|
|
master.lazyLibraries[lazyName] = object()
|
|
return master
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# 1. lazyLibraries dict keys get arch-suffixed for fallback entries.
|
|
# ---------------------------------------------------------------------------
|
|
def test_renameFallbacksPerArch_archSuffixesLazyKeys():
|
|
master = _make_master_with_fallback(
|
|
lazyName="TensileLibrary_Type_Hi_fallback",
|
|
treeName="TensileLibrary_Type_Hi_fallback",
|
|
)
|
|
masterLibraries = {"gfx942": master}
|
|
|
|
renameFallbacksPerArch(masterLibraries)
|
|
|
|
keys = list(masterLibraries["gfx942"].lazyLibraries.keys())
|
|
assert "TensileLibrary_Type_Hi_fallback_gfx942" in keys, (
|
|
"Patched producer must arch-suffix fallback lazy-library keys; "
|
|
"without the rename the key stays at 'TensileLibrary_Type_Hi_fallback' "
|
|
"and the per-arch Mapping filter (`name.endswith(\"_gfx942\")`) drops it."
|
|
)
|
|
assert "TensileLibrary_Type_Hi_fallback" not in keys, (
|
|
"Old (unsuffixed) key must be removed; leaving both around would "
|
|
"double-count solutions in the per-arch Mapping."
|
|
)
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# 2. PlaceholderLibrary nodes inside master.library tree are renamed too.
|
|
# ---------------------------------------------------------------------------
|
|
def test_renameFallbacksPerArch_walksTreeAndRenamesPlaceholders():
|
|
master = _make_master_with_fallback(
|
|
lazyName="TensileLibrary_Type_Hi_fallback",
|
|
treeName="TensileLibrary_Type_Hi_fallback",
|
|
)
|
|
masterLibraries = {"gfx942": master}
|
|
|
|
renameFallbacksPerArch(masterLibraries)
|
|
|
|
# Re-walk to find the PlaceholderLibrary the helper produced:
|
|
placeholder = (
|
|
masterLibraries["gfx942"]
|
|
.library.rows[0]["library"] # PredicateLibrary -> ProblemMapLibrary
|
|
.mapping["key"] # ProblemMapLibrary -> PlaceholderLibrary
|
|
)
|
|
assert isinstance(placeholder, PlaceholderLibrary)
|
|
assert placeholder.filenamePrefix == "TensileLibrary_Type_Hi_fallback_gfx942", (
|
|
"The PlaceholderLibrary in the master library tree drives the runtime "
|
|
"load path. If only the lazyLibraries key is renamed but the tree node "
|
|
"is not, the runtime asks for the un-suffixed file and finds nothing."
|
|
)
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# 3. Multi-arch safety: deepcopy must isolate per-arch changes.
|
|
# ---------------------------------------------------------------------------
|
|
def test_renameFallbacksPerArch_deepcopiesPerArchToAvoidAliasing():
|
|
"""`MasterSolutionLibrary.merge` aliases the same fallback lazy library
|
|
across every arch's master. Renaming without a per-arch deep copy would
|
|
suffix the shared object once per arch, producing
|
|
`..._fallback_gfx942_gfx1100` and corrupting both arches.
|
|
"""
|
|
master_gfx942 = _make_master_with_fallback(
|
|
lazyName="TensileLibrary_Type_Hi_fallback",
|
|
treeName="TensileLibrary_Type_Hi_fallback",
|
|
)
|
|
master_gfx1100 = _make_master_with_fallback(
|
|
lazyName="TensileLibrary_Type_Hi_fallback",
|
|
treeName="TensileLibrary_Type_Hi_fallback",
|
|
)
|
|
# Mimic the post-merge alias: both arches reference the same tree node.
|
|
shared_placeholder = master_gfx942.library.rows[0]["library"].mapping["key"]
|
|
master_gfx1100.library.rows[0]["library"].mapping["key"] = shared_placeholder
|
|
|
|
masterLibraries = {"gfx942": master_gfx942, "gfx1100": master_gfx1100}
|
|
renameFallbacksPerArch(masterLibraries)
|
|
|
|
p942 = (
|
|
masterLibraries["gfx942"]
|
|
.library.rows[0]["library"]
|
|
.mapping["key"]
|
|
)
|
|
p1100 = (
|
|
masterLibraries["gfx1100"]
|
|
.library.rows[0]["library"]
|
|
.mapping["key"]
|
|
)
|
|
assert p942.filenamePrefix == "TensileLibrary_Type_Hi_fallback_gfx942"
|
|
assert p1100.filenamePrefix == "TensileLibrary_Type_Hi_fallback_gfx1100"
|
|
# Without the deepcopy these would point to the same mutated object and
|
|
# one of the asserts above would fail with the other arch's suffix.
|
|
assert p942 is not p1100, (
|
|
"renameFallbacksPerArch must deep-copy each arch's master library; "
|
|
"without it, the shared fallback PlaceholderLibrary is mutated twice "
|
|
"and one arch ends up with the other arch's filename."
|
|
)
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# 4. Idempotency: a second pass must not double-suffix.
|
|
# ---------------------------------------------------------------------------
|
|
def test_renameFallbacksPerArch_isIdempotent():
|
|
master = _make_master_with_fallback(
|
|
lazyName="TensileLibrary_Type_Hi_fallback",
|
|
treeName="TensileLibrary_Type_Hi_fallback",
|
|
)
|
|
masterLibraries = {"gfx942": master}
|
|
|
|
renameFallbacksPerArch(masterLibraries)
|
|
# Capture the post-first-pass state and re-run.
|
|
keys_after_first = set(masterLibraries["gfx942"].lazyLibraries.keys())
|
|
renameFallbacksPerArch(masterLibraries)
|
|
keys_after_second = set(masterLibraries["gfx942"].lazyLibraries.keys())
|
|
|
|
assert keys_after_first == keys_after_second, (
|
|
"A naive implementation without the `endswith('_<arch>')` guard "
|
|
"would suffix again, producing '..._fallback_gfx942_gfx942'."
|
|
)
|
|
assert "TensileLibrary_Type_Hi_fallback_gfx942_gfx942" not in keys_after_second
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# 5. Round-trip discrimination: the per-arch Mapping filter the producer uses
|
|
# must keep every fallback entry once the rename has run.
|
|
#
|
|
# This is the test that would have caught the CI failure: it asserts the
|
|
# completeness invariant the runtime depends on.
|
|
# ---------------------------------------------------------------------------
|
|
def _per_arch_mapping_keys(libraryMapping, arch):
|
|
"""Mirror the producer's per-arch filter logic from Run.py."""
|
|
return {
|
|
idx
|
|
for idx, name in libraryMapping.items()
|
|
if name.endswith("_" + arch)
|
|
}
|
|
|
|
|
|
def test_perArchMapping_keepsFallbackEntries_afterRename():
|
|
"""Build a synthetic post-merge libraryMapping (the dict that
|
|
`generateLogicDataAndSolutions` returns) and verify that after rename
|
|
the per-arch filter retains every entry the runtime needs."""
|
|
# Pre-rename: lazyLibraries has both a tuned-for-arch entry and a fallback
|
|
# entry that came in via merge. The producer's libraryMapping would map
|
|
# solution indexes to these names.
|
|
libraryMapping_pre = {
|
|
0: "TensileLibrary_Type_Hi_gfx942", # tuned solution
|
|
1: "TensileLibrary_Type_Hi_fallback", # fallback solution (e.g. fp16)
|
|
}
|
|
|
|
# Apply the per-arch filter as the unpatched producer would:
|
|
unpatched_kept = _per_arch_mapping_keys(libraryMapping_pre, "gfx942")
|
|
assert unpatched_kept == {0}, (
|
|
"Sanity check: under the unpatched producer the fallback entry is "
|
|
"filtered out because its name ends in '_fallback' not '_gfx942'. "
|
|
"This is the bug that causes 'NO solution found!' for fp16 at runtime."
|
|
)
|
|
|
|
# Post-rename libraryMapping (what the patched producer feeds to the filter):
|
|
libraryMapping_post = {
|
|
0: "TensileLibrary_Type_Hi_gfx942",
|
|
1: "TensileLibrary_Type_Hi_fallback_gfx942",
|
|
}
|
|
patched_kept = _per_arch_mapping_keys(libraryMapping_post, "gfx942")
|
|
assert patched_kept == {0, 1}, (
|
|
"Patched producer must surface every solution index in the per-arch "
|
|
"Mapping. Missing fallback entries here means the runtime cannot "
|
|
"resolve solutions whose dtype lives only in the fallback library."
|
|
)
|