fix: robust gfx arch detection for Strix Halo / HIP-runtime-only installs
Covers users who have the HIP runtime (amd-smi available) but not the
full HIP SDK (no hipinfo), which is common on Strix Halo iGPU systems.
Without this, $ROCmGfxArch stays null and the installer silently falls
back to CPU-only PyTorch despite a working GPU.
Detection waterfall (setup.ps1 + install.ps1):
1. hipinfo gcnArchName -- full HIP SDK (existing, unchanged)
2. amd-smi list gfx pattern -- newer amd-smi versions embed arch
3. amd-smi static --asic -- ROCm 6+ ASIC details with GFX target
4. UNSLOTH_ROCM_GFX_ARCH env -- manual override escape hatch
5. GPU name → arch table -- best-effort from marketing name:
890M / Strix Halo → gfx1151 (RDNA 3.5 iGPU, Strix Halo)
880M / Strix Point → gfx1150 (RDNA 3.5 iGPU, Strix Point)
780M / Phoenix → gfx1103 (RDNA 3 iGPU)
RX 7900/7800/7700 → gfx1100 (RDNA 3 desktop)
RX 9070 XT / 9080 → gfx1201 (RDNA 4)
RX 9070 / 9060 XT → gfx1200 (RDNA 4)
When arch is inferred from name, a Cyan substep tells the user to set
UNSLOTH_ROCM_GFX_ARCH to skip inference on future installs.
WMI block intentionally does not set $HasROCm (no runtime confirmation).
Tests: 11 new tests in TestStrixHaloGfxArchDetection covering all five
detection levels, WMI safety, and gfx regex in both ps1 files.
This commit is contained in:
parent
370debe46b
commit
7a5e93b42c
3 changed files with 194 additions and 4 deletions
51
install.ps1
51
install.ps1
|
|
@ -1235,7 +1235,24 @@ shell.Run cmd, 0, False
|
|||
$smiOut = & $amdSmiExe.Source list 2>&1 | Out-String
|
||||
if ($LASTEXITCODE -eq 0 -and $smiOut -match "(?im)^GPU\s*[:\[]\s*\d") {
|
||||
$HasROCm = $true
|
||||
$ROCmGpuLabel = "AMD ROCm"
|
||||
# Attempt 1: newer amd-smi versions embed the gfx arch in list output
|
||||
if ($smiOut -match "(?i)\b(gfx\d+[a-z]?)\b") {
|
||||
$ROCmGfxArch = $Matches[1].ToLower()
|
||||
$ROCmGpuLabel = "AMD ROCm ($ROCmGfxArch)"
|
||||
} else {
|
||||
# Attempt 2: 'static --asic' exposes ASIC details on ROCm 6+,
|
||||
# including the GFX target needed for wheel index selection.
|
||||
$smiAsicOut = ""
|
||||
try { $smiAsicOut = & $amdSmiExe.Source static --asic 2>&1 | Out-String } catch {}
|
||||
if ($smiAsicOut -match "(?i)\b(gfx\d+[a-z]?)\b") {
|
||||
$ROCmGfxArch = $Matches[1].ToLower()
|
||||
$ROCmGpuLabel = "AMD ROCm ($ROCmGfxArch)"
|
||||
} elseif ($smiAsicOut -match "(?im)Market.?Name\s*[:\|]\s*([^\r\n]+)") {
|
||||
$ROCmGpuLabel = "AMD ROCm ($($Matches[1].Trim()))"
|
||||
} else {
|
||||
$ROCmGpuLabel = "AMD ROCm"
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch {}
|
||||
}
|
||||
|
|
@ -1248,6 +1265,38 @@ shell.Run cmd, 0, False
|
|||
if ($wmiGpu) { $ROCmGpuLabel = $wmiGpu.Name }
|
||||
} catch {}
|
||||
}
|
||||
# ── Arch resolution: env-var override → name inference ──────────────
|
||||
# Covers users whose amd-smi is too old to report the GFX target and
|
||||
# who don't have hipinfo (HIP-runtime-only, common on Strix Halo / iGPU).
|
||||
if ($HasROCm -and -not $ROCmGfxArch) {
|
||||
# 1. Manual override: set UNSLOTH_ROCM_GFX_ARCH=gfx1151 before running.
|
||||
if ($env:UNSLOTH_ROCM_GFX_ARCH) {
|
||||
$ROCmGfxArch = $env:UNSLOTH_ROCM_GFX_ARCH.Trim().ToLower()
|
||||
$ROCmGpuLabel = "AMD ROCm ($ROCmGfxArch)"
|
||||
substep "gfx arch from UNSLOTH_ROCM_GFX_ARCH env override: $ROCmGfxArch" "Cyan"
|
||||
}
|
||||
# 2. Best-effort name → arch lookup from marketing name (amd-smi / WMI).
|
||||
elseif ($ROCmGpuLabel) {
|
||||
$nameArchTable = @(
|
||||
@{ P = "9070 XT|9080"; A = "gfx1201" } # RDNA 4
|
||||
@{ P = "9070|9060"; A = "gfx1200" } # RDNA 4
|
||||
@{ P = "890M|Strix Halo|HX 37[05]|HX 38[05]|AI 9 HX"; A = "gfx1151" } # RDNA 3.5 iGPU (Strix Halo)
|
||||
@{ P = "880M|Strix Point|AI 9 36[05]|AI 7 35[05]|AI 5 34[05]"; A = "gfx1150" } # RDNA 3.5 iGPU (Strix Point)
|
||||
@{ P = "RX 7900|RX 7800|RX 7700(?! S)"; A = "gfx1100" } # RDNA 3 desktop
|
||||
@{ P = "RX 7600"; A = "gfx1102" } # RDNA 3
|
||||
@{ P = "780M|760M|740M|Phoenix"; A = "gfx1103" } # RDNA 3 iGPU (Phoenix)
|
||||
)
|
||||
foreach ($row in $nameArchTable) {
|
||||
if ($ROCmGpuLabel -match $row.P) {
|
||||
$ROCmGfxArch = $row.A
|
||||
$ROCmGpuLabel = "AMD ROCm ($ROCmGfxArch)"
|
||||
substep "gfx arch inferred from GPU name: $ROCmGfxArch" "Cyan"
|
||||
substep "Tip: set UNSLOTH_ROCM_GFX_ARCH=$ROCmGfxArch to skip inference next time" "Cyan"
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
# Capture ROCm version for wheel selection (hipconfig, then amd-smi)
|
||||
if ($HasROCm) {
|
||||
$hipConfigExe = Get-Command hipconfig -ErrorAction SilentlyContinue
|
||||
|
|
|
|||
|
|
@ -703,7 +703,10 @@ if (-not $HasNvidiaSmi) {
|
|||
}
|
||||
} catch {}
|
||||
}
|
||||
# amd-smi list fallback: look for "GPU: <digit>" data rows
|
||||
# amd-smi fallback: HIP runtime present but hipinfo unavailable (no full HIP SDK).
|
||||
# Confirms GPU visibility via 'list', then attempts 'static --asic' to extract
|
||||
# the gfx arch that hipinfo would have provided. Critical for Strix Halo
|
||||
# (gfx1151) and other iGPUs where only the HIP runtime is installed.
|
||||
if (-not $HasROCm) {
|
||||
$amdSmiExe = Get-Command "amd-smi" -ErrorAction SilentlyContinue
|
||||
if ($amdSmiExe) {
|
||||
|
|
@ -711,12 +714,33 @@ if (-not $HasNvidiaSmi) {
|
|||
$smiOut = & $amdSmiExe.Source list 2>&1 | Out-String
|
||||
if ($LASTEXITCODE -eq 0 -and $smiOut -match "(?im)^GPU\s*[:\[]\s*\d") {
|
||||
$HasROCm = $true
|
||||
$ROCmGpuLabel = "AMD ROCm"
|
||||
# Attempt 1: newer amd-smi versions embed the gfx arch in list output
|
||||
if ($smiOut -match "(?i)\b(gfx\d+[a-z]?)\b") {
|
||||
$script:ROCmGfxArch = $Matches[1].ToLower()
|
||||
$ROCmGpuLabel = "AMD ROCm ($script:ROCmGfxArch)"
|
||||
} else {
|
||||
# Attempt 2: 'static --asic' exposes ASIC details on ROCm 6+,
|
||||
# including the GFX target needed for wheel index selection.
|
||||
$smiAsicOut = ""
|
||||
try { $smiAsicOut = & $amdSmiExe.Source static --asic 2>&1 | Out-String } catch {}
|
||||
if ($smiAsicOut -match "(?i)\b(gfx\d+[a-z]?)\b") {
|
||||
$script:ROCmGfxArch = $Matches[1].ToLower()
|
||||
$ROCmGpuLabel = "AMD ROCm ($script:ROCmGfxArch)"
|
||||
} elseif ($smiAsicOut -match "(?im)Market.?Name\s*[:\|]\s*([^\r\n]+)") {
|
||||
$ROCmGpuLabel = "AMD ROCm ($($Matches[1].Trim()))"
|
||||
} else {
|
||||
$ROCmGpuLabel = "AMD ROCm"
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch {}
|
||||
}
|
||||
}
|
||||
# WMI fallback: AMD GPU in device list but no HIP SDK → guide the user
|
||||
# WMI fallback: AMD GPU in device list but no HIP SDK → guide the user.
|
||||
# WMI gives a marketing name (e.g. "AMD Radeon 890M") but never a gfx arch.
|
||||
# $HasROCm is intentionally NOT set here — we cannot confirm ROCm runtime
|
||||
# support without hipinfo or amd-smi. The name is saved to $ROCmGpuLabel
|
||||
# so the name-based inference below can still attempt an arch lookup.
|
||||
if (-not $HasROCm) {
|
||||
try {
|
||||
$wmiGpu = Get-WmiObject Win32_VideoController -ErrorAction SilentlyContinue |
|
||||
|
|
@ -725,6 +749,40 @@ if (-not $HasNvidiaSmi) {
|
|||
if ($wmiGpu) { $ROCmGpuLabel = $wmiGpu.Name }
|
||||
} catch {}
|
||||
}
|
||||
# ── Arch resolution: env-var override → name inference ──────────────────
|
||||
# Runs after all probe methods. Covers users whose amd-smi version is too
|
||||
# old to report the GFX target and who don't have hipinfo (HIP-runtime-only
|
||||
# installs, common on Strix Halo / iGPU systems).
|
||||
if ($HasROCm -and -not $script:ROCmGfxArch) {
|
||||
# 1. Manual override: set UNSLOTH_ROCM_GFX_ARCH=gfx1151 before running.
|
||||
if ($env:UNSLOTH_ROCM_GFX_ARCH) {
|
||||
$script:ROCmGfxArch = $env:UNSLOTH_ROCM_GFX_ARCH.Trim().ToLower()
|
||||
$ROCmGpuLabel = "AMD ROCm ($script:ROCmGfxArch)"
|
||||
substep "gfx arch from UNSLOTH_ROCM_GFX_ARCH env override: $script:ROCmGfxArch" "Cyan"
|
||||
}
|
||||
# 2. Best-effort name → arch lookup from marketing name (amd-smi / WMI).
|
||||
# Ordered most-specific first; first match wins.
|
||||
elseif ($ROCmGpuLabel) {
|
||||
$nameArchTable = @(
|
||||
@{ P = "9070 XT|9080"; A = "gfx1201" } # RDNA 4
|
||||
@{ P = "9070|9060"; A = "gfx1200" } # RDNA 4
|
||||
@{ P = "890M|Strix Halo|HX 37[05]|HX 38[05]|AI 9 HX"; A = "gfx1151" } # RDNA 3.5 iGPU (Strix Halo)
|
||||
@{ P = "880M|Strix Point|AI 9 36[05]|AI 7 35[05]|AI 5 34[05]"; A = "gfx1150" } # RDNA 3.5 iGPU (Strix Point)
|
||||
@{ P = "RX 7900|RX 7800|RX 7700(?! S)"; A = "gfx1100" } # RDNA 3 desktop
|
||||
@{ P = "RX 7600"; A = "gfx1102" } # RDNA 3
|
||||
@{ P = "780M|760M|740M|Phoenix"; A = "gfx1103" } # RDNA 3 iGPU (Phoenix)
|
||||
)
|
||||
foreach ($row in $nameArchTable) {
|
||||
if ($ROCmGpuLabel -match $row.P) {
|
||||
$script:ROCmGfxArch = $row.A
|
||||
$ROCmGpuLabel = "AMD ROCm ($script:ROCmGfxArch)"
|
||||
substep "gfx arch inferred from GPU name: $script:ROCmGfxArch" "Cyan"
|
||||
substep "Tip: set UNSLOTH_ROCM_GFX_ARCH=$script:ROCmGfxArch to skip inference next time" "Cyan"
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
# Capture ROCm version early for display and wheel selection
|
||||
if ($HasROCm) {
|
||||
$script:ROCmVersion = $null
|
||||
|
|
|
|||
|
|
@ -1993,5 +1993,88 @@ class TestRocmTorchPkgSpecs:
|
|||
assert mapping.get(arch) == "gfx110X-all", f"{arch} missing from mapping"
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# TEST: setup.ps1 / install.ps1 -- Strix Halo gfx arch detection
|
||||
# =============================================================================
|
||||
|
||||
_SETUP_PS1_PATH = PACKAGE_ROOT / "studio" / "setup.ps1"
|
||||
_INSTALL_PS1_PATH = PACKAGE_ROOT / "install.ps1"
|
||||
|
||||
|
||||
class TestStrixHaloGfxArchDetection:
|
||||
"""Verify that setup.ps1 and install.ps1 have robust gfx arch detection
|
||||
for Strix Halo / iGPU users who only have the HIP runtime (no hipinfo)."""
|
||||
|
||||
def test_amd_smi_static_asic_attempted_in_setup(self):
|
||||
"""setup.ps1 must try 'amd-smi static --asic' when list output lacks gfx arch."""
|
||||
source = _SETUP_PS1_PATH.read_text(encoding = "utf-8")
|
||||
assert "static --asic" in source
|
||||
|
||||
def test_amd_smi_static_asic_attempted_in_install(self):
|
||||
"""install.ps1 must try 'amd-smi static --asic' when list output lacks gfx arch."""
|
||||
source = _INSTALL_PS1_PATH.read_text(encoding = "utf-8")
|
||||
assert "static --asic" in source
|
||||
|
||||
def test_env_var_override_in_setup(self):
|
||||
"""setup.ps1 must honour UNSLOTH_ROCM_GFX_ARCH as a manual arch override."""
|
||||
source = _SETUP_PS1_PATH.read_text(encoding = "utf-8")
|
||||
assert "UNSLOTH_ROCM_GFX_ARCH" in source
|
||||
|
||||
def test_env_var_override_in_install(self):
|
||||
"""install.ps1 must honour UNSLOTH_ROCM_GFX_ARCH as a manual arch override."""
|
||||
source = _INSTALL_PS1_PATH.read_text(encoding = "utf-8")
|
||||
assert "UNSLOTH_ROCM_GFX_ARCH" in source
|
||||
|
||||
def test_name_arch_table_covers_strix_halo_in_setup(self):
|
||||
"""setup.ps1 name→arch table must map 890M / Strix Halo to gfx1151."""
|
||||
source = _SETUP_PS1_PATH.read_text(encoding = "utf-8")
|
||||
assert "gfx1151" in source
|
||||
assert "890M" in source or "Strix Halo" in source
|
||||
|
||||
def test_name_arch_table_covers_strix_halo_in_install(self):
|
||||
"""install.ps1 name→arch table must map 890M / Strix Halo to gfx1151."""
|
||||
source = _INSTALL_PS1_PATH.read_text(encoding = "utf-8")
|
||||
assert "gfx1151" in source
|
||||
assert "890M" in source or "Strix Halo" in source
|
||||
|
||||
def test_name_arch_table_covers_strix_point_in_setup(self):
|
||||
"""setup.ps1 name→arch table must map 880M / Strix Point to gfx1150."""
|
||||
source = _SETUP_PS1_PATH.read_text(encoding = "utf-8")
|
||||
assert "gfx1150" in source
|
||||
assert "880M" in source or "Strix Point" in source
|
||||
|
||||
def test_name_arch_table_covers_strix_point_in_install(self):
|
||||
"""install.ps1 name→arch table must map 880M / Strix Point to gfx1150."""
|
||||
source = _INSTALL_PS1_PATH.read_text(encoding = "utf-8")
|
||||
assert "gfx1150" in source
|
||||
assert "880M" in source or "Strix Point" in source
|
||||
|
||||
def test_name_arch_table_covers_rdna3_phoenix_in_setup(self):
|
||||
"""setup.ps1 name→arch table must map 780M / Phoenix to gfx1103."""
|
||||
source = _SETUP_PS1_PATH.read_text(encoding = "utf-8")
|
||||
assert "gfx1103" in source
|
||||
assert "780M" in source or "Phoenix" in source
|
||||
|
||||
def test_wmi_does_not_set_hasrocm_in_setup(self):
|
||||
"""WMI block in setup.ps1 must NOT set $HasROCm = $true (no runtime confirmation)."""
|
||||
source = _SETUP_PS1_PATH.read_text(encoding = "utf-8")
|
||||
# Find the WMI block and confirm HasROCm is not set inside it
|
||||
wmi_idx = source.find("Win32_VideoController")
|
||||
assert wmi_idx != -1, "WMI block not found in setup.ps1"
|
||||
# The nearest HasROCm = $true must not appear between the WMI block
|
||||
# and the closing brace of that if-block. We check by confirming
|
||||
# $HasROCm = $true does NOT appear within 300 chars of the WMI call.
|
||||
wmi_context = source[wmi_idx : wmi_idx + 300]
|
||||
assert "$HasROCm = $true" not in wmi_context
|
||||
|
||||
def test_gfx_arch_regex_parses_from_amd_smi_output(self):
|
||||
"""Both files must use the gfx\\d+[a-z]? regex to parse arch from amd-smi output."""
|
||||
for path in (_SETUP_PS1_PATH, _INSTALL_PS1_PATH):
|
||||
source = path.read_text(encoding = "utf-8")
|
||||
# The regex pattern used to match gfx arches
|
||||
assert "gfx\\d+" in source or r"gfx\d+" in source, \
|
||||
f"gfx arch regex not found in {path.name}"
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
pytest.main([__file__, "-v"])
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue