WSL fallback: self-heal Studio server deps, seed pip, auto-build CUDA llama.cpp
A clean-slate reinstall on an ARM64+NVIDIA box surfaced three follow-on gaps in the WSL path. All fixes are additive, best-effort, and confined to the $torchOk success branch of the WSL fallback, so they only run on the ARM64+NVIDIA machines that reach it -- no other platform is affected. install.ps1: - Self-heal Studio's web-server deps. install_python_stack.py installs the Studio UI deps (fastapi/uvicorn/structlog/starlette) in a late step; if that run is cut short, torch+unsloth land but the server stack is missing and `unsloth studio` dies at launch on ModuleNotFoundError. Import-check the stack after the torch.cuda probe and, if absent, install it WITHOUT re-pinning huggingface-hub/transformers/ datasets, so the verified GPU torch path is never disturbed. - Seed pip into the (uv-managed, pip-less) venv via ensurepip so save_pretrained_gguf -> check_pip() works regardless of how Studio is launched. - Auto-build a CUDA llama-server for GGUF inference in the background via the new provision script (below), so GGUF chat/tool-calling lights up a few minutes after install with zero manual steps. Opt out with UNSLOTH_NO_LLAMA_CUDA=1. studio/scripts/provision_llama_cuda.sh (new): - Idempotent, best-effort (always exits 0). Builds a CUDA llama.cpp into ~/.unsloth/llama.cpp (Studio's resolver path). Generic across NVIDIA Linux/WSL incl. aarch64 (DGX Spark, N1X): derives the arch from the GPU's compute_cap, installs gcc-14 + CUDA 13.3 only when nvcc is missing (gcc-15 is rejected by nvcc; CUDA <13.3 hits the glibc>=2.41 rsqrt header clash), and builds the full target set (llama-server llama-cli llama-quantize llama-mtmd-cli llama-gguf-split) so it satisfies both Studio inference and save_pretrained_gguf without a later rebuild. Validated on an NVIDIA N1X (sm_121): training, GPU inference, GGUF q4_k_m export, `unsloth studio` via both Desktop + Start Menu shortcuts (HTTP 200), GGUF chat at 121 tok/s (BLACKWELL_NATIVE_FP4=1) and OpenAI-style tool-calling. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
b8f8fbffbf
commit
25c11b3e98
2 changed files with 198 additions and 2 deletions
75
install.ps1
75
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"
|
||||
|
|
|
|||
125
studio/scripts/provision_llama_cuda.sh
Normal file
125
studio/scripts/provision_llama_cuda.sh
Normal file
|
|
@ -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: <dir>/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
|
||||
Loading…
Add table
Add a link
Reference in a new issue