The in-distro cleanup probed a hardcoded candidate set ('', Ubuntu,
Ubuntu-24.04, Ubuntu-22.04, Debian) on every Windows uninstall, wiping
/root/.unsloth in any reachable distro even when the WoA fallback never
ran -- on an x86 AMD box this deletes a ROCm-on-WSL Studio the AMD flow
installed. Use the evidence the installer already records: clean only
the wsl-distro.txt marker distro or UNSLOTH_WSL_DISTRO; keep the broad
candidate probe solely for legacy marker-less installs, which can only
exist on ARM64 hosts.
Addresses the open Codex P1 on this path. Verified gating matrix:
x86+no-marker -> no cleanup; marker/env -> that distro only;
ARM64+no-marker -> legacy broad probe unchanged.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Real fixes:
- uninstall.ps1: scope the WSL process kill to argv referencing
/root/.unsloth/ (the fallback's install dir, which its Studio server,
llama-server, and build runner all reference) instead of the bare
'[l]lama-server' / '[u]nsloth_studio' name patterns -- uninstalling the
Windows shim must not kill a user's own unrelated llama.cpp server or a
/home Studio in a probed distro. Proven live: the path pattern matched
exactly the three fallback processes while a planted /tmp/llama-server
decoy matched the old pattern and not the new one. The backslash in
'/root/\.unslot[h]/' keeps the pattern from matching the cleanup
command's own argv.
- install.ps1: bridge UNSLOTH_LLAMA_TAG / UNSLOTH_LLAMA_PR into the
background CUDA-build runner -- the provisioner honors both pins, but
Windows env vars don't cross into WSL on their own, so a user's pin was
silently ignored by the deferred build. (Deliberately NOT forwarded into
the inner install.sh env: setup.sh skips its deferral when a PR pin is
visible there, which would CPU-build the pin in the foreground.)
- kernels/flex_attention.py: make _flex_is_dgx_spark() CUDA-free
(nvidia-smi device names, mirroring _is_dgx_spark_no_cuda_init) -- it
runs at module import and called torch.cuda.get_device_name(), which
initializes the CUDA allocator before patch_dgx_spark_memory_config()
can set PYTORCH_CUDA_ALLOC_CONF on exactly the Spark hosts it targets
(reachable via vision.py importing ..kernels before ._utils). Verified
on the N1X: detects the machine with torch.cuda.is_initialized() still
False.
- _utils.py: the TrainingArguments __post_init__ wrapper now forwards
*args/**kwargs (robustness against future InitVar signatures).
Disproven on hardware (no change): the five "high" PS-5.1 claims --
String.TrimEnd('\', '/') with multiple char args binds fine to
params char[] (verified on PS 5.1.28000.1737, and the uninstaller's PATH
cleanup using exactly that code ran successfully this same day), and
[Text.Encoding] resolves via the System namespace prefix (the background
build dispatch using it has run in every install this week). The worker
"_sp possibly undefined" claim is false: `import subprocess as _sp` is at
worker.py line 25.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- install.sh: gate the new aarch64 bitsandbytes block on SKIP_TORCH=false --
with --no-torch/UNSLOTH_NO_TORCH (GGUF-only install) it would have pulled
torch back into the venv through bitsandbytes' dependencies.
- studio worker: in the new Spark OOM-guard section, decide
PYTORCH_CUDA_ALLOC_CONF (expandable_segments) BEFORE the guard's first CUDA
touch -- get_device_properties initializes the CUDA allocator, after which
the env var is ignored, and the later `import unsloth`
(patch_dgx_spark_memory_config) is too late for the worker process. Uses
the same CUDA-free nvidia-smi name sniff, append-don't-override, and
UNSLOTH_NO_EXPANDABLE_SEGMENTS opt-out as the library patch. Live-verified
on the N1X: env set while torch.cuda.is_initialized() is still False.
- uninstall.ps1: only run `fuser -k 8888/tcp` in a probed WSL distro when an
Unsloth install actually exists there (checked BEFORE the rm deletes the
marker) -- an unrelated listener on 8888 (e.g. Jupyter) in a clean distro
must survive a Windows-side uninstall. The Unsloth-specific pkills stay
unconditional.
- install.ps1 + uninstall.ps1: persist the chosen WSL distro to
%LOCALAPPDATA%\Unsloth\wsl-distro.txt at install; uninstall reads it
(before removing the directory) and prepends it to the cleanup candidates,
so a custom UNSLOTH_WSL_DISTRO install is cleaned without the env var
being set again at uninstall time.
- provision_llama_cuda.sh: honor UNSLOTH_LLAMA_PR (numeric-validated,
best-effort fetch of pull/N/head after clone) so a provisioned tree
matches a PR pin the way setup.sh does; and require only llama-server in
the main cmake build (mirroring setup.sh), building the helper targets
(llama-cli/quantize/mtmd-cli/gguf-split) best-effort afterwards -- an
older UNSLOTH_LLAMA_TAG pin lacking a newer helper target no longer fails
the whole provision.
Not changed: the "--tauri rejection doesn't restore the venv rollback"
comment is incorrect -- the rejection returns through Exit-InstallFailure,
which itself calls Restore-StudioVenvRollback.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
install.ps1 (WoA WSL fallback):
- report failure (non-zero) + restore the rolled-aside venv when the WSL GPU
install fails (torch.cuda absent) or when WSL needs enabling+reboot, instead of
returning success — so -File/Tauri callers don't see a broken install as complete
- on WSL success, Complete-StudioVenvRollback so the previous-venv backup isn't orphaned
- refuse under --tauri with a clear "use the CLI installer" message (the desktop
launcher resolves a Windows-venv backend, which a WSL-only install can't provide)
- reset $LASTEXITCODE before each wsl.exe / python probe (a stale 0 could mark WSL
ready / torch OK if the native command fails to launch)
- torch-availability probe: --reinstall so an already-installed CPU torch in a
migrated venv isn't accepted as "satisfied" (would wrongly skip the WSL path)
- treat a null HKCU PATH as empty (fresh profile) so shim PATH update can't throw
- keep apt stderr visible inside WSL (only stdout -> /dev/null) for diagnosability
scripts/uninstall.ps1:
- scope WSL cleanup to /root (the fallback's install location); stop deleting
/home/*/.unsloth, which could erase an unrelated WSL user's own Unsloth/cache
studio/setup.sh:
- direct (non-install.ps1) WSL installs now provision CUDA llama.cpp themselves
instead of being left with no GGUF server: install.ps1 exports
UNSLOTH_WSL_LLAMA_DEFERRED=1, and the aarch64+NVIDIA provision block runs under
WSL only when that marker is absent
- mark a provisioner-built llama.cpp as Studio-owned in custom-STUDIO_HOME mode so
the next setup's _assert_studio_owned_or_absent doesn't abort
- glibc>=2.41 check: also match a future major>2 (e.g. 3.0)
studio/scripts/provision_llama_cuda.sh:
- install base tools (cmake/git/curl) in their own apt transaction before the
best-effort gcc-14/g++-14 (unavailable on Ubuntu 22.04 / Debian 12, where bundling
them aborted the whole transaction and left no build tools)
- back up an existing (e.g. CPU-only) llama.cpp before the destructive clone and
restore it on clone failure, so a failed clone doesn't leave the user with no server
- honor a pinned llama.cpp ref via UNSLOTH_LLAMA_TAG instead of always tracking main
unsloth/models/_utils.py:
- set PYTORCH_CUDA_ALLOC_CONF (expandable_segments) via a CUDA-free Spark detector
(nvidia-smi, not torch.cuda.get_device_name) so it takes effect before CUDA/the
caching allocator initialize — previously it was a silent no-op on auto-detected Spark
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Root cause (diagnosed live on an N1X WoA box, confirmed by on-screen checks):
the Windows shell's sandboxed icon-extraction broker cannot read a standalone
.ico stored under %LOCALAPPDATA% (it gets a redirected/virtualized view), so
the Desktop + Start Menu shortcuts render BLANK -- regardless of icon format
(BMP vs PNG frames), ACLs, icon cache, or shortcut-creation method, all of
which were ruled out. The IDENTICAL .ico renders correctly from a path under
the user profile. Fix: write unsloth.ico to %USERPROFILE%\.unsloth instead of
%LOCALAPPDATA%\Unsloth (shim/launcher stay in %LOCALAPPDATA%). uninstall.ps1
removes the icon at the new location. Also drops the speculative SHGetFileInfo
"image-list prime" block added while chasing the wrong (format/cache) theory.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Shorten the multi-line rationale comments added by this PR across the
remaining changed files to 1-2 lines each, preserving intent (gotchas,
workarounds, why-notes). Comment-only changes; no code, strings, or
behavior altered. Verified: PowerShell AST parser, bash -n, and python
ast.parse all pass; diffs confirmed comment-only.
Files: install.ps1, scripts/uninstall.ps1, scripts/uninstall.sh,
studio/setup.sh, unsloth/models/_utils.py, unsloth/kernels/flex_attention.py
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* Update Install Scripts
Add SPDX AGPL-3.0 headers to the installer scripts and let the piped web
installs take their common options from the environment.
- install.sh / install.ps1: read UNSLOTH_NO_TORCH (and UNSLOTH_PYTHON for
install.sh) so a piped install needs no positional flags. Flags and the
pipe forms still work; an explicit flag wins.
- Fix the UNSLOTH_STUDIO_HOME example so the variable sits after the pipe
and reaches sh instead of curl.
- Add SPDX headers to install.sh, install.ps1, the uninstall scripts, and
the MLX install scripts.
- Drop the internal test package names from the studio install comments.
* Mirror UNSLOTH_PYTHON env var to install.ps1
install.ps1 now reads UNSLOTH_PYTHON to pin the Python version, matching
install.sh, and lists all three env vars (UNSLOTH_NO_TORCH, UNSLOTH_PYTHON,
UNSLOTH_STUDIO_HOME) in the header examples. The requested version is
preferred during detection and used as the winget install target; behavior
is unchanged when the variable is unset.
The WSL distro-probe loop tries a candidate list that intentionally
includes distros that may not exist; the last failed `wsl -d <name> -- true`
probe left $LASTEXITCODE=255, so `& .\uninstall.ps1` returned non-zero even
when every cleanup step succeeded. Reset $global:LASTEXITCODE=0 at the end
(not `exit 0`, so the `irm ... | iex` usage does not kill the caller shell).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The WSL cleanup ran `pkill -9 -f unsloth_studio` then `pkill -9 -f llama-server`,
but the `bash -lc <cmd>` shell's own argv contains those literal patterns, so the
first pkill SIGKILLed the shell before the llama-server pkill (and trailing `true`)
ever ran -- leaving a running llama-server (dynamic port, not covered by
`fuser -k 8888`) alive after uninstall. Use the [x]-regex self-exclusion trick
('[u]nsloth_studio' / '[l]lama-server') so the shell's argv no longer contains the
matched substring; real target processes still match. Verified in WSL: shell
survives, both dummy processes are killed.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
install.ps1: fetch repo-versioned WSL-fallback assets (provision_llama_cuda.sh,
unsloth.ico) from a configurable git ref via new UNSLOTH_INSTALL_REF env var
(defaults to main, so existing users are byte-for-byte unaffected). Lets the
ARM64+NVIDIA WSL-fallback GPU path be exercised end-to-end on a branch before it
merges (provision_llama_cuda.sh does not exist on main until then).
uninstall.ps1: the WSL cleanup rm -rf'd /root/.unsloth but left the
~/.local/bin/unsloth launcher symlink dangling, so `unsloth` still resolved on
PATH after an uninstall. Also remove /root/.local/bin/unsloth and
/home/*/.local/bin/unsloth.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The WSL-distro cleanup parsed 'wsl --list --quiet', whose UTF-16 output PowerShell
often mis-parses into an EMPTY list, so the WSL install (/root/.unsloth + CUDA
llama build) was silently never removed. Probe a candidate set ('' = default
distro, Ubuntu, Ubuntu-24.04, ...) by 'wsl -d <d> -- true' exit code instead
(encoding-proof; same idiom install.ps1 uses), then run the idempotent cleanup.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- uninstall.ps1: the WSL-distro cleanup ran 'pkill -f "unsloth studio"' before the
rm inside a single bash -lc, but that pattern matches the bash -lc's own argv ->
pkill SIGKILLs the shell before rm runs, so /root/.unsloth survived. Reorder: rm
FIRST (guaranteed), then non-self-matching fuser -k 8888/tcp + pkill best-effort;
also remove the fetched provision script + build log.
- uninstall.sh: also remove ~/.unsloth/llama.cpp (CUDA build from provision on
native-Linux Spark) + the fetched provision_llama_cuda.sh.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- install.ps1: refresh the shell icon cache (ie4uinit -show) right after creating the
Desktop/Start Menu shortcuts, so the (valid) .ico renders immediately instead of showing
a blank icon (Explorer caches per-.lnk icons; programmatically-created links need a poke).
- scripts/uninstall.ps1 + scripts/uninstall.sh: also remove the WSL-fallback artifacts the
native uninstall missed -- the %LOCALAPPDATA%\Unsloth shim/launcher/icon dir, its user-PATH
entry, and the real Studio install inside each WSL distro (rm ~/.unsloth + any CUDA llama
build). Previously the native uninstaller only cleaned the (empty) native venv + .lnk files.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* Move uninstall scripts into scripts/ and fix all references
Relocates `uninstall.sh` and `uninstall.ps1` from the repo root into
the existing `scripts/` directory, alongside the other helper scripts.
Reference fixes:
* `README.md`: Studio uninstall instructions now point at the raw
GitHub URLs under `scripts/`. The previous `unsloth.ai/uninstall.*`
short URLs currently 404 (unlike `unsloth.ai/install.sh`, which
301s to the raw github URL), so the raw URL is the working entry
point until that redirect is configured.
* `scripts/uninstall.sh` header `Usage:` example updated to the new
raw GitHub path.
* `scripts/uninstall.ps1` header `Usage:` example updated to the new
raw GitHub path.
* `.github/workflows/studio-update-smoke.yml`: `paths:` trigger and
round-trip exec/exists checks now use `scripts/uninstall.sh`.
* `.github/workflows/studio-mac-update-smoke.yml`: same.
* `.github/workflows/studio-windows-update-smoke.yml`: `paths:`
trigger and round-trip exec/exists checks now use
`scripts/uninstall.ps1`.
The in-script help hints (e.g. `sh uninstall.sh`, `.\uninstall.ps1`)
are left unchanged because they are user-facing examples shown after
the user already has the file locally, and the basename form works
regardless of which directory the user downloaded the script into.
Follow-up note for unsloth.ai: once this lands, please add the
`unsloth.ai/uninstall.sh` and `unsloth.ai/uninstall.ps1` short-URL
redirects to `raw.githubusercontent.com/unslothai/unsloth/main/scripts/...`
(matching the existing `unsloth.ai/install.sh` redirect pattern).
* Update remaining uninstall script help hints for new scripts/ path
Three user-facing strings inside the uninstall scripts still showed
the old basename form, which became misleading after the move:
* `scripts/uninstall.ps1` header `# Local:` example: now references
`.\scripts\uninstall.ps1` (the actual path from the cloned repo
root).
* `scripts/uninstall.sh` env-var re-run hint: now shows the canonical
curl-pipe form documented in README, since callers who came via
`curl -fsSL ... | sh` never had a local `uninstall.sh` to invoke.
* `scripts/uninstall.ps1` env-var re-run hint: same, switched to the
`irm ... | iex` form documented in README.
Pure string changes, no behavior change.
---------
Co-authored-by: Lee Jackson <130007945+Imagineer99@users.noreply.github.com>