unsloth/tests/studio
Daniel Han ca55acbb5f
Studio: unblock install on Linux ARM64 + Windows ARM64 + Intel Mac (#5790)
* 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>
2026-05-27 04:53:38 -07:00
..
install ci: broaden Linux + narrow Windows llama.cpp runtime patterns + trim #5741 comments (#5746) 2026-05-23 21:48:12 -07:00
load_freeze studio: unblock /load event loop on detect_audio_type (#5642, #5635) (#5669) 2026-05-22 05:47:58 -07:00
_playwright_robust.py Studio: unblock install on Linux ARM64 + Windows ARM64 + Intel Mac (#5790) 2026-05-27 04:53:38 -07:00
playwright_chat_ime_i18n.py studio/chat: release stuck IME flag when compositionend never fires (#5551) 2026-05-18 06:30:38 -07:00
playwright_chat_ui.py studio/ci: harden three pre-existing CI flakes (#5627) 2026-05-20 02:20:15 -07:00
playwright_extra_ui.py Studio: stop hint, Uvicorn log rename, reachability check + Mac UI CI retry hardening (#5503) 2026-05-17 07:44:06 -07:00
run_real_mlx_smoke.py tests: unblock three stale assertions broken on main (MLX CI + Backend CI) (#5803) 2026-05-27 00:30:30 -07:00
studio_api_smoke.py studio: security and hardening pass (auth rate-limit, sandbox, path containment, schema validation, headers) (#5375) 2026-05-13 06:12:18 -07:00
test_auth_form_input_count.py studio/frontend: hide Current password input on first boot (#5545) 2026-05-18 04:27:21 -07:00
test_cancel_atomicity.py Studio: make stop button actually stop generation (#5069) 2026-04-24 10:09:25 -07:00
test_cancel_id_wiring.py Studio: make stop button actually stop generation (#5069) 2026-04-24 10:09:25 -07:00
test_chat_preset_builtin_invariants.py Studio: Dark theme refactor, right sidebar redesign, and chat UI polish (#5150) 2026-05-07 14:33:31 +04:00
test_cli_repo_variant.py Studio: forward llama-server args from unsloth studio run , activate unsloth run , and allow passing model:quant to load models (#5271) 2026-05-04 17:08:04 +04:00
test_cli_run_alias.py Studio: forward llama-server args from unsloth studio run , activate unsloth run , and allow passing model:quant to load models (#5271) 2026-05-04 17:08:04 +04:00
test_cli_studio_defaults.py Default Studio host to 127.0.0.1 and prompt before auto-start (#5267) 2026-05-04 13:03:16 +04:00
test_composer_rtl_bidi_attribute.py Studio: expose --parallel / -np flag on unsloth studio run (#5737) 2026-05-26 23:13:45 -07:00
test_export_output_path_contract.py feat(studio): MLX training tab on Apple Silicon (LoRA / full FT, VLM, export) (#5265) 2026-05-05 23:54:58 -07:00
test_frontend_dep_removal.py ci: deterministic check for studio/frontend dep removals (#5478) 2026-05-16 05:46:22 -07:00
test_hardware_dispatch_matrix.py CI: scope GITHUB_TOKEN permissions, add MLX CI, unblock ~60 skipped tests (#5312) 2026-05-11 03:19:13 -07:00
test_is_mlx_dispatch_gate.py MLX training support for Studio on Apple Silicon (#5340) 2026-05-14 05:24:20 -07:00
test_llama_cpp_wall_clock_cap.py Studio: make stop button actually stop generation (#5069) 2026-04-24 10:09:25 -07:00
test_mlx_training_worker_behaviors.py MLX training support for Studio on Apple Silicon (#5340) 2026-05-14 05:24:20 -07:00
test_stream_cancel_registration_timing.py Studio: make stop button actually stop generation (#5069) 2026-04-24 10:09:25 -07:00
test_studio_gguf_export_script_pin.py Pin Studio GGUF export to llama.cpp's local convert script (#5275) 2026-05-05 04:03:28 -07:00
test_studio_text_descender_clipping.py Studio: Fix clipped model selector text descenders (#5210) 2026-04-29 02:51:25 -07:00