From fc2d577d0da202cf4a9a490cc2d6bdb00037ccf7 Mon Sep 17 00:00:00 2001 From: Daniel Han Date: Wed, 18 Mar 2026 07:23:41 +0000 Subject: [PATCH] Fix studio setup: venv isolation, centralized .venv_t5, uv targeting - All platforms (including Colab) now create ~/.unsloth/studio/.venv with --without-pip fallback for broken ensurepip environments - Add --python sys.executable to uv pip install in install_python_stack.py so uv targets the correct venv instead of system Python - Centralize .venv_t5 bootstrap in transformers_version.py with proper validation (checks required packages exist, not just non-empty dir) - Replace ~150 lines of duplicated install code across 3 worker files with calls to the shared _ensure_venv_t5_exists() helper - Use uv-if-present with pip fallback; do not install uv at runtime - Add site.addsitedir() shim in colab.py so notebook cells can import studio packages from the venv without system-Python double-install - Update .venv_t5 packages: huggingface_hub 1.3.0->1.7.1, add hf_xet - Bump transformers pin 4.57.1->4.57.6 in requirements + constraints - Add Fast-Install helper to setup.ps1 with uv+pip fallback - Keep Colab-specific completion banner in setup.sh --- studio/backend/colab.py | 18 ++++ studio/backend/core/export/worker.py | 60 +++-------- studio/backend/core/inference/worker.py | 60 +++-------- studio/backend/core/training/worker.py | 60 +++-------- .../backend/requirements/extras-no-deps.txt | 2 +- .../requirements/single-env/constraints.txt | 2 +- studio/backend/utils/transformers_version.py | 85 +++++++++++---- studio/install_python_stack.py | 12 ++- studio/setup.ps1 | 58 +++++++--- studio/setup.sh | 102 +++++++++++------- 10 files changed, 237 insertions(+), 222 deletions(-) diff --git a/studio/backend/colab.py b/studio/backend/colab.py index 7162b6d4c2..5776715d70 100644 --- a/studio/backend/colab.py +++ b/studio/backend/colab.py @@ -7,8 +7,26 @@ Uses Colab's built-in proxy - no external tunneling needed! """ from pathlib import Path +import site import sys + +def _bootstrap_studio_venv() -> None: + """Expose the Studio venv's site-packages to the current interpreter. + + On Colab, notebook cells run outside the venv subshell. Instead of + installing the full stack into system Python, we add the venv's + site-packages so that packages like structlog, fastapi, etc. are + importable from notebook cells. + """ + venv_lib = Path.home() / ".unsloth" / "studio" / ".venv" / "lib" + for sp in venv_lib.glob("python*/site-packages"): + if str(sp) not in sys.path: + site.addsitedir(str(sp)) + + +_bootstrap_studio_venv() + # Add backend to path early so local modules like loggers can be imported backend_path = str(Path(__file__).parent) if backend_path not in sys.path: diff --git a/studio/backend/core/export/worker.py b/studio/backend/core/export/worker.py index 4f74f662ee..6af6ff1193 100644 --- a/studio/backend/core/export/worker.py +++ b/studio/backend/core/export/worker.py @@ -40,59 +40,25 @@ def _activate_transformers_version(model_name: str) -> None: if backend_path not in sys.path: sys.path.insert(0, backend_path) - from utils.transformers_version import needs_transformers_5, _resolve_base_model + from utils.transformers_version import ( + needs_transformers_5, + _resolve_base_model, + _ensure_venv_t5_exists, + _VENV_T5_DIR, + ) resolved = _resolve_base_model(model_name) if needs_transformers_5(resolved): - venv_t5 = os.path.join( - os.path.expanduser("~"), ".unsloth", "studio", ".venv_t5" - ) - if os.path.isdir(venv_t5): - sys.path.insert(0, venv_t5) - logger.info("Activated transformers 5.x from %s", venv_t5) - else: - # Fallback: pip install at runtime (slower, ~10-15s) - logger.warning(".venv_t5 not found at %s — installing at runtime", venv_t5) - import subprocess as sp - - os.makedirs(venv_t5, exist_ok = True) - r1 = sp.run( - [ - sys.executable, - "-m", - "pip", - "install", - "--target", - venv_t5, - "--no-deps", - "transformers==5.3.0", - ], - stdout = sp.PIPE, - stderr = sp.STDOUT, + if not _ensure_venv_t5_exists(): + raise RuntimeError( + f"Cannot activate transformers 5.x: .venv_t5 missing at {_VENV_T5_DIR}" ) - r2 = sp.run( - [ - sys.executable, - "-m", - "pip", - "install", - "--target", - venv_t5, - "--no-deps", - "huggingface_hub==1.3.0", - ], - stdout = sp.PIPE, - stderr = sp.STDOUT, - ) - if r1.returncode != 0 or r2.returncode != 0: - raise RuntimeError( - f"Failed to install transformers 5.x into {venv_t5}. " - f"pip returncode: transformers={r1.returncode}, huggingface_hub={r2.returncode}" - ) - sys.path.insert(0, venv_t5) + if _VENV_T5_DIR not in sys.path: + sys.path.insert(0, _VENV_T5_DIR) + logger.info("Activated transformers 5.x from %s", _VENV_T5_DIR) # Propagate to child subprocesses (e.g. GGUF converter) _pp = os.environ.get("PYTHONPATH", "") - os.environ["PYTHONPATH"] = venv_t5 + (os.pathsep + _pp if _pp else "") + os.environ["PYTHONPATH"] = _VENV_T5_DIR + (os.pathsep + _pp if _pp else "") else: logger.info("Using default transformers (4.57.x) for %s", model_name) diff --git a/studio/backend/core/inference/worker.py b/studio/backend/core/inference/worker.py index 0693908178..2eb46f3217 100644 --- a/studio/backend/core/inference/worker.py +++ b/studio/backend/core/inference/worker.py @@ -42,59 +42,25 @@ def _activate_transformers_version(model_name: str) -> None: if backend_path not in sys.path: sys.path.insert(0, backend_path) - from utils.transformers_version import needs_transformers_5, _resolve_base_model + from utils.transformers_version import ( + needs_transformers_5, + _resolve_base_model, + _ensure_venv_t5_exists, + _VENV_T5_DIR, + ) resolved = _resolve_base_model(model_name) if needs_transformers_5(resolved): - venv_t5 = os.path.join( - os.path.expanduser("~"), ".unsloth", "studio", ".venv_t5" - ) - if os.path.isdir(venv_t5): - sys.path.insert(0, venv_t5) - logger.info("Activated transformers 5.x from %s", venv_t5) - else: - # Fallback: pip install at runtime (slower, ~10-15s) - logger.warning(".venv_t5 not found at %s — installing at runtime", venv_t5) - import subprocess as sp - - os.makedirs(venv_t5, exist_ok = True) - r1 = sp.run( - [ - sys.executable, - "-m", - "pip", - "install", - "--target", - venv_t5, - "--no-deps", - "transformers==5.3.0", - ], - stdout = sp.PIPE, - stderr = sp.STDOUT, + if not _ensure_venv_t5_exists(): + raise RuntimeError( + f"Cannot activate transformers 5.x: .venv_t5 missing at {_VENV_T5_DIR}" ) - r2 = sp.run( - [ - sys.executable, - "-m", - "pip", - "install", - "--target", - venv_t5, - "--no-deps", - "huggingface_hub==1.3.0", - ], - stdout = sp.PIPE, - stderr = sp.STDOUT, - ) - if r1.returncode != 0 or r2.returncode != 0: - raise RuntimeError( - f"Failed to install transformers 5.x into {venv_t5}. " - f"pip returncode: transformers={r1.returncode}, huggingface_hub={r2.returncode}" - ) - sys.path.insert(0, venv_t5) + if _VENV_T5_DIR not in sys.path: + sys.path.insert(0, _VENV_T5_DIR) + logger.info("Activated transformers 5.x from %s", _VENV_T5_DIR) # Propagate to child subprocesses (e.g. GGUF converter) _pp = os.environ.get("PYTHONPATH", "") - os.environ["PYTHONPATH"] = venv_t5 + (os.pathsep + _pp if _pp else "") + os.environ["PYTHONPATH"] = _VENV_T5_DIR + (os.pathsep + _pp if _pp else "") else: logger.info("Using default transformers (4.57.x) for %s", model_name) diff --git a/studio/backend/core/training/worker.py b/studio/backend/core/training/worker.py index 284fcf228d..ccd805b7ac 100644 --- a/studio/backend/core/training/worker.py +++ b/studio/backend/core/training/worker.py @@ -36,59 +36,25 @@ def _activate_transformers_version(model_name: str) -> None: if backend_path not in sys.path: sys.path.insert(0, backend_path) - from utils.transformers_version import needs_transformers_5, _resolve_base_model + from utils.transformers_version import ( + needs_transformers_5, + _resolve_base_model, + _ensure_venv_t5_exists, + _VENV_T5_DIR, + ) resolved = _resolve_base_model(model_name) if needs_transformers_5(resolved): - venv_t5 = os.path.join( - os.path.expanduser("~"), ".unsloth", "studio", ".venv_t5" - ) - if os.path.isdir(venv_t5): - sys.path.insert(0, venv_t5) - logger.info("Activated transformers 5.x from %s", venv_t5) - else: - # Fallback: pip install at runtime (slower, ~10-15s) - logger.warning(".venv_t5 not found at %s — installing at runtime", venv_t5) - import subprocess as sp - - os.makedirs(venv_t5, exist_ok = True) - r1 = sp.run( - [ - sys.executable, - "-m", - "pip", - "install", - "--target", - venv_t5, - "--no-deps", - "transformers==5.3.0", - ], - stdout = sp.PIPE, - stderr = sp.STDOUT, + if not _ensure_venv_t5_exists(): + raise RuntimeError( + f"Cannot activate transformers 5.x: .venv_t5 missing at {_VENV_T5_DIR}" ) - r2 = sp.run( - [ - sys.executable, - "-m", - "pip", - "install", - "--target", - venv_t5, - "--no-deps", - "huggingface_hub==1.3.0", - ], - stdout = sp.PIPE, - stderr = sp.STDOUT, - ) - if r1.returncode != 0 or r2.returncode != 0: - raise RuntimeError( - f"Failed to install transformers 5.x into {venv_t5}. " - f"pip returncode: transformers={r1.returncode}, huggingface_hub={r2.returncode}" - ) - sys.path.insert(0, venv_t5) + if _VENV_T5_DIR not in sys.path: + sys.path.insert(0, _VENV_T5_DIR) + logger.info("Activated transformers 5.x from %s", _VENV_T5_DIR) # Propagate to child subprocesses (e.g. GGUF converter) _pp = os.environ.get("PYTHONPATH", "") - os.environ["PYTHONPATH"] = venv_t5 + (os.pathsep + _pp if _pp else "") + os.environ["PYTHONPATH"] = _VENV_T5_DIR + (os.pathsep + _pp if _pp else "") else: logger.info("Using default transformers (4.57.x) for %s", model_name) diff --git a/studio/backend/requirements/extras-no-deps.txt b/studio/backend/requirements/extras-no-deps.txt index 3bdce81cc1..4b5aa86b5f 100644 --- a/studio/backend/requirements/extras-no-deps.txt +++ b/studio/backend/requirements/extras-no-deps.txt @@ -11,4 +11,4 @@ git+https://github.com/meta-pytorch/OpenEnv.git # executorch>=1.0.1 # 41.5 MB - no imports in unsloth/zoo/studio torch-c-dlpack-ext sentence_transformers==5.2.0 -transformers==4.57.1 +transformers==4.57.6 diff --git a/studio/backend/requirements/single-env/constraints.txt b/studio/backend/requirements/single-env/constraints.txt index 1789bbf713..156f78567e 100644 --- a/studio/backend/requirements/single-env/constraints.txt +++ b/studio/backend/requirements/single-env/constraints.txt @@ -1,6 +1,6 @@ # Single-env pins for unsloth + studio + data-designer # Keep compatible with unsloth transformers bounds. -transformers==4.57.1 +transformers==4.57.6 trl==0.23.1 huggingface-hub==0.36.2 diff --git a/studio/backend/utils/transformers_version.py b/studio/backend/utils/transformers_version.py index 5666b3be35..500c154420 100644 --- a/studio/backend/utils/transformers_version.py +++ b/studio/backend/utils/transformers_version.py @@ -26,6 +26,7 @@ import json import structlog from loggers import get_logger import os +import shutil import subprocess import sys from pathlib import Path @@ -58,7 +59,7 @@ _tokenizer_class_cache: dict[str, bool] = {} # Versions TRANSFORMERS_5_VERSION = "5.3.0" -TRANSFORMERS_DEFAULT_VERSION = "4.57.1" +TRANSFORMERS_DEFAULT_VERSION = "4.57.6" # Pre-installed directory for transformers 5.x — created by setup.sh / setup.ps1 _VENV_T5_DIR = str(Path.home() / ".unsloth" / "studio" / ".venv_t5") @@ -216,29 +217,73 @@ def _purge_modules() -> int: return len(to_remove) +_VENV_T5_PACKAGES = ( + f"transformers=={TRANSFORMERS_5_VERSION}", + "huggingface_hub==1.7.1", + "hf_xet==1.4.2", +) + + +def _venv_t5_is_valid() -> bool: + """Return True if .venv_t5/ has all required packages installed.""" + if not os.path.isdir(_VENV_T5_DIR) or not os.listdir(_VENV_T5_DIR): + return False + # Check that the key package directories actually exist + for pkg_spec in _VENV_T5_PACKAGES: + pkg_name = pkg_spec.split("==")[0].replace("-", "_") + if not any( + (Path(_VENV_T5_DIR) / d).is_dir() + for d in (pkg_name, pkg_name.replace("_", "-")) + ): + return False + return True + + +def _install_to_venv_t5(pkg: str) -> bool: + """Install a single package into .venv_t5/, preferring uv then pip.""" + # Try uv first (faster) if already on PATH -- do NOT install uv at runtime + if shutil.which("uv"): + result = subprocess.run( + [ + "uv", "pip", "install", + "--python", sys.executable, + "--target", _VENV_T5_DIR, + "--no-deps", pkg, + ], + stdout = subprocess.PIPE, + stderr = subprocess.STDOUT, + text = True, + ) + if result.returncode == 0: + return True + logger.warning("uv install of %s failed, falling back to pip", pkg) + + # Fallback to pip + result = subprocess.run( + [ + sys.executable, "-m", "pip", "install", + "--target", _VENV_T5_DIR, + "--no-deps", pkg, + ], + stdout = subprocess.PIPE, + stderr = subprocess.STDOUT, + text = True, + ) + if result.returncode != 0: + logger.error("install failed:\n%s", result.stdout) + return False + return True + + def _ensure_venv_t5_exists() -> bool: - """Ensure .venv_t5/ exists. Install at runtime if missing.""" - if os.path.isdir(_VENV_T5_DIR) and os.listdir(_VENV_T5_DIR): + """Ensure .venv_t5/ exists with all required packages. Install if missing.""" + if _venv_t5_is_valid(): return True - logger.warning(".venv_t5 not found at %s — installing at runtime", _VENV_T5_DIR) + logger.warning(".venv_t5 not found or incomplete at %s -- installing at runtime", _VENV_T5_DIR) os.makedirs(_VENV_T5_DIR, exist_ok = True) - for pkg in (f"transformers=={TRANSFORMERS_5_VERSION}", "huggingface_hub==1.3.0"): - cmd = [ - sys.executable, - "-m", - "pip", - "install", - "--target", - _VENV_T5_DIR, - "--no-deps", - pkg, - ] - result = subprocess.run( - cmd, stdout = subprocess.PIPE, stderr = subprocess.STDOUT, text = True - ) - if result.returncode != 0: - logger.error("pip install failed:\n%s", result.stdout) + for pkg in _VENV_T5_PACKAGES: + if not _install_to_venv_t5(pkg): return False logger.info("Installed transformers 5.x to %s", _VENV_T5_DIR) return True diff --git a/studio/install_python_stack.py b/studio/install_python_stack.py index 20011a298a..a141c64425 100644 --- a/studio/install_python_stack.py +++ b/studio/install_python_stack.py @@ -140,15 +140,15 @@ def _bootstrap_uv() -> bool: global UV_NEEDS_SYSTEM if not shutil.which("uv"): return False - # Probe: try a dry-run install without --system. - # If uv can't find a venv it exits with code 2. + # Probe: try a dry-run install targeting the current Python explicitly. + # Without --python, uv can ignore the activated venv on some platforms. probe = subprocess.run( - ["uv", "pip", "install", "--dry-run", "pip"], + ["uv", "pip", "install", "--dry-run", "--python", sys.executable, "pip"], stdout = subprocess.PIPE, stderr = subprocess.STDOUT, ) if probe.returncode != 0: - # Retry with --system to confirm it works + # Retry with --system (some envs need it when uv can't find a venv) probe_sys = subprocess.run( ["uv", "pip", "install", "--dry-run", "--system", "pip"], stdout = subprocess.PIPE, @@ -204,6 +204,10 @@ def _build_uv_cmd(args: tuple[str, ...]) -> list[str]: cmd = ["uv", "pip", "install"] if UV_NEEDS_SYSTEM: cmd.append("--system") + # Always pass --python so uv targets the correct environment. + # Without this, uv can ignore an activated venv and install into + # the system Python (observed on Colab and similar environments). + cmd.extend(["--python", sys.executable]) cmd.extend(_translate_pip_args_for_uv(args)) cmd.append("--torch-backend=auto") return cmd diff --git a/studio/setup.ps1 b/studio/setup.ps1 index f797313964..06363cfd79 100644 --- a/studio/setup.ps1 +++ b/studio/setup.ps1 @@ -872,7 +872,35 @@ $ErrorActionPreference = "Continue" $ActivateScript = Join-Path $VenvDir "Scripts\Activate.ps1" . $ActivateScript -pip install --upgrade pip 2>&1 | Out-Null + +# Try to use uv (much faster than pip), fall back to pip if unavailable +$UseUv = $false +if (Get-Command uv -ErrorAction SilentlyContinue) { + $UseUv = $true +} else { + Write-Host " Installing uv package manager..." -ForegroundColor Cyan + try { + powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex" 2>&1 | Out-Null + Refresh-Environment + # Re-activate venv since Refresh-Environment rebuilds PATH from + # registry and drops the venv's Scripts directory + . $ActivateScript + if (Get-Command uv -ErrorAction SilentlyContinue) { $UseUv = $true } + } catch { } +} + +# Helper: install a package, preferring uv with pip fallback +function Fast-Install { + param([Parameter(ValueFromRemainingArguments=$true)]$Args_) + if ($UseUv) { + $VenvPy = (Get-Command python).Source + $result = & uv pip install --python $VenvPy @Args_ 2>&1 + if ($LASTEXITCODE -eq 0) { return } + } + & python -m pip install @Args_ 2>&1 +} + +Fast-Install --upgrade pip | Out-Null # if (-not $IsPipInstall) { # # Running from repo: copy requirements and do editable install @@ -909,14 +937,14 @@ Write-Host "[OK] TORCHINDUCTOR_CACHE_DIR set to $TorchCacheDir (avoids MAX_PATH $CuTag = Get-PytorchCudaTag Write-Host " Installing PyTorch with CUDA support ($CuTag)..." -ForegroundColor Cyan -pip install torch torchvision torchaudio --index-url "https://download.pytorch.org/whl/$CuTag" 2>&1 | Out-Null +Fast-Install torch torchvision torchaudio --index-url "https://download.pytorch.org/whl/$CuTag" | Out-Null -# Install Triton for Windows (enables torch.compile — without it training can hang) +# Install Triton for Windows (enables torch.compile -- without it training can hang) Write-Host " Installing Triton for Windows..." -ForegroundColor Cyan -pip install "triton-windows<3.7" 2>&1 | Out-Null +Fast-Install "triton-windows<3.7" | Out-Null Write-Host "[OK] Triton for Windows installed (enables torch.compile)" -ForegroundColor Green -# Ordered heavy dependency installation — shared cross-platform script +# Ordered heavy dependency installation -- shared cross-platform script Write-Host " Running ordered dependency installation..." -ForegroundColor Cyan python "$PSScriptRoot\install_python_stack.py" # Restore ErrorActionPreference after pip/python work @@ -925,7 +953,7 @@ $ErrorActionPreference = $prevEAP # ── Pre-install transformers 5.x into .venv_t5/ ── # Models like GLM-4.7-Flash need transformers>=5.3.0. Instead of pip-installing # at runtime (slow, ~10-15s), we pre-install into a separate directory. -# The training subprocess just prepends .venv_t5/ to sys.path — instant switch. +# The training subprocess just prepends .venv_t5/ to sys.path -- instant switch. Write-Host "" Write-Host " Pre-installing transformers 5.x for newer model support..." -ForegroundColor Cyan $VenvT5Dir = Join-Path $env:USERPROFILE ".unsloth\studio\.venv_t5" @@ -933,17 +961,13 @@ if (Test-Path $VenvT5Dir) { Remove-Item -Recurse -Force $VenvT5Dir } New-Item -ItemType Directory -Path $VenvT5Dir -Force | Out-Null $prevEAP_t5 = $ErrorActionPreference $ErrorActionPreference = "Continue" -pip install --target $VenvT5Dir --no-deps "transformers==5.3.0" 2>&1 | Out-Null -if ($LASTEXITCODE -ne 0) { - Write-Host "[FAIL] Could not install transformers 5.3.0 into .venv_t5/" -ForegroundColor Red - $ErrorActionPreference = $prevEAP_t5 - exit 1 -} -pip install --target $VenvT5Dir --no-deps "huggingface_hub==1.3.0" 2>&1 | Out-Null -if ($LASTEXITCODE -ne 0) { - Write-Host "[FAIL] Could not install huggingface_hub 1.3.0 into .venv_t5/" -ForegroundColor Red - $ErrorActionPreference = $prevEAP_t5 - exit 1 +foreach ($pkg in @("transformers==5.3.0", "huggingface_hub==1.7.1", "hf_xet==1.4.2")) { + Fast-Install --target $VenvT5Dir --no-deps $pkg | Out-Null + if ($LASTEXITCODE -ne 0) { + Write-Host "[FAIL] Could not install $pkg into .venv_t5/" -ForegroundColor Red + $ErrorActionPreference = $prevEAP_t5 + exit 1 + } } $ErrorActionPreference = $prevEAP_t5 Write-Host "[OK] Transformers 5.x pre-installed to .venv_t5/" -ForegroundColor Green diff --git a/studio/setup.sh b/studio/setup.sh index c0694df1aa..558d9bdd2c 100755 --- a/studio/setup.sh +++ b/studio/setup.sh @@ -240,50 +240,73 @@ install_python_stack() { python "$SCRIPT_DIR/install_python_stack.py" } -if [ "$IS_COLAB" = true ]; then - # Colab: install packages directly without venv - install_python_stack -else - # Local: create venv under ~/.unsloth/studio/ (shared location, not in repo) - STUDIO_HOME="$HOME/.unsloth/studio" - VENV_DIR="$STUDIO_HOME/.venv" - VENV_T5_DIR="$STUDIO_HOME/.venv_t5" - mkdir -p "$STUDIO_HOME" +# Create venv under ~/.unsloth/studio/ (shared location, not in repo). +# All platforms (including Colab) use the same isolated venv so that +# studio dependencies are never installed into the system Python. +STUDIO_HOME="$HOME/.unsloth/studio" +VENV_DIR="$STUDIO_HOME/.venv" +VENV_T5_DIR="$STUDIO_HOME/.venv_t5" +mkdir -p "$STUDIO_HOME" - # Clean up legacy in-repo venvs if they exist - [ -d "$REPO_ROOT/.venv" ] && rm -rf "$REPO_ROOT/.venv" - [ -d "$REPO_ROOT/.venv_overlay" ] && rm -rf "$REPO_ROOT/.venv_overlay" - [ -d "$REPO_ROOT/.venv_t5" ] && rm -rf "$REPO_ROOT/.venv_t5" +# Clean up legacy in-repo venvs if they exist +[ -d "$REPO_ROOT/.venv" ] && rm -rf "$REPO_ROOT/.venv" +[ -d "$REPO_ROOT/.venv_overlay" ] && rm -rf "$REPO_ROOT/.venv_overlay" +[ -d "$REPO_ROOT/.venv_t5" ] && rm -rf "$REPO_ROOT/.venv_t5" - rm -rf "$VENV_DIR" - rm -rf "$VENV_T5_DIR" - "$BEST_PY" -m venv "$VENV_DIR" +rm -rf "$VENV_DIR" +rm -rf "$VENV_T5_DIR" +# Try creating venv with pip; fall back to --without-pip + bootstrap +# (some environments like Colab have broken ensurepip) +if ! "$BEST_PY" -m venv "$VENV_DIR" 2>/dev/null; then + "$BEST_PY" -m venv --without-pip "$VENV_DIR" source "$VENV_DIR/bin/activate" - cd "$SCRIPT_DIR" - install_python_stack + curl -sS https://bootstrap.pypa.io/get-pip.py | python > /dev/null +else + source "$VENV_DIR/bin/activate" +fi - # ── 6b. Pre-install transformers 5.x into .venv_t5/ ── - # Models like GLM-4.7-Flash need transformers>=5.3.0. Instead of pip-installing - # at runtime (slow, ~10-15s), we pre-install into a separate directory. - # The training subprocess just prepends .venv_t5/ to sys.path — instant switch. - echo "" - echo " Pre-installing transformers 5.x for newer model support..." - mkdir -p "$VENV_T5_DIR" - run_quiet "pip install transformers 5.x" pip install --target "$VENV_T5_DIR" --no-deps "transformers==5.3.0" - run_quiet "pip install huggingface_hub for t5" pip install --target "$VENV_T5_DIR" --no-deps "huggingface_hub==1.3.0" - echo "✅ Transformers 5.x pre-installed to $VENV_T5_DIR/" +# ── Ensure uv is available (much faster than pip) ── +USE_UV=false +if command -v uv &>/dev/null; then + USE_UV=true +elif curl -LsSf https://astral.sh/uv/install.sh | sh > /dev/null 2>&1; then + export PATH="$HOME/.local/bin:$PATH" + command -v uv &>/dev/null && USE_UV=true +fi - # ── 7. WSL: pre-install GGUF build dependencies ── - # On WSL, sudo requires a password and can't be entered during GGUF export - # (runs in a non-interactive subprocess). Install build deps here instead. - if grep -qi microsoft /proc/version 2>/dev/null; then - echo "" - echo "⚠️ WSL detected — installing build dependencies for GGUF export..." - echo " You may be prompted for your password." - sudo apt-get update -y - sudo apt-get install -y build-essential cmake curl git libcurl4-openssl-dev - echo "✅ GGUF build dependencies installed" +# Helper: install a package, preferring uv with pip fallback +fast_install() { + if [ "$USE_UV" = true ]; then + uv pip install --python "$(command -v python)" "$@" && return 0 fi + python -m pip install "$@" +} + +cd "$SCRIPT_DIR" +install_python_stack + +# ── 6b. Pre-install transformers 5.x into .venv_t5/ ── +# Models like GLM-4.7-Flash need transformers>=5.3.0. Instead of pip-installing +# at runtime (slow, ~10-15s), we pre-install into a separate directory. +# The training subprocess just prepends .venv_t5/ to sys.path -- instant switch. +echo "" +echo " Pre-installing transformers 5.x for newer model support..." +mkdir -p "$VENV_T5_DIR" +run_quiet "install transformers 5.x" fast_install --target "$VENV_T5_DIR" --no-deps "transformers==5.3.0" +run_quiet "install huggingface_hub for t5" fast_install --target "$VENV_T5_DIR" --no-deps "huggingface_hub==1.7.1" +run_quiet "install hf_xet for t5" fast_install --target "$VENV_T5_DIR" --no-deps "hf_xet==1.4.2" +echo "✅ Transformers 5.x pre-installed to $VENV_T5_DIR/" + +# ── 7. WSL: pre-install GGUF build dependencies ── +# On WSL, sudo requires a password and can't be entered during GGUF export +# (runs in a non-interactive subprocess). Install build deps here instead. +if grep -qi microsoft /proc/version 2>/dev/null; then + echo "" + echo "⚠️ WSL detected -- installing build dependencies for GGUF export..." + echo " You may be prompted for your password." + sudo apt-get update -y + sudo apt-get install -y build-essential cmake curl git libcurl4-openssl-dev + echo "✅ GGUF build dependencies installed" fi # ── 8. Build llama.cpp binaries for GGUF inference + export ── @@ -422,6 +445,9 @@ if [ "$IS_COLAB" = true ]; then echo "╠══════════════════════════════════════╣" echo "║ Unsloth Studio is ready to start ║" echo "║ in your Colab notebook! ║" + echo "║ ║" + echo "║ from colab import start ║" + echo "║ start() ║" echo "╚══════════════════════════════════════╝" else echo "╔══════════════════════════════════════╗"