diff --git a/install.ps1 b/install.ps1 index 6811bd3301..410e9af2a0 100644 --- a/install.ps1 +++ b/install.ps1 @@ -2452,8 +2452,10 @@ exit 0 $_jobsLine = if ($env:UNSLOTH_LLAMA_BUILD_JOBS) { "export UNSLOTH_LLAMA_BUILD_JOBS=$($env:UNSLOTH_LLAMA_BUILD_JOBS)`n" } else { "" } # Bridge UNSLOTH_LLAMA_TAG / UNSLOTH_LLAMA_PR pins into WSL, else the deferred build # ignores them. sh-single-quoted since tags/PRs are simple tokens. - $_tagLine = if ($env:UNSLOTH_LLAMA_TAG) { "export UNSLOTH_LLAMA_TAG='$($env:UNSLOTH_LLAMA_TAG)'`n" } else { "" } - $_prLine = if ($env:UNSLOTH_LLAMA_PR) { "export UNSLOTH_LLAMA_PR='$($env:UNSLOTH_LLAMA_PR)'`n" } else { "" } + # Same allow-lists as the other forwarded knobs: a quote in the + # value would break out of the single-quoted export in the runner. + $_tagLine = if ($env:UNSLOTH_LLAMA_TAG -and ($env:UNSLOTH_LLAMA_TAG -match '^[A-Za-z0-9][A-Za-z0-9._/-]*$')) { "export UNSLOTH_LLAMA_TAG='$($env:UNSLOTH_LLAMA_TAG)'`n" } else { "" } + $_prLine = if ($env:UNSLOTH_LLAMA_PR -and ($env:UNSLOTH_LLAMA_PR -match '^\d+$')) { "export UNSLOTH_LLAMA_PR='$($env:UNSLOTH_LLAMA_PR)'`n" } else { "" } $_runner = "#!/usr/bin/env bash`n" + $_pathLine + $_jobsLine + $_tagLine + $_prLine + "exec bash /root/.unsloth/provision_llama_cuda.sh > /root/.unsloth/llama_cuda_build.log 2>&1`n" $_runnerB64 = [Convert]::ToBase64String([Text.Encoding]::UTF8.GetBytes($_runner)) $_fetchCmd = '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; echo ' + $_runnerB64 + ' | base64 -d > /root/.unsloth/run_llama_build.sh; chmod +x /root/.unsloth/run_llama_build.sh; echo PROV_FETCHED; else echo PROV_NOSCRIPT; fi' @@ -2476,8 +2478,12 @@ exit 0 } if ($torchOk) { # Success: the Windows venv is vestigial (everything runs in WSL), so drop the - # rolled-aside previous-venv backup instead of orphaning it. - Complete-StudioVenvRollback + # rolled-aside previous-venv backup instead of orphaning it. EXCEPT for a + # custom UNSLOTH_STUDIO_HOME: the installer told the user above that their + # custom root is not used by the WSL install, so deleting the venv that + # lived there would contradict that disclaimer -- put it back instead + # (the WSL shim does not depend on the Windows venv). + if ($envOverride) { Restore-StudioVenvRollback } else { Complete-StudioVenvRollback } substep "GPU training + GGUF export run inside WSL. (GGUF *inference* additionally needs a CUDA llama.cpp build.)" "Yellow" $global:LASTEXITCODE = 0 return diff --git a/scripts/uninstall.sh b/scripts/uninstall.sh index 3c450c8d93..911d5914cf 100755 --- a/scripts/uninstall.sh +++ b/scripts/uninstall.sh @@ -216,6 +216,20 @@ _remove_path "$HOME/.unsloth/studio" # by deleting it). No-op in env/custom mode (they nest under the custom root) and # when absent. A user-set UNSLOTH_LLAMA_CPP_PATH is intentionally kept. _remove_path "$HOME/.unsloth/llama.cpp" +# Stop a detached CUDA llama.cpp build before deleting its tree: _pkill_studio +# only matches Studio roots, and a live cmake/nvcc under ~/.unsloth/llama.cpp +# would keep burning CPU/thermals, recreate build/ files, and defeat the +# trailing rmdir. TERM first, then KILL after the same grace _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 + 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 +fi # WoA/Spark CUDA-build path artifacts (provision script fetched by setup.sh, # install.ps1's background-build runner + log, and the persisted shortcut-skip # marker). No-ops when absent. diff --git a/studio/backend/core/training/worker.py b/studio/backend/core/training/worker.py index 68b06b57e2..f8d3b0affd 100644 --- a/studio/backend/core/training/worker.py +++ b/studio/backend/core/training/worker.py @@ -2876,7 +2876,16 @@ def run_training_process(*, event_queue: Any, stop_queue: Any, config: dict) -> import torch as _torch_mem if _torch_mem.cuda.is_available(): _props = _torch_mem.cuda.get_device_properties(0) - _marker, _is_spark_uma = _nvidia_classify_spark_unified_memory(_props) + # Same UNSLOTH_FORCE_DGX_SPARK override the detectors honor, so a + # forced Spark with an unlisted name still gets the fraction guard + # and FORCE=0 can disable it on a token-matched device. + _force_spark = os.environ.get("UNSLOTH_FORCE_DGX_SPARK") + if _force_spark == "1": + _marker, _is_spark_uma = "forced", True + elif _force_spark == "0": + _marker, _is_spark_uma = "forced-off", False + else: + _marker, _is_spark_uma = _nvidia_classify_spark_unified_memory(_props) if _is_spark_uma: _mem_fraction = 0.80 _frac_env = os.environ.get("UNSLOTH_SPARK_MEM_FRACTION") diff --git a/studio/scripts/provision_llama_cuda.sh b/studio/scripts/provision_llama_cuda.sh index b9041199c5..f540e99c86 100644 --- a/studio/scripts/provision_llama_cuda.sh +++ b/studio/scripts/provision_llama_cuda.sh @@ -212,10 +212,16 @@ fi # Back up any existing (e.g. CPU-only) llama.cpp: restored on any failure exit, # dropped only once the fresh build yields a server -- never leave NO server. _LLAMA_BAK="" +_FRESH_CLONE=0 _restore_prev() { if [ -n "$_LLAMA_BAK" ] && [ -e "$_LLAMA_BAK" ]; then rm -rf "$LLAMA_DIR" 2>/dev/null mv "$_LLAMA_BAK" "$LLAMA_DIR" 2>/dev/null && log "restored previous llama.cpp install" + elif [ "$_FRESH_CLONE" = "1" ] && [ ! -x "$SERVER" ]; then + # We created this clone and produced no server. Leaving a markerless git + # tree under a custom STUDIO_HOME bricks reruns: setup.sh's ownership + # assert refuses the unmarked dir and aborts the whole install. + rm -rf "$LLAMA_DIR" 2>/dev/null fi } if [ ! -d "$LLAMA_DIR/.git" ]; then @@ -231,6 +237,7 @@ if [ ! -d "$LLAMA_DIR/.git" ]; then 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 + [ "$_clone_ok" -eq 1 ] && _FRESH_CLONE=1 if [ "$_clone_ok" -ne 1 ]; then log "git clone failed" _restore_prev diff --git a/studio/setup.sh b/studio/setup.sh index f5d797f16e..115a76282a 100755 --- a/studio/setup.sh +++ b/studio/setup.sh @@ -2021,6 +2021,7 @@ if [ "$_HOST_SYSTEM" = "Linux" ] \ && command -v nvidia-smi >/dev/null 2>&1 \ && nvidia-smi -L 2>/dev/null | awk '/^GPU[[:space:]]+[0-9]+:/{found=1} END{exit !found}' \ && [ "${_setup_nvidia_usable:-}" = true ] \ + && [ "${_LOCAL_LLAMA_CPP_LINKED:-false}" != true ] \ && ! _have_cuda_llama_server; then # Under WSL this runs ONLY for a DIRECT `install.sh` run: install.ps1 sets # UNSLOTH_WSL_LLAMA_DEFERRED=1 and builds in the background; a direct run has