* Studio: unblock cross-platform install on Linux ARM64 + Windows ARM64
Three independent bugs that together prevent `install.sh` /
`install.ps1` from completing on the ARM machines GitHub Actions now
ships (`ubuntu-24.04-arm`, `windows-11-arm`) and on equivalent real
hosts (Ampere Altra, Raspberry Pi 5, Snapdragon X Elite, ...).
Validated on the staging-2 cross-OS smoke suite -- five per-OS
workflows pinned to `ubuntu-latest`, `ubuntu-24.04-arm`, `macos-14`,
`macos-15-intel`, `windows-11-arm`. Before this change Windows ARM
exits 1 in the winget gate and Linux ARM source-builds llama.cpp
because the prebuilt selector returns 0 attempts; with it both reach
healthy /api/health.
1. studio/install_llama_prebuilt.py -- resolve_simple_install_release_plans
had explicit branches for windows+x86_64, macos+arm64, macos+x86_64
and linux+x86_64 only. Upstream ggml-org/llama.cpp ships
`llama-bNNNN-bin-ubuntu-arm64.tar.gz` and
`llama-bNNNN-bin-win-cpu-arm64.zip` (visible in the b9334 release
manifest), so the missing elif branches force every Linux ARM64 and
Windows ARM64 host into a source build even when a perfectly good
upstream prebuilt is one HTTP GET away. Two new branches mirror the
existing CPU variants; runtime_patterns_for_choice and
runtime_payload_health_groups gain `linux-arm64` (.so layout) and
`windows-arm64` (.dll layout) so the health-check pass-through
matches the asset shape.
2. studio/setup.sh -- the helper-release-repo selector routed any
non-x86_64 Linux to `unslothai/llama.cpp`, which only publishes the
Linux CUDA bundle set. The result on Linux ARM64 was a guaranteed
`direct_linux_release_plan` raise of "no compatible Linux prebuilt
asset was found" on every release in the scan, then a source-build
fallback. Pin Linux ARM64 (CPU-only) to `ggml-org/llama.cpp` so the
new branch in (1) can see the upstream asset. setup.ps1 already
hardcodes `ggml-org/llama.cpp`, so Windows ARM64 picks up (1)
without an additional change.
3. install.ps1 -- the winget pre-check hard-failed before Python or uv
detection. `windows-11-arm` runners (and many corporate Windows
hosts without the Microsoft Store) ship without winget but already
have a usable Python plus the Astral uv PowerShell installer
reachable. Demote the winget check to a soft warning, defer the
hard failure to the Python install branch (which is the only path
that genuinely needs winget), and let the uv install fall through
to `https://astral.sh/uv/install.ps1` when winget is absent. The
uv PowerShell installer was already the existing fallback for the
"winget present but uv install failed" case; this just makes it
the primary path on hosts without winget.
* [pre-commit.ci] auto fixes from pre-commit.com hooks
for more information, see https://pre-commit.ci
* Studio: filter torchcodec on platforms without wheels
torchcodec 0.10.0 ships wheels for manylinux_2_28_x86_64,
macosx_12_0_arm64, and win_amd64 only -- visible on its PyPI page and
in the resolver error reported by #4446. install_python_stack.py
pulls torchcodec via extras-no-deps.txt, which is now installed
unconditionally during `unsloth studio update --local` (the update
command has no --no-torch flag). Result on Linux aarch64 /
Windows ARM64 / Intel Mac (when invoked outside the install.sh
auto-skip-torch path):
ERROR: Could not find a version that satisfies the requirement
torchcodec==0.10.0 (from versions: 0.0.0.dev0, ...)
ERROR: No matching distribution found for torchcodec==0.10.0
error Installing extras (no-deps) (pip) failed (exit code 1)
`NO_TORCH_SKIP_PACKAGES` already lists torchcodec but only fires
when NO_TORCH is true -- the update path inherits no NO_TORCH from
the original install and inferrence falls back to IS_MAC_INTEL only,
so Linux aarch64 / Windows ARM64 sail past the guard. Adds a
platform predicate PLATFORM_LACKS_TORCHCODEC_WHEEL and applies the
torchcodec filter unconditionally there, independent of NO_TORCH.
Surfaced by the staging-2 cross-OS smoke `unsloth studio update`
step on ubuntu-24.04-arm; verified the same step is green with this
patch overlaid.
* Studio: skip librosa on no-torch hosts (unblocks Intel Mac install)
Closes the last cross-platform install gap surfaced by the staging-2
cross-OS smoke (see unslothai/unsloth#5046 for the original report):
`install.sh --local` on macos-15-intel fails at
× Failed to build `llvmlite==0.47.0`
error: failed-wheel-build-for-install
╰─> llvmlite
error studio setup failed (exit code 1)
Root cause: upstream llvmlite dropped the macosx_x86_64 wheel between
0.42.0 and 0.46.0 (https://pypi.org/project/llvmlite/0.47.0/#files --
only macosx_arm64 / manylinux / win_amd64 remain). pip falls back to
a from-source build of llvmlite's FFI, which needs LLVM 14/15 dev
headers and matching llvm-config -- not present in Xcode Command
Line Tools' libclang and not installed by install.sh's MAC_INTEL
deps branch.
llvmlite enters Studio's tree via librosa -> numba -> llvmlite in
extras.txt. openai-whisper (extras.txt:28) would also pull numba but
is already filtered on no-torch hosts. Adding librosa to the same
NO_TORCH_SKIP_PACKAGES set makes the install go through cleanly on
Intel Mac (auto-detected NO_TORCH=true via the MAC_INTEL branch) and
on any user-passed --no-torch host where torch-dependent audio
pipelines would not run anyway.
Tracked / verified on the danielhanchen/unsloth-staging-2#154 smoke
matrix (macos-15-intel).
* Studio UI tests: retry evaluate_fetch on transport-level failure (PR #5790)
Mac Studio UI CI on this PR (run 26496820814, job 78026959359) failed
with /api/models/list status=0 error='TypeError: Failed to fetch'.
The artifact studio.log shows the server answered the two preceding
/api/models/list calls from the React mount (both 200) but never
received the third call from the test script: the browser reused a
kept-alive HTTP/1.1 socket that uvicorn (5s keep_alive_timeout) had
closed ~130ms earlier. Chromium under --single-process on macos-14
free runners is most prone to this; the post /api/auth/change-password
session churn accelerates it. A rerun on the same SHA passed, which is
the classic flake signature.
evaluate_fetch in tests/studio/_playwright_robust.py already returns a
structured {status: 0, body: None, error: "..."} on JS-side throws, but
every caller treats status=0 as fatal. Add a bounded retry inside the
helper so the one class of failure recovers transparently:
status != 0 -> real HTTP response (incl. 4xx/5xx); propagate.
error has "AbortError" -> caller's AbortSignal deadline; propagate.
else (status==0) -> stale-keepalive or other transport failure;
retry after 250ms / 500ms backoff so the pool
evicts the dead socket before the next attempt.
Defaults transport_retries=2, transport_backoff_ms=250 (max added
latency on the happy path is zero; on a transport failure: up to
750ms of sleep). Callers keep the existing {status, body, error} shape;
no call-site changes needed.
Verified: tests/studio/_playwright_robust.py compiles; signature
gains two kwonly args (transport_retries, transport_backoff_ms);
8 evaluate_fetch call sites in playwright_chat_ui.py +
playwright_extra_ui.py pick up the retry without change.
---------
Co-authored-by: danielhanchen <info@unsloth.ai>
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>