mirror of
https://github.com/unslothai/unsloth.git
synced 2026-05-02 13:20:28 +00:00
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>
This commit is contained in:
parent
d57a4d993d
commit
e9ac785346
6 changed files with 259 additions and 40 deletions
|
|
@ -13,6 +13,7 @@ PATH to point at the venv.
|
|||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import platform
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
|
|
@ -21,6 +22,25 @@ import urllib.request
|
|||
from pathlib import Path
|
||||
|
||||
IS_WINDOWS = sys.platform == "win32"
|
||||
IS_MACOS = sys.platform == "darwin"
|
||||
IS_MAC_INTEL = IS_MACOS and platform.machine() == "x86_64"
|
||||
|
||||
|
||||
def _infer_no_torch() -> 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",
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue