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 <crt/math_functions.h> 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 <d> -- true'.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Daniel Han 2026-06-02 03:43:50 -07:00
commit b7e9dae027
2 changed files with 96 additions and 0 deletions

View file

@ -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

View file

@ -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
# <crt/math_functions.h> 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)