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

################################################################################
#
# Copyright (C) 2022-2025 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.
#
################################################################################
from typing import List, Optional
from . import Properties
from Tensile.Common.Architectures import \
SUPPORTED_CHIP_ID_FALLBACKS, SUPPORTED_BUILD_CHIP_IDS, GFX_CHIP_IDS, \
isaToGfx, supportsChipIdPredicate, print1
import copy
import re
def parseDeviceNameToHex(deviceName: Optional[str]) -> Optional[str]:
"""Parse 'Device 75a3' into 75a3 (hex).
Args:
deviceName: Of format 'Device XXXX'
Returns:
Hex-formatted chip ID string (without "id=" prefix), e.g. "75a3"
Raise:
SystemExit if format is invalid, enforces library logic at build time.
"""
if deviceName is None:
return None
match = re.match(r'^Device\s+([0-9a-fA-F]+)$', deviceName.strip())
if match:
return match.group(1)
#return int(match.group(1), 16)
raise ValueError(f"Invalid device name format: '{deviceName}', expected 'Device XXXX'")
def _extractPciChipIds(pred: Optional[Properties.Predicate]) -> frozenset[int]:
"""Extract chip ID(s) from PciChipId or the 'Or' predicate.
Returns:
A frozenset of chip IDs. Empty set means no chip IDs were found.
"""
if pred is None:
return frozenset()
if pred.tag == "PciChipId":
return frozenset([pred.value])
if pred.tag == "Or":
ids = [p.value for p in pred.value if p.tag == "PciChipId"]
return frozenset(ids)
return frozenset()
def _buildChipIdFallbackGraph():
"""Build source->fallback-target graph using configured chip ID fallbacks."""
graph = {}
for sourceKey, fallbackKeys in SUPPORTED_CHIP_ID_FALLBACKS.items():
sourceId = int(sourceKey.split("=", 1)[1], 16)
sourceFallbacks = graph.setdefault(sourceId, set())
for fallbackKey in fallbackKeys:
fallbackId = int(fallbackKey.split("=", 1)[1], 16)
sourceFallbacks.add(fallbackId)
graph.setdefault(fallbackId, set())
return graph
_CHIP_ID_FALLBACK_GRAPH = _buildChipIdFallbackGraph()
_CHIP_ID_TOPO_RANK_CACHE = {}
def _chipIdTopologicalRank(chipId: int, visiting=None):
"""Higher rank means farther away from fallback roots (more specific/source-like)."""
if chipId in _CHIP_ID_TOPO_RANK_CACHE:
return _CHIP_ID_TOPO_RANK_CACHE[chipId]
if visiting is None:
visiting = set()
# Defensive cycle handling: break cycles by treating the back-edge as depth 0.
if chipId in visiting:
return 0
visiting.add(chipId)
fallbackIds = _CHIP_ID_FALLBACK_GRAPH.get(chipId, set())
rank = 0 if not fallbackIds else 1 + max(_chipIdTopologicalRank(x, visiting) for x in fallbackIds)
visiting.remove(chipId)
_CHIP_ID_TOPO_RANK_CACHE[chipId] = rank
return rank
def _chipIdSetSortKey(chipIds: frozenset):
"""Sort key for a chip-ID set using fallback-aware topological rank."""
return tuple(
sorted(((_chipIdTopologicalRank(chipId), chipId) for chipId in chipIds), reverse=True)
)
class HardwarePredicate(Properties.Predicate):
# TODO- And also FromISA() is hard to detect CU-fallback case.
# Perhaps we can always use FromHardware(). FromISA() is not used so far.
@classmethod
def FromISA(cls, isa):
gfxArch = isaToGfx(tuple(isa))
return cls("AMDGPU", value=cls("Processor", value=gfxArch))
@classmethod
def FromHardware(cls, isa, cuCount=None, deviceNames=None, logicFile=None):
"""Create a HardwarePredicate from hardware specifications.
Args:
isa: ISA tuple (e.g., (9, 5, 0) for gfx950)
cuCount: Optional compute unit count
deviceNames: Optional list of device name strings like ["Device 75a0", "Device 75a2"],
or a single string, or None
logicFile: Optional source logic file path used only for diagnostics
"""
gfxArch = isaToGfx(tuple(isa))
props = [cls("Processor", value=gfxArch)]
if cuCount is not None:
props.append(cls("CUCount", value=cuCount))
if supportsChipIdPredicate(gfxArch):
pciChipIdPred = cls._createPciChipIdPredicate(deviceNames, gfxArch, logicFile)
if pciChipIdPred is not None:
props.append(pciChipIdPred)
if len(props) == 1:
return cls("AMDGPU", value=props[0])
else:
return cls("AMDGPU", value=cls.And(props))
@classmethod
def _createPciChipIdPredicate(cls, deviceNames, gfx: str, logicFile=None):
"""Create PciChipId predicate(s) from device names.
Args:
deviceNames: Can be:
- None: returns None
- Empty list []: returns None
- Single string "Device XXXX": returns one PciChipId predicate
- Single item list ["Device XXXX"]: returns one PciChipId predicate
- Multiple item list: returns Or predicate with all PciChipId predicates
Returns:
A HardwarePredicate of type "PciChipId"
"""
if isinstance(deviceNames, str):
deviceNames = [deviceNames]
supportedChipIds = cls._collectSupportedChipIds(deviceNames, gfx, logicFile)
pciChipIds = []
for chipId in supportedChipIds:
if chipId is not None:
# Serialize chip IDs as integers so YAML emits decimal numeric values.
pciChipIds.append(int(chipId, 16) if isinstance(chipId, str) else int(chipId))
if len(pciChipIds) == 0:
return None
if len(pciChipIds) == 1:
return cls("PciChipId", value=pciChipIds[0])
pciChipIdPredicates = [cls("PciChipId", value=chipId)
for chipId in pciChipIds]
return cls.Or(pciChipIdPredicates)
@classmethod
def _collectSupportedChipIds(
cls, deviceNames: Optional[List[str]], gfx: str, logicFile: Optional[str] = None
) -> List[str]:
chipIdsRaw = [parseDeviceNameToHex(name) for name in deviceNames] if deviceNames else []
chipIds = [chipId for chipId in chipIdsRaw if chipId is not None]
expectedIds = GFX_CHIP_IDS.get(gfx, [])
supportedChipIds = [chipId for chipId in chipIds if f"id={chipId.lower()}" in SUPPORTED_BUILD_CHIP_IDS]
unsupportedChipIds = [chipId for chipId in chipIds if f"id={chipId.lower()}" not in SUPPORTED_BUILD_CHIP_IDS]
if expectedIds and (not supportedChipIds or unsupportedChipIds):
print1("")
print1("********************************************************************************")
print1("* WARNING: Logic file has invalid or unsupported chip IDs")
print1(f"* File: {logicFile if logicFile else '<unknown>'}")
print1(f"* Architecture: {gfx}")
print1(f"* Found chip IDs: {', '.join(chipIds) if chipIds else '<none>'}")
print1(f"* Supported chip IDs: {', '.join(expectedIds)}")
if supportedChipIds:
print1("* This logic file will still be used for the supported chip-ID variant(s)")
print1(f"* Using chip IDs: {', '.join(supportedChipIds)}")
print1(f"* Ignoring chip IDs: {', '.join(unsupportedChipIds)}")
else:
print1(f"* No supported chip IDs found; using this file as fallback for all {gfx} devices")
print1(f"* For optimal kernel selection, specify IDs like: `[Device {expectedIds[0]}]`")
print1("********************************************************************************")
print1("")
return supportedChipIds
def __lt__(self, other):
# Use superclass logic for TruePreds
if other.tag == 'TruePred' or self.tag == 'TruePred':
return super().__lt__(other)
# Compute unit counts are embedded as 'And' with
# 'Processor' and 'ComputeUnitCount' as children
if self.value.tag == 'And':
myAndPred = self.value
myProcPred = next(
iter(x for x in myAndPred.value if x.tag == "Processor"), None)
myCUPred = next(
iter(x for x in myAndPred.value if x.tag == "CUCount"), None)
myPciChipIdPred = next(
iter(x for x in myAndPred.value if x.tag in ("PciChipId", "Or")), None)
myCUCount = myCUPred.value if myCUPred is not None else None
myPciChipIds = _extractPciChipIds(myPciChipIdPred)
else:
myProcPred = self.value
myCUCount = None
myPciChipIds = frozenset()
if other.value.tag == 'And':
otherAndPred = other.value
otherProcPred = next(
iter(x for x in otherAndPred.value if x.tag == "Processor"), None)
otherCUPred = next(
iter(x for x in otherAndPred.value if x.tag == "CUCount"), None)
otherPciChipIdPred = next(
iter(x for x in otherAndPred.value if x.tag in ("PciChipId", "Or")), None)
otherCUCount = otherCUPred.value if otherCUPred is not None else None
otherPciChipIds = _extractPciChipIds(otherPciChipIdPred)
else:
otherProcPred = other.value
otherCUCount = None
otherPciChipIds = frozenset()
# Prioritize ChipId (more specific match first)
# A predicate with a chip ID set is more specific than one without
if myPciChipIds and not otherPciChipIds:
return True
if not myPciChipIds and otherPciChipIds:
return False
if myPciChipIds and otherPciChipIds and myPciChipIds != otherPciChipIds:
# Prefer source-like chip IDs (exact-capable rows) before fallback targets.
# This ensures ordering like:
# Equality -> Origami/Predication -> Equality fallback -> Origami/Predication fallback.
myChipKey = _chipIdSetSortKey(myPciChipIds)
otherChipKey = _chipIdSetSortKey(otherPciChipIds)
# Extract the highest rank from each set (first element of the sorted key)
myMaxRank = myChipKey[0][0] if myChipKey else -1
otherMaxRank = otherChipKey[0][0] if otherChipKey else -1
# First, compare by maximum rank (higher rank = more specific = comes first)
if myMaxRank != otherMaxRank:
return myMaxRank > otherMaxRank
# For same maximum rank, prefer smaller sets (exact chip matches) over multi-chip sets
if len(myPciChipIds) != len(otherPciChipIds):
return len(myPciChipIds) < len(otherPciChipIds)
# For same rank and same size, compare by full chip key
return myChipKey > otherChipKey
# If CU properties are empty, then compare processor predicates
if myCUCount is None and otherCUCount is None:
# Make sure that we have valid processor preds
assert myProcPred is not None and otherProcPred is not None, "Missing processor predicate"
assert myProcPred.tag == otherProcPred.tag == "Processor", "Invalid processor predicate"
# Downgrade to base class so that we don't recurse
myProcPredCopy = copy.deepcopy(myProcPred)
otherProcPredCopy = copy.deepcopy(otherProcPred)
myProcPredCopy.__class__ = otherProcPredCopy.__class__ = Properties.Predicate
return myProcPredCopy < otherProcPredCopy
# Higher priority given to higher CU count (None treated as lowest priority)
myCUVal = myCUCount if myCUCount is not None else 0
otherCUVal = otherCUCount if otherCUCount is not None else 0
return myCUVal > otherCUVal