From 9ca396b665d805b1e7c1ea6529c924dbbffb543d Mon Sep 17 00:00:00 2001 From: Daniel Han Date: Sat, 18 Jul 2026 13:59:55 +0000 Subject: [PATCH] install: four round-seven review fixes across installer and uninstall Seventh review round; each item reproduced against the live tree first. The aarch64 bitsandbytes step gated on a bare nvidia-smi, which root login shells cannot see under WSL2 GPU-PV (the binary lives only in /usr/lib/wsl/lib, dropped from PATH by the /etc/profile reset), so Spark and N1X WSL installs finished with CUDA torch but no 4-bit QLoRA. The gate now resolves nvidia-smi explicitly with the same PATH, /usr/lib/wsl/lib, /usr/bin order as setup.sh's resolver. uninstall.sh's CUDA-build kill matched patterns against argv, but the provisioner cds into the tree before `cmake --build build`, so cmake and make children carry relative argv no pattern can match; killing only the wrapper orphaned them mid-build. Each match's whole process group is now signalled (TERM then KILL), with a plain PID kill as fallback when the pgid is unreadable or shared with init. Verified in a sandbox: a child with unmatchable argv in the wrapper's group dies with it. The WSL shim dir was appended to user PATH while the native installer prepends its own %USERPROFILE%\.unsloth\studio\bin, whose unsloth.exe outlives the venv the fallback rolls aside, so on a native-to-WSL rerun a new terminal resolved unsloth to the dead native launcher. The shim is now prepended via Add-ToUserPath (which de-dupes and hoists), and the dead default-root native shim is removed when the venv binary it targets is gone; custom-root shims are left alone since the prepend outranks them. UNSLOTH_NPM_REGISTRY was not forwarded into the inner WSL shell even though setup.sh threads it into every npm/bun install, so mirror-required networks failed the frontend step (and with it the install) while the outer installer honored the mirror. It is now forwarded with the same strict http(s) allow-list and single-quoting as UNSLOTH_PYTORCH_MIRROR. Verified: bash -n on both shell scripts, PowerShell AST parse on install.ps1, the group-kill sandbox above, resolver smoke tests for the bitsandbytes gate, and the sh test battery matches the branch baseline. --- install.ps1 | 32 ++++++++++++++++++++++++-------- install.sh | 10 ++++++++-- scripts/uninstall.sh | 26 ++++++++++++++++++++------ 3 files changed, 52 insertions(+), 16 deletions(-) diff --git a/install.ps1 b/install.ps1 index 079176bd60..4b350c915b 100644 --- a/install.ps1 +++ b/install.ps1 @@ -2223,6 +2223,13 @@ exit 0 if ($env:UNSLOTH_PYTORCH_MIRROR -and ($env:UNSLOTH_PYTORCH_MIRROR -match '^https?://[A-Za-z0-9._~:/?#@%+=&-]+$')) { $_fwdEnv += "export UNSLOTH_PYTORCH_MIRROR='$($env:UNSLOTH_PYTORCH_MIRROR)'; " } + # Forward the npm mirror the same way: setup.sh threads UNSLOTH_NPM_REGISTRY + # into every npm/bun install, and on mirror-required networks the WSL + # frontend/OXC steps would otherwise hit registry.npmjs.org and fail the + # install. Same strict http(s) allow-list + single-quote as above. + if ($env:UNSLOTH_NPM_REGISTRY -and ($env:UNSLOTH_NPM_REGISTRY -match '^https?://[A-Za-z0-9._~:/?#@%+=&-]+$')) { + $_fwdEnv += "export UNSLOTH_NPM_REGISTRY='$($env:UNSLOTH_NPM_REGISTRY)'; " + } # Forward an explicit UNSLOTH_PYTHON pin: Windows env vars do not cross into # WSL, so without this the inner install.sh silently built the venv on its # default Python while the installer reported success. Strict version shape @@ -2379,14 +2386,23 @@ exit 0 # Record the distro so the uninstaller can clean a custom UNSLOTH_WSL_DISTRO install # without the env var set. try { Set-Content -LiteralPath (Join-Path (Split-Path $shimDir -Parent) "wsl-distro.txt") -Value $distro -Encoding ASCII } catch {} - # A fresh profile may have no HKCU 'Path'; null would make TrimEnd() throw. - $userPath = [Environment]::GetEnvironmentVariable("Path", "User") - if (-not $userPath) { $userPath = "" } - if (($userPath -split ';') -notcontains $shimDir) { - $newUserPath = if ($userPath.Trim()) { $userPath.TrimEnd(';') + ";" + $shimDir } else { $shimDir } - [Environment]::SetEnvironmentVariable("Path", $newUserPath, "User") - } - $env:Path = $env:Path.TrimEnd(';') + ";" + $shimDir + # PREPEND (not append): a previous NATIVE install prepended + # %USERPROFILE%\.unsloth\studio\bin (unsloth.exe) to user PATH, + # and that exe outlives the venv this fallback just rolled aside + # -- an appended shim would lose to the dead native launcher in + # every new terminal. Add-ToUserPath de-dupes and hoists. + $null = Add-ToUserPath -Directory $shimDir -Position 'Prepend' + $env:Path = $shimDir + ";" + $env:Path.TrimStart(';') + # Drop the dead default-root native shim outright when the venv + # binary it launches is gone (custom-root shims are left alone; + # the PATH prepend above already outranks them). + try { + $staleNativeShim = Join-Path $env:USERPROFILE ".unsloth\studio\bin\unsloth.exe" + $staleNativeTarget = Join-Path $env:USERPROFILE ".unsloth\studio\unsloth_studio\Scripts\unsloth.exe" + if ((Test-Path -LiteralPath $staleNativeShim) -and -not (Test-Path -LiteralPath $staleNativeTarget)) { + Remove-Item -LiteralPath $staleNativeShim -Force -ErrorAction Stop + } + } catch {} step "shim" "created native 'unsloth' command -> forwards to WSL '$distro'" "Green" substep "open a NEW terminal, then (no WSL knowledge needed):" "Cyan" substep " unsloth studio # runs in WSL; opens http://localhost:8888" "Cyan" diff --git a/install.sh b/install.sh index a30b6b57aa..17baa76550 100755 --- a/install.sh +++ b/install.sh @@ -2979,10 +2979,16 @@ elif [ -n "$TORCH_INDEX_URL" ]; then # extras break 4-bit QLoRA, but aarch64 manylinux wheels work (verified on # sm_121 via PTX JIT). Best-effort: no wheel keeps 16-bit LoRA / full finetuning. # SKIP_TORCH gate stops a --no-torch (GGUF-only) install dragging torch back in. + # nvidia-smi may live only in /usr/lib/wsl/lib (WSL2 GPU-PV), which root login + # shells drop from PATH -- resolve explicitly (same order as setup.sh's + # _resolve_nvsmi) so the WoA/WSL install still gets 4-bit QLoRA support. + _bnb_nvsmi="$(command -v nvidia-smi 2>/dev/null || true)" + [ -z "$_bnb_nvsmi" ] && [ -x /usr/lib/wsl/lib/nvidia-smi ] && _bnb_nvsmi=/usr/lib/wsl/lib/nvidia-smi + [ -z "$_bnb_nvsmi" ] && [ -x /usr/bin/nvidia-smi ] && _bnb_nvsmi=/usr/bin/nvidia-smi if [ "$SKIP_TORCH" = false ] \ && { [ "$(uname -m)" = "aarch64" ] || [ "$(uname -m)" = "arm64" ]; } \ - && command -v nvidia-smi >/dev/null 2>&1 \ - && nvidia-smi -L 2>/dev/null | awk '/^GPU[[:space:]]+[0-9]+:/{found=1} END{exit !found}' \ + && [ -n "$_bnb_nvsmi" ] \ + && "$_bnb_nvsmi" -L 2>/dev/null | awk '/^GPU[[:space:]]+[0-9]+:/{found=1} END{exit !found}' \ && ! "$_VENV_PY" -c "import bitsandbytes" >/dev/null 2>&1; then substep "installing bitsandbytes (aarch64 wheels; enables 4-bit QLoRA)..." if ! uv pip install --python "$_VENV_PY" "bitsandbytes>=0.45.5,!=0.46.0,!=0.48.0" >/dev/null 2>&1; then diff --git a/scripts/uninstall.sh b/scripts/uninstall.sh index 52e5def945..3cc49b3ab7 100755 --- a/scripts/uninstall.sh +++ b/scripts/uninstall.sh @@ -219,13 +219,27 @@ _remove_path "$HOME/.unsloth/studio" # _pkill_studio uses. if command -v pkill >/dev/null 2>&1; then _llama_re=$(_pkill_escape "$HOME/.unsloth/llama.cpp") - for _pat in "run_llama_build\.sh" "provision_llama_cuda\.sh" "$_llama_re"; do - pkill -TERM -f "$_pat" 2>/dev/null || true - done + # Signal the whole process GROUP of each match, not just the matching PID: + # the provisioner cds into the tree before `cmake --build build`, so cmake/ + # make children carry relative argv that no pattern can match, and killing + # only the wrapper orphans them mid-build. Group kill sweeps the tree; PID + # kill remains the fallback when pgid is unreadable or shared with init. + _kill_llama_build() { + _sig="$1" + for _pat in "run_llama_build\.sh" "provision_llama_cuda\.sh" "$_llama_re"; do + for _pid in $(pgrep -f "$_pat" 2>/dev/null); do + _pgid=$(ps -o pgid= -p "$_pid" 2>/dev/null | tr -d '[:space:]') + case "$_pgid" in + ''|0|1) kill -s "$_sig" "$_pid" 2>/dev/null || true ;; + *) kill -s "$_sig" -- "-$_pgid" 2>/dev/null \ + || kill -s "$_sig" "$_pid" 2>/dev/null || true ;; + esac + done + done + } + _kill_llama_build TERM sleep 0.5 - for _pat in "run_llama_build\.sh" "provision_llama_cuda\.sh" "$_llama_re"; do - pkill -KILL -f "$_pat" 2>/dev/null || true - done + _kill_llama_build KILL fi # Default-mode shared llama.cpp build + cache are siblings of studio (not removed # by deleting it). No-op in env/custom mode (they nest under the custom root) and