diff --git a/install.ps1 b/install.ps1 index f5768ac0cc..855151cbbc 100644 --- a/install.ps1 +++ b/install.ps1 @@ -1533,6 +1533,50 @@ shell.Run cmd, 0, False & 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 } + # Self-heal Studio's web-server deps. install.sh installs them in a late step + # (install_python_stack.py "studio deps", step 8). If that step is cut short -- + # an interrupted download, or a transient resolver hiccup -- torch + unsloth still + # land, but the server stack (fastapi/uvicorn/structlog/starlette) is missing and + # `unsloth studio` dies at launch with ModuleNotFoundError. If the stack can't + # import, install it WITHOUT disturbing the working ML stack: we deliberately do + # NOT pin huggingface-hub / transformers / datasets here, so the GPU torch path + # we just verified stays intact (those pins live in studio.txt for a fresh env). + if ($torchOk) { + $_studioPy = "/root/.unsloth/studio/unsloth_studio/bin/python" + $_serverOk = $false + $prevEapS = $ErrorActionPreference; $ErrorActionPreference = "Continue" + try { + & wsl.exe -d $distro -u root -- $_studioPy -c "import structlog, fastapi, uvicorn, starlette" *> $null + $_serverOk = ($LASTEXITCODE -eq 0) + } catch {} finally { $ErrorActionPreference = $prevEapS } + if (-not $_serverOk) { + substep "Studio web-server deps incomplete (install.sh step cut short) -- installing them now..." "Cyan" + # Mirrors studio/backend/requirements/studio.txt MINUS the huggingface-hub + # pin (protected above). Prefer uv (matches install.sh); fall back to pip. + $_deps = 'typer fastapi uvicorn matplotlib pandas nest_asyncio pyjwt easydict addict "structlog>=24.1.0" diceware ddgs "cryptography>=42.0.0" "httpx>=0.27.0" "fastmcp>=3.0.2"' + $_repair = 'PY=/root/.unsloth/studio/unsloth_studio/bin/python; UV="$(command -v uv 2>/dev/null || echo /root/.local/bin/uv)"; if [ -x "$UV" ] || command -v uv >/dev/null 2>&1; then "$UV" pip install --python "$PY" ' + $_deps + '; else "$PY" -m pip install ' + $_deps + '; fi' + $prevEapR = $ErrorActionPreference; $ErrorActionPreference = "Continue" + try { & wsl.exe -d $distro -u root -- bash -lc $_repair } catch {} finally { $ErrorActionPreference = $prevEapR } + $prevEapS2 = $ErrorActionPreference; $ErrorActionPreference = "Continue" + try { + & wsl.exe -d $distro -u root -- $_studioPy -c "import structlog, fastapi, uvicorn, starlette" *> $null + $_serverOk = ($LASTEXITCODE -eq 0) + } catch {} finally { $ErrorActionPreference = $prevEapS2 } + if ($_serverOk) { substep "Studio web-server deps installed." "Green" } + else { substep "(could not auto-install Studio server deps; 'unsloth studio' may fail to start)" "Yellow" } + } + # GGUF export robustness: the venv is uv-managed and ships no `pip`, but + # unsloth-zoo's exporter calls check_pip() and only finds `uv pip` when uv is + # on PATH (true for the login-shell launcher, not for every code path). + # Seeding pip into the venv makes `save_pretrained_gguf` work regardless. + $prevEapP = $ErrorActionPreference; $ErrorActionPreference = "Continue" + try { + & wsl.exe -d $distro -u root -- $_studioPy -m pip --version *> $null + if ($LASTEXITCODE -ne 0) { + & wsl.exe -d $distro -u root -- $_studioPy -m ensurepip --upgrade *> $null + } + } catch {} finally { $ErrorActionPreference = $prevEapP } + } 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 @@ -1587,12 +1631,39 @@ shell.Run cmd, 0, False $sc.Save() } step "shortcuts" "created Desktop + Start Menu shortcuts (launch WSL Studio + open browser)" "Green" - # Refresh the shell icon cache so the brand-new .lnk icons render immediately instead of - # showing blank (Explorer caches per-.lnk icons; programmatically-created links often need a poke). + # Make the brand-new .lnk icons render immediately instead of blank. Explorer caches + # per-.lnk icons, so a freshly-created shortcut often shows blank until the shell is told + # to re-read it. ie4uinit -show alone is unreliable; also broadcast SHChangeNotify so + # Explorer refreshes the icons without needing a restart or re-login. try { & "$env:SystemRoot\System32\ie4uinit.exe" -show 2>$null } catch {} + try { + if (-not ("UnslothShell.Notify" -as [type])) { + Add-Type -Namespace UnslothShell -Name Notify -MemberDefinition '[System.Runtime.InteropServices.DllImport("shell32.dll")] public static extern void SHChangeNotify(int eventId, uint flags, System.IntPtr item1, System.IntPtr item2);' + } + # SHCNE_ASSOCCHANGED (0x08000000) with SHCNF_IDLIST (0) -> flush shell icon associations. + [UnslothShell.Notify]::SHChangeNotify(0x08000000, 0, [System.IntPtr]::Zero, [System.IntPtr]::Zero) + } catch {} } catch { substep "(could not create shortcuts: $($_.Exception.Message))" "Yellow" } + # GGUF *inference* needs a CUDA-linked llama-server. There is no published + # aarch64+CUDA llama.cpp prebuilt (NVIDIA DGX Spark / N1X), so build one into + # ~/.unsloth/llama.cpp (Studio's resolver path) IN THE BACKGROUND: the user gets + # Studio + training immediately, and GGUF inference lights up a few minutes later + # with zero manual steps. Best-effort; opt out with UNSLOTH_NO_LLAMA_CUDA=1. + if ($env:UNSLOTH_NO_LLAMA_CUDA -ne '1') { + $prevEapL = $ErrorActionPreference; $ErrorActionPreference = "Continue" + try { + $_llamaUrl = "https://raw.githubusercontent.com/unslothai/unsloth/main/studio/scripts/provision_llama_cuda.sh" + $_provCmd = 'mkdir -p /root/.unsloth; if curl -fsSL "' + $_llamaUrl + '" -o /root/.unsloth/provision_llama_cuda.sh && [ -s /root/.unsloth/provision_llama_cuda.sh ]; then chmod +x /root/.unsloth/provision_llama_cuda.sh; nohup setsid bash /root/.unsloth/provision_llama_cuda.sh > /root/.unsloth/llama_cuda_build.log 2>&1 < /dev/null & echo PROV_STARTED; else echo PROV_NOSCRIPT; fi' + $_provOut = & wsl.exe -d $distro -u root -- bash -lc $_provCmd 2>$null + if ("$_provOut" -match 'PROV_STARTED') { + 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" + } + } 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" diff --git a/studio/scripts/provision_llama_cuda.sh b/studio/scripts/provision_llama_cuda.sh new file mode 100644 index 0000000000..cea357a595 --- /dev/null +++ b/studio/scripts/provision_llama_cuda.sh @@ -0,0 +1,125 @@ +#!/usr/bin/env bash +# Provision a CUDA-enabled llama.cpp for Unsloth Studio GGUF *inference*. +# +# Builds into ~/.unsloth/llama.cpp (the dir Unsloth Studio's llama-server +# resolver checks: /build/bin/llama-server). Best-effort and idempotent: +# safe to re-run, never hard-fails the caller (always exits 0). +# +# Why this exists: torch ships its own bundled CUDA runtime, so training + +# GGUF *export* work without a system CUDA toolkit. But GGUF *inference* needs +# a CUDA-linked llama-server, and on NVIDIA ARM machines (NVIDIA DGX Spark / +# GB10, N1X "RTX" laptops) there is no published aarch64+CUDA prebuilt, so we +# build one. Handles the known gotchas on these platforms: +# * nvcc rejects gcc-15 -> force gcc-14 / g++-14 as the host compiler +# * glibc >= 2.41 vs CUDA < 13.3 -> install CUDA 13.3 (rsqrt header clash) +# * sm_121 (Blackwell) GPUs -> derive arch from the GPU's compute_cap +# +# Opt out entirely with UNSLOTH_NO_LLAMA_CUDA=1 (handled by the caller). +set -uo pipefail + +LLAMA_DIR="${UNSLOTH_LLAMA_CPP_PATH:-$HOME/.unsloth/llama.cpp}" +SERVER="$LLAMA_DIR/build/bin/llama-server" +log() { printf ' - %s\n' "$*"; } + +is_cuda_server() { [ -x "$1" ] && ldd "$1" 2>/dev/null | grep -qi 'libggml-cuda'; } + +# 0. Already provisioned? +if is_cuda_server "$SERVER"; then + log "CUDA llama-server already present: $SERVER" + exit 0 +fi + +# 1. Require an NVIDIA GPU (this script is only meaningful with one). +if ! command -v nvidia-smi >/dev/null 2>&1; then + log "no nvidia-smi found; skipping CUDA llama.cpp build" + exit 0 +fi + +SUDO=""; [ "$(id -u)" -ne 0 ] && SUDO="sudo" +HAVE_APT=0; command -v apt-get >/dev/null 2>&1 && HAVE_APT=1 + +# 2. Base toolchain. gcc-14 is required because nvcc rejects gcc-15. +if [ "$HAVE_APT" -eq 1 ]; then + $SUDO apt-get update -y >/dev/null 2>&1 || true + $SUDO apt-get install -y --no-install-recommends \ + build-essential cmake git curl ca-certificates gcc-14 g++-14 >/dev/null 2>&1 || true +fi + +# 3. Locate nvcc; install the CUDA toolkit if missing. +find_nvcc() { command -v nvcc 2>/dev/null || ls /usr/local/cuda*/bin/nvcc 2>/dev/null | sort -V | tail -1; } +NVCC="$(find_nvcc)" +if [ -z "$NVCC" ] && [ "$HAVE_APT" -eq 1 ]; then + log "CUDA toolkit (nvcc) not found - installing CUDA 13.3 (matches torch cu13x; avoids glibc>=2.41 rsqrt clash)" + # shellcheck disable=SC1091 + . /etc/os-release 2>/dev/null || true + case "$(uname -m)" in + aarch64) NV_ARCH=sbsa ;; + x86_64) NV_ARCH=x86_64 ;; + *) NV_ARCH="" ;; + esac + case "${ID:-}${VERSION_ID:-}" in + ubuntu24.04) NV_DISTRO=ubuntu2404 ;; + ubuntu22.04) NV_DISTRO=ubuntu2204 ;; + debian12) NV_DISTRO=debian12 ;; + *) NV_DISTRO="" ;; + esac + if [ -n "$NV_ARCH" ] && [ -n "$NV_DISTRO" ]; then + KR=/tmp/cuda-keyring.deb + if curl -fsSL "https://developer.download.nvidia.com/compute/cuda/repos/$NV_DISTRO/$NV_ARCH/cuda-keyring_1.1-1_all.deb" -o "$KR" 2>/dev/null; then + $SUDO dpkg -i "$KR" >/dev/null 2>&1 || true + $SUDO apt-get update -y >/dev/null 2>&1 || true + $SUDO apt-get install -y cuda-toolkit-13-3 >/dev/null 2>&1 \ + || $SUDO apt-get install -y cuda-toolkit >/dev/null 2>&1 || true + fi + fi + NVCC="$(find_nvcc)" +fi + +if [ -z "$NVCC" ]; then + log "could not provision a CUDA toolkit. Training + GGUF export still work;" + log "GGUF *inference* in Studio will be unavailable until a CUDA toolkit exists." + log "Re-run this script after installing one to enable GGUF inference." + exit 0 +fi + +CUDA_HOME="$(dirname "$(dirname "$NVCC")")" +export PATH="$CUDA_HOME/bin:$PATH" +export CUDAToolkit_ROOT="$CUDA_HOME" + +# 4. Host compiler: prefer gcc-14 / g++-14 (nvcc rejects 15). +HCC=gcc; command -v gcc-14 >/dev/null 2>&1 && HCC=gcc-14 +HCXX=g++; command -v g++-14 >/dev/null 2>&1 && HCXX=g++-14 +export CC="$HCC" CXX="$HCXX" CUDAHOSTCXX="$HCXX" + +# 5. CUDA arch from the GPU's compute capability (e.g. "12.1" -> 121). Fallback: native. +CC_CAP="$(nvidia-smi --query-gpu=compute_cap --format=csv,noheader 2>/dev/null | head -1 | tr -d ' .')" +if [ -n "$CC_CAP" ]; then CUDA_ARCH="$CC_CAP"; else CUDA_ARCH="native"; fi + +# 6. Clone + build into ~/.unsloth/llama.cpp. +mkdir -p "$(dirname "$LLAMA_DIR")" +if [ ! -d "$LLAMA_DIR/.git" ]; then + rm -rf "$LLAMA_DIR" + git clone --depth 1 https://github.com/ggml-org/llama.cpp "$LLAMA_DIR" >/dev/null 2>&1 \ + || { log "git clone failed"; exit 0; } +fi +cd "$LLAMA_DIR" || exit 0 + +log "building CUDA llama.cpp (arch=$CUDA_ARCH, host=$HCXX) - this takes a few minutes..." +cmake -S . -B build -DCMAKE_BUILD_TYPE=Release \ + -DGGML_CUDA=ON -DGGML_CUDA_F16=ON \ + -DCMAKE_CUDA_ARCHITECTURES="$CUDA_ARCH" \ + -DCMAKE_CUDA_HOST_COMPILER="$HCXX" \ + -DLLAMA_CURL=ON >/dev/null 2>&1 || { log "cmake configure failed"; exit 0; } +# Build the full set unsloth-zoo's GGUF exporter expects too (llama-mtmd-cli, +# llama-gguf-split), so a pre-provisioned build satisfies both Studio inference +# AND save_pretrained_gguf without triggering a --clean-first rebuild later. +cmake --build build -j"$(nproc)" --target \ + llama-server llama-cli llama-quantize llama-mtmd-cli llama-gguf-split >/dev/null 2>&1 \ + || { log "cmake build failed"; exit 0; } + +if is_cuda_server "$SERVER"; then + log "CUDA llama-server ready: $SERVER" +else + log "build finished but CUDA llama-server could not be confirmed" +fi +exit 0