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",