diff --git a/install.ps1 b/install.ps1 index f4d4599d30..69d0db6c7c 100644 --- a/install.ps1 +++ b/install.ps1 @@ -1563,6 +1563,29 @@ shell.Run cmd, 0, False if (-not $haveDistro) { substep "installing WSL distro '$distro' (first time only)..." "Cyan" try { & wsl.exe --install -d $distro --no-launch } catch {} + } else { + # A PRE-EXISTING distro may be WSL1, which has no GPU passthrough: the existence + # probe passes but the full install would only fail at the final torch.cuda + # check. Detect WSL1 up-front from inside the distro (kernel string + libcuda -- + # encoding-proof, unlike parsing UTF-16 `wsl -l -v` output) and convert in place; + # `wsl --set-version` preserves the distro's files. Freshly installed distros + # are WSL2 (default version 2), so only the pre-existing case needs this. + $_wsl2Probe = 'grep -qiE ''microsoft-standard|WSL2'' /proc/version 2>/dev/null || test -e /usr/lib/wsl/lib/libcuda.so' + $_isWsl2 = $false + $global:LASTEXITCODE = -1 + try { & wsl.exe -d $distro -u root -- bash -c $_wsl2Probe *> $null; $_isWsl2 = ($LASTEXITCODE -eq 0) } catch {} + if (-not $_isWsl2) { + substep "distro '$distro' looks like WSL1 (no GPU passthrough) -- converting to WSL2 (one-time; can take a few minutes)..." "Yellow" + $global:LASTEXITCODE = -1 + try { & wsl.exe --set-version $distro 2 } catch {} + $global:LASTEXITCODE = -1 + try { & wsl.exe -d $distro -u root -- bash -c $_wsl2Probe *> $null; $_isWsl2 = ($LASTEXITCODE -eq 0) } catch {} + if (-not $_isWsl2) { + Restore-StudioVenvRollback + return (Exit-InstallFailure "WSL distro '$distro' is WSL1 and automatic conversion failed; NVIDIA GPU passthrough needs WSL2. Convert it, then re-run the installer: wsl --set-version `"$distro`" 2" 1) + } + substep "'$distro' converted to WSL2." "Green" + } } substep "installing Unsloth Studio inside WSL '$distro' with full GPU (this downloads PyTorch)..." "Cyan" # For a non-main ref, fetch + export THAT ref so the WSL venv gets the branch's @@ -1575,10 +1598,16 @@ shell.Run cmd, 0, False # itself instead of leaving them with no GGUF server.) # apt stderr is kept visible (only stdout -> /dev/null) so network/DNS/repo failures inside # WSL are diagnosable rather than silently swallowed. + # Forward the CUDA llama.cpp opt-out into WSL: without it the inner setup.sh would + # defer its build to a background builder this script then never starts (the same + # opt-out skips the dispatch below), leaving no llama-server and a misleading + # "building in background" footer. Forwarded, setup.sh keeps its own build instead. + $_fwdEnv = '' + if ($env:UNSLOTH_NO_LLAMA_CUDA -eq '1') { $_fwdEnv = 'export UNSLOTH_NO_LLAMA_CUDA=1; ' } if ($_instRef -eq 'main') { - $wslInstall = 'export DEBIAN_FRONTEND=noninteractive UNSLOTH_WSL_LLAMA_DEFERRED=1; apt-get update -y >/dev/null; apt-get install -y build-essential cmake git curl pciutils >/dev/null; curl -fsSL https://unsloth.ai/install.sh | sh' + $wslInstall = $_fwdEnv + 'export DEBIAN_FRONTEND=noninteractive UNSLOTH_WSL_LLAMA_DEFERRED=1; apt-get update -y >/dev/null; apt-get install -y build-essential cmake git curl pciutils libcurl4-openssl-dev >/dev/null; curl -fsSL https://unsloth.ai/install.sh | sh' } else { - $wslInstall = 'export DEBIAN_FRONTEND=noninteractive UNSLOTH_WSL_LLAMA_DEFERRED=1; export UNSLOTH_INSTALL_REF=' + $_instRef + '; apt-get update -y >/dev/null; apt-get install -y build-essential cmake git curl pciutils >/dev/null; curl -fsSL https://raw.githubusercontent.com/unslothai/unsloth/' + $_instRef + '/install.sh | sh' + $wslInstall = $_fwdEnv + 'export DEBIAN_FRONTEND=noninteractive UNSLOTH_WSL_LLAMA_DEFERRED=1; export UNSLOTH_INSTALL_REF=' + $_instRef + '; apt-get update -y >/dev/null; apt-get install -y build-essential cmake git curl pciutils libcurl4-openssl-dev >/dev/null; curl -fsSL https://raw.githubusercontent.com/unslothai/unsloth/' + $_instRef + '/install.sh | sh' } # install.sh may exit non-zero on the optional llama.cpp prebuilt step (no aarch64 prebuilt) # though torch + unsloth + Studio still install, so lower EAP so it doesn't abort under Stop. @@ -1651,7 +1680,9 @@ shell.Run cmd, 0, False New-Item -ItemType Directory -Force -Path $shimDir *> $null $shimLines = @( '@echo off', - "wsl.exe -d $distro -u root -- /root/.unsloth/studio/unsloth_studio/bin/unsloth %*" + # Quote the distro: an UNSLOTH_WSL_DISTRO with spaces (e.g. "Ubuntu Preview") + # would otherwise split after -d and break every `unsloth ...` invocation. + "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 # A fresh Windows profile may have no HKCU 'Path' value at all -> $userPath is null @@ -1668,7 +1699,7 @@ shell.Run cmd, 0, False 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" + substep "(shim creation failed; launch manually): wsl -d `"$distro`" -u root -- bash -lic 'unsloth studio -p 8888'" "Yellow" } # Desktop + Start Menu shortcuts: launch the WSL Studio and open the browser when ready. try { @@ -1768,13 +1799,13 @@ shell.Run cmd, 0, False Start-Process -WindowStyle Hidden -FilePath 'wsl.exe' -ArgumentList @('-d', $distro, '--cd', '/root', '-u', 'root', '--', 'bash', '/root/.unsloth/run_llama_build.sh') | Out-Null step "llama.cpp" "building CUDA llama.cpp for GGUF inference in the background (a few min); log: ~/.unsloth/llama_cuda_build.log" "Green" } else { - substep "(GGUF inference needs a CUDA llama.cpp build; build later: wsl -d $distro -u root -- bash ~/.unsloth/provision_llama_cuda.sh)" "Yellow" + substep "(GGUF inference needs a CUDA llama.cpp build; build later: wsl -d `"$distro`" -u root -- bash ~/.unsloth/provision_llama_cuda.sh)" "Yellow" } } catch {} finally { $ErrorActionPreference = $prevEapL } } } 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 "retry, or launch manually: wsl -d `"$distro`" -u root -- bash -lic 'unsloth studio -p 8888'" "Cyan" } if ($torchOk) { # WSL GPU install succeeded. On this path the Windows venv is vestigial (everything diff --git a/studio/scripts/provision_llama_cuda.sh b/studio/scripts/provision_llama_cuda.sh index a8c15fbbd0..a15d69e1d2 100644 --- a/studio/scripts/provision_llama_cuda.sh +++ b/studio/scripts/provision_llama_cuda.sh @@ -49,8 +49,11 @@ HAVE_APT=0; command -v apt-get >/dev/null 2>&1 && HAVE_APT=1 # without the basic build tools needed to clone/configure llama.cpp. if [ "$HAVE_APT" -eq 1 ]; then $SUDO apt-get update -y >/dev/null 2>&1 || true + # libcurl4-openssl-dev: _cmake_configure forces -DLLAMA_CURL=ON, and on the WSL + # deferred path this script is the only build path -- setup.sh's GGUF dep install + # (which covers libcurl) was skipped, so configure would fail without the headers. $SUDO apt-get install -y --no-install-recommends \ - build-essential cmake git curl ca-certificates >/dev/null 2>&1 || true + build-essential cmake git curl ca-certificates libcurl4-openssl-dev >/dev/null 2>&1 || true $SUDO apt-get install -y --no-install-recommends gcc-14 g++-14 >/dev/null 2>&1 || true fi @@ -112,11 +115,17 @@ if [ -n "$CC_CAP" ]; then CUDA_ARCH="$CC_CAP"; else CUDA_ARCH="native"; fi # the user's request instead of always tracking ggml-org main. mkdir -p "$(dirname "$LLAMA_DIR")" _LLAMA_REF="${UNSLOTH_LLAMA_TAG:-}" +# Preserve any existing (e.g. CPU-only) llama.cpp so a failed clone OR a failed CUDA +# build doesn't leave the user with NO server: the backup is restored on any failure +# exit and only dropped once a server from the fresh build is confirmed. +_LLAMA_BAK="" +_restore_prev() { + if [ -n "$_LLAMA_BAK" ] && [ -e "$_LLAMA_BAK" ]; then + rm -rf "$LLAMA_DIR" 2>/dev/null + mv "$_LLAMA_BAK" "$LLAMA_DIR" 2>/dev/null && log "restored previous llama.cpp install" + fi +} if [ ! -d "$LLAMA_DIR/.git" ]; then - # Preserve any existing (e.g. CPU-only) llama.cpp so a FAILED clone doesn't leave the user - # with NO server -- restore it on clone failure. A successful clone makes it obsolete (the - # fresh CUDA build replaces it), so the backup is dropped then. - _LLAMA_BAK="" if [ -e "$LLAMA_DIR" ]; then _LLAMA_BAK="${LLAMA_DIR}.prev.$$" rm -rf "$_LLAMA_BAK" 2>/dev/null @@ -131,12 +140,11 @@ if [ ! -d "$LLAMA_DIR/.git" ]; then fi if [ "$_clone_ok" -ne 1 ]; then log "git clone failed" - [ -n "$_LLAMA_BAK" ] && mv "$_LLAMA_BAK" "$LLAMA_DIR" 2>/dev/null # restore previous server + _restore_prev exit 0 fi - [ -n "$_LLAMA_BAK" ] && rm -rf "$_LLAMA_BAK" 2>/dev/null fi -cd "$LLAMA_DIR" || exit 0 +cd "$LLAMA_DIR" || { _restore_prev; exit 0; } log "building CUDA llama.cpp (arch=$CUDA_ARCH, host=$HCXX) - this takes a few minutes..." _cmake_configure() { @@ -153,7 +161,7 @@ _cmake_configure() { if ! _cmake_configure; then log "stale/incompatible CMake cache detected; wiping build dir for a clean CUDA configure" rm -rf build - _cmake_configure || { log "cmake configure failed"; exit 0; } + _cmake_configure || { log "cmake configure failed"; cd /; _restore_prev; exit 0; } fi # Build the full target set unsloth-zoo's GGUF exporter also needs (llama-mtmd-cli, # llama-gguf-split) so one build serves both Studio inference and save_pretrained_gguf. @@ -192,13 +200,19 @@ if ! _cmake_build; then # Wipe build/ and rebuild clean once before giving up. log "build failed (likely interrupted/partial); wiping build dir and rebuilding clean" rm -rf build - _cmake_configure || { log "cmake configure failed"; exit 0; } - _cmake_build || { log "cmake build failed"; exit 0; } + _cmake_configure || { log "cmake configure failed"; cd /; _restore_prev; exit 0; } + _cmake_build || { log "cmake build failed"; cd /; _restore_prev; exit 0; } fi if is_cuda_server "$SERVER"; then log "CUDA llama-server ready: $SERVER" -else + [ -n "$_LLAMA_BAK" ] && rm -rf "$_LLAMA_BAK" 2>/dev/null +elif [ -x "$SERVER" ]; then + # A server exists but isn't CUDA-confirmed; still better than the old backup. log "build finished but CUDA llama-server could not be confirmed" + [ -n "$_LLAMA_BAK" ] && rm -rf "$_LLAMA_BAK" 2>/dev/null +else + log "build finished but no llama-server was produced" + cd /; _restore_prev fi exit 0 diff --git a/studio/setup.sh b/studio/setup.sh index 6ad42f0ff6..a6e92c7b6f 100755 --- a/studio/setup.sh +++ b/studio/setup.sh @@ -1488,7 +1488,11 @@ if [ "$_HOST_SYSTEM" = "Linux" ] \ elif [ -f "$LLAMA_SERVER_BIN" ]; then substep "CUDA build unavailable; keeping existing (CPU) llama-server" "$C_WARN" else - substep "CUDA build unavailable; see ~/.unsloth/llama.cpp build output" "$C_WARN" + substep "CUDA build unavailable and no llama-server present; see $LLAMA_CPP_DIR build output" "$C_WARN" + # No server at all (e.g. the provisioner replaced a previous build and then + # failed): mark degraded so the arm64 CPU-prebuilt last resort below and the + # installer failure exit fire instead of reporting a working install. + _LLAMA_CPP_DEGRADED=true fi fi fi