From b7e9dae027aec338c0017cf6143f099cd64dfab0 Mon Sep 17 00:00:00 2001 From: Daniel Han Date: Tue, 2 Jun 2026 03:43:50 -0700 Subject: [PATCH] feat(install): Windows-on-Arm + NVIDIA WSL2 fallback; glibc>=2.41/CUDA<13.3 build diagnostic Two independent, purely-additive changes for Grace-Blackwell aarch64 (NVIDIA N1X "RTX Spark" and DGX-Spark-class) and new-glibc hosts. 78 insertions, 0 deletions. studio/setup.sh: when the GPU llama.cpp source build runs on glibc >= 2.41 with a CUDA toolkit < 13.3, nvcc fails on the rsqrt/rsqrtf exception- spec clash (fixed upstream in CUDA 13.3 via _NV_RSQRT_SPECIFIER) and the build silently falls back to CPU. Add a clear diagnostic recommending CUDA >= 13.3. Diagnostic only, strictly inside the existing NVIDIA CUDA branch (Metal/ROCm/CPU/x86 unaffected). install.ps1: native Windows-ARM64 has no CUDA PyTorch / Triton wheels, so the native install can't deliver GPU. When ARM64 + NVIDIA is detected, automatically set up WSL2 and run the Linux installer there (full GPU), then print the launch command. Strictly gated on ARM64 && NVIDIA && not --no-torch; x86_64 Windows (NVIDIA/AMD) and ARM64-without-NVIDIA are byte-for-byte unchanged. Opt out: UNSLOTH_NO_WSL_FALLBACK=1; distro: UNSLOTH_WSL_DISTRO. Encoding-proof distro detection via 'wsl -d -- true'. Co-Authored-By: Claude Opus 4.8 --- install.ps1 | 76 +++++++++++++++++++++++++++++++++++++++++++++++++ studio/setup.sh | 20 +++++++++++++ 2 files changed, 96 insertions(+) diff --git a/install.ps1 b/install.ps1 index 47c72bcdc1..d836d2ad69 100644 --- a/install.ps1 +++ b/install.ps1 @@ -1457,6 +1457,82 @@ shell.Run cmd, 0, False } $TorchIndexUrl = Get-TorchIndexUrl + # ===== 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. + # 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" + + $wslReady = $false + if (Get-Command wsl.exe -ErrorAction SilentlyContinue) { + try { & wsl.exe --status *> $null; if ($LASTEXITCODE -eq 0) { $wslReady = $true } } catch {} + } + + if (-not $wslReady) { + # Enabling WSL2 is a one-time operation that requires admin + a reboot. + $isAdmin = $false + try { $isAdmin = ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltinRole]::Administrator) } catch {} + step "wsl" "WSL2 isn't enabled yet -- one-time setup (needs admin + reboot)" "Yellow" + if ($isAdmin) { + substep "enabling WSL2..." "Cyan" + try { & wsl.exe --install --no-launch } catch {} + substep "WSL2 enabled. REBOOT, then re-run: irm https://unsloth.ai/install.ps1 | iex" "Green" + } else { + substep "in an ADMINISTRATOR PowerShell run: wsl --install" "Cyan" + substep "reboot, then re-run: irm https://unsloth.ai/install.ps1 | iex" "Cyan" + } + return + } + + $distro = if ($env:UNSLOTH_WSL_DISTRO) { $env:UNSLOTH_WSL_DISTRO } else { "Ubuntu-24.04" } + # Detect the distro by exit code (encoding-proof; wsl --list emits UTF-16 that PS mis-parses). + $haveDistro = $false + try { & wsl.exe -d $distro -- true *> $null; if ($LASTEXITCODE -eq 0) { $haveDistro = $true } } catch {} + if (-not $haveDistro) { + substep "installing WSL distro '$distro' (first time only)..." "Cyan" + try { & wsl.exe --install -d $distro --no-launch } catch {} + } + substep "installing Unsloth Studio inside WSL '$distro' with full GPU (this downloads PyTorch)..." "Cyan" + $wslInstall = 'export DEBIAN_FRONTEND=noninteractive; apt-get update -y >/dev/null 2>&1; apt-get install -y build-essential cmake git curl pciutils >/dev/null 2>&1; curl -fsSL https://unsloth.ai/install.sh | sh' + # install.sh writes diagnostics to stderr and may exit non-zero on the optional llama.cpp + # prebuilt step (no aarch64 prebuilt exists) -- that must NOT abort us under -ErrorAction Stop, + # since torch + unsloth + Studio still install. Lower EAP around the call (same idiom as above). + $prevEapWsl = $ErrorActionPreference + $ErrorActionPreference = "Continue" + try { + & wsl.exe -d $distro -u root -- bash -lc $wslInstall + $wslRc = $LASTEXITCODE + } finally { + $ErrorActionPreference = $prevEapWsl + } + Write-Host "" + # The optional llama.cpp prebuilt step exits non-zero on aarch64 (no prebuilt) even when + # torch + unsloth + Studio installed fine -- so verify torch.cuda directly instead of trusting $wslRc. + $torchOk = $false + $prevEapChk = $ErrorActionPreference + $ErrorActionPreference = "Continue" + try { + & wsl.exe -d $distro -u root -- /root/.unsloth/studio/unsloth_studio/bin/python -c "import torch,sys; sys.exit(0 if torch.cuda.is_available() else 3)" *> $null + $torchOk = ($LASTEXITCODE -eq 0) + } catch {} finally { $ErrorActionPreference = $prevEapChk } + if ($torchOk) { + step "done" "Unsloth Studio installed in WSL '$distro' -- GPU ready (torch.cuda available)." "Green" + } else { + step "wsl" "WSL Studio install did not finish cleanly (torch.cuda not detected; inner exit $wslRc) -- see log above." "Yellow" + } + 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 + } + # ── GPU arch → newest compatible Windows ROCm wheel release ── # Wheels bundle their own ROCm runtime; the installed HIP SDK version does # not constrain which release to use. Always picks the newest release that diff --git a/studio/setup.sh b/studio/setup.sh index 9b29def859..309e3c4460 100755 --- a/studio/setup.sh +++ b/studio/setup.sh @@ -1164,6 +1164,26 @@ else else CMAKE_ARGS="$CMAKE_ARGS -DGGML_CUDA=ON" + # glibc >= 2.41 added rsqrt()/rsqrtf() (gated by __GLIBC_USE(IEC_60559_FUNCS_EXT_C23), + # which g++ enables via _GNU_SOURCE). CUDA Toolkits < 13.3 declare these in + # without a matching exception specifier -> every .cu fails + # "exception specification is incompatible", and the GPU build silently drops to CPU. + # -allow-unsupported-compiler does NOT fix this (header clash, not the GNU-version + # #error); no host gcc avoids it. NVIDIA fixed it in CUDA 13.3 (_NV_RSQRT_SPECIFIER). + # Diagnostic only: never changes flags / never aborts -> cannot regress any platform. + _GLIBC_VER="$(getconf GNU_LIBC_VERSION 2>/dev/null | awk '{print $2}')" || _GLIBC_VER="" + if [ -n "$_GLIBC_VER" ]; then + _GLIBC_MAJ="${_GLIBC_VER%%.*}"; _GLIBC_MIN="${_GLIBC_VER#*.}"; _GLIBC_MIN="${_GLIBC_MIN%%.*}" + _CU_MAJ="${_NVCC_VER%%.*}"; _CU_MIN="${_NVCC_VER#*.}"; _CU_MIN="${_CU_MIN%%.*}" + if [ "${_GLIBC_MAJ:-0}" -eq 2 ] 2>/dev/null && [ "${_GLIBC_MIN:-0}" -ge 41 ] 2>/dev/null \ + && { [ "${_CU_MAJ:-0}" -lt 13 ] 2>/dev/null \ + || { [ "${_CU_MAJ:-0}" -eq 13 ] 2>/dev/null && [ "${_CU_MIN:-0}" -lt 3 ] 2>/dev/null; }; }; then + substep "CUDA toolkit ${_NVCC_VER} is incompatible with glibc ${_GLIBC_VER} (rsqrt/rsqrtf header clash)." "$C_ERR" + substep "the GPU build will fail to compile and fall back to CPU -- install CUDA Toolkit >= 13.3:" "$C_WARN" + substep "https://developer.nvidia.com/cuda-downloads (setup.sh auto-selects the newest /usr/local/cuda-*)" "$C_WARN" + fi + fi + CUDA_ARCHS="" if command -v nvidia-smi &>/dev/null; then _raw_caps=$(nvidia-smi --query-gpu=compute_cap --format=csv,noheader 2>/dev/null || true)