From 8fbd8e3fa27140a9fe678b385419f371cfef6c25 Mon Sep 17 00:00:00 2001 From: Daniel Han Date: Tue, 2 Jun 2026 04:13:12 -0700 Subject: [PATCH] Harden Windows-on-Arm WSL fallback: native-CUDA probe + WSL-forwarding unsloth shim - Future-proof: probe whether a CUDA torch wheel is installable natively for win_arm64 (uv pip install --dry-run). If it resolves (NVIDIA ships the wheel) keep the NATIVE install; otherwise fall back to WSL. WSL is used ONLY when native genuinely can't. - Create a native Windows unsloth.cmd shim (on user PATH) that forwards every "unsloth ..." into the WSL GPU env, so "unsloth studio" / "unsloth studio run" typed in PowerShell run inside WSL and stream output + the http://localhost:8888 URL back. - Run the WSL install under Continue-EAP and verify torch.cuda before reporting success so the optional (aarch64) llama.cpp prebuilt failure cannot abort or mis-report. Co-Authored-By: Claude Opus 4.8 --- install.ps1 | 52 +++++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 43 insertions(+), 9 deletions(-) diff --git a/install.ps1 b/install.ps1 index d836d2ad69..79bcbe0e39 100644 --- a/install.ps1 +++ b/install.ps1 @@ -1459,15 +1459,27 @@ shell.Run cmd, 0, False # ===== Windows-on-ARM + NVIDIA GPU -> automatic WSL2 fallback (N1X "RTX Spark" / DGX Spark-class) ===== # Native Windows-ARM64 has no CUDA PyTorch wheel and no Triton wheel for win_arm64, so the GPU - # training/inference stack cannot run natively. When an NVIDIA GPU is present on ARM64, transparently - # set up the supported path for an average user: enable/install WSL2 and run the Linux installer there - # (full GPU). STRICTLY gated on ARM64 + NVIDIA -> normal x86_64 Windows (NVIDIA or AMD) and - # ARM64-without-NVIDIA are byte-for-byte unaffected and continue the native install below. + # stack can't run natively today. When an NVIDIA GPU is present on ARM64 AND native CUDA PyTorch is + # NOT installable for this platform, set up the supported path: enable/install WSL2, run the Linux + # installer there (full GPU), and create a Windows `unsloth` shim that forwards into WSL. + # STRICTLY gated -> normal x86_64 Windows (NVIDIA or AMD) and ARM64-without-NVIDIA are byte-for-byte + # unaffected and continue the native install below. FUTURE-PROOF: if NVIDIA ships a win_arm64 CUDA + # torch wheel, the probe below passes and the native install is kept automatically. # Opt out with UNSLOTH_NO_WSL_FALLBACK=1; choose the distro with UNSLOTH_WSL_DISTRO. try { $_winArm64 = ([System.Runtime.InteropServices.RuntimeInformation]::OSArchitecture.ToString() -ieq 'Arm64') } catch { $_winArm64 = $false } - if ($_winArm64 -and $HasNvidiaSmi -and (-not $SkipTorch) -and ($env:UNSLOTH_NO_WSL_FALLBACK -ne '1')) { - step "wsl" "Windows on ARM + NVIDIA detected -- routing GPU setup through WSL2 (supported path)" - substep "native Windows-ARM64 has no CUDA PyTorch/Triton yet; WSL2 delivers full GPU." "Yellow" + $_nativeCudaTorchOk = $false + if ($_winArm64 -and $HasNvidiaSmi -and (-not $SkipTorch)) { + # Future-proof check: can a CUDA-capable torch wheel be resolved natively for this platform/index? + $prevEapProbe = $ErrorActionPreference; $ErrorActionPreference = "Continue" + try { + & uv pip install --python $VenvPython --dry-run torch --index-url $TorchIndexUrl *> $null + $_nativeCudaTorchOk = ($LASTEXITCODE -eq 0) + } catch { $_nativeCudaTorchOk = $false } finally { $ErrorActionPreference = $prevEapProbe } + if ($_nativeCudaTorchOk) { step "gpu" "native CUDA PyTorch now available for win_arm64 -- keeping native install" "Green" } + } + if ($_winArm64 -and $HasNvidiaSmi -and (-not $_nativeCudaTorchOk) -and (-not $SkipTorch) -and ($env:UNSLOTH_NO_WSL_FALLBACK -ne '1')) { + step "wsl" "Windows on ARM + NVIDIA, native CUDA unavailable -- routing GPU setup through WSL2" + substep "no win_arm64 CUDA PyTorch/Triton yet; WSL2 delivers full GPU (DGX Spark / RTX Spark path)." "Yellow" $wslReady = $false if (Get-Command wsl.exe -ErrorAction SilentlyContinue) { @@ -1523,11 +1535,33 @@ shell.Run cmd, 0, False } catch {} finally { $ErrorActionPreference = $prevEapChk } if ($torchOk) { step "done" "Unsloth Studio installed in WSL '$distro' -- GPU ready (torch.cuda available)." "Green" + # Native Windows `unsloth` shim: forward every `unsloth ...` into the WSL GPU env so the user + # never has to touch WSL. `unsloth studio` runs inside WSL and streams output + URL back here; + # WSL2 forwards 127.0.0.1, so http://localhost:8888 works in the Windows browser. + try { + $shimDir = Join-Path $env:LOCALAPPDATA "Unsloth\bin" + New-Item -ItemType Directory -Force -Path $shimDir *> $null + $shimLines = @( + '@echo off', + "wsl.exe -d $distro -u root -- /root/.unsloth/studio/unsloth_studio/bin/unsloth %*" + ) + Set-Content -LiteralPath (Join-Path $shimDir "unsloth.cmd") -Value $shimLines -Encoding ASCII + $userPath = [Environment]::GetEnvironmentVariable("Path", "User") + if (($userPath -split ';') -notcontains $shimDir) { + [Environment]::SetEnvironmentVariable("Path", ($userPath.TrimEnd(';') + ";" + $shimDir), "User") + } + $env:Path = $env:Path.TrimEnd(';') + ";" + $shimDir + step "shim" "created native 'unsloth' command -> forwards to WSL '$distro'" "Green" + substep "open a NEW terminal, then (no WSL knowledge needed):" "Cyan" + substep " unsloth studio # runs in WSL; opens http://localhost:8888" "Cyan" + substep " unsloth studio run # also forwarded into WSL" "Cyan" + } catch { + substep "(shim creation failed; launch manually): wsl -d $distro -u root -- bash -lic 'unsloth studio -p 8888'" "Yellow" + } } else { step "wsl" "WSL Studio install did not finish cleanly (torch.cuda not detected; inner exit $wslRc) -- see log above." "Yellow" + substep "retry, or launch manually: wsl -d $distro -u root -- bash -lic 'unsloth studio -p 8888'" "Cyan" } - substep "Launch it from Windows (then open http://localhost:8888):" "Cyan" - substep " wsl -d $distro -u root -- bash -lic 'unsloth studio -p 8888'" "Cyan" substep "GPU training + GGUF export run inside WSL. (GGUF *inference* additionally needs a CUDA llama.cpp build.)" "Yellow" if ($torchOk) { $global:LASTEXITCODE = 0 } return