@@ -112,7 +112,7 @@ function Install-UnslothStudio {
112112 if ([string ]::IsNullOrWhiteSpace($TorchIndexUrl )) { return " none" }
113113 # Drop query/fragment first so a token-authenticated pin classifies by family.
114114 $leaf = (($TorchIndexUrl -split ' [?#]' , 2 )[0 ].TrimEnd(' /' ) -split ' /' )[-1 ].ToLowerInvariant()
115- if (@ (" cpu" , " cu118" , " cu124" , " cu126" , " cu128" , " cu130" ) -contains $leaf ) { return $leaf }
115+ if (@ (" cpu" , " xpu " , " cu118" , " cu124" , " cu126" , " cu128" , " cu130" ) -contains $leaf ) { return $leaf }
116116 if ($leaf -match ' ^rocm[0-9]+\.[0-9]+$' ) { return $leaf }
117117 return " auto"
118118 }
@@ -123,6 +123,7 @@ function Install-UnslothStudio {
123123 # Require a digit after "cu" so /current or /custom isn't branded CUDA (parity ^cu[0-9]).
124124 if ($TorchIndexFamily -match ' ^cu[0-9]' ) { return " cuda" }
125125 if ($TorchIndexFamily -like " rocm*" ) { return " rocm" }
126+ if ($TorchIndexFamily -eq " xpu" ) { return " xpu" }
126127 if ($TorchIndexFamily -eq " cpu" ) { return " cpu" }
127128 return " unknown"
128129 }
@@ -2222,8 +2223,50 @@ exit 0
22222223 substep " UNSLOTH_ROCM_GFX_ARCH to enable GPU ROCm PyTorch:" " Yellow"
22232224 substep " https://rocm.docs.amd.com/en/latest/deploy/windows/index.html" " Yellow"
22242225 } else {
2225- step " gpu" " none (chat-only / GGUF)" " Yellow"
2226- substep " Training and GPU inference require an NVIDIA or AMD ROCm GPU." " Yellow"
2226+ # ── Intel GPU detection (Arc / Data Center GPU Max / Flex) ──
2227+ # PyTorch publishes XPU (SYCL) wheels at download.pytorch.org/whl/xpu that ship
2228+ # their own oneAPI runtime. Windows also needs the Intel GPU driver (and Unsloth
2229+ # additionally documents oneAPI + Level Zero), so an Intel adapter alone is not
2230+ # proof of a usable XPU: $HasIntelGpu is "an Intel GPU is present",
2231+ # $script:IsIntelXpu is "XPU wheels are appropriate for it".
2232+ # Get-CimInstance, not Get-WmiObject: the latter does not exist in PowerShell 7.
2233+ # Only Arc / Data Center parts are XPU-capable; UHD / HD / Iris Xe are not.
2234+ $HasIntelGpu = $false
2235+ $IntelGpuLabel = $null
2236+ try {
2237+ $intelGpus = @ (Get-CimInstance Win32_VideoController - ErrorAction SilentlyContinue |
2238+ Where-Object { $_.Name -match " (?i)Intel" })
2239+ if ($intelGpus.Count -gt 0 ) {
2240+ $HasIntelGpu = $true
2241+ $xpuGpu = $intelGpus | Where-Object { $_.Name -match " (?i)Intel.*(Arc|Data Center GPU)" } | Select-Object - First 1
2242+ $IntelGpuLabel = if ($xpuGpu ) { $xpuGpu.Name } else { $intelGpus [0 ].Name }
2243+ if ($xpuGpu ) { $script :IsIntelXpu = $true }
2244+ }
2245+ } catch {}
2246+ # torch.xpu.is_available() is authoritative when torch is already installed
2247+ # (migrated env), so let it both confirm and veto the name match above.
2248+ if (Test-Path - LiteralPath $VenvPython ) {
2249+ try {
2250+ $xpuCheck = & $VenvPython - c " import torch; print(torch.xpu.is_available())" 2> $null | Out-String
2251+ if ($xpuCheck.Trim () -eq ' True' ) {
2252+ $HasIntelGpu = $true
2253+ $script :IsIntelXpu = $true
2254+ if (-not $IntelGpuLabel ) { $IntelGpuLabel = " Intel GPU (detected by PyTorch XPU)" }
2255+ } elseif ($xpuCheck.Trim () -eq ' False' ) {
2256+ $script :IsIntelXpu = $false
2257+ }
2258+ } catch {}
2259+ }
2260+ if ($script :IsIntelXpu ) {
2261+ step " gpu" " Intel GPU detected" " Green"
2262+ substep " $IntelGpuLabel "
2263+ # The wheel-index message lives with the reroute below: only that point
2264+ # knows whether a pin or --no-torch overrode XPU, and the real mirror URL.
2265+ } else {
2266+ step " gpu" " none (chat-only / GGUF)" " Yellow"
2267+ if ($HasIntelGpu ) { substep " Detected: $IntelGpuLabel (not XPU-capable)" " Yellow" }
2268+ substep " Training and GPU inference require an NVIDIA, AMD ROCm, or Intel Arc GPU." " Yellow"
2269+ }
22272270 }
22282271 # On an AMD GPU (no NVIDIA), surface the optional WSL-ROCm driver hint.
22292272 if (-not $HasNvidiaSmi -and ($ROCmGfxArch -or $ROCmGpuLabel )) { Show-AmdWslDriverHint }
@@ -2240,6 +2283,14 @@ exit 0
22402283 return $value.Substring (0 , $idx ).TrimEnd(' /' ) + $value.Substring ($idx )
22412284 }
22422285
2286+ # Index leaf (cpu / cu128 / xpu / gfx1201), query and fragment stripped so a
2287+ # token-authenticated mirror still classifies by family. Shared by the callers below.
2288+ function Get-TorchIndexLeafName {
2289+ param ([string ]$Url )
2290+ if ([string ]::IsNullOrWhiteSpace($Url )) { return " " }
2291+ return ((($Url -split ' [?#]' , 2 )[0 ].TrimEnd(' /' ) -split ' /' )[-1 ]).ToLowerInvariant()
2292+ }
2293+
22432294 # ── Choose the correct PyTorch index URL based on driver CUDA version ──
22442295 # Mirrors Get-PytorchCudaTag in setup.ps1.
22452296 function Get-TorchIndexUrl {
@@ -2302,6 +2353,7 @@ exit 0
23022353 if (-not $TorchVersion ) { return $null }
23032354 if ($TorchVersion -match ' \+(cu\d+)' ) { return $Matches [1 ] }
23042355 if ($TorchVersion -match ' \+rocm' ) { return ' rocm' }
2356+ if ($TorchVersion -match ' \+xpu' ) { return ' xpu' }
23052357 if ($TorchVersion -match ' \+cpu' ) { return ' cpu' }
23062358 return ' cpu'
23072359 }
@@ -2316,6 +2368,7 @@ exit 0
23162368 $leaf = (($TorchIndexUrl -split ' [?#]' , 2 )[0 ].TrimEnd(' /' ) -split ' /' )[-1 ].ToLowerInvariant()
23172369 if ($leaf -match ' ^cu\d+$' ) { return $leaf }
23182370 if ($leaf -eq ' cpu' ) { return ' cpu' }
2371+ if ($leaf -eq ' xpu' ) { return ' xpu' }
23192372 if ($leaf -match ' ^rocm' ) { return ' rocm' }
23202373 # gfx must be followed by a digit (an architecture leaf); gfx-private is custom.
23212374 if ($leaf -match ' ^gfx[0-9]' ) { return ' rocm' }
@@ -2359,6 +2412,14 @@ exit 0
23592412 (-not [string ]::IsNullOrWhiteSpace($env: UNSLOTH_TORCH_INDEX_FAMILY ))
23602413 $TorchIndexUrl = Get-TorchIndexUrl
23612414
2415+ # Intel XPU reroute. Must run AFTER Get-TorchIndexUrl or it would be overwritten;
2416+ # an explicit pin still wins, exactly like the AMD ROCm reroute below.
2417+ if ($script :IsIntelXpu -and -not $TorchIndexPinned -and -not $SkipTorch ) {
2418+ $XpuBaseUrl = if ($env: UNSLOTH_PYTORCH_MIRROR ) { $env: UNSLOTH_PYTORCH_MIRROR.TrimEnd (' /' ) } else { " https://download.pytorch.org/whl" }
2419+ $TorchIndexUrl = " $XpuBaseUrl /xpu"
2420+ substep " PyTorch XPU (SYCL) wheels will be installed from $ ( Remove-IndexUrlCredentials $TorchIndexUrl ) "
2421+ }
2422+
23622423 # ── GPU arch → newest compatible Windows ROCm wheel release ──
23632424 # Wheels bundle their own ROCm runtime; the installed HIP SDK version does
23642425 # not constrain which release to use. Always picks the newest release that
@@ -2476,6 +2537,10 @@ exit 0
24762537 } elseif ($ROCmGpuLabel ) {
24772538 substep " Installing CPU-only PyTorch (AMD GPU arch unknown -- install the HIP SDK" " Yellow"
24782539 substep " or set UNSLOTH_ROCM_GFX_ARCH to enable GPU ROCm)." " Yellow"
2540+ } elseif ($HasIntelGpu -and -not $script :IsIntelXpu ) {
2541+ substep " Intel GPU detected but not XPU-capable. Installing CPU-only PyTorch." " Yellow"
2542+ substep " PyTorch XPU needs Intel Arc or Data Center GPU plus a current driver." " Yellow"
2543+ substep " See: https://unsloth.ai/docs/get-started/install/intel" " Yellow"
24792544 } else {
24802545 substep " No NVIDIA GPU detected." " Yellow"
24812546 }
@@ -2590,6 +2655,30 @@ exit 0
25902655 $ROCmIndexUrl = $null
25912656 $ROCmTorchFloor = $null
25922657 }
2658+ } elseif ($script :IsIntelXpu -and (Get-TorchIndexLeafName $TorchIndexUrl ) -eq " xpu" ) {
2659+ # ── Intel Arc / XPU PyTorch install ──
2660+ # XPU wheels ship their own oneAPI runtime (intel-sycl-rt et al.) and
2661+ # are published at https://download.pytorch.org/whl/xpu under PEP 503.
2662+ Write-TauriLog " STEP" " Installing PyTorch (Intel XPU)"
2663+ substep " installing PyTorch from $ ( Remove-IndexUrlCredentials $TorchIndexUrl ) ..."
2664+ # Bound the trio exactly like every other index: the XPU index serves torch
2665+ # past Unsloth's ceiling (2.13.0), and torchaudio dropped its exact torch pin,
2666+ # so bare names resolve a mismatched pair and drag unsloth back to an old release.
2667+ $torchInstallExit = Invoke-InstallCommandRetry - Label " install PyTorch (Intel XPU)" { uv pip install -- python $VenvPython -- force- reinstall " torch>=2.4,<2.11.0" " torchvision>=0.19,<0.26.0" " torchaudio>=2.4,<2.11.0" -- default- index $TorchIndexUrl }
2668+ if ($torchInstallExit -ne 0 ) {
2669+ # Transient XPU-index failure: fall back to CPU base.
2670+ $CpuFallbackIndexUrl = if ($env: UNSLOTH_PYTORCH_MIRROR ) { " $ ( $env: UNSLOTH_PYTORCH_MIRROR.TrimEnd (' /' )) /cpu" } else { " https://download.pytorch.org/whl/cpu" }
2671+ substep " XPU PyTorch install failed (exit $torchInstallExit ); using a CPU base." " Yellow"
2672+ $torchInstallExit = Invoke-InstallCommandRetry - Label " install PyTorch (CPU fallback)" { uv pip install -- python $VenvPython -- force- reinstall " torch>=2.4,<2.11.0" " torchvision>=0.19,<0.26.0" " torchaudio>=2.4,<2.11.0" -- default- index $CpuFallbackIndexUrl }
2673+ if ($torchInstallExit -ne 0 ) {
2674+ Write-Host " [ERROR] Failed to install PyTorch (XPU and CPU base both failed, exit code $torchInstallExit )" - ForegroundColor Red
2675+ return (Exit-InstallFailure " Failed to install PyTorch (exit code $torchInstallExit )" $torchInstallExit )
2676+ }
2677+ # CPU base is in; drop the XPU expectation so the flavor-repair block
2678+ # below won't retry the index that just failed (mirrors the ROCm path).
2679+ $script :IsIntelXpu = $false
2680+ $TorchIndexUrl = $CpuFallbackIndexUrl
2681+ }
25932682 } else {
25942683 Write-TauriLog " STEP" " Installing PyTorch"
25952684 # Windows on ARM lacks only torchaudio (whl/cpu win_arm64: torch 42,
0 commit comments