From e9ac7853460df1805a97eab34d50976de77fa18d Mon Sep 17 00:00:00 2001 From: Daniel Han Date: Fri, 27 Mar 2026 02:09:21 -0700 Subject: [PATCH] fix: install.sh Mac Intel compatibility + Studio no-torch support (#4624) * fix: install.sh Mac Intel compatibility + Studio no-torch support (#4621) On Intel Macs (x86_64), PyTorch has no wheels for torch >= 2.3, so the installer crashes. Even when torch is absent, Studio crashes on startup because two files have bare top-level torch imports. Studio's GGUF inference (llama.cpp) does not need PyTorch. Training and HF-inference already isolate torch to subprocesses. Only 2 files in the server startup chain had top-level torch imports preventing startup. Changes: - install.sh: detect architecture, default to Python 3.12 on Intel Mac, skip torch install, add Python 3.13.8 guard for arm64, pass UNSLOTH_NO_TORCH env var to setup.sh - data_collators.py: remove unused `import torch` (no torch.* refs) - chat_templates.py: lazy-import IterableDataset into function bodies - install_python_stack.py: add IS_MACOS/NO_TORCH constants, skip torch-dependent packages, skip overrides.txt, skip triton on macOS No existing working flow changes. Linux/WSL and macOS arm64 behavior is identical. * tests: add test suite for Mac Intel compat + no-torch mode Shell tests (test_mac_intel_compat.sh): - version_ge edge cases (9 tests) - Architecture detection for Darwin x86_64/arm64, Linux x86_64/aarch64 - get_torch_index_url returns cpu on simulated Darwin - UNSLOTH_NO_TORCH propagation to both setup.sh branches Python unit tests (test_no_torch_filtering.py): - _filter_requirements with NO_TORCH_SKIP_PACKAGES - NO_TORCH env var parsing (true/1/TRUE/false/0/unset) - IS_MACOS constant check - Overrides skip and triton macOS skip guards Python import tests (test_studio_import_no_torch.py): - data_collators.py loads in isolated no-torch venv - chat_templates.py has no top-level torch imports - Negative control confirms import torch fails without torch * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * tests: add E2E sandbox tests for Mac Intel no-torch mode Replace static/synthetic test stubs with real sandbox tests: - Shell: E2E uv venv creation at Python 3.12, mock uv shim to verify torch install is skipped when MAC_INTEL=true, dynamic env propagation test for UNSLOTH_NO_TORCH in both local and non-local install paths - Python filtering: test real extras.txt and extras-no-deps.txt with NO_TORCH_SKIP_PACKAGES, subprocess mock of install_python_stack() for 5 platform configs (NO_TORCH+macOS, Windows+NO_TORCH, normal Linux, Windows-only, macOS-only), VCS URL and env marker edge cases - Python imports: parametrized Python 3.12+3.13 venv fixture, dataclass instantiation for all 3 collator classes, chat_templates.py exec with stubs, negative controls proving import torch and torchao install fail in no-torch venvs 91 total tests, all passing. * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * fix: address reviewer findings for Intel Mac no-torch mode P1 fixes: - Auto-infer NO_TORCH in install_python_stack.py via platform.machine() so `unsloth studio update` preserves GGUF-only mode without needing the UNSLOTH_NO_TORCH env var (6/10 reviewers) - Add openai-whisper and transformers-cfg to NO_TORCH_SKIP_PACKAGES since both have unconditional torch dependencies (4/10 reviewers) - Skip unsloth-zoo on Intel Mac --local installs (depends on torch) in both migrated and fresh install paths (1/10) - Recreate stale 3.13 venvs as 3.12 on Intel Mac re-runs (1/10) - Detect Apple Silicon under Rosetta via sysctl hw.optional.arm64 and warn user to use native arm64 terminal (1/10) P2 fixes: - Wire new test files into tests/run_all.sh (4/10 reviewers) - Add update-path tests (skip_base=False) for Intel Mac - Add _infer_no_torch tests for platform auto-detection P3 fixes: - Fix macOS progress bar total (triton step skipped but was counted) - Fix temp file leak when Windows + NO_TORCH filters stack All tests pass: 30 shell, 66 Python (96 total). * feat: add --python override flag to install.sh Lets users force a specific Python version, e.g. ./install.sh --python 3.12. Addresses M2 Mac users whose systems resolve to a problematic 3.13.x patch. When --python is set, the Intel Mac stale-venv guard and 3.13.8 auto-downgrade are skipped so the user's choice is respected. * tests: add comprehensive E2E sandbox tests for no-torch mode Add test_e2e_no_torch_sandbox.py with 7 test groups (43 tests total) covering the full no-torch import chain, edge cases, and install logic: - Group 1: BEFORE vs AFTER import chain comparison (proves the bug existed and the fix works by synthetically prepending top-level torch imports) - Group 2: Dataclass instantiation without torch - Group 3: Edge cases with broken/fake torch modules on sys.path - Group 4: Hardware detection fallback to CPU without torch - Group 5: install.sh flag parsing, version resolution, arch detection - Group 6: install_python_stack.py NO_TORCH filtering - Group 7: Live server startup without torch (marked @server, skipped when studio venv is unavailable) All 43 tests pass on both Python 3.12 and 3.13 isolated venvs. * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * feat: add --no-torch flag to install.sh/ps1, fix lazy import bug in dataset formatting - Fix chat_templates.py: narrow torch IterableDataset import into inner try/except ImportError so dataset.map() works without torch installed - Fix format_conversion.py: same lazy import fix for convert_chatml_to_alpaca and convert_alpaca_to_chatml - Add --no-torch flag to install.sh with unified SKIP_TORCH variable (driven by --no-torch flag OR MAC_INTEL auto-detection) - Add --no-torch flag to install.ps1 with $SkipTorch variable - Print CPU hint when no GPU detected and --no-torch not set - Replace MAC_INTEL guards with SKIP_TORCH in torch install sections - Update shell tests (40 pass) and Python tests (90 pass) * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * fix: address reviewer findings for --no-torch installer paths - Fix migrated-env branch in install.sh and install.ps1: check SKIP_TORCH first, then branch on STUDIO_LOCAL_INSTALL. Previously SKIP_TORCH+non-local fell into else and installed unsloth-zoo (which depends on torch), defeating --no-torch mode. - Fix $env:UNSLOTH_NO_TORCH leak in install.ps1: always set to "true" or "false" instead of only setting on the true branch. Prevents stale no-torch state from leaking across runs in the same PS session. - Fix install_python_stack.py update path: add NO_TORCH guard around base.txt install so unsloth studio update does not reinstall unsloth-zoo (which depends on torch) in no-torch mode. * fix: install unsloth + unsloth-zoo with --no-deps in no-torch mode Instead of skipping unsloth-zoo entirely (which breaks unsloth's dependency on it), install both packages with --no-deps so they are present but torch is not pulled in transitively. Applied consistently across all no-torch paths: migrated-env, fresh-local, fresh-non-local in install.sh, install.ps1, and install_python_stack.py. * chore: temporarily remove test files (will be added in a follow-up) * refactor: deduplicate SKIP_TORCH conditional branches in installers Collapse if/else blocks that differ only by --no-deps into a single branch with a conditional flag variable. Applied to migrated-env and fresh-local paths in install.sh, install.ps1, and install_python_stack.py. * fix: apply --no-deps to fresh non-local --no-torch install path The non-local else branch was missing $_no_deps_arg/$noDepsArg, so uv pip install unsloth would resolve torch from PyPI metadata (the published unsloth package still declares torch as a hard dep). Now --no-deps is applied consistently to all SKIP_TORCH code paths. --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- install.ps1 | 40 ++++-- install.sh | 132 ++++++++++++++++-- .../backend/utils/datasets/chat_templates.py | 20 ++- .../backend/utils/datasets/data_collators.py | 1 - .../utils/datasets/format_conversion.py | 18 ++- studio/install_python_stack.py | 88 ++++++++++-- 6 files changed, 259 insertions(+), 40 deletions(-) diff --git a/install.ps1 b/install.ps1 index 3ef251715e..41e8efa5c3 100644 --- a/install.ps1 +++ b/install.ps1 @@ -1,6 +1,7 @@ # Unsloth Studio Installer for Windows PowerShell # Usage: irm https://raw.githubusercontent.com/unslothai/unsloth/main/install.ps1 | iex # Local: Set-ExecutionPolicy -Scope Process -ExecutionPolicy Bypass; .\install.ps1 --local +# NoTorch: .\install.ps1 --no-torch (skip PyTorch, GGUF-only mode) # Test: .\install.ps1 --package roland-sloth function Install-UnslothStudio { @@ -10,11 +11,13 @@ function Install-UnslothStudio { $StudioLocalInstall = $false $PackageName = "unsloth" $RepoRoot = "" + $SkipTorch = $false $argList = $args for ($i = 0; $i -lt $argList.Count; $i++) { switch ($argList[$i]) { - "--local" { $StudioLocalInstall = $true } - "--package" { + "--local" { $StudioLocalInstall = $true } + "--no-torch" { $SkipTorch = $true } + "--package" { $i++ if ($i -ge $argList.Count) { Write-Host "[ERROR] --package requires an argument." -ForegroundColor Red @@ -585,6 +588,16 @@ shell.Run cmd, 0, False } $TorchIndexUrl = Get-TorchIndexUrl + # ── Print CPU-only hint when no GPU detected ── + if (-not $SkipTorch -and $TorchIndexUrl -like "*/cpu") { + Write-Host "" + Write-Host " NOTE: No NVIDIA GPU detected." -ForegroundColor Yellow + Write-Host " Installing CPU-only PyTorch. If you only need GGUF chat/inference," + Write-Host " re-run with --no-torch for a faster, lighter install:" + Write-Host " .\install.ps1 --no-torch" + Write-Host "" + } + # ── Install PyTorch first, then unsloth separately ── # # Why two steps? @@ -607,26 +620,32 @@ shell.Run cmd, 0, False # Migrated env: force-reinstall unsloth+unsloth-zoo to ensure clean state # in the new venv location, while preserving existing torch/CUDA Write-Host "==> Upgrading unsloth in migrated environment..." - uv pip install --python $VenvPython --reinstall-package unsloth --reinstall-package unsloth-zoo "unsloth>=2026.3.14" unsloth-zoo + $noDepsArg = if ($SkipTorch) { "--no-deps" } else { $null } + uv pip install --python $VenvPython $noDepsArg --reinstall-package unsloth --reinstall-package unsloth-zoo "unsloth>=2026.3.14" unsloth-zoo if ($StudioLocalInstall) { Write-Host "==> Overlaying local repo (editable)..." uv pip install --python $VenvPython -e $RepoRoot --no-deps } } elseif ($TorchIndexUrl) { - Write-Host "==> Installing PyTorch ($TorchIndexUrl)..." - uv pip install --python $VenvPython "torch>=2.4,<2.11.0" torchvision torchaudio --index-url $TorchIndexUrl - if ($LASTEXITCODE -ne 0) { - Write-Host "[ERROR] Failed to install PyTorch (exit code $LASTEXITCODE)" -ForegroundColor Red - return + if ($SkipTorch) { + Write-Host "==> Skipping PyTorch (--no-torch flag set)." + } else { + Write-Host "==> Installing PyTorch ($TorchIndexUrl)..." + uv pip install --python $VenvPython "torch>=2.4,<2.11.0" torchvision torchaudio --index-url $TorchIndexUrl + if ($LASTEXITCODE -ne 0) { + Write-Host "[ERROR] Failed to install PyTorch (exit code $LASTEXITCODE)" -ForegroundColor Red + return + } } Write-Host "==> Installing unsloth (this may take a few minutes)..." + $noDepsArg = if ($SkipTorch) { "--no-deps" } else { $null } if ($StudioLocalInstall) { - uv pip install --python $VenvPython --upgrade-package unsloth "unsloth>=2026.3.14" unsloth-zoo + uv pip install --python $VenvPython $noDepsArg --upgrade-package unsloth "unsloth>=2026.3.14" unsloth-zoo Write-Host "==> Overlaying local repo (editable)..." uv pip install --python $VenvPython -e $RepoRoot --no-deps } else { - uv pip install --python $VenvPython --upgrade-package unsloth "$PackageName" + uv pip install --python $VenvPython $noDepsArg --upgrade-package unsloth "$PackageName" } } else { # Fallback: GPU detection failed to produce a URL -- let uv resolve torch @@ -659,6 +678,7 @@ shell.Run cmd, 0, False # Tell setup.ps1 to skip base package installation (install.ps1 already did it) $env:SKIP_STUDIO_BASE = "1" $env:STUDIO_PACKAGE_NAME = $PackageName + $env:UNSLOTH_NO_TORCH = if ($SkipTorch) { "true" } else { "false" } if ($StudioLocalInstall) { $env:STUDIO_LOCAL_INSTALL = "1" $env:STUDIO_LOCAL_REPO = $RepoRoot diff --git a/install.sh b/install.sh index a0f6ef2ee5..2024622603 100755 --- a/install.sh +++ b/install.sh @@ -3,22 +3,34 @@ # Usage (curl): curl -fsSL https://unsloth.ai/install.sh | sh # Usage (wget): wget -qO- https://unsloth.ai/install.sh | sh # Usage (local): ./install.sh --local (install from local repo instead of PyPI) +# Usage (no-torch): ./install.sh --no-torch (skip PyTorch, GGUF-only mode) # Usage (test): ./install.sh --package roland-sloth (install a different package name) +# Usage (py): ./install.sh --python 3.12 (override auto-detected Python version) set -e # ── Parse flags ── STUDIO_LOCAL_INSTALL=false PACKAGE_NAME="unsloth" +_USER_PYTHON="" +_NO_TORCH_FLAG=false _next_is_package=false +_next_is_python=false for arg in "$@"; do if [ "$_next_is_package" = true ]; then PACKAGE_NAME="$arg" _next_is_package=false continue fi + if [ "$_next_is_python" = true ]; then + _USER_PYTHON="$arg" + _next_is_python=false + continue + fi case "$arg" in --local) STUDIO_LOCAL_INSTALL=true ;; --package) _next_is_package=true ;; + --python) _next_is_python=true ;; + --no-torch) _NO_TORCH_FLAG=true ;; esac done @@ -26,8 +38,12 @@ if [ "$_next_is_package" = true ]; then echo "❌ ERROR: --package requires an argument." >&2 exit 1 fi +if [ "$_next_is_python" = true ]; then + echo "❌ ERROR: --python requires a version argument (e.g. --python 3.12)." >&2 + exit 1 +fi -PYTHON_VERSION="3.13" +PYTHON_VERSION="" # resolved after platform detection STUDIO_HOME="$HOME/.unsloth/studio" VENV_DIR="$STUDIO_HOME/unsloth_studio" @@ -563,6 +579,47 @@ elif grep -qi microsoft /proc/version 2>/dev/null; then fi echo "==> Platform: $OS" +# ── Architecture detection & Python version ── +_ARCH=$(uname -m) +MAC_INTEL=false +if [ "$OS" = "macos" ] && [ "$_ARCH" = "x86_64" ]; then + # Guard against Apple Silicon running under Rosetta (reports x86_64). + # sysctl hw.optional.arm64 returns "1" on Apple Silicon even in Rosetta. + if [ "$(sysctl -in hw.optional.arm64 2>/dev/null || echo 0)" = "1" ]; then + echo "" + echo " WARNING: Apple Silicon detected, but this shell is running under Rosetta (x86_64)." + echo " Re-run install.sh from a native arm64 terminal for full PyTorch support." + echo " Continuing in GGUF-only mode for now." + echo "" + fi + MAC_INTEL=true +fi + +if [ -n "$_USER_PYTHON" ]; then + PYTHON_VERSION="$_USER_PYTHON" + echo " Using user-specified Python $PYTHON_VERSION (--python override)" +elif [ "$MAC_INTEL" = true ]; then + PYTHON_VERSION="3.12" +else + PYTHON_VERSION="3.13" +fi + +if [ "$MAC_INTEL" = true ]; then + echo "" + echo " NOTE: Intel Mac (x86_64) detected." + echo " PyTorch is unavailable for this platform (dropped Jan 2024)." + echo " Studio will install in GGUF-only mode." + echo " Chat, inference via GGUF, and data recipes will work." + echo " Training requires Apple Silicon or Linux with GPU." + echo "" +fi + +# ── Unified SKIP_TORCH: --no-torch flag OR Intel Mac auto-detection ── +SKIP_TORCH=false +if [ "$_NO_TORCH_FLAG" = true ] || [ "$MAC_INTEL" = true ]; then + SKIP_TORCH=true +fi + # ── Check system dependencies ── # cmake and git are needed by unsloth studio setup to build the GGUF inference # engine (llama.cpp). build-essential and libcurl-dev are also needed on Linux. @@ -714,11 +771,38 @@ torch.testing.assert_close(torch.unique(E), torch.tensor((20,), device=E.device, fi fi +# If an Intel Mac has a stale 3.13 venv from a previous failed install, recreate +# (skip when the user explicitly chose a version via --python) +if [ "$SKIP_TORCH" = true ] && [ "$MAC_INTEL" = true ] && [ -z "$_USER_PYTHON" ] && [ -x "$VENV_DIR/bin/python" ]; then + _PY_MM=$("$VENV_DIR/bin/python" -c \ + "import sys; print('{}.{}'.format(*sys.version_info[:2]))" 2>/dev/null || echo "") + if [ "$_PY_MM" != "3.12" ]; then + echo " Recreating Intel Mac environment with Python 3.12 (was $_PY_MM)..." + rm -rf "$VENV_DIR" + fi +fi + if [ ! -x "$VENV_DIR/bin/python" ]; then echo "==> Creating Python ${PYTHON_VERSION} virtual environment (${VENV_DIR})..." uv venv "$VENV_DIR" --python "$PYTHON_VERSION" -else - echo "==> Using migrated environment at ${VENV_DIR}" +fi + +# Guard against Python 3.13.8 torch import bug on Apple Silicon +# (skip when the user explicitly chose a version via --python) +if [ -z "$_USER_PYTHON" ] && [ "$OS" = "macos" ] && [ "$_ARCH" = "arm64" ]; then + _PY_VER=$("$VENV_DIR/bin/python" -c \ + "import sys; print('{}.{}.{}'.format(*sys.version_info[:3]))" 2>/dev/null || echo "") + if [ "$_PY_VER" = "3.13.8" ]; then + echo " WARNING: Python 3.13.8 has a known torch import bug." + echo " Recreating venv with Python 3.12..." + rm -rf "$VENV_DIR" + PYTHON_VERSION="3.12" + uv venv "$VENV_DIR" --python "$PYTHON_VERSION" + fi +fi + +if [ -x "$VENV_DIR/bin/python" ]; then + echo "==> Using environment at ${VENV_DIR}" fi # ── Resolve repo root (for --local installs) ── @@ -759,13 +843,31 @@ get_torch_index_url() { } TORCH_INDEX_URL=$(get_torch_index_url) +# ── Print CPU-only hint when no GPU detected ── +case "$TORCH_INDEX_URL" in + */cpu) + if [ "$SKIP_TORCH" = false ] && [ "$OS" != "macos" ]; then + echo "" + echo " NOTE: No NVIDIA GPU detected (nvidia-smi not found)." + echo " Installing CPU-only PyTorch. If you only need GGUF chat/inference," + echo " re-run with --no-torch for a faster, lighter install:" + echo " curl -fsSL https://unsloth.ai/install.sh | sh -s -- --no-torch" + echo "" + fi + ;; +esac + # ── Install unsloth directly into the venv (no activation needed) ── _VENV_PY="$VENV_DIR/bin/python" if [ "$_MIGRATED" = true ]; then # Migrated env: force-reinstall unsloth+unsloth-zoo to ensure clean state # in the new venv location, while preserving existing torch/CUDA echo "==> Upgrading unsloth in migrated environment..." - uv pip install --python "$_VENV_PY" \ + _no_deps_arg="" + if [ "$SKIP_TORCH" = true ]; then + _no_deps_arg="--no-deps" + fi + uv pip install --python "$_VENV_PY" $_no_deps_arg \ --reinstall-package unsloth --reinstall-package unsloth-zoo \ "unsloth>=2026.3.14" unsloth-zoo if [ "$STUDIO_LOCAL_INSTALL" = true ]; then @@ -773,19 +875,27 @@ if [ "$_MIGRATED" = true ]; then uv pip install --python "$_VENV_PY" -e "$_REPO_ROOT" --no-deps fi elif [ -n "$TORCH_INDEX_URL" ]; then - # Fresh: Step 1 - install torch from explicit index - echo "==> Installing PyTorch ($TORCH_INDEX_URL)..." - uv pip install --python "$_VENV_PY" "torch>=2.4,<2.11.0" torchvision torchaudio \ - --index-url "$TORCH_INDEX_URL" + # Fresh: Step 1 - install torch from explicit index (skip when --no-torch or Intel Mac) + if [ "$SKIP_TORCH" = true ]; then + echo "==> Skipping PyTorch (--no-torch or Intel Mac x86_64)." + else + echo "==> Installing PyTorch ($TORCH_INDEX_URL)..." + uv pip install --python "$_VENV_PY" "torch>=2.4,<2.11.0" torchvision torchaudio \ + --index-url "$TORCH_INDEX_URL" + fi # Fresh: Step 2 - install unsloth, preserving pre-installed torch echo "==> Installing unsloth (this may take a few minutes)..." + _no_deps_arg="" + if [ "$SKIP_TORCH" = true ]; then + _no_deps_arg="--no-deps" + fi if [ "$STUDIO_LOCAL_INSTALL" = true ]; then - uv pip install --python "$_VENV_PY" \ + uv pip install --python "$_VENV_PY" $_no_deps_arg \ --upgrade-package unsloth "unsloth>=2026.3.14" unsloth-zoo echo "==> Overlaying local repo (editable)..." uv pip install --python "$_VENV_PY" -e "$_REPO_ROOT" --no-deps else - uv pip install --python "$_VENV_PY" \ + uv pip install --python "$_VENV_PY" $_no_deps_arg \ --upgrade-package unsloth "$PACKAGE_NAME" fi else @@ -837,10 +947,12 @@ if [ "$STUDIO_LOCAL_INSTALL" = true ]; then STUDIO_PACKAGE_NAME="$PACKAGE_NAME" \ STUDIO_LOCAL_INSTALL=1 \ STUDIO_LOCAL_REPO="$_REPO_ROOT" \ + UNSLOTH_NO_TORCH="$SKIP_TORCH" \ bash "$SETUP_SH" bool: + """Determine whether to run in no-torch (GGUF-only) mode. + + Checks UNSLOTH_NO_TORCH env var first. When unset, falls back to + platform detection so that Intel Macs automatically use GGUF-only + mode even when invoked from ``unsloth studio update`` (which does + not inject the env var). + """ + env = os.environ.get("UNSLOTH_NO_TORCH") + if env is not None: + return env.strip().lower() in ("1", "true") + return IS_MAC_INTEL + + +NO_TORCH = _infer_no_torch() # -- Verbosity control ---------------------------------------------------------- # By default the installer shows a minimal progress bar (one line, in-place). @@ -161,6 +181,19 @@ def run( # Packages to skip on Windows (require special build steps) WINDOWS_SKIP_PACKAGES = {"open_spiel", "triton_kernels"} +# Packages to skip when torch is unavailable (Intel Mac GGUF-only mode). +# These packages either *are* torch extensions or have unconditional +# ``Requires-Dist: torch`` in their published metadata, so installing +# them would pull torch back into the environment. +NO_TORCH_SKIP_PACKAGES = { + "torch-stoi", + "timm", + "torchcodec", + "torch-c-dlpack-ext", + "openai-whisper", + "transformers-cfg", +} + # -- uv bootstrap ------------------------------------------------------ USE_UV = False # Set by _bootstrap_uv() at the start of install_python_stack() @@ -273,8 +306,13 @@ def pip_install( constraint_args = ["-c", str(CONSTRAINTS)] actual_req = req + temp_reqs: list[Path] = [] if req is not None and IS_WINDOWS and WINDOWS_SKIP_PACKAGES: actual_req = _filter_requirements(req, WINDOWS_SKIP_PACKAGES) + temp_reqs.append(actual_req) + if actual_req is not None and NO_TORCH and NO_TORCH_SKIP_PACKAGES: + actual_req = _filter_requirements(actual_req, NO_TORCH_SKIP_PACKAGES) + temp_reqs.append(actual_req) req_args: list[str] = [] if actual_req is not None: req_args = ["-r", str(actual_req)] @@ -298,8 +336,8 @@ def pip_install( pip_cmd = _build_pip_cmd(args) + constraint_args + req_args run(f"{label} (pip)" if USE_UV else label, pip_cmd) finally: - if actual_req is not None and actual_req != req: - actual_req.unlink(missing_ok = True) + for temp_req in temp_reqs: + temp_req.unlink(missing_ok = True) def download_file(url: str, dest: Path) -> None: @@ -352,6 +390,8 @@ def install_python_stack() -> int: # When --local is used, overlay a local repo checkout after updating deps local_repo = os.environ.get("STUDIO_LOCAL_REPO", "") base_total = 10 if IS_WINDOWS else 11 + if IS_MACOS: + base_total -= 1 # triton step is skipped on macOS _TOTAL = (base_total - 1) if skip_base else base_total # 1. Try to use uv for faster installs (must happen before pip upgrade @@ -399,6 +439,28 @@ def install_python_stack() -> int: # 3. Core packages: unsloth-zoo + unsloth (or custom package name) if skip_base: print(_green(f"✅ {package_name} already installed — skipping base packages")) + elif NO_TORCH: + # No-torch mode: install unsloth + unsloth-zoo without torch deps + _progress("base packages (no torch)") + pip_install( + "Updating base packages (no-torch mode)", + "--no-cache-dir", + "--no-deps", + "--upgrade-package", + "unsloth", + "--upgrade-package", + "unsloth-zoo", + req = REQ_ROOT / "base.txt", + ) + if local_repo: + pip_install( + "Overlaying local repo (editable)", + "--no-cache-dir", + "--no-deps", + "-e", + local_repo, + constrain = False, + ) elif local_repo: # Local dev install: update deps from base.txt, then overlay the # local checkout as an editable install (--no-deps so torch is @@ -462,16 +524,22 @@ def install_python_stack() -> int: ) # 4. Overrides (torchao, transformers) -- force-reinstall - _progress("dependency overrides") - pip_install( - "Installing dependency overrides", - "--force-reinstall", - "--no-cache-dir", - req = REQ_ROOT / "overrides.txt", - ) + # Skip entirely when torch is unavailable (e.g. Intel Mac GGUF-only mode) + # because overrides.txt contains torchao which requires torch. + if NO_TORCH: + _progress("dependency overrides (skipped, no torch)") + else: + _progress("dependency overrides") + pip_install( + "Installing dependency overrides", + "--force-reinstall", + "--no-cache-dir", + req = REQ_ROOT / "overrides.txt", + ) # 5. Triton kernels (no-deps, from source) - if not IS_WINDOWS: + # Skip on Windows (no support) and macOS (no support). + if not IS_WINDOWS and not IS_MACOS: _progress("triton kernels") pip_install( "Installing triton kernels",