From 849da89605aac35f94fd131fa2786be7ed7d78a0 Mon Sep 17 00:00:00 2001 From: Daniel Han Date: Tue, 26 May 2026 07:23:13 -0700 Subject: [PATCH] Fix unsloth studio update silently downgrading on macOS arm64 (#5767) * Fix unsloth studio update silently downgrading on macOS arm64 Root cause: studio/install_python_stack.py's "Updating base packages" step passes `--upgrade-package unsloth -r base.txt -c constraints.txt` with base.txt's `unsloth` and `unsloth-zoo` entries unpinned. On macOS arm64 the resolver silently backtracks to an older unsloth (2026.5.2 or even 2025.7.2) whenever a transitive constraint (the most common one is bitsandbytes wheel availability: 0.49.0+ ships macosx_14_0_arm64 wheels, older versions do not) makes the unpinned requirement satisfiable by an older release. install.sh already maintains an explicit `unsloth>=N.N.N` floor for the same reason, but the floor was missing from the in-venv update path. Reproduced on macos-14 across 2026.3.18 / 2026.4.8 / 2026.5.2 / 2026.5.6 starting states. All four ended on unsloth==2026.5.2 after a clean `unsloth studio update` invocation (2026.5.6 was a true downgrade, others were stale or partial advances). Fix mirrors install.sh: query PyPI at runtime for the current latest version of unsloth and unsloth-zoo, then pass `unsloth>=` and `unsloth-zoo>=` as extra positional pins alongside the existing `--upgrade-package` flags. Network failures fall back to the historical unpinned behaviour so offline installs continue to work. Applied to all three upgrade branches (standard update, local-repo overlay, no-torch). Also fix the cosmetic `Hardware detected: MLX -- Apple Silicon (i386)` banner. platform.processor() reads `uname -p` which returns "i386" on many universal2-shaped Python builds even on a native arm64 interpreter; platform.machine() is the reliable source ("arm64" once is_apple_silicon has gated us). * Dedup floor-pin call sites + LRU cache PyPI lookup Three upgrade branches each rebuilt the same conditional `unsloth>=` / `unsloth-zoo>=` arg list with two PyPI round-trips per branch -- six round-trips per `unsloth studio update` invocation. Extract a `_pin_floor_args(*, include_unsloth=True)` helper and wrap `_resolve_latest_pypi_version` in `functools.lru_cache` so the three branches share a single PyPI request per package. Functionally equivalent; pure cleanup on top of the previous commit. * Warn when PyPI is unreachable so the silent fallback is visible If `_resolve_latest_pypi_version` returns None for either lookup the floor args are silently dropped, which restores the pre-fix resolver behaviour. Print a single cyan `warning` line in `_pin_floor_args` when that happens so users behind a proxy / captive portal / firewalled PyPI mirror know the upgrade has degraded -- and can supply network egress or a `--index-url` mirror and retry. * Soft floor with unpinned-fallback for hosts where floor is unsatisfiable Reviewer found that the unconditional unsloth-zoo>=LATEST floor turns a previously-resolvable macOS 13 arm64 update into a hard resolver failure: unsloth-zoo 2026.5.4 requires mlx-vlm>=0.4.4 -> mlx>=0.30.0, and mlx 0.30+ only publishes macosx_14_0_arm64 wheels. The pre-fix behaviour backtracked to an older unsloth instead of erroring. We should not turn "stale" into "fail". Add pip_install_with_floor_fallback: first try the install with the floor appended; if the resolver cannot satisfy it (subprocess exit code != 0), retry the install without the floor and print a clear warning. The fall-through preserves the legacy "succeed-but-stale" contract on hosts where wheel availability is the bottleneck. Also extend pip_install_try with a req= kwarg so the floor attempt can pass `-r base.txt` like pip_install does, and add an UNSLOTH_NO_PYPI_FLOOR=1 opt-out for air-gapped CI / corporate PyPI mirrors that intentionally do not expose pypi.org directly. All three upgrade branches (standard, local-repo, no-torch) now go through the helper so the fallback behaviour is consistent. * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Add second fallback level: floor without constraints macOS arm64 floored attempt with -c constraints.txt fails because the single-env constraint `transformers==4.57.6` conflicts with the new unsloth-zoo 2026.5.4 -> mlx-vlm 0.4.4+ -> transformers>=5.1.0 chain. First fallback level retries the floored install without constraints (transformers freely resolves to a mlx-vlm-compatible version); downstream pip_install calls still apply constraints.txt to anything that doesn't transitively conflict. If THAT still fails (wheel availability rather than constraint conflict), drop the floor and fall back unpinned as before. Verified locally with uv pip compile against aarch64-apple-darwin python-3.13: strict-constrained floor errors, no-constraint floor resolves cleanly to unsloth==2026.5.7 + unsloth-zoo==2026.5.4 + transformers==5.5.0 + mlx-vlm==0.5.0. * setup.sh/.ps1: also gate fast-path on unsloth-zoo being up to date The version-check fast-path in setup.sh / setup.ps1 only looked at unsloth itself. If unsloth was at the PyPI latest but unsloth-zoo was stale, the gate set _SKIP_PYTHON_DEPS=true and install_python_stack.py never ran -- so the new floor pin from PR #5767 had no effect for the exact "unsloth at latest, zoo behind" state several reviewers flagged. Probe both packages' installed-vs-latest versions and only skip the deps step when BOTH match. When either is behind, fall through to install_python_stack.py so the new resolver fix gets a chance to run. Verified setup.sh with `bash -n`; the setup.ps1 change uses PowerShell if-expressions for the null-default pattern rather than bash-style ${var:-default} which is not valid PowerShell. * Skip unsloth-zoo floor too for custom no-torch test packages Reviewer found the asymmetric guard: the no-torch branch was already gating the unsloth floor on package_name == "unsloth" (test side packages may not publish to PyPI), but the unsloth-zoo floor was still added unconditionally. A custom no-torch update that ships its own forked zoo metadata could now hit a public PyPI floor that does not match the fork's published version. Add a symmetric `include_zoo` parameter to `_pin_floor_args` and gate both pins on the same `package_name == "unsloth"` check. * Address review feedback: simpler except clause + private-index note Gemini flagged TimeoutError in the PyPI fetch exception list. OSError already covers socket timeouts and the 3.11+ TimeoutError subclass on every supported Python, so drop the redundant entry and explain what each remaining exception catches. Codex flagged that floor lookups against pypi.org could break installs behind a lagging private mirror. Step 3 of pip_install_with_floor_fallback already recovers transparently in that case; expand the docstring so the behavior is discoverable without reading the body. * extras-no-deps: skip transformers==4.57.6 on macOS arm64 Reviewer flagged that the resolver-selected transformers from the no-constraints base step on macOS arm64 (transformers 5.x for mlx-vlm 0.4.4+) gets silently downgraded back to 4.57.6 by extras-no-deps.txt during the very next step, breaking mlx-vlm imports at runtime even though unsloth itself reports as latest. Add a PEP 508 platform marker so the pin only applies off macOS arm64. constraints.txt still enforces 4.57.6 everywhere else; mlx-vlm only publishes wheels for darwin arm64, so other platforms are unaffected. * setup.sh/.ps1: gate fast-path zoo probe on _PKG_NAME == unsloth Reviewer found the asymmetric custom-package regression: the new zoo-aware fast-path probes public unsloth-zoo unconditionally, but a custom STUDIO_PACKAGE_NAME side build may ship its own zoo fork via dependency metadata and not install public unsloth-zoo at all. The previous behaviour (skip Python deps if the custom package itself is at its declared latest) is preserved by only running the zoo probe when the managed package literally IS unsloth. Matches the include_zoo gate already in _pin_floor_args() at install_python_stack.py. * install_python_stack: all-or-nothing floor + uv-to-pip retry Two reviewer findings on the floor-pin helpers: 1. _pin_floor_args() previously kept a half-floor if one PyPI lookup succeeded and the other failed. With unsloth at latest but the zoo lookup down, the resolver could still backtrack zoo while we required unsloth at latest, defeating the pin. Return [] on any lookup failure so the unpinned legacy path runs cleanly. 2. pip_install_try() ran ONLY uv when USE_UV was true; a uv-specific failure short-circuited to False even when pip itself could have applied the floor. Mirror pip_install()'s uv-to-pip fallback: try uv, fall through to pip on non-zero exit, and only then give up. * extras-no-deps: rewrite marker without `not` for PEP 508 parsers pip's vendored packaging rejects `not (...)` in PEP 508 markers; the grammar only specifies `and` / `or` between boolean atoms. The staging macos-14 matrix failed every job at "Installing extras (no-deps)" with `Expected a marker variable or quoted string`. Apply De Morgan's law so the marker uses `or` between two `!=` checks, which both pip and uv parse cleanly. Behaviour identical: skip the 4.57.6 pin only on darwin arm64; pin everywhere else. * constraints: skip transformers==4.57.6 pin on macOS arm64 too Marker-gating the extras-no-deps.txt pin was not sufficient. Every subsequent pip_install in the update pipeline passes -c single-env/constraints.txt, and constraints.txt itself pinned transformers==4.57.6 unconditionally. The latest staging-2 run shows the base step's no-constraints fallback installed transformers 5.5.0 correctly, but a later constrained step (extras / studio / data-designer deps) silently downgraded it back to 4.57.6, leaving mlx-vlm 0.5.0 in the venv with an unsatisfied transformers>=5.5.0 requirement. Apply the same `sys_platform != "darwin" or platform_machine != "arm64"` marker to the constraints.txt entry so it is inert on darwin arm64. Other platforms still pin 4.57.6 because mlx-vlm only publishes wheels for darwin arm64; no other platform is affected. * constraints: carve out darwin arm64 from every == pin Marker-gating only transformers was not enough; staging-2 still failed with the same `transformers==4.57.6 in venv after the update` outcome because the resolver hit a `huggingface-hub==0.36.2` (and adjacent) conflict with mlx-vlm's `huggingface-hub>=1.5.0` requirement, then fell back to a stale stack even after my no-constraints level fired on the base step. Apply the same `sys_platform != "darwin" or platform_machine != "arm64"` marker to every == pin in constraints.txt. Range pins (mcp, fastmcp, websockets) stay active everywhere because they do not conflict with the mlx-vlm chain. mlx-vlm only publishes wheels for darwin arm64, so no other platform is affected. * install_python_stack: also --upgrade-package transformers and mlx-vlm Staging-2 showed that even after the constraints.txt carve-out for darwin arm64, the venv still ended up with the OLD `transformers==4.57.6` paired with a NEW `mlx-vlm==0.5.0` from unsloth-zoo's transitive upgrade. The resolver's --upgrade-package flag only freshens the named packages and their newly-pulled transitive deps; transformers was already installed at a version that satisfied unsloth-zoo's range (`>=4.51.3,<=5.5.0` with exclusions), so the resolver did not upgrade it -- even though mlx-vlm 0.5.0 requires `transformers>=5.5.0`. Add `--upgrade-package transformers` and `--upgrade-package mlx-vlm` to all three base-step branches. Both are no-ops when the package is absent (mlx-vlm only ships wheels on darwin arm64); on darwin arm64 this is what nudges the resolver to upgrade both together so the final venv is internally consistent. On Linux/Windows, transformers stays at 4.57.6 because constraints.txt still pins it there and mlx-vlm never enters the resolution. * install_python_stack: explicit mlx-vlm + transformers realign on macOS arm64 Even with --upgrade-package hints, uv leaves the venv with the already-installed transformers (4.57.6 inherited from the OLD venv's constrained install) when that version still happens to satisfy unsloth's own metadata range -- but it does not also re-resolve mlx-vlm's stricter `transformers>=5.5.0` requirement, so the venv ends up with mlx-vlm 0.5.0 paired with transformers 4.57.6 and mlx-vlm imports break at runtime. After the base step, on darwin arm64 only, run an explicit `pip install --upgrade mlx-vlm transformers` with constrain=False. This forces both packages through the resolver again as direct top-level requirements, so transformers is pulled up to whatever mlx-vlm's metadata requires (5.5.0 today). No effect on any other platform because mlx-vlm has no wheels off darwin arm64 and the branch is gated on IS_MAC_ARM. * requirements: marker-gate every == pin that conflicts with mlx-vlm chain Staging-2 kept ending up with transformers==4.57.6 even after the realign step, because studio.txt unconditionally pins huggingface-hub==0.36.2 (and datasets==4.3.0). Installing studio.txt with constraints active pulls the resolver back to a huggingface-hub that only recent transformers (4.x) supports, which silently downgrades the realigned 5.5.0 to 4.57.6 -- exactly the inconsistency we tried to prevent. Also extras-no-deps.txt still pinned trl==0.23.1 unconditionally; the 0.23.1 wheel transitively requires huggingface-hub<1, same coupling. Marker-gate all three. The carve-out is identical to constraints.txt's: inactive on darwin arm64 (where the mlx-vlm chain dictates newer versions), active everywhere else (where Linux/Windows users rely on the single-env pins). mlx-vlm only publishes wheels for darwin arm64 so no other platform is affected. * realign: --force-reinstall mlx-vlm + transformers + huggingface_hub Plain --upgrade does not force uv to re-resolve mlx-vlm's transformers requirement when the already-installed transformers happens to satisfy unsloth's own range. Switch to --force-reinstall on the three packages so the resolver tears them down and brings them back together with consistent versions. Include huggingface_hub because transformers 5.x requires hf-hub>=1.5.0 and the resolver would not touch it otherwise. * realign: pin transformers via mlx-vlm's own metadata spec `pip install --force-reinstall mlx-vlm transformers` still resolved to an already-installed transformers 4.57.6 because uv treats it as satisfying unsloth's transformers range without re-checking mlx-vlm's stricter requirement. Pull mlx-vlm's actual transformers specifier from its installed metadata at runtime and pass it as an explicit version requirement (e.g. `transformers>=5.5.0` for mlx-vlm 0.5.0). That removes the resolver's wiggle room: it MUST pick a transformers satisfying mlx-vlm AND unsloth, which on darwin arm64 with the latest unsloth-zoo means transformers==5.5.0. Falls back to unpinned `transformers` if metadata read fails, so this never errors. * realign: uninstall-then-install to bypass uv's incumbent bias Every flag-based approach failed: --upgrade, --upgrade-package, --force-reinstall, and even an explicit `transformers>=5.5.0` requirement all left the venv with transformers==4.57.6 because uv treats the already-installed version as satisfying unsloth-zoo's range and refuses to disturb it, even when it does not satisfy mlx-vlm's stricter requirement. Replace the realign step with an explicit uninstall of the conflicting trio (transformers / mlx-vlm / huggingface_hub) followed by a fresh install. With no transformers in the venv, the resolver MUST pick a version satisfying every installed package's metadata, which on darwin arm64 with the latest unsloth-zoo is uniquely 5.5.0. * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Trim verbose comments across PR #5767 changes * Simplify mac-arm64 fix: install MLX stack with --no-deps The previous approach (PyPI floor pin + 3-level fallback + macOS arm64 realign step + marker carve-outs on every == pin) was fighting symptoms. The root cause is that unsloth-zoo declares mlx-vlm>=0.4.4 as a darwin arm64 dep, and mlx-vlm 0.5.0's metadata pulls in transformers>=5.5.0, which conflicts with the main venv's transformers==4.57.6 pin and forces the resolver to backtrack unsloth. Severing that chain at its source: install mlx + mlx-metal + mlx-lm + mlx-vlm with --no-deps BEFORE unsloth-zoo. The resolver sees mlx-vlm already installed (>=0.4.4) and never inspects its transformers metadata. Per-model transformers version routing is already handled at runtime by the side-car venvs in utils/transformers_version.py (.venv_t5_530 for Ministral/GLM/Qwen3 MoE, .venv_t5_550 for Gemma 4). Net change: -224 / +71 lines across install.sh, install_python_stack.py and the three requirements files. Reverted: - _resolve_latest_pypi_version + _pin_floor_args + pip_install_with_floor_fallback - macOS arm64 realign step (pip uninstall + reinstall) - --upgrade-package transformers --upgrade-package mlx-vlm in base steps - All ; sys_platform != "darwin" or platform_machine != "arm64" markers in constraints.txt, studio.txt, extras-no-deps.txt - pip_install_try restored to its pre-PR signature Added: - install.sh: Apple Silicon MLX --no-deps install before unsloth (both fresh and migrated branches) - install_python_stack.py: same step gated on IS_MAC_ARM and not skip_base Kept (independent bugs): - setup.sh / setup.ps1 dual-package zoo version check - platform.processor() -> platform.machine() hardware-detect fix * Minimise PR to mac-arm64-specific changes only Revert setup.sh and setup.ps1 to main -- the dual-package zoo check was defensive and not strictly needed once mlx-vlm is installed --no-deps (the resolver-backtrack scenario that produced stale zoo no longer happens). Tighten remaining comments in install.sh and install_python_stack.py. Final PR-attributable changes: install.sh +24/-5 (MLX --no-deps in 2 places) studio/install_python_stack.py +19 (MLX --no-deps + IS_MAC_ARM) studio/backend/utils/hardware/hardware.py +6/-6 (processor() -> machine()) studio/backend/requirements/*.txt unchanged * Revert "Minimise PR to mac-arm64-specific changes only" This reverts commit 9470daa855f8c1588350585c6d7b82041228e32e. * Revert "Simplify mac-arm64 fix: install MLX stack with --no-deps" This reverts commit f8a43b87e8b46fd5e3b7942d38f15b58f3844aae. * Revert "Trim verbose comments across PR #5767 changes" This reverts commit c3f293a10fa11a74212a6a7b97f46e619dedc38e. * Simplify mac-arm64 fix: --no-deps MLX + METADATA patch Root cause: unsloth-zoo declares mlx-vlm>=0.4.4 as a darwin-arm64 dep, and mlx-vlm 0.5.0's published metadata declares transformers>=5.5.0. Every subsequent resolver run with constraints.txt's transformers==4.57.6 sees the conflict and backtracks unsloth to escape it (user-reported downgrade). The aggressive pin doesn't reflect what mlx-vlm actually requires at top-level import time -- the symbols it loads (AutoProcessor, AutoTokenizer, ProcessorMixin, BatchFeature) are stable across transformers 4.51+. Model- specific submodules that genuinely need 5.x APIs are only loaded once the 3-tier transformers dispatcher (utils/transformers_version.py) has activated the matching .venv_t5_530 / .venv_t5_550 side-car at runtime. Fix: on Apple Silicon, install the MLX stack with --no-deps then rewrite mlx-vlm/mlx-lm's installed METADATA to declare transformers>=4.51.3. Now the resolver sees mlx-vlm 0.5.0 as compatible with the main venv's transformers==4.57.6 and there's nothing to backtrack. Reverts the previous heavy machinery: - _resolve_latest_pypi_version, _pin_floor_args, pip_install_with_floor_fallback - macOS arm64 realign step (pip uninstall + reinstall) - --upgrade-package transformers --upgrade-package mlx-vlm in base steps - All ; sys_platform != "darwin" or platform_machine != "arm64" markers in constraints.txt / studio.txt / extras-no-deps.txt - setup.sh / setup.ps1 dual-package zoo check (Windows never had the bug; with this fix in place stale zoo no longer happens on macOS either) - pip_install_try restored to pre-PR signature Kept: - install.sh: MLX --no-deps install in fresh + migrated branches - install_python_stack.py: same step gated on IS_MAC_ARM and not skip_base - _relax_mlx_metadata() helper, called immediately after each MLX install - studio/backend/utils/hardware/hardware.py: platform.processor() -> platform.machine() cosmetic fix * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Use UV_OVERRIDE to relax mlx-vlm transformers pin uv supports --overrides / UV_OVERRIDE which globally overrides any package's stated dependency requirement. mlx-vlm 0.5.0 declares transformers>=5.5.0 and mlx-lm 0.31.3 declares transformers>=5.0.0; neither is true at top-level import time (their imports use AutoProcessor / AutoTokenizer / ProcessorMixin / BatchFeature which are stable across transformers 4.51+). Per-model 5.x routing is handled at runtime via the .venv_t5_530 / .venv_t5_550 side-cars. Override file (overrides-darwin-arm64.txt) declares transformers>=4.51.3 ; exported via UV_OVERRIDE env var on Apple Silicon by both install.sh and install_python_stack.py. uv then resolves mlx-vlm as compatible with the main venv's transformers==4.57.6 (constraints.txt) and unsloth advances cleanly to LATEST. Drops, vs. the previous attempts: - _resolve_latest_pypi_version + _pin_floor_args + pip_install_with_floor_fallback (floor-pin machinery -- replaced by single UV_OVERRIDE line) - macOS arm64 realign step (pip uninstall + reinstall) - --upgrade-package transformers --upgrade-package mlx-vlm in base steps - All ; sys_platform != "darwin" or platform_machine != "arm64" markers - _relax_mlx_metadata() helper + sed METADATA patch (uv reads from index, not dist-info, so dist-info patches were ineffective) Kept: - install.sh / install_python_stack.py: MLX latest install on Apple Silicon (now without --no-deps, the override lets the resolver pick a consistent set) - studio/backend/utils/hardware/hardware.py: platform.machine() cosmetic fix * Trim UV_OVERRIDE comments; bump override floor to 4.57.6 Match the main venv's constraints.txt pin exactly so the override file reads as the actual installed version rather than mlx-vlm's API floor. Comments collapsed to one-liners where possible. --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- install.sh | 14 +++++++------ .../single-env/overrides-darwin-arm64.txt | 5 +++++ studio/backend/utils/hardware/hardware.py | 15 ++++++------- studio/install_python_stack.py | 21 +++++++++++++++++++ 4 files changed, 42 insertions(+), 13 deletions(-) create mode 100644 studio/backend/requirements/single-env/overrides-darwin-arm64.txt diff --git a/install.sh b/install.sh index d12abe298f..49bb7a7b89 100755 --- a/install.sh +++ b/install.sh @@ -1290,6 +1290,14 @@ if [ "$_NO_TORCH_FLAG" = true ] || [ "$MAC_INTEL" = true ]; then SKIP_TORCH=true fi +# Apple Silicon: override mlx-vlm / mlx-lm's transformers pin (see overrides file). +if [ "$OS" = "macos" ] && [ "$_ARCH" = "arm64" ]; then + _OVERRIDES_FILE="$(cd "$(dirname "$0" 2>/dev/null || echo ".")" && pwd)/studio/backend/requirements/single-env/overrides-darwin-arm64.txt" + if [ -f "$_OVERRIDES_FILE" ]; then + export UV_OVERRIDE="$_OVERRIDES_FILE" + fi +fi + _TAURI_INITIAL_GPU_BRANCH="unknown" if [ "$SKIP_TORCH" = true ]; then _TAURI_INITIAL_GPU_BRANCH="no_torch" @@ -2108,12 +2116,6 @@ else fi fi -# ── Install mlx-vlm on Apple Silicon (optional, for VLM training) ── -if [ "$OS" = "macos" ] && [ "$_ARCH" = "arm64" ]; then - substep "installing mlx-vlm (VLM training support)..." - run_install_cmd "install mlx-vlm" uv pip install --python "$_VENV_PY" mlx-vlm -fi - # ── Run studio setup ── tauri_log "STEP" "Running Studio setup" # When --local, use the repo's own setup.sh directly. diff --git a/studio/backend/requirements/single-env/overrides-darwin-arm64.txt b/studio/backend/requirements/single-env/overrides-darwin-arm64.txt new file mode 100644 index 0000000000..2cd03d8b78 --- /dev/null +++ b/studio/backend/requirements/single-env/overrides-darwin-arm64.txt @@ -0,0 +1,5 @@ +# mlx-vlm / mlx-lm declare transformers>=5.x which conflicts with the +# main venv's constraints.txt pin transformers==4.57.6 and forces uv to +# backtrack unsloth. Relax to match the pin -- per-model 5.x routing +# happens at runtime via the side-car venvs. +transformers>=4.57.6 diff --git a/studio/backend/utils/hardware/hardware.py b/studio/backend/utils/hardware/hardware.py index 3764e38272..ede37e2953 100644 --- a/studio/backend/utils/hardware/hardware.py +++ b/studio/backend/utils/hardware/hardware.py @@ -144,7 +144,10 @@ def detect_hardware() -> DeviceType: if is_apple_silicon() and _has_mlx(): DEVICE = DeviceType.MLX CHAT_ONLY = False - chip = platform.processor() or platform.machine() + # platform.processor() runs `uname -p` which returns "i386" on most + # universal2 / Rosetta-shaped Python builds even on native arm64. + # platform.machine() is "arm64" once is_apple_silicon() has gated us. + chip = platform.machine() or "arm64" print(f"Hardware detected: MLX — Apple Silicon ({chip})") return DEVICE @@ -279,13 +282,11 @@ def get_gpu_memory_info() -> Dict[str, Any]: try: info = mx.device_info() - gpu_name = ( - info.get("device_name") - or platform.processor() - or platform.machine() - ) + # See detect_hardware(): platform.processor() can return "i386" + # on native arm64 Python builds, so prefer machine() as fallback. + gpu_name = info.get("device_name") or platform.machine() or "arm64" except Exception: - gpu_name = platform.processor() or platform.machine() + gpu_name = platform.machine() or "arm64" return { "available": True, diff --git a/studio/install_python_stack.py b/studio/install_python_stack.py index 9166d35ce3..ca7fe3f004 100644 --- a/studio/install_python_stack.py +++ b/studio/install_python_stack.py @@ -37,6 +37,7 @@ from backend.utils.wheel_utils import ( IS_WINDOWS = sys.platform == "win32" IS_MACOS = sys.platform == "darwin" IS_MAC_INTEL = IS_MACOS and platform.machine() == "x86_64" +IS_MAC_ARM = IS_MACOS and platform.machine() == "arm64" # ── ROCm / AMD GPU support ───────────────────────────────────────────────────── # Mapping from detected ROCm (major, minor) to the best PyTorch wheel tag on @@ -423,6 +424,7 @@ def _infer_no_torch() -> bool: NO_TORCH = _infer_no_torch() + # -- Verbosity control ---------------------------------------------------------- # By default the installer shows a minimal progress bar (one line, in-place). # Set UNSLOTH_VERBOSE=1 in the environment to restore full per-step output: @@ -448,6 +450,11 @@ LOCAL_DD_GITHUB_PLUGIN = ( SCRIPT_DIR / "backend" / "plugins" / "data-designer-github-repo-seed" ) +# Apple Silicon: override mlx-vlm/mlx-lm's transformers pin (see overrides file). +_MLX_OVERRIDES = SINGLE_ENV / "overrides-darwin-arm64.txt" +if IS_MAC_ARM and _MLX_OVERRIDES.is_file(): + os.environ.setdefault("UV_OVERRIDE", str(_MLX_OVERRIDES)) + # -- Unicode-safe printing --------------------------------------------- # On Windows the default console encoding can be a legacy code page # (e.g. CP1252) that cannot represent Unicode glyphs such as ✅ or ❌. @@ -960,6 +967,20 @@ def install_python_stack() -> int: [sys.executable, "-m", "pip", "install", "--upgrade", "pip"], ) + # macOS arm64: install MLX stack at latest (UV_OVERRIDE relaxes the + # mlx-vlm / mlx-lm transformers pin -- set at module load). + if IS_MAC_ARM and not skip_base: + _progress("MLX stack (Apple Silicon)") + pip_install( + "Installing MLX stack (mlx + mlx-lm + mlx-vlm)", + "--no-cache-dir", + "--upgrade", + "mlx", + "mlx-metal", + "mlx-lm", + "mlx-vlm", + ) + # 3. Core packages: unsloth-zoo + unsloth (or custom package name) if skip_base: pass