fix(install): address PR review (Codex + Gemini) — exit codes, over-broad uninstall, Spark allocator, provision robustness

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>
This commit is contained in:
Daniel Han 2026-06-08 07:06:10 -07:00
commit ad77ae6cae
5 changed files with 145 additions and 28 deletions

View file

@ -1504,8 +1504,12 @@ shell.Run cmd, 0, False
# as the real install ("torch>=2.4,<2.11.0"): a bare `torch` probe can match an out-of-range
# wheel on the index, a false positive that skips WSL then fails the real pinned install.
$prevEapProbe = $ErrorActionPreference; $ErrorActionPreference = "Continue"
# --reinstall forces resolution from the index instead of accepting an already-installed
# (e.g. CPU-only) torch in a migrated venv as "satisfied" -- otherwise the probe could pass
# without proving a native win_arm64 CUDA wheel exists, wrongly skipping the WSL path.
$global:LASTEXITCODE = -1
try {
& uv pip install --python $VenvPython --dry-run "torch>=2.4,<2.11.0" --index-url $TorchIndexUrl *> $null
& uv pip install --python $VenvPython --dry-run --reinstall "torch>=2.4,<2.11.0" --index-url $TorchIndexUrl *> $null
$_nativeCudaTorchOk = ($LASTEXITCODE -eq 0)
} catch { $_nativeCudaTorchOk = $false } finally { $ErrorActionPreference = $prevEapProbe }
if ($_nativeCudaTorchOk) { step "gpu" "native CUDA PyTorch now available for win_arm64 -- keeping native install" "Green" }
@ -1514,8 +1518,19 @@ shell.Run cmd, 0, False
step "wsl" "Windows on ARM + NVIDIA, native CUDA unavailable -- routing GPU setup through WSL2"
substep "no win_arm64 CUDA PyTorch/Triton yet; WSL2 delivers full GPU (DGX Spark / RTX Spark path)." "Yellow"
# The bundled desktop app passes --tauri and launches its backend from a Windows venv
# (resolve_backend_binary), not from WSL -- so a WSL-only install would report complete yet
# fail to start. Until the Tauri launcher can drive a WSL backend, send desktop-app users to
# the CLI installer rather than leaving them with a broken-looking app.
if ($TauriMode) {
return (Exit-InstallFailure "Windows-on-ARM + NVIDIA GPU needs the WSL2 GPU install, which the desktop app can't launch yet. Install from PowerShell instead: irm https://unsloth.ai/install.ps1 | iex" 1)
}
$wslReady = $false
if (Get-Command wsl.exe -ErrorAction SilentlyContinue) {
# Reset first: if wsl.exe throws/fails to start, $LASTEXITCODE keeps its prior value
# (a stale 0 from an earlier command would wrongly mark WSL ready).
$global:LASTEXITCODE = -1
try { & wsl.exe --status *> $null; if ($LASTEXITCODE -eq 0) { $wslReady = $true } } catch {}
}
@ -1532,12 +1547,18 @@ shell.Run cmd, 0, False
substep "in an ADMINISTRATOR PowerShell run: wsl --install" "Cyan"
substep "reboot, then re-run: irm https://unsloth.ai/install.ps1 | iex" "Cyan"
}
# WSL2 must be enabled + the machine rebooted before anything can install. Restore any
# rolled-aside previous venv and signal not-complete so -File callers don't treat this
# deferred state as a successful install.
Restore-StudioVenvRollback
$global:LASTEXITCODE = 1
return
}
$distro = if ($env:UNSLOTH_WSL_DISTRO) { $env:UNSLOTH_WSL_DISTRO } else { "Ubuntu-24.04" }
# Detect the distro by exit code (encoding-proof; wsl --list emits UTF-16 that PS mis-parses).
$haveDistro = $false
$global:LASTEXITCODE = -1
try { & wsl.exe -d $distro -- true *> $null; if ($LASTEXITCODE -eq 0) { $haveDistro = $true } } catch {}
if (-not $haveDistro) {
substep "installing WSL distro '$distro' (first time only)..." "Cyan"
@ -1548,15 +1569,22 @@ shell.Run cmd, 0, False
# setup.sh + unsloth patches (otherwise install.sh pulls released PyPI unsloth and the
# branch never runs pre-merge). main is byte-identical to plain unsloth.ai/install.sh.
$_instRef = Get-UnslothInstallRef
# UNSLOTH_WSL_LLAMA_DEFERRED=1 tells the inner setup.sh that install.ps1 will build the CUDA
# llama.cpp in the background after install -- so setup.sh skips its own foreground build.
# (A user who runs install.sh DIRECTLY inside WSL won't set it, so setup.sh provisions CUDA
# 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.
if ($_instRef -eq 'main') {
$wslInstall = 'export DEBIAN_FRONTEND=noninteractive; apt-get update -y >/dev/null 2>&1; apt-get install -y build-essential cmake git curl pciutils >/dev/null 2>&1; curl -fsSL https://unsloth.ai/install.sh | sh'
$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'
} else {
$wslInstall = 'export DEBIAN_FRONTEND=noninteractive; export UNSLOTH_INSTALL_REF=' + $_instRef + '; apt-get update -y >/dev/null 2>&1; apt-get install -y build-essential cmake git curl pciutils >/dev/null 2>&1; curl -fsSL https://raw.githubusercontent.com/unslothai/unsloth/' + $_instRef + '/install.sh | sh'
$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'
}
# 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.
$prevEapWsl = $ErrorActionPreference
$ErrorActionPreference = "Continue"
$global:LASTEXITCODE = -1
try {
& wsl.exe -d $distro --cd /root -u root -- bash -lc $wslInstall
$wslRc = $LASTEXITCODE
@ -1568,6 +1596,8 @@ shell.Run cmd, 0, False
$torchOk = $false
$prevEapChk = $ErrorActionPreference
$ErrorActionPreference = "Continue"
# Reset first so a stale 0 from a prior command can't mark torch OK if this fails to launch.
$global:LASTEXITCODE = -1
try {
& wsl.exe -d $distro --cd /root -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)
@ -1624,9 +1654,13 @@ shell.Run cmd, 0, False
"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
# and $userPath.TrimEnd() would throw, losing the shim. Treat null as empty.
$userPath = [Environment]::GetEnvironmentVariable("Path", "User")
if (-not $userPath) { $userPath = "" }
if (($userPath -split ';') -notcontains $shimDir) {
[Environment]::SetEnvironmentVariable("Path", ($userPath.TrimEnd(';') + ";" + $shimDir), "User")
$newUserPath = if ($userPath.Trim()) { $userPath.TrimEnd(';') + ";" + $shimDir } else { $shimDir }
[Environment]::SetEnvironmentVariable("Path", $newUserPath, "User")
}
$env:Path = $env:Path.TrimEnd(';') + ";" + $shimDir
step "shim" "created native 'unsloth' command -> forwards to WSL '$distro'" "Green"
@ -1742,8 +1776,19 @@ shell.Run cmd, 0, False
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 "GPU training + GGUF export run inside WSL. (GGUF *inference* additionally needs a CUDA llama.cpp build.)" "Yellow"
if ($torchOk) { $global:LASTEXITCODE = 0 }
if ($torchOk) {
# WSL GPU install succeeded. On this path the Windows venv is vestigial (everything
# runs in WSL), so drop the rolled-aside previous-venv backup instead of orphaning it.
Complete-StudioVenvRollback
substep "GPU training + GGUF export run inside WSL. (GGUF *inference* additionally needs a CUDA llama.cpp build.)" "Yellow"
$global:LASTEXITCODE = 0
return
}
# WSL GPU install failed (torch.cuda unavailable). Restore any rolled-aside previous venv so
# a reinstall-over-existing isn't left worse off, and report non-zero so -File callers don't
# treat a broken install as success.
Restore-StudioVenvRollback
$global:LASTEXITCODE = 1
return
}

View file

@ -374,11 +374,12 @@ function Uninstall-UnslothStudio {
# `wsl --list` emits UTF-16 PowerShell mis-parses (empty list -> cleanup skipped), so probe a
# candidate set by exit code instead ('' = default distro), which is encoding-proof.
# rm runs FIRST (guaranteed) since the kills could SIGKILL this shell. Also rm the dangling
# ~/.local/bin/unsloth symlink (its target under ~/.unsloth is gone but the link still resolves
# on PATH). pkill patterns use the [x]-regex self-exclusion trick: '[u]nsloth_studio' keeps the
# shell's own argv from matching (no literal "unsloth_studio" substring) while real processes
# still match. Same for '[l]lama-server' (a dynamic port not covered by fuser -k 8888).
$_clean = 'rm -rf /root/.unsloth /home/*/.unsloth /root/llama-cuda /root/provision_llama_cuda.sh /root/llama_cuda_build.log 2>/dev/null; rm -f /root/.local/bin/unsloth /home/*/.local/bin/unsloth 2>/dev/null; fuser -k 8888/tcp 2>/dev/null; pkill -9 -f ''[u]nsloth_studio'' 2>/dev/null; pkill -9 -f ''[l]lama-server'' 2>/dev/null; true'
# /root/.local/bin/unsloth symlink (its target under /root/.unsloth is gone but the link still
# resolves on PATH). Scope STRICTLY to /root: the WoA fallback installs there (wsl -u root), so
# touching /home/*/.unsloth would erase an unrelated WSL user's own Unsloth/cache that this
# installer never created. pkill patterns use the [x]-regex self-exclusion trick: '[u]nsloth_studio'
# keeps the shell's own argv from matching while real processes still match. Same for '[l]lama-server'.
$_clean = 'rm -rf /root/.unsloth /root/llama-cuda /root/provision_llama_cuda.sh /root/llama_cuda_build.log 2>/dev/null; rm -f /root/.local/bin/unsloth 2>/dev/null; fuser -k 8888/tcp 2>/dev/null; pkill -9 -f ''[u]nsloth_studio'' 2>/dev/null; pkill -9 -f ''[l]lama-server'' 2>/dev/null; true'
$_cands = @('', 'Ubuntu', 'Ubuntu-24.04', 'Ubuntu-22.04', 'Debian')
if ($env:UNSLOTH_WSL_DISTRO) { $_cands = @($env:UNSLOTH_WSL_DISTRO) + $_cands }
$_done = @{}

View file

@ -42,11 +42,16 @@ 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.
# 2. Base toolchain (must succeed) THEN gcc-14 (best-effort, separate transaction).
# gcc-14 is preferred because nvcc rejects gcc-15, but it isn't in the default apt
# sources on Ubuntu 22.04 / Debian 12 -- installing it in the SAME transaction as
# cmake/git/curl would make apt abort the whole transaction there, leaving the box
# 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
$SUDO apt-get install -y --no-install-recommends \
build-essential cmake git curl ca-certificates gcc-14 g++-14 >/dev/null 2>&1 || true
build-essential cmake git curl ca-certificates >/dev/null 2>&1 || true
$SUDO apt-get install -y --no-install-recommends gcc-14 g++-14 >/dev/null 2>&1 || true
fi
# 3. Locate nvcc; install the CUDA toolkit if missing.
@ -102,12 +107,34 @@ export CC="$HCC" CXX="$HCXX" CUDAHOSTCXX="$HCXX"
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.
# 6. Clone + build into ~/.unsloth/llama.cpp. Honor a pinned llama.cpp ref
# (UNSLOTH_LLAMA_TAG, the same var setup.sh uses) so a provisioner-built tree matches
# the user's request instead of always tracking ggml-org main.
mkdir -p "$(dirname "$LLAMA_DIR")"
_LLAMA_REF="${UNSLOTH_LLAMA_TAG:-}"
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; }
# 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
mv "$LLAMA_DIR" "$_LLAMA_BAK" 2>/dev/null || { rm -rf "$LLAMA_DIR" 2>/dev/null; _LLAMA_BAK=""; }
fi
_clone_ok=0
if [ -n "$_LLAMA_REF" ]; then
git clone --depth 1 --branch "$_LLAMA_REF" https://github.com/ggml-org/llama.cpp "$LLAMA_DIR" >/dev/null 2>&1 && _clone_ok=1
fi
if [ "$_clone_ok" -ne 1 ]; then
git clone --depth 1 https://github.com/ggml-org/llama.cpp "$LLAMA_DIR" >/dev/null 2>&1 && _clone_ok=1
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
exit 0
fi
[ -n "$_LLAMA_BAK" ] && rm -rf "$_LLAMA_BAK" 2>/dev/null
fi
cd "$LLAMA_DIR" || exit 0

View file

@ -1203,7 +1203,8 @@ else
if [ -n "$_GLIBC_VER" ]; then
_GLIBC_MAJ="${_GLIBC_VER%%.*}"; _GLIBC_MIN="${_GLIBC_VER#*.}"; _GLIBC_MIN="${_GLIBC_MIN%%.*}"
_CU_MAJ="${_NVCC_VER%%.*}"; _CU_MIN="${_NVCC_VER#*.}"; _CU_MIN="${_CU_MIN%%.*}"
if [ "${_GLIBC_MAJ:-0}" -eq 2 ] 2>/dev/null && [ "${_GLIBC_MIN:-0}" -ge 41 ] 2>/dev/null \
if { [ "${_GLIBC_MAJ:-0}" -gt 2 ] 2>/dev/null \
|| { [ "${_GLIBC_MAJ:-0}" -eq 2 ] 2>/dev/null && [ "${_GLIBC_MIN:-0}" -ge 41 ] 2>/dev/null; }; } \
&& { [ "${_CU_MAJ:-0}" -lt 13 ] 2>/dev/null \
|| { [ "${_CU_MAJ:-0}" -eq 13 ] 2>/dev/null && [ "${_CU_MIN:-0}" -lt 3 ] 2>/dev/null; }; }; then
substep "CUDA toolkit ${_NVCC_VER} is incompatible with glibc ${_GLIBC_VER} (rsqrt/rsqrtf header clash)." "$C_ERR"
@ -1444,14 +1445,17 @@ _have_cuda_llama_server() {
}
if [ "$_HOST_SYSTEM" = "Linux" ] \
&& { [ "$_HOST_MACHINE" = "aarch64" ] || [ "$_HOST_MACHINE" = "arm64" ]; } \
&& ! grep -qi microsoft /proc/version 2>/dev/null \
&& { ! grep -qi microsoft /proc/version 2>/dev/null || [ "${UNSLOTH_WSL_LLAMA_DEFERRED:-0}" != "1" ]; } \
&& [ "${UNSLOTH_NO_LLAMA_CUDA:-0}" != "1" ] \
&& command -v nvidia-smi >/dev/null 2>&1 \
&& nvidia-smi -L 2>/dev/null | awk '/^GPU[[:space:]]+[0-9]+:/{found=1} END{exit !found}' \
&& ! _have_cuda_llama_server; then
# WSL2 is excluded above: there install.ps1 runs this in the background after
# setup, so doing it here would duplicate the work in the foreground. Native
# Linux (DGX Spark / GB10) only.
# Native Linux (DGX Spark / GB10) runs this. Under WSL it runs ONLY for a DIRECT
# `install.sh` invocation: when install.ps1 drives the WSL install it exports
# UNSLOTH_WSL_LLAMA_DEFERRED=1 and builds the CUDA llama.cpp in the background after
# setup, so this foreground build is skipped to avoid duplicating it. A user who runs
# install.sh themselves inside WSL has no background builder, so we provision here
# rather than leave them with no GGUF server.
# Resolve provision_llama_cuda.sh: copy beside setup.sh, then local-dev repo,
# else fetch from GitHub so `curl | sh` works on an older wheel without it.
_PROV_SH=""
@ -1475,6 +1479,12 @@ if [ "$_HOST_SYSTEM" = "Linux" ] \
if _have_cuda_llama_server; then
step "llama.cpp" "CUDA llama-server ready (aarch64 + NVIDIA)"
_LLAMA_CPP_DEGRADED=false
# The provisioner just created $LLAMA_CPP_DIR. In custom-STUDIO_HOME mode the next
# setup/update runs _assert_studio_owned_or_absent on it, so claim ownership now or
# that assert would abort on a directory this installer made.
if [ "$_STUDIO_HOME_IS_CUSTOM" = true ]; then
: > "$LLAMA_CPP_DIR/$_STUDIO_OWNED_MARKER" 2>/dev/null || true
fi
elif [ -f "$LLAMA_SERVER_BIN" ]; then
substep "CUDA build unavailable; keeping existing (CPU) llama-server" "$C_WARN"
else

View file

@ -936,6 +936,38 @@ def is_dgx_spark():
return False
@functools.lru_cache(maxsize = None)
def _is_dgx_spark_no_cuda_init():
"""Spark detection that never initializes a CUDA context.
`is_dgx_spark()` calls `torch.cuda.get_device_name()`, which lazily initializes CUDA
(and the caching allocator). Settings consumed at allocator-init time --
`PYTORCH_CUDA_ALLOC_CONF` (expandable_segments) -- must be decided BEFORE that, so this
variant reads the GPU name from `nvidia-smi` (a separate process) instead of torch.
Honors the same UNSLOTH_FORCE_DGX_SPARK override. Falls back to False on any error.
"""
_force = os.environ.get("UNSLOTH_FORCE_DGX_SPARK")
if _force == "1":
return True
if _force == "0":
return False
try:
import platform
if platform.machine().lower() not in ("aarch64", "arm64"):
return False
import subprocess
out = subprocess.run(
["nvidia-smi", "--query-gpu=name", "--format=csv,noheader"],
capture_output = True, text = True, timeout = 5,
)
names = (out.stdout or "").upper()
return any(token in names for token in _DGX_SPARK_DEVICE_TOKENS)
except Exception:
return False
def patch_dgx_spark_caching_allocator_warmup():
"""No-op `transformers.modeling_utils.caching_allocator_warmup` on Spark UMA.
@ -975,13 +1007,15 @@ def patch_dgx_spark_memory_config():
fragmentation OOMs; headroom for larger models / longer sequences). Pure memory
management: it never changes any computed value, so accuracy is unaffected.
Strictly no-op off-Spark (gated by `is_dgx_spark()`). Respects an existing
PYTORCH_CUDA_ALLOC_CONF (only appends `expandable_segments` when absent, never
overrides a user's setting) and an explicit opt-out
(UNSLOTH_NO_EXPANDABLE_SEGMENTS=1). Must run before the first CUDA allocation;
`import unsloth` precedes model load, so it is set in time for normal use.
Strictly no-op off-Spark. Respects an existing PYTORCH_CUDA_ALLOC_CONF (only appends
`expandable_segments` when absent, never overrides a user's setting) and an explicit
opt-out (UNSLOTH_NO_EXPANDABLE_SEGMENTS=1). Must run before the first CUDA allocation,
so it gates on the CUDA-free `_is_dgx_spark_no_cuda_init()` -- the regular
`is_dgx_spark()` calls `torch.cuda.get_device_name()`, which would initialize CUDA (and
the allocator) before this env var could take effect. `import unsloth` precedes model
load, so it is set in time for normal use.
"""
if not is_dgx_spark():
if not _is_dgx_spark_no_cuda_init():
return
if os.environ.get("UNSLOTH_NO_EXPANDABLE_SEGMENTS") == "1":
return