diff --git a/install.ps1 b/install.ps1 index 53c0eea4a1..92487f99e9 100644 --- a/install.ps1 +++ b/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 diff --git a/studio/setup.ps1 b/studio/setup.ps1 index b61b64dd33..ada93991c1 100644 --- a/studio/setup.ps1 +++ b/studio/setup.ps1 @@ -703,7 +703,10 @@ if (-not $HasNvidiaSmi) { } } catch {} } - # amd-smi list fallback: look for "GPU: " 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 diff --git a/tests/studio/install/test_rocm_support.py b/tests/studio/install/test_rocm_support.py index 2f896c57f1..233471d5ba 100644 --- a/tests/studio/install/test_rocm_support.py +++ b/tests/studio/install/test_rocm_support.py @@ -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"])