Commit graph

164 commits

Author SHA1 Message Date
Daniel Han
2c316862f8 docker: address review round 4 (jupyter probe, CPU messaging, llama EXDEV, %pip shim)
- docker-publish smoke + docker_confirm.sh probe Jupyter /login, not /api: the
  launcher always configures a password hash so /api returns 403 and curl -f
  would never flip the health flag (false build failure).
- entrypoint.sh CPU messaging: CPU mode covers Jupyter, GGUF tooling and
  llama.cpp (GGUF) Studio chat; training AND loading an Unsloth model
  (FastLanguageModel) still need a GPU, since from_pretrained runs CUDA probes.
- install_llama_prebuilt.py: rollback/activation moves used bare os.replace,
  which fails with EXDEV across overlayfs in a Docker build and fell back to a
  broken source build (no nvcc). Add is_cross_device_error + move_install_dir_aside
  (os.replace fast path, copy+remove on EXDEV; busy errors still re-raise).
- notebooks: %pip / %uv line magics and the `!python -m pip` form bypassed the
  PATH pip/uv shim and could overwrite the baked cu128 torch/vLLM stack. Add
  unsloth_nb_pip_magic.py to re-point them at the shim, wired via the IPython
  startup hook and installed into the venv site-packages.
2026-06-27 08:46:47 +00:00
Daniel Han
d476c7764b docker: address review round 3 (notebook -r filter, studio zoo ref, pinned notebooks commit)
- unsloth_pip_shim.py: filter protected packages out of a notebook
  `pip install -r requirements.txt`. The -r value was passed to the real pip
  unchanged, so torch / transformers / vLLM / nvidia pins inside the file could
  overwrite the baked cu128 stack or push transformers into the base venv.
  _filter_requirements_file() applies the same _KEEP / transformers-sidecar
  rules per line, writes the survivors to a temp file, keeps comments, option
  lines, nested includes and urls verbatim, and records a pinned transformers
  version for the sidecar.

- install.sh + Dockerfile.studio + docker-publish.yml: forward the resolved
  unsloth-zoo ref into the Studio build. install.sh --local overlaid
  unsloth-zoo from git main regardless of the operator-requested or base-image
  ref, so the full image could run a different zoo than the base. install.sh
  now honors UNSLOTH_ZOO_REF across all four --local overlays, Dockerfile.studio
  passes UNSLOTH_STUDIO_ZOO_REF through to it, and the workflow resolves one zoo
  ref in the prepare job and shares it with both the base and Studio builds.

- Dockerfile + docker-publish.yml: pin unslothai/notebooks to one resolved
  commit. Each arch leg cloned HEAD independently, so the same tag could seed
  different baked templates and .unsloth_template_commit depending on the pulled
  platform. The prepare job freezes notebooks to one sha (like the llama.cpp
  prebuilt tag) and the Dockerfile fetches that single ref at depth 1.
2026-06-26 10:51:51 +00:00
Daniel Han
0ebbdbb9cc docker: address review follow-ups (pip-shim flags, sync ownership, tags, zoo ref, arch list)
- pip shim: do not treat the value of an index-url / find-links / constraint flag
  as an install target. A cell like 'pip install --extra-index-url <url> torch'
  now no-ops after keeping the baked stack instead of exec'ing a bare
  'pip install --extra-index-url <url>' that fails. Positional . / url / vcs and
  -r/--requirement files still count as targets.
- notebook sync: on first boot, record only files we actually wrote (or that are
  byte-identical to the template), never a kept pre-existing user file; and on the
  GitHub refresh, treat a file present in DEST but absent from the sync state as
  user-owned and keep it. Previously a bind-mounted notebook was recorded as
  managed and then overwritten by upstream.
- docker-publish: add flavor latest=false to the Studio metadata steps too, so a
  v* tag push cannot emit an implicit :latest via metadata-action's latest=auto;
  :latest stays default-branch-only, and the smoke test pulls the published tag.
- unsloth-studio-update: resolve the unsloth-zoo ref independently of --ref (new
  --zoo-ref, else use the ref only when the zoo repo has it, else fall back to
  main) so 'update --ref <unsloth-tag/sha>' does not fail on a missing zoo ref.
- Dockerfile: drop 10.3 (compute_103) from TORCH_CUDA_ARCH_LIST in both the
  builder and runtime stages. B300 runs sm_100 SASS, and the bundled CUDA 12.8
  nvcc cannot compile compute_103 (added in 12.9), which broke arch-list-honoring
  source / JIT builds.
2026-06-26 09:06:39 +00:00
Daniel Han
8402dcebdd docker-publish: pin one llama.cpp prebuilt release across both arch legs
The base build-args never passed LLAMA_PREBUILT_TAG, so the Dockerfile fell back
to latest and each matrix leg resolved whatever unslothai/llama.cpp release was
current at its own build time. If latest moved between the amd64 and arm64 legs,
one published manifest could carry different GGUF binaries per arch.

Resolve the release once in a new prepare job (explicit llama_prebuilt_tag
dispatch input for a frozen build, else follow the /releases/latest redirect to a
concrete tag, mirroring docker/build.sh) and pass that single tag to both legs.
2026-06-26 08:27:11 +00:00
Daniel Han
8f693c6207 docker: fix notebook pip-shim drops, first-boot overwrite, base :latest tag, arm64 decord
- pip shim: count editable/local/url/vcs targets (-e ., ., git+https, wheel
  URLs) as install targets, not just canonical package names, so they are no
  longer silently skipped inside notebooks
- notebook sync: never overwrite a pre-existing user notebook on first boot
  (match the refresh path's ownership rule); skip .unsloth_sync_state.tmp when
  recording state so it is not tracked as a managed file
- docker-publish: set flavor latest=false on the base image metadata so a v*
  tag push cannot publish :latest from the base image (the Studio image owns it)
- notebook deps: pin to tested versions and install decord on its own, hard on
  amd64 and fail-soft on arm64 (no aarch64 wheel) so the arm64 base build works
2026-06-26 08:20:36 +00:00
Daniel Han
053a4f3853 docker-publish: clean build-args + add least-privilege default permissions
- Move the explanatory prose out of the two `build-args:` blocks.
  docker/build-push-action forwards every non-empty line verbatim, so a
  leading-# line is passed as a bogus --build-arg; the comments now live above
  each block. This workflow has not run yet, so the issue was latent.
- Add a top-level `permissions: contents: read` default so every job (including
  smoke-test, which had none) limits the GITHUB_TOKEN. The merge jobs keep their
  own `packages: write` blocks. Addresses the CodeQL "workflow does not contain
  permissions" findings.
2026-06-26 05:41:51 +00:00
Daniel Han
c0abb0ab6a Merge remote-tracking branch 'origin/main' into dbb-merge-main
# Conflicts:
#	.gitignore
#	tests/studio/run_real_mlx_smoke.py
2026-06-26 05:37:15 +00:00
Daniel Han
ed5e2a1590
Verify linuxdeploy AppImage digest before use in desktop release (#6673)
* Verify linuxdeploy AppImage digest before use in desktop release

The desktop release workflow downloaded linuxdeploy-x86_64.AppImage from a
GitHub release and ran chmod +x with no integrity check. Pinning the
versioned release path is reproducibility, not integrity: a release asset
can be replaced (or its delivery path compromised) after upload. The next
step builds the AppImage with the Tauri signing private key and a
contents:write GITHUB_TOKEN in scope, so a substituted linuxdeploy that
ran during packaging could exfiltrate signing material or tamper with
published release artifacts.

Pin the immutable SHA-256 of the asset and verify it with sha256sum -c
before chmod +x, so a mismatch fails the job closed before the binary is
ever executable. Extend the existing in-workflow guard to require both the
pinned digest and the verification step, so a future edit cannot silently
drop the check.

* Scope linuxdeploy guard to real step content, not its own text

The self-check searched every workflow line, so the digest assertion was
satisfied by the guard's own expectedLinuxdeployDigest line and the
verification assertion by a comment. Deleting the LINUXDEPLOY_SHA256 env
pin or the actual sha256sum -c command would still have passed.

Match the digest against the LINUXDEPLOY_SHA256 env line specifically and
require sha256sum -c on a non-comment line, so dropping either the pin or
the verification now fails the guard.

* Scope linuxdeploy guard to the Pin step block and check ordering

The previous predicate still scanned the whole workflow, so the literal
sha256sum -c in the guard's own code satisfied the verification check; a
deleted or post-chmod verification command would still pass.

Extract the 'Pin linuxdeploy for AppImage' step block and assert within it:
the LINUXDEPLOY_SHA256 env pins the expected digest, a non-comment line
runs sha256sum -c, and that verification precedes chmod +x.
2026-06-25 20:45:24 -07:00
Wasim Yousef Said
2aef1a23cb
Fix Linux AppImage packaging (#6657)
* Fix Linux AppImage packaging stack

* Fix desktop release workflow guard
2026-06-24 19:40:00 -07:00
Wasim Yousef Said
e25e7895a5
Polish Studio desktop chrome (#6332)
* Polish Studio desktop chrome

* Fix desktop chrome chat header overlap

* Blend desktop titlebar with sidebar

* Refine desktop chrome alignment

* Fix desktop chrome review items

* Reserve mac sidebar chrome space

* Fix mac chrome review items

* Polish macOS desktop chrome

* Align macOS desktop chrome controls

* Lower macOS traffic lights

* Remove mac sidebar logo from chrome row

* Match Tauri update banner styling

* Update Tauri updater public key

* Fix Tauri startup screen spacing

* Work around AppImage WebKitGTK blank screen

* Mark Linux AppImage as experimental

* Address true desktop chrome review issues

* Fix remaining desktop chrome review issues

* Fix desktop titlebar inset review issues

* Refresh desktop platform after backend auth
2026-06-24 17:56:25 -07:00
Daniel Han
af3f29de83
Withhold HF_TOKEN from pull_request CI runs (#6600)
* Withhold HF_TOKEN from pull_request runs of CI workflows

The pull_request-triggered CI workflows check out and execute PR-controlled
code (install.sh, .github/scripts/**, tests/**) with secrets.HF_TOKEN in the
step environment. For a same-repo PR, GitHub provides repository secrets to the
run, so a malicious or compromised branch could modify a checked-out script to
read and exfiltrate HF_TOKEN, including by writing it into the uploaded logs/
artifact. HF_TOKEN is an external Hugging Face credential of unknown scope, so
this is the high-value exposure.

Gate every HF_TOKEN reference in these workflows with
`github.event_name != 'pull_request' && secrets.HF_TOKEN || ''`, so the real
token flows only on the trusted schedule/push/workflow_dispatch runs and PR runs
see an empty string. All model repos used by these jobs are public
(unsloth/*-GGUF), so anonymous download still works on PRs; install_llama_prebuilt.py
only sends HF auth to Hugging Face hosts and tolerates an absent token.

GITHUB_TOKEN (passed as GH_TOKEN) is intentionally left in place: it is the
auto-provisioned, job-scoped, contents:read token that expires with the job and
gives a same-repo PR author nothing they do not already have, and
install_llama_prebuilt.py needs it to authenticate the GitHub releases API or
the prebuilt llama.cpp download hits the anonymous rate-limit bucket and 403s.

* Trim the HF_TOKEN gating comments to one line per site

Comment/whitespace-only: collapse the per-step rationale to a single line and
shorten the local-agent-guides header note. No workflow logic changes (verified
each file's parsed YAML is identical before/after).
2026-06-23 03:59:12 -07:00
Daniel Han
70926822db
studio/setup.sh: guard empty CUDA arch detection in the source build (#5854) (#6481)
* studio/setup.sh: guard empty CUDA arch detection in the source build

PR #5826 hardened setup.sh for fresh CUDA toolkits, but the source build
still set -DCMAKE_CUDA_ARCHITECTURES only when nvidia-smi reported a
compute capability. When that query returns nothing the build proceeded
with no explicit arch list, so llama.cpp built PTX only. On a driver older
than the toolkit that binary fails at runtime with "the provided PTX was
compiled with an unsupported toolchain" - the build succeeds, so neither
the build-time check nor the CPU fallback caught it (issue #5854).

Resolve the arch list before committing to a CUDA build. A new pure helper
_resolve_cuda_archs parses and de-duplicates the nvidia-smi compute_cap
output and honors an explicit UNSLOTH_LLAMA_CUDA_ARCHS override. When the
result is empty, build CPU llama.cpp instead of a PTX-only binary, with a
clear message pointing at the override - so the user still ends up with a
working llama-server. The override also lets advanced users force a native
build on hosts where nvidia-smi cannot report compute_cap.

No behavior change when an arch is detected: -DGGML_CUDA=ON plus the arch,
CUDA flags and NVCC_PREPEND_FLAGS are assembled exactly as before.

Adds tests/sh/test_resolve_cuda_archs.sh (single/multi/dedup/empty/garbage/
whitespace/override cases), wired into tests/run_all.sh and the
studio-backend-ci.yml shell-test loop.

* studio/setup.sh: resolve nvidia-smi via /usr/bin fallback for arch detection

Addresses review feedback on the empty-CUDA-arch guard: _setup_has_usable_nvidia_gpu
classifies a host as NVIDIA-usable using nvidia-smi on PATH OR /usr/bin/nvidia-smi,
but the new arch detection probed only `command -v nvidia-smi`. On a GPU host where
nvidia-smi is off PATH (reachable only at /usr/bin), arch detection returned empty
and the new empty-arch branch dropped the build to CPU, losing CUDA. Mirror the same
PATH-then-/usr/bin resolution so those hosts still get a native CUDA build.

Also scope _resolve_cuda_archs locals with `local` (no behavior change; it already
runs under command substitution).

* tests: update compute_cap-probe assertion for $_smi_bin resolution

The nvidia-smi /usr/bin fallback parameterized the binary in the compute_cap
probe (_setup_run_smi "$_smi_bin" ...), so the literal-string assertion in
test_compute_cap_probe_timeout_wrapped no longer matched. Assert the probe is
preceded by _setup_run_smi (timeout-wrapped) instead, scanning all occurrences
so the comment mention is ignored. Same intent, binary-agnostic.

* tests: ruff-format the compute_cap probe assertion (pre-commit)

Collapse the backslash-continued assert onto one line and normalize slice
spacing so the ruff-format pre-commit hook (0.6.9) is satisfied. Formatting
only; no behavior change.

* Tighten code comments (no logic change)

* studio(windows): build CPU when CUDA arch is undetectable (#5854)

The Windows source build added -DGGML_CUDA=ON unconditionally but only set
-DCMAKE_CUDA_ARCHITECTURES when $CudaArch was detected. With no detectable
compute capability that produced a PTX-only binary, the same hole the Linux
fix closed. Build CPU llama.cpp in that case, and honor UNSLOTH_LLAMA_CUDA_ARCHS
to force a CUDA build, matching setup.sh. Detected-arch builds are unchanged.

* test: anchor NVCC_PREPEND_FLAGS scope check on the final CPU branch

The undetectable-arch CPU fallback adds an earlier -DGGML_CUDA=OFF, so the
ordering check now anchors on -DGGML_CUDA=ON and the last -DGGML_CUDA=OFF
instead of the first.

---------

Co-authored-by: Daniel Han <michaelhan2050@gmail.com>
2026-06-23 01:26:43 -07:00
Daniel Han
e226e0ac35
CI: fix import-hoist false positive, vision-cache test cwd, llama.cpp CLI smoke (#6598)
Three independent upstream CI fixes that currently fail on every open PR:

verify_import_hoist.py: TARGET-CHANGED only flags a genuine swap (a BEFORE
target no longer reachable in AFTER). A pure superset growth such as adding
import urllib.error next to import urllib.request binds the same top-level
package and loses nothing, so it is no longer a blocker (transformers_version.py).

test_vision_cache.py: run each test from a fresh empty cwd. is_vision_model
calls is_local_path first, and a relative model id that happens to exist on
disk short-circuits before the mocked detection runs; the CI cwd and HF cache
can contain dirs colliding with the synthetic ids, causing 'called 0 times'.
Production code is correct; only the test needed cwd isolation.

consolidated-tests-ci.yml: the llama.cpp smoke probes the first of
llama-cli / llama-mtmd-cli / llama-server that exists instead of hard-requiring
llama-cli, which upstream no longer always builds. llama-cli stays first so it
is preferred when present. Adds Windows .exe + build/bin/Release handling.
2026-06-23 01:16:47 -07:00
Daniel Han
264f1a04f8
Add Local Agent Guides CI (#6547)
Boot `unsloth run --disable-tools` against a small GGUF and drive each
supported coding agent (claude, codex, hermes, openclaw, opencode, pi)
through its documented `unsloth connect <agent> --no-launch` recipe, so
the connect flow in unsloth_cli/commands/connect.py stays exercised end
to end and regressions surface as a failing check.

Per-agent matrix, three jobs:
- connection: assert a non-empty, error-free reply to a trivial prompt
- file-edit: a two-turn create-and-run hello.py test (dispatch/schedule
  only, skipped on pull_request)
- prompt-cache: verify llama.cpp prefix-cache reuse across requests

The GitHub-hosted runners are CPU-only, so each request is trimmed to
the smallest prompt that still drives the recipe: claude with --tools to
drop unused tool schemas (--allowedTools only gates permission, it does
not shrink the prompt), hermes with an empty platform_toolsets.cli, and
openclaw with a minimal agent definition. hermes and openclaw run a
multi-turn tool loop in file-edit that a CPU runner cannot finish in
time, so those two cells are best-effort; their endpoint wiring is still
hard-gated by the connection job.

A preflight step HTTP-checks each agent's API dialect before install so a
server-side contract regression is reported separately from agent or
guide drift.
2026-06-22 04:21:48 -07:00
Daniel Han
e83d4ae072
Windows installer: fix DiskPart UAC mid-install, drive-root cache, and spurious unsloth.exe rename warning (#6296)
* Windows installer: fix DiskPart UAC, drive-root cache, spurious rename warning, CPU-base messaging

amd-smi gate (DiskPart UAC mid-install): the AMD torch wheel ships hipInfo.exe
inside the venv, and the bitsandbytes fix prepends that venv Scripts dir to PATH.
shutil.which("hipinfo") then found it and flipped _amd_smi_allowed() to True, so
the post-install AMD probe fell through to `amd-smi list` (the venv hipInfo failed
to report gcnArchName, which is why the arch came from the GPU-name table) and
amd-smi elevated, popping the DiskPart UAC. Fix: a hipinfo resolved inside the
active venv (sys.prefix) is the torch-wheel binary, not a HIP SDK, and must not
open the gate. Mirrored in install_python_stack.py, install_llama_prebuilt.py, and
backend utils/hardware/amd.py (the runtime VRAM poller had the same latent prompt).

TORCHINDUCTOR_CACHE_DIR: move from C:\tc to <StudioHome>\TORCHINDUCTOR_CACHE_DIR so
the inductor/Triton cache lives under the user's Studio home, not the system drive
root. Long paths are already enabled above so deep inductor paths still fit.

unsloth.exe rename: skip the rename (and its "pip may fail with WinError 32"
warning) when SKIP_STUDIO_BASE=1. In the install.ps1 flow base packages are not
reinstalled, so unsloth.exe is never rewritten; the self-rename only failed because
setup runs via unsloth.exe (the running launcher holds its own file). The
'studio update' flow still attempts it.

CPU PyTorch messaging: clarify that the CPU base is temporary and setup replaces it
with GPU ROCm wheels, and print an explicit "GPU ROCm PyTorch installed" line after
the AMD wheels land, so the log makes clear the final install is GPU-accelerated.

Adds two regression tests covering the venv-internal vs external hipInfo gate.

Verified end-to-end on a Strix Halo box (Radeon 8060S / gfx1151): install.ps1
--local from this branch completed exit 0 with no DiskPart prompt, no rename
warning, the cache under the Studio home, and "GPU ROCm PyTorch installed
(gfx1151)"; Studio then booted and detected "ROCm (HIP 7.13.99004) -- AMD Radeon
8060S Graphics".

* Windows installer: drop the unreliable unsloth.exe rename and its WinError 32 warning

setup.ps1 used to rename the running unsloth.exe out of the way before the
base-package upgrade so pip could replace it. That rename never actually
worked: setup runs *via* unsloth.exe, so renaming our own running
uv-trampoline launcher failed with a sharing violation (WinError 32) and only
printed a scary 'could not rename unsloth.exe; pip may fail with WinError 32'
warning on every Windows install and update.

It also was not needed. pip tolerates a running/locked console-script .exe: it
moves the old one aside and writes the new one. The base upgrade routes through
pip on Windows, so the upgrade succeeds (or, in the install.ps1 flow with
SKIP_STUDIO_BASE=1, the base is not touched at all) and unsloth.exe is left
intact either way.

Removing the rename block and its failed-install restore block removes the
false warning for all Windows devices in both the install and update flows.

* Windows installer: gate venv-internal hipInfo.exe in PowerShell amd-smi probe; harden venv path checks

Follow-up to PR #6296.

- install.ps1 and setup.ps1: ignore the AMD torch wheel hipInfo.exe that lives
  inside the Studio venv when probing for a HIP SDK, so amd-smi no longer reopens
  the DiskPart UAC during install/update. Mirrors _path_inside_venv in the Python
  installers, which already do this.
- amd.py, install_llama_prebuilt.py, install_python_stack.py: normcase the venv
  containment check (Windows paths are case-insensitive) and run the
  HIP_PATH/ROCM_PATH candidate through it too.
- setup.ps1: fall back to a short TORCHINDUCTOR cache dir when long paths are
  unavailable, and create the dir wildcard-safely.
- tests: isolate sys.prefix in the gate helper, add HIP_PATH/ROCM_PATH cases, and
  assert the PowerShell venv exclusion.

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Windows installer: install ROCm PyTorch directly for a known AMD arch

When the GPU arch is known (name-inferred from the GPU-name table) but ROCm
could not be probe-verified (no HIP SDK, no amd-smi), the bootstrap installed
a CPU PyTorch base that setup.ps1 then force-reinstalled as ROCm. The
repo.amd.com wheels bundle their own runtime (no HIP SDK required), which
setup.ps1 already relies on, so the CPU base was a pure wasted download/install.

- Gate the ROCm index on a known arch, not only on probe-verified ROCm, so a
  mapped arch installs ROCm torch directly. Unmapped arches and no-GPU hosts
  still get CPU (unchanged).
- Fall back to a CPU base if the ROCm-index install fails, so a transient
  repo.amd.com outage does not abort the install (setup.ps1 retries ROCm).
- Correct the stale comment that claimed ROCm wheels need a confirmed HIP SDK.
- Add a regression test for the arch-based gate.

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Windows installer: correct the unsloth.exe rename-removal comment

The comment claimed the base upgrade 'routes through pip on Windows' and that
pip 'moves the old unsloth.exe aside, then writes the new one'. That is not what
the code does. install_python_stack tries uv first; on a locked launcher uv
aborts and falls back to pip, but the pip fallback strips --upgrade-package and
base.txt lists only bare unsloth/unsloth-zoo, so pip finds them already
satisfied and no-ops. The running unsloth.exe is left intact at its current
version either way. Reword the comment to describe the real uv-first /
pip-fallback-no-op behavior. No functional change.

* Windows installer: close two gaps in the venv-internal hipinfo exclusion

Review follow-up. The amd-smi/DiskPart gate could still reopen in two cases:

- setup.ps1 ran the HIP probe long before $VenvDir is assigned, so without
  VIRTUAL_ENV (the `unsloth studio update` path) $venvRoots was empty and the
  venv-internal hipInfo.exe was not recognized. Seed the venv root from
  UNSLOTH_SETUP_PYTHON and the default Studio home too (both installers).
- The HIP_PATH/ROCM_PATH candidate was accepted without the venv filter, so an
  env var pointing into the venv (AMD wheel) still set $HipSdkInstalled. Run
  Test-HipinfoIsVenvInternal on the candidate as well (both installers).

Extend the PS gate test to assert both. Both .ps1 parse clean; install tests
pass (the venv-internal / HIP probe coverage at 359 passed).

* Windows installer: correct the CPU-base message for arches with no ROCm wheels

After gating the ROCm index on a known arch, a mapped arch sets $ROCmIndexUrl
and installs ROCm directly, so it no longer reaches the "temporary CPU base"
branch. That branch is now reached only by a name-inferred arch with no ROCm
wheels (e.g. RDNA2 gfx103X), where setup.ps1 does NOT install ROCm. The old
text ("setup replaces it with GPU ROCm wheels ... the final install IS
GPU-accelerated") was therefore always wrong there. Say plainly that PyTorch
stays on CPU for this GPU.

* Windows installer: seed the venv-internal hipInfo check from a custom Studio home

Test-HipinfoIsVenvInternal seeded the venv root from VIRTUAL_ENV, VenvDir, the
setup python, and the default %USERPROFILE% path only. A standalone
`unsloth studio update` with a custom UNSLOTH_STUDIO_HOME (or STUDIO_HOME alias)
and none of those set would not recognize the venv hipInfo on PATH, reopening the
amd-smi/DiskPart gate. Seed the custom home too, in both installers, and assert
it in the gate test.

* Studio installer: resolve venv aliases and expand ~ in the hipInfo venv filter

Two review points on the amd-smi/DiskPart UAC gate:

1. _path_inside_venv compared os.path.abspath of sys.prefix and the hipInfo
   path, which does not resolve symlinks, junctions, or 8.3 short names. A venv
   reached through an aliased path then fails the check, so its bundled
   hipInfo.exe is mistaken for an external HIP SDK and amd-smi runs (the
   DiskPart prompt this fix exists to suppress). Switch to os.path.realpath in
   all three copies (amd.py, install_llama_prebuilt.py, install_python_stack.py).

2. setup.ps1's early venv-internal hipInfo probe seeded the venv root from a
   custom Studio home (UNSLOTH_STUDIO_HOME / STUDIO_HOME) without expanding a
   leading ~, while the canonical resolver does. With a tilde form,
   [IO.Path]::GetFullPath kept the literal ~ relative to cwd, so the custom-home
   hipInfo escaped the filter and reopened the gate. Expand ~ in the probe the
   same way as the resolver.

tests/studio/install/test_pr5940_followups.py: 30 passed (adds a symlink
realpath case and a setup.ps1 tilde-expansion guard).

* Studio installer: mirror the hipInfo venv filter and ROCm wheel pins into install.ps1

Follow-up review on the same install.ps1 paths:

1. install.ps1's venv-internal hipInfo probe (Test-HipinfoIsVenvInternal)
   seeded the venv root from a custom Studio home without expanding a leading
   ~, unlike the canonical resolver and setup.ps1. A tilde form left
   [IO.Path]::GetFullPath with the literal ~ (relative to cwd), so the
   custom-home hipInfo escaped the filter and reopened the amd-smi/DiskPart
   gate. Expand ~ in the probe, matching the setup.ps1 fix.

2. The AMD ROCm path installed torchvision/torchaudio bare while pinning torch
   to below 2.12. AMD's per-arch index publishes the companions independently
   and may ship torchvision 0.27 (for torch 2.12) before removing 0.26, so a
   bare resolve can pick an ABI-incompatible set and fall back to CPU. Add
   torchvision/torchaudio floor maps and pass the pinned specs, mirroring
   setup.ps1 and install_python_stack.py.

3. The ROCm-to-CPU fallback torch install used Invoke-InstallCommand (no
   retry), the only torch step in the file without it. Switch to
   Invoke-InstallCommandRetry so the recovery path survives a transient index
   failure.

tests/studio/install/test_pr5940_followups.py: 33 passed (parametrized tilde
check over both installers, a torch/companion floor-map parity test, and a
CPU-fallback retry guard).

* Studio installer: scan all PATH hipinfo so the venv copy can't shadow a real HIP SDK

The amd-smi HIP-SDK probe used shutil.which("hipinfo") / Get-Command hipinfo,
which return only the first hit on PATH. The AMD torch wheel ships hipInfo.exe
inside the venv and the bnb fix (plus the Studio backend) prepend the venv
Scripts dir to PATH, so that venv-internal copy lands first. When a real HIP SDK
hipinfo sits later on PATH with HIP_PATH/ROCM_PATH unset, the first-hit probe
stopped at the venv copy, treated it as "not a HIP SDK", and closed the amd-smi
gate -- AMD users in that PATH-only SDK setup lost amd-smi telemetry and could
fall back to CPU. Scan every PATH entry and keep the first hipinfo that is not
venv-internal; only the venv copy is ignored, so the UAC/DiskPart suppression is
unchanged.

Applied to all three Python copies (install_llama_prebuilt.py,
install_python_stack.py, backend/utils/hardware/amd.py) via a new
_external_hipinfo_on_path helper, and both PowerShell callers (install.ps1,
setup.ps1) now use Get-Command hipinfo -All filtered by Test-HipinfoIsVenvInternal.

tests/studio/install/test_pr5940_followups.py: 36 passed (real-PATH scan tests, a
shadow-regression test for the exact venv-first ordering, and a parity check that
every Python copy uses the scanning helper).

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Studio uninstallers: fix leftovers (false "removed", shared icon, llama lock)

Auditing a dual native+WSL uninstall on a real device surfaced three leftovers:

1. uninstall.ps1 removed the data dir (which holds unsloth.ico) before the
   shortcuts that reference that icon, so Explorer's icon cache briefly held it
   open. Remove-Item -Recurse reported success yet left the locked file, and the
   dir was never re-attempted, so it orphaned with a false "removed" log.
   _RemovePath now verifies the path is actually gone (retrying transient locks)
   and reports honestly, and the data dir is re-swept after the shortcuts go.

2. install.sh writes a shared unsloth.ico to %LOCALAPPDATA%\Unsloth Studio for
   the WSL shortcut, but uninstall.sh never removed it, orphaning the icon (and
   dir) after a WSL uninstall. uninstall.sh now drops that icon and the dir when
   empty, in both the powershell.exe and drvfs-fallback paths.

3. ~/.unsloth/.llama.cpp.install.lock was never removed, so the rmdir of
   ~/.unsloth failed and the dir lingered. Both uninstallers now remove the lock.

Verified by running both uninstallers on a real dual install: device fully clean
(no install dirs, shortcuts, PATH/registry entries, shared icon, or lock left).

* install.sh: auto-route Strix Halo WSL to an existing Ubuntu 24.04

ROCm-on-WSL is the GPU runtime for Strix Halo and only targets Ubuntu
24.04. When the installer runs in a newer default distro (e.g. 26.04) it
cannot enable the GPU and silently falls back to CPU. If a 24.04 distro
already exists, re-run the install there and stop in the current one so the
GPU path is taken without the user having to know about the distro
requirement.

Runs before venv creation so the wrong distro is left untouched, guards
against re-route loops via UNSLOTH_WSL_REROUTED, leaves a working ROCm
distro alone (librocdxg present), and skips the GGUF-only / opt-out /
non-Strix cases. When no 24.04 distro exists we keep today's behaviour:
continue to CPU and print the `wsl --install Ubuntu-24.04` guidance, never
auto-downloading a distro.

Adds tests/sh/test_strixhalo_wsl_reroute.sh (hermetic: extracts the
function, rewrites its paths to fixtures, mocks wsl.exe) covering the full
decision matrix, wired into tests/run_all.sh.

* uninstall.ps1: keep shared unsloth.ico for a surviving WSL shortcut

A dual native+WSL install shares %LOCALAPPDATA%\Unsloth Studio\unsloth.ico:
install.sh points the WSL shortcut's icon there while the native install owns the
dir. The native uninstaller removed the whole dir unconditionally, so uninstalling
native while keeping WSL left the WSL shortcut with a blank icon. The old code only
avoided this when Explorer happened to hold the icon open, which is unreliable; on a
real dual install the dir was deleted and the WSL shortcut went blank.

_RemoveDataDirKeepingWslIcon now scans the Start Menu + Desktop for a surviving
"Unsloth Studio (WSL ...).lnk" and, if found, removes everything in the data dir
except unsloth.ico (keeping the dir) instead of deleting it; with no WSL shortcut it
removes the dir as before. uninstall.sh still drops the icon and the empty dir when
WSL itself is uninstalled, so every uninstall order ends clean.

Adds tests/studio/test_uninstall_dual_install_icon.ps1 (AST-extracts the helper and
runs it against a temp dir with controlled shortcut dirs) covering the dual,
native-only, empty, and missing-dir cases, wired into the windows-inference smoke
workflow. Verified on a real dual install: native uninstall now keeps unsloth.ico
and the WSL shortcut's icon stays intact.

* installer: condense AMD/ROCm code comments (no behavior change)

Tighten the comments added for the Strix Halo native+WSL installer work so
they are shorter and clearer without losing intent: the venv-internal hipInfo
amd-smi gate, the ROCm torch/companion floor maps, the WSL 24.04 reroute, and
the dual-install uninstall icon handling. Comment-only; code paths unchanged.
107 insertions, 166 deletions across 11 files.

* install.sh: run the Strix Halo WSL reroute before any STUDIO_HOME write

The reroute fired after mkdir -p "$STUDIO_HOME" and the legacy-venv migration,
so rerouting 26.04 -> 24.04 left an empty ~/.unsloth/studio stub in the origin
distro (and ran venv migration in the distro about to be abandoned). Move the
reroute ahead of the venv section so the origin distro is left untouched, matching
the function's own comment. Behavior is identical on every non-reroute path.

* installer: fix ROCm CPU-fallback, hipinfo gate edge cases, uninstall icon, WSL 22.04

- install.ps1: clear $ROCmIndexUrl/$ROCmTorchFloor after the CPU fallback so the
  flavor-repair block does not retry the failed ROCm index and abort the install;
  pin the ROCm companion specs ($visionSpec/$audioSpec) in the repair path too.
- install.ps1 + setup.ps1: skip a bare drive root in Test-HipinfoIsVenvInternal so a
  non-venv UNSLOTH_SETUP_PYTHON does not match the whole drive; iterate
  HIP_PATH/HIP_PATH_57/ROCM_PATH and take the first non-venv hipinfo.
- amd.py, install_llama_prebuilt.py, install_python_stack.py: strip surrounding
  quotes from PATH entries before probing for hipinfo.
- install.sh: pipefail the WSL reroute curl|sh; do not reroute supported Ubuntu 22.04.
- uninstall.sh: keep the shared unsloth.ico while any Unsloth shortcut (native or
  another WSL distro) still references it, in both the powershell and drvfs paths.
- tests: regression coverage for all of the above.

* installer: forward reroute options, guard ROCm bootstrap, harden hipinfo gate

- install.sh: forward the caller's --package/--python/--verbose/--tauri and a custom
  UNSLOTH_STUDIO_HOME into the WSL reroute (was a bare default install); bail on
  --local; run the reroute BEFORE dependency/uv install so the origin distro is left
  untouched; set UNSLOTH_SKIP_ROCM_WSL_SETUP after a failed reroute so the later
  ROCm-on-WSL bootstrap does not install into the unsupported origin distro.
- install.ps1 + setup.ps1: Get-Command hipinfo -CommandType Application so only real
  executables match (not an alias/function named hipinfo).
- uninstall.ps1: guard $env:APPDATA when building the default shortcut search dirs.
- tests: cover option forwarding, --local bail, the bootstrap guard, and the gate change.

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* installer: guard origin ROCm bootstrap on every CPU-only fallback; harden ~ expansion

WSL reroute: the no-wsl.exe, no-24.04-target and --local fallbacks all tell the
user the install continues CPU-only, but only the failed-reroute branch set
UNSLOTH_SKIP_ROCM_WSL_SETUP=1. The later _maybe_bootstrap_rocm_wsl gate keys off
that flag, so the other three branches could still install ROCm into the
unsupported origin distro (e.g. 26.04). Set the skip guard on all of them.

Forward UNSLOTH_ROCM_WSL_AUTO into the reroute so a Tauri/consented GPU bootstrap
carries through to the rerouted 24.04 child instead of dropping to the prompt path.

install.ps1/setup.ps1: guard the venv-probe ~ expansion on a non-empty
$env:USERPROFILE so Join-Path does not throw on a profile-less service account.

Tests: add no-wsl.exe and UNSLOTH_ROCM_WSL_AUTO reroute cases, the USERPROFILE
guard assertion, and route shell-test fixtures through a single trap-cleaned root.

* installer: pin + soften Windows ROCm Python repair, reroute to 22.04, harden gates

install_python_stack.py: the Windows AMD ROCm repair in _ensure_rocm_torch()
installed bare torch/torchvision/torchaudio via the fatal pip_install -- the same
asymmetry already fixed on the PowerShell side. A transient repo.amd.com failure
could abort the whole install even after install.ps1/setup.ps1 fell back to CPU.
Pin companions per-arch (gfx120X/Strix -> the rocm7.2 trio, mirroring the PS floor
maps) and make the retry nonfatal: keep the existing build and let the user re-run
update to retry ROCm, so the chain install.ps1 -> setup.ps1 -> stack stays CPU-safe.

install.sh: reroute now targets an installed Ubuntu 24.04 OR 22.04 (24.04 preferred);
both are AMD-supported for ROCm-on-WSL, matching the leave-alone set, so a box with
only 22.04 reaches the GPU instead of staying CPU-only.

install.ps1/setup.ps1: a bare ~ for UNSLOTH_STUDIO_HOME left an empty Join-Path child
(PS 5.1 throws); fall back to USERPROFILE directly and only join a real remainder.

_path_inside_venv (amd.py + both installers): guard a root-dir sys.prefix so commonpath
can't classify every path on the drive as venv-internal (defensive; venv never at root).

uninstall.sh: guard an empty LOCALAPPDATA in the PS-interop icon cleanup (mirror APPDATA).

Tests: add 22.04-target reroute cases, Windows ROCm pin+nonfatal coverage (text +
behavioral), root-dir guard coverage, and bare-~/LOCALAPPDATA guard assertions.

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* install.sh: match WSL reroute target by exact distro name, not substring

The 24.04/22.04 reroute target was chosen with grep -F (substring), so a custom
distro such as 'Ubuntu-24.04-test' (with no exact Ubuntu-24.04) was picked as the
target; the later 'wsl -d Ubuntu-24.04' then fails and the Strix Halo install stays
CPU-only. Match whole lines (grep -ixF) and reuse the matched name so only a real
Ubuntu-24.04/22.04 is targeted. Adds substring-rejection + exact-vs-custom tests.

* install.sh: keep the WSL reroute target to Ubuntu 24.04 (helper-supported only)

The ROCm-on-WSL bootstrap (scripts/install_rocm_wsl_strixhalo.sh) dies on any
VERSION_ID other than 24.04 and pins the noble repo, so treating 22.04 as
GPU-supported let the parent report a successful reroute while the child fell
back to CPU. Drop 22.04 from the supported set and the reroute target list;
24.04 stays the sole target (keeping the exact whole-line distro match). An
already-working ROCm on any other version is still left alone by the librocdxg
check above.

tests: reroute 22.04 cases updated to the 24.04-only behavior; make the
"no wsl.exe" case hermetic so a real host wsl.exe can't leak in on dev boxes;
stop the tauri exit-order check from mis-flagging the reroute helper's
[ "$TAURI_MODE" = true ] && ... --tauri one-liner.

* installer: tighten comment wording across the Strix Halo install/uninstall paths

Condense the verbose multi-line comment blocks (amd-smi hipinfo gate, ROCm
torch install + CPU fallback, WSL reroute, uninstall icon-keep) into fewer,
clearer lines. Comments and a few docstrings only; no code, logic, or
behavior change. Verified with bash -n, the PowerShell parser, and ast.parse,
and the installer test suite still passes.

* add AGPL-3.0 SPDX headers to the .sh/.ps1 scripts missing them

Every shell and PowerShell script under the Studio/installer surface now
carries the standard SPDX-License-Identifier: AGPL-3.0-only + copyright
header (after the shebang where present): the installer (install.sh,
install.ps1), build.sh, the .github and src-tauri scripts, the installer
test suite, and the moe kernel test. Header-only, line endings preserved;
bash -n, the PowerShell parser, and the installer tests all pass.

* installer: drop the duplicate AGPL header from install.sh and install.ps1

Both already carry an SPDX-License-Identifier: AGPL-3.0-only header below
their usage comment block; the prior header pass added a second one at the
top because it only scanned the first few lines. Remove the duplicate so each
file keeps a single original header.

* installer: force-reinstall CPU fallback torch; propagate Tauri NEED_SUDO from reroute

install.ps1/setup.ps1: when the AMD ROCm wheel install fails and we fall back to a
CPU base, force-reinstall the torch/vision/audio triplet. A failed ROCm install can
leave an unpinned ROCm torch (e.g. 2.10.0+rocm on gfx110X/gfx90a) that still
satisfies the CPU torch>=2.4,<2.11.0 range, so without --force-reinstall uv keeps the
ROCm build and only swaps the companions -- a mismatched venv the flavor-repair block
won't fix. setup.ps1 scopes the forced reinstall to the ROCm-fallback path
() so the genuine CPU-only install stays fast.

install.sh: the Strix Halo WSL reroute treated every nonzero child exit as a reroute
failure and fell back to CPU. In --tauri mode the child uses exit 2 ([TAURI:NEED_SUDO])
to ask the desktop app to elevate for the target distro; capture the child's exit code
and propagate exit 2 in Tauri mode (the child already printed the NEED_SUDO line)
instead of masking it. CLI mode still falls back to CPU on a generic failure.

Tests: reroute Tauri exit-2 propagation (and non-Tauri CPU-fallback) cases;
run_func now preserves the child exit code; force-reinstall assertions for both
PowerShell installers.

Note: codex's _rr_q apostrophe finding is a false positive -- the helper already
emits POSIX-correct 'O'\''Brien' and round-trips under both sh and bash.

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* setup.ps1: fix $cpuForce array collapse in the ROCm->CPU torch fallback

An if-expression assignment ($cpuForce = if ($ROCmCpuFallback) { @("--force-reinstall") })
collapses the single-element array to a scalar string, so @cpuForce splatting enumerated
it character-by-character into broken single-letter args (- - f o r c e ...), which made
uv/pip reject the install and aborted the whole Studio setup on the AMD ROCm->CPU fallback
path. Build $cpuForce as a real array assigned outside the if-expression so the splat passes
a single --force-reinstall arg. Genuine CPU-only installs stay fast (empty array, no flag).
Test now asserts the array-build form and rejects the if-expression form.

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* uninstall: remove the isolated Node.js runtime (~/.unsloth/node)

The isolated Node.js runtime (install_node_prebuilt.py, added with the managed-Node
change) installs to ~/.unsloth/node in default mode -- a sibling of studio, so deleting
<studio> leaves it behind (~200MB orphaned after uninstall). Both uninstallers already
remove the other default-mode siblings (llama.cpp/.cache/.staging); add node alongside
them. uninstall.ps1 also adds it to the handle-lock sweep so a held node.exe can't block
the delete. Env/custom mode nests node under the custom root, removed with that root.

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2026-06-22 03:09:08 -07:00
Daniel Han
a41b8c7a44
Make Visual Studio + CMake optional on Windows (prebuilt llama.cpp needs no build tools) (#6499)
* studio/setup.ps1: complete Visual Studio 2026 support for the CUDA llama.cpp build

Builds on #6038 (VS 2026 / v18 detection). Once the generator is detected as
Visual Studio 18 2026, two things still broke the CUDA llama.cpp build:

- the CUDA to VS MSBuild integration copied the CUDA .targets into a hardcoded
  VC\v170 (VS 2022) BuildCustomizations folder, so a VS 2026 (v180) toolchain
  saw no CUDA toolset and cmake failed with "No CUDA toolset found".
- cmake was installed with no version check, but the "Visual Studio 18 2026"
  generator requires CMake 4.2+.

This adds Get-VcBuildCustomizationsDir (derives v160/v170/v180 from the detected
generator, falls back to v170), a CMake 4.2 guard for the VS 2026 generator
(upgrades via winget once, else fails with a clear message), and routes both the
copy target and the failure hint through the derived path.

No behavior change for VS 2022/2019/2017: the folder resolves to v170 and the
guard is skipped. Adds windows-latest Pester unit tests (tests/studio_setup_ps1)
plus a workflow that runs them.

* Address review: make VS 2026 self-contained + gate CMake guard to source build

- Find-VsBuildTools now detects VS 2026: vswhere catalog_productLineVersion 2026
  -> "Visual Studio 18 2026", and the filesystem scan covers the "18"/"2026" dirs
  (incl. non-standard editions like Preview). Adapted from #6038 by
  @LeoBorcherding, so the v180 BuildCustomizations path and the CMake guard are
  actually reachable on a VS 2026-only host.
- Move the CMake 4.2 guard out of Phase 1 into the committed-source-build branch.
  The preferred prebuilt llama.cpp path never reaches it, so a VS 2026 host on
  CMake < 4.2 is no longer blocked from using the prebuilt.
- winget upgrade -> install fallback when the on-PATH cmake is not the Kitware
  winget package, and log winget failures instead of swallowing them.
- Add a windows-latest Find-VsBuildTools VS 2026 discovery regression test.

* tests(vs2026): define New-FakeVsTree in BeforeAll so It blocks can see it

The Find-VsBuildTools discovery tests are Windows-only (-Skip on non-Windows),
so they first ran on the windows-latest Pester job, where New-FakeVsTree raised
CommandNotFoundException: it was defined in the Describe body, which Pester 5
executes only during discovery, so the function did not persist into the
run-phase It scope. Move it into a BeforeAll block (which runs in the run phase
and is visible to the It blocks). No production code change.

* Address review: probe cmake generator support, fall back to older VS, fix cmake PATH after winget

The VS 2026 CMake guard previously gated only on the cmake version (>= 4.2)
and hard-failed otherwise. Review on #6473 raised three real gaps:

- A VS-bundled cmake below 4.2 can still drive the VS 2026 generator. Probe
  cmake --help (Test-CmakeListsGenerator / Test-CmakeCanDriveGenerator) and
  accept it when the generator is advertised, not just on the version floor.
- After winget upgrade/install, an older cmake earlier on PATH kept being
  resolved. Add-DefaultCmakeToPath prepends the default install dir so the new
  cmake wins before re-probing.
- When cmake cannot drive VS 2026 but an older Visual Studio (2022/2019/2017)
  is installed and usable, fall back to it (Get-FallbackVsGenerator) instead of
  hard-failing, preserving the pre-VS-2026 build path.

Tests mock the cmake command rather than dropping a shim on PATH: PowerShell
caches its application-path table, so a real cmake on the runner (present on
windows-latest) wins over a PATH shim. A function mock is resolved first and is
cache-proof cross-platform.

* Detect VS installed under the Preview edition dir for older versions

Find-VsBuildTools already scans every subdir for VS 2026, but the older-version
(2017/2019/2022) filesystem fallback and Get-FallbackVsGenerator only checked
BuildTools/Community/Professional/Enterprise. A Preview-channel install lives
under a 'Preview' edition folder, so it was missed when vswhere was also
unavailable. Add 'Preview' to both edition lists and guard each with a Windows
Pester test.

* Add real-VS integration matrix: detect actual VS 2022 and VS 2026 in parallel

The unit tests validate VS detection logic with mocked vswhere and fake install
trees (all five versions). This adds a parallel integration job that runs the
real Find-VsBuildTools / Get-VcBuildCustomizationsDir against the Visual Studio
actually preinstalled on GitHub-hosted runners:
  - windows-2022        -> real Visual Studio 2022, expect generator v170
  - windows-2025-vs2026 -> real Visual Studio 2026, expect generator v180
It asserts our detection matches the real install, the install path exists, the
derived toolset matches, and that the derived v-number is a real folder on the VS
install. VS 2017/2019/2015 are retired from hosted images, so only 2022 and 2026
can be exercised against a genuine install; the rest stay covered by the mocks.

* Detect VS 2026 via vswhere: it reports productLineVersion '18', not '2026'

Real-VS CI on the windows-2025-vs2026 runner showed vswhere reports
catalog_productLineVersion='18' (the internal major) for Visual Studio 2026, not
the marketing year '2026' that VS <= 2022 report. The vswhere map only had
'2026', so on a real VS 2026 host the vswhere branch returned null and detection
survived only via the filesystem scan (Source='filesystem'); a VS 2026 installed
outside the default Program Files location would not be found at all.

Extract a pure Resolve-VsGeneratorFromLabel that accepts both the year and the
internal-major form ('18'/'17'/'16'/'15' as well as '2026'/'2022'/'2019'/'2017')
and use it for both the vswhere and filesystem branches. Add pure unit tests
(cross-platform) for the mapping, including the '18' -> VS 2026 case.

* ci: dot-source Resolve-VsGeneratorFromLabel in the real-VS integration job

Find-VsBuildTools now calls Resolve-VsGeneratorFromLabel, so the integration
step must extract it too; without it the job failed with the helper not
recognized.

* Defer Visual Studio + CMake to the llama.cpp source build (prebuilt path needs no build tools)

The Windows installer required Visual Studio Build Tools and CMake eagerly in
Phase 1 (winget install + exit 1 if absent), before the llama.cpp prebuilt-vs-
source decision. But the preferred path downloads a prebuilt llama.cpp (no
compiler), the backend only shells out to the prebuilt llama-server.exe, and
PyTorch is pip wheels -- so VS and CMake are only needed for the from-source
build last resort. The eager requirement forced every Windows user to install
multi-GB Visual Studio + CMake they never use, or the installer failed.

Change (mirrors the already-lazy Resolve-CudaToolkit / OpenSSL):
- Phase 1c/1d now only DETECT cmake / VS and log; they never winget-install or
  exit. The prebuilt install runs zero build-tool installs and is unblocked on
  hosts without build tools.
- New Ensure-BuildToolsForLlamaSourceBuild installs CMake (best effort) + VS
  (hard requirement, exit 1 with the existing guidance if it cannot be found),
  called only when a source build is actually committed, before
  Resolve-CudaToolkit. git stays eager (pip needs it for git+ deps).

Tests:
- Pester: the early probe (Find-VsBuildTools) returns null without exiting when
  no VS is present; Ensure-BuildToolsForLlamaSourceBuild no-ops when VS is
  already detected.
- New studio-windows-no-vs-smoke.yml: Job A renames Visual Studio + vswhere away
  and hides cmake, runs the real install.ps1 --local --no-torch, and asserts the
  prebuilt llama.cpp installed (no source-build fallback, no VS/CMake install),
  PyTorch CPU imports, the backend is healthy, and a /v1/chat/completions
  inference returns a reply -- all with no Visual Studio. Job B confirms the GPU
  CUDA prebuilt is available and the resolver runs without VS.

* Fix VS 2026 CUDA source build ordering and fallback VS discovery

Same fix as on the stacked base branch (studio-vs2026-cuda-msbuild):

- Move Resolve-CudaToolkit below the CMake gate/fallback in the source build path. It copies the CUDA MSBuild .targets into the current VS generator's BuildCustomizations folder, so running it before a VS 2026 to older-VS fallback left the .targets under v180 while cmake configured v170 ("No CUDA toolset found"). It now runs after the final generator is selected.
- Get-FallbackVsGenerator now queries vswhere first, matching Find-VsBuildTools, so a VS installed outside the default Program Files roots is found instead of failing with a hard exit.
- Add Pester regression tests: the source build resolves CUDA after the fallback, and the fallback queries vswhere.

* Ensure the Visual C++ Redistributable is present for the prebuilt llama.cpp and PyTorch

The prebuilt llama-server.exe and the PyTorch wheels dynamically link the MSVC runtime (VCRUNTIME140.dll, MSVCP140.dll, VCRUNTIME140_1.dll). The Universal CRT ships with Windows 10+, but the VC++ 2015-2022 redistributable does not, so a clean box can fail to launch llama-server or import torch with a missing VCRUNTIME140.dll.

- Add Test-VCRedistInstalled (System32 vcruntime140_1.dll, with a registry fallback gated on version 14.20+) and Ensure-VCRedist (winget Microsoft.VCRedist.2015+.x64, non-fatal), called as Phase 1b.5 so it runs even on the no-build-tools prebuilt path. It is a no-op when the runtime is already present, which is the common case.
- Add Pester tests for the detection: present via the DLL, present via the registry, absent, and an old 2015-only redist that is too low.

* Add a CI job that validates the VC++ runtime detection on a real Windows runner

Runs on windows-latest and windows-2025-vs2026: asserts Test-VCRedistInstalled reports present on the stock image, removes both detection signals (the System32 DLL via a redirected SystemRoot and the HKLM runtime keys, restorably) to confirm detection fires on a genuinely clean box, then does a literal uninstall/reinstall round trip with the official installer and the Ensure-VCRedist winget path. The runtime is restored before the job ends.

* Dot-source the full logging closure in the VC++ runtime CI job

Ensure-VCRedist calls step/substep, which reach Write-StudioStdoutMirror and Get-StudioAnsi; extract those too so the job does not fail with an unrecognized command. Also note that the runtime is ref-counted by Visual Studio on the hosted image, so the literal package uninstall is a no-op there (the clean-box section already proves detection fires when the runtime is genuinely absent).

* Tighten comments in setup.ps1, the VS2026 tests and workflow

Comment-only: condense the verbose helper/test/CI comments to one or two lines, drop the obvious ones, keep the non-obvious rationale. Verified comment-only by comparing the PowerShell code-token stream before and after (no code tokens changed); Pester suite still green.

* Fold the no-VS and setup.ps1 VS2026 Windows CI into studio-windows-inference-smoke.yml

Move the no-vs-cpu/no-vs-gpu-resolve and pester/vs-integration/vcredist-clean-box jobs into the existing Windows GGUF CI workflow and delete the two standalone files, so a studio change triggers one Windows workflow instead of three. Path filter gains tests/studio_setup_ps1/**; job keys and artifact names stay unique.

* CI: assert a Windows ROCm prebuilt exists in the no-VS resolve job

The no-vs-gpu-resolve job confirmed a Windows CUDA asset but never a ROCm one,
and the resolver step resolves to CPU on hosted runners (no AMD GPU), so the
AMD no-VS guarantee rode only on shared resolver code. Grep the per-gfx
windows-x64-rocm-gfx bundles in the same asset-availability step so a release
that drops the Windows ROCm prebuilts fails loudly.

---------

Co-authored-by: Daniel Han <michaelhan2050@gmail.com>
2026-06-22 01:11:09 -07:00
Daniel Han
9f39cc2c39
Studio: use an isolated Node.js for the frontend build instead of replacing the system Node/npm (#6533)
* Studio: use an isolated Node.js for the frontend build instead of replacing the system Node/npm

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Studio: address Node isolation review (no-Node probe crash, PATH refresh, OXC provisioning, venv python, runtime node resolver)

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Fix/adjust Node isolation for PR #6533

* Studio Node: don't cache a negative node resolution; accept Node metadata in setup.sh ownership guard

- node_runtime: memoize only a version-adequate executable so a Node installed
  by a separate-process 'studio update' is picked up without a backend restart.
- setup.sh: _studio_owned_adoptable also accepts UNSLOTH_NODE_PREBUILT_INFO.json,
  matching the setup.ps1 Node ownership guard (custom-home parity).

* Studio setup.ps1: skip OXC npm install gracefully when npm is absent

Mirror setup.sh's `command -v npm` guard so a pip-installed Studio with no
system Node skips the OXC runtime install (validator degrades at runtime) instead
of exit 1 aborting the whole setup. Tighten test_node_probe_guard.ps1's probe
regex so it only matches the two system-version probes, not this new npm guard.

* Wire test_node_probe_guard.ps1 into Windows CI for PR #6533

* Harden isolated Node install and probes for PR #6533

- install_node_prebuilt.py: keep an existing, still-usable isolated Node
  when nodejs.org's dist index is unreachable instead of aborting the
  update on a transient outage (existing_install_usable + tolerant fetch).
- install_node_prebuilt.py: pin NPM_CONFIG_PREFIX/npm_config_prefix and
  drop NODE_PATH in _run_node so any npm -g stays inside the isolated
  prefix; Windows npm otherwise writes to %APPDATA%\npm.
- install_node_prebuilt.py: resolve tar hard-link targets against the
  archive root (symlink targets stay link-parent relative).
- setup.ps1: wrap the system node/npm probes in try/catch so a present
  but broken shim degrades to the bundled Node instead of aborting setup.
- setup.ps1: run the isolated Node install with the handed-off/venv Python
  (ReusedSetupPython); the main resolver runs later and bare python may be
  a Store stub this early.
- setup.sh: log when the OXC validator runtime is skipped for missing npm,
  matching setup.ps1.
- node_runtime.py: move the version-floor comment onto _version_meets_floor.
- Tests for the offline-reuse and broken-shim paths.

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Trim verbose comments across the Studio Node installer for PR #6533

Comments-only pass: collapse the multi-line section banners to single lines,
drop comments that restate obvious code, and tighten the remaining docstrings
and "why" notes without losing intent. No code changes (verified with an AST
comment-only check on the Python files and a non-comment-diff scan on setup.sh
and setup.ps1). Net 109 fewer lines; the install, decision, and probe-guard
suites stay green.

* Harden Node install from review: validated Python, version floor, legacy home, lock race

For PR #6533, addressing the latest review pass:

- setup.ps1: run the isolated Node install with the validated reused/venv Python.
  An incompatible reused interpreter (old venv, conda, stale UNSLOTH_SETUP_PYTHON)
  is no longer used; fall back to the resolved python instead.
- setup.ps1: a STUDIO_HOME/UNSLOTH_STUDIO_HOME override equal to the legacy default
  now uses the legacy sibling node dir (~/.unsloth/node), matching the runtime
  resolver and setup.sh, so OXC can find the Node it installed.
- install_node_prebuilt.py: reject an explicit --node-version below the floor
  (^20.19 || >=22.12 || >=23) instead of installing a Node the build cannot use.
- install_node_prebuilt.py: atomically rename a stale install lock before unlinking
  so two concurrent runs without filelock cannot both acquire it.

Tests added for the version floor (parametrized + explicit-below-floor rejection).
Full install suite: 937 passed, 1 skipped; setup.ps1 parses; decision tests green.

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Address latest review: armv7l + later-fetch offline reuse for PR #6533

- install_node_prebuilt.py: reject 32-bit ARM (armv7l) up front. Node 24 LTS
  ships no linux-armv7l build, so the old path failed late with a confusing
  "no sha256"; it now fails fast with a clear unsupported-architecture error.
- install_node_prebuilt.py: extend the offline-reuse fallback to the SHASUMS and
  archive fetches. If index.json resolves a newer Node but a later download fails
  and a usable isolated Node is already on disk, keep it instead of aborting a
  non-force update.

Tests added: armv7l/armhf are unsupported; a SHASUMS failure keeps an existing
usable Node and re-raises when none is present. Full install suite: 941 passed.

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Add UNSLOTH_STUDIO_HOME node-dir tests (install side + resolver) for PR #6533

* Add regression tests pinning the reuse path read-only and isolating installer writes

Lock in the two invariants behind the isolated-Node design: reusing a good
system Node never mutates the user's Node/npm, and the installer's own npm
calls only ever write inside its install_dir.

- tests/studio/install/test_install_node_prebuilt_logic.py: assert _run_node
  redirects NPM_CONFIG_PREFIX/npm_config_prefix into install_dir and drops an
  inherited NODE_PATH; assert _ensure_npm_floor scopes the npm self-upgrade to
  install_dir (never -g against the system) and is a no-op once npm meets the floor.
- tests/sh/test_system_node_readonly.sh (new, wired into studio-backend-ci.yml):
  the setup.sh NODE_SOURCE=system arm runs no global install and sets no
  NPM_CONFIG_PREFIX, with a positive control that the bundled arm does.
- tests/studio/test_node_decision.ps1: symmetric structural guard that the prefix
  pin and the only global install (bun) live in the bundled branch, not the system arm.

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: wasimysaid <wasimysdev@gmail.com>
2026-06-21 21:17:29 -07:00
Daniel Han
a65672d947
Installer: repair stale/CPU-only PyTorch and warn on silent CPU fallback (NVIDIA + AMD, Win/Linux/Mac/WSL) (#5942)
* Windows installer: repair a stale CPU PyTorch instead of looping forever

A Windows machine with an NVIDIA CUDA 13 driver (e.g. RTX 6000 Pro on enterprise
drivers) could get permanently stuck at:

  Stale venv detected (torch cpu != required cu130).
  [ERROR] The existing Studio environment needs repair.
          Re-run install.ps1 so it can replace the environment safely with rollback.

Re-running install.ps1 did not help. install.ps1 installs torch with
"torch>=2.4,<2.11.0" --index-url .../cu130 but no --force-reinstall, so when a
torch==X+cpu is already present uv treats it as satisfying the range (PEP 440
ignores the +cpu/+cuXXX local label) and makes no change -- the CPU wheel is
never replaced. setup.ps1 then rejects the venv as cpu != cu130 and exits, but it
cannot create a venv or install torch, so the loop never resolves. The migrated-
venv branch also preserves existing torch and never reinstalls it.

After the install step, detect the installed torch flavor (cuXXX/cpu/rocm) and,
when it does not match the tag implied by the selected index, force-reinstall the
torch/torchvision/torchaudio triplet from the correct index via three
--reinstall-package flags. No-op on a healthy matching venv; skipped for
--no-torch, ROCm (already --force-reinstalls), and CPU-only machines.

Adds two pure helpers (ConvertTo-TorchFlavorTag, Get-ExpectedTorchFlavorTag), a
PowerShell unit test (tests/studio/test_torch_flavor.ps1), and a CI parse gate for
install.ps1 (previously unparsed).

* install.sh: repair a stale CPU PyTorch on Linux too (parity with install.ps1)

install.sh has the same latent bug as the Windows installer: the CUDA torch
install uses "torch>=2.4,<2.11.0" --index-url .../cuXXX with no
--force-reinstall, so an already-present torch==X+cpu satisfies the version
range (PEP 440 ignores the +cpu/+cuXXX local label) and uv leaves it in place.
The migrated-venv branch also preserves existing torch. Unlike Windows there is
no stale-venv check in setup.sh, so on Linux the symptom is silent CPU training
rather than a hard loop -- same root cause.

Mirror the install.ps1 fix: after the install block, detect the installed torch
flavor (_torch_flavor_tag) and, when it does not match the index tag
(_expected_torch_flavor_tag), force-reinstall the torch/torchvision/torchaudio
triplet from the selected index via --reinstall-package. No-op on a healthy
matching venv; skipped for --no-torch, ROCm (its own repair force-reinstalls),
and CPU-only / macOS hosts. Adds tests/sh/test_torch_flavor.sh (run in
studio-backend-ci and run_all.sh).

* Installer: catch CPU-fallback on AMD/WSL too (repair ROCm, warn when unfixable)

Extend the torch-flavor safety net beyond NVIDIA:

- install.sh now auto-repairs a stale CPU torch on standard pytorch.org ROCm
  indexes too (the rocm-index install path lacked --force-reinstall, unlike the
  Windows ROCm install). Reuses the rocm-adjusted $TORCH_CONSTRAINT + rocm index,
  so it pulls the correct ROCm wheels.
- Both installers gain a universal post-install warning: when a GPU build was
  expected (cuXXX / rocm, including the repo.amd.com gfx* arch indexes) but torch
  is still CPU-only, warn loudly instead of silently training on CPU. This catches
  the cases auto-repair cannot safely fix (AMD gfx arch indexes that need
  --find-links, a migrated AMD venv on Windows where the ROCm install was skipped).
- Mac / Intel / CPU-only hosts resolve to the cpu index -> expected == installed
  -> no-op, no false warning. WSL uses install.sh, so the NVIDIA repair + warning
  apply there.

Adds Get-InstalledTorchTag (ps1) and _torch_index_repairable (sh) helpers and
extends both unit tests. gfx*/AMD indexes now map to the 'rocm' expected flavor.

* Installer: tighten torch-flavor comments (no logic change)

Condense the rationale comments added for the stale/CPU PyTorch repair in
install.ps1, install.sh and the two helper unit tests; same intent, fewer
lines. Comment-only: AST parse of install.ps1/setup.ps1 clean, helper unit
tests (15 ps1, 24 sh under bash and dash) and the integration sims
(24 ps1, 28 sh) still pass, banner markers the sims slice on are unchanged.

* Installer: bound torch probe, auto-repair gfx, fix ROCm gate parity

install.ps1: in Get-InstalledTorchTag, call WaitForExit(30000) and drain stdout
and stderr asynchronously instead of reading stdout synchronously first, so a
hung or noisy "import torch" (a wedged CUDA/driver, the exact failure this PR
targets) can no longer block the probe past the timeout.

install.sh and install.ps1: treat the repo.amd.com gfx* indexes as plain
--index-url reinstallable. They are PEP 503 simple indexes uv resolves in full
(torch plus every transitive dep) via --index-url, the same URLs the fresh
ROCm install paths already use, so a stale CPU torch on AMD Strix now auto-repairs
to the correct ROCm build instead of only warning.

install.sh: include */gfx* alongside */rocm* in the bitsandbytes install and
ROCm torch repair gates, so a custom UNSLOTH_AMD_ROCM_MIRROR whose path lacks
/rocm/ still installs the AMD bitsandbytes build and repairs ROCm torch.

tests/sh/test_torch_flavor.sh: gfx indexes now assert repairable, plus a
gfx1151 case and an unknown-mirror not-repairable case.

* install.ps1: guard Get-InstalledTorchTag against an empty python path

Make the early return explicit for an empty $PythonExe instead of relying on
Test-Path -LiteralPath '' returning false, so the probe stays safe under
Set-StrictMode or a future refactor that drops the [string] annotation.
2026-06-18 08:57:17 -07:00
Daniel Han
d50a2e2d07
Studio: remove the Windows VBS launcher to clear the Kaspersky false positive (#6326)
* Studio: drop the VBS launcher to clear the Kaspersky false positive

The Windows shortcut launched Unsloth Studio through wscript.exe ->
launch-studio.vbs, and that VBS used CreateObject("WScript.Shell").Run to
start a hidden -ExecutionPolicy Bypass PowerShell. That wscript + .vbs +
bypass-powershell shape is the canonical trigger for generic VBS-dropper
heuristics (Kaspersky HEUR:Trojan.VBS.Agent.gen). The launcher is benign;
only its shape is the problem.

- install.ps1: stop generating launch-studio.vbs and point the Desktop /
  Start Menu .lnk straight at powershell.exe -WindowStyle Hidden running
  launch-studio.ps1. The shortcut is saved WindowStyle 7 (minimized) so the
  brief console flash is muted. launch-studio.ps1 (health poll, port,
  mutex, browser) is byte-for-byte unchanged.
- install.ps1: delete a pre-existing launch-studio.vbs on upgrade, so the
  flagged file does not linger on machines that already installed it.
- install.ps1 / install.sh: run the heavier ie4uinit -ClearIconCache plus
  StartMenuExperienceHost tile-cache rebuild only on a first install or a
  real icon change, instead of on every no-op reinstall. That repeated
  clear-cache plus kill cluster is itself a dropper-like behavioral pattern.
- tests: forbid VBS generation and require the legacy-VBS cleanup.

Linux, macOS and WSL install paths are unchanged. WSL already targets
wsl.exe from its .lnk and never used a VBS; its only change is the same
icon-cache gating.

* Studio: add launcher-chain smoke coverage to the Windows UI CI

The shortcut launch path was previously untested: studio-windows-ui-smoke
installed then booted `unsloth studio` directly, so a broken .lnk could ship
silently. After install the job now seeds a legacy launch-studio.vbs, asserts
the upgrade removed it, asserts the .lnk targets hidden powershell.exe (never
wscript.exe), and launches via the shortcut's stored command, waiting for
/api/health to report healthy.

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2026-06-16 04:00:18 -07:00
Daniel Han
c20f25afac
ci(studio): fix Linux tool-calling flake (Q4_K_XL) + capture server logs (#6360)
Linux tool calling used Qwen3.5-2B IQ3_XXS. That quant is aggressive
enough that the model intermittently emits a malformed tool call
(doubled </parameter>, stray </tool_call>), and llama-server's
peg-native parser rejects it with a 500, failing the job. Mac and
Windows already run this test at Q4_K_XL; align Linux.

Also copy ~/.unsloth/studio/logs (backend server log + llama-server log)
into the tool-calling and ui-smoke artifacts on stop. Previously only
studio.log + install.log were uploaded, so a /v1/chat/completions or
/api/inference/load 500 had no server-side traceback to diagnose from.
2026-06-16 03:43:35 -07:00
Daniel Han
21612c2e32
Package scanners: cut false positives and make the CI gate blocking (#6355)
* Package scanners: cut false positives and make the CI gate blocking

scan_packages.py and scan_npm_packages.py red-failed on legitimate
library code, so the security-audit steps were left advisory. Reduce
the false positives at the source and flip both gates to blocking.

scan_packages.py:
- Scan code only: blank comments and bare docstrings/doctests before
  matching (line numbers preserved), so prose and >>> examples cannot
  trip a finding.
- Drop the platform.system() branch from the anti-analysis regex (under
  DOTALL it matched across the whole file, so every cross-platform
  library tripped it) and fix the dead /proc/self/status alternative.
- Add a reviewed baseline allowlist (scan_packages_baseline.json) keyed
  on (package, basename, check): only non-baselined CRITICAL/HIGH exit
  1, and a new kind of finding in a listed file still fails.
- sdist fallback: when --with-deps cannot resolve a shard (a sdist-only
  package or a version conflict), drop to per-spec and fetch the raw
  sdist from the PyPI JSON API (no pip build, no setup.py), so every
  package is still scanned and no shard exits 2.

scan_npm_packages.py:
- Mirror the code-only JS/TS scanning (blank // and /* */ comments,
  string/template/regex aware) and the baseline allowlist. The npm
  corpus is clean today, so the baseline is empty.

security-audit.yml:
- Flip both scan steps to blocking (SCAN_ENFORCE=1), capturing the
  scanner exit via PIPESTATUS so tee does not mask it.

tests/security: add coverage for the strip, baseline and sdist paths.

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Address review feedback on the package scanners

- Do not blank f-strings during code-only scanning (they evaluate at
  import); and when a file uses exec/eval, rescan the original for
  payload carriers hidden in a docstring/string so exec(__doc__) style
  payloads stay visible.
- sdist fallback: recover transitive deps with their version specifier
  (fetch the pinned version, not latest), and recover deps in the
  --no-deps branch too so a sdist-only transitive dependency is still
  scanned instead of silently skipped.
- Baseline: key by package-relative path, not basename, so a future
  same-named file in another directory is not auto-suppressed.
  Regenerated the baseline accordingly.

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2026-06-16 01:46:15 -07:00
Daniel Han
5c5b5348d3 Merge remote-tracking branch 'origin/main' into pr-5748-head 2026-06-12 17:51:31 +00:00
Daniel Han
36ea9a9196
Run cross-platform parity test on Windows and macOS in CI (#6241) 2026-06-12 03:40:50 -07:00
Daniel Han
3d563794af docker ci: aggressive runner disk reclaim before image builds
A staging run of the studio image build died with ENOSPC during the
Studio venv install: the hosted runners' default free space does not
fit the base image plus buildkit state plus the Studio layer. Drop all
unused preinstalled toolchains and the runner's preloaded docker
images in both build jobs.
2026-06-12 06:11:15 +00:00
Daniel Han
81b0d1ef10 docker: second review pass fixes
- Dockerfile: lift numba past vllm's 0.61.2 pin after the numpy>=2.4
  re-upgrade; 0.61.2 refuses numpy 2.3+ at import time and the stack
  cannot move numpy down. Verified numba 0.65 + numpy 2.4.6 + vllm
  import cleanly together.
- docker-publish.yml: resolve UNSLOTH_ZOO_REF in a step that mirrors
  the pushed tag only when the tag exists in unsloth-zoo (the zoo
  currently cuts no tags, so blind mirroring broke every tag publish);
  falls back to main.
- Dockerfile.studio: Studio venv stays on cu128 for arm64 too, matching
  the base venv (cu130 wheels would lift the driver floor to 580+), and
  gets the same NVRTC cu13 swap for DGX Spark / GB10 sm_121 support.
- docker_confirm.sh: do not drop to CPU mode when docker info lacks a
  nvidia runtime entry; CDI installs and Docker Desktop WSL2 expose
  GPUs without one. The phase 3 --gpus probe is now the authority.
- docker_confirm.ps1: GPU selector built as an args array; comma device
  lists get version-aware CSV quoting (native arg passing changed in
  PowerShell 7.3).
- studio_launch.sh: no fixed Jupyter default password; generate a
  random one and print it when JUPYTER_PASSWORD is unset. Env snapshot
  for SSH sessions now written via shlex.quote instead of sed so
  values with quotes or command substitution cannot break or inject
  into /etc/profile.d.
- install.ps1: honour UNSLOTH_TORCH_INDEX_FAMILY like install.sh does.
2026-06-12 05:59:52 +00:00
danielhanchen
f4e378e8b5 docker: review fixes from the 8-reviewer pass and staging CI
entrypoint.sh: a container started without a GPU request has no
nvidia-smi at all (the toolkit injects it), so the old check 1 reported
'CUDA runtime in this image is broken, re-pull' for the most common user
error. Fold the missing-binary case into the actionable 'No GPU visible'
message and document the CPU-only option (UNSLOTH_ALLOW_CPU=1).

run.sh / test_locally.sh: guard empty-array expansions with the
${arr[@]+...} form; bash 3.2 (macOS /bin/bash) treats "${empty[@]}"
as unbound under set -u, which broke the documented macOS CPU path.

studio_launch.sh: exclude *_TOKEN, *_API_KEY, *_PASSWORD, *_SECRET,
*_LICENSE from the env snapshot written for SSH sessions; secrets stay
in process env only, never on disk.

supervisord.conf / Dockerfile.studio: pin HOME=/root for the studio and
jupyter programs (jupyter would silently fall back to token auth if HOME
were unset), default JUPYTER_PORT and UNSLOTH_ENABLE_SSHD at the image
level so a direct supervisord invocation cannot hit a bad %(ENV_*)s
expansion, and document the root-services decision (non-root parity with
the previous production image is a tracked follow-up).

docker_confirm.ps1: mirror the bash script's GPU selector translation so
GPUS=0 / 0,1 select devices instead of silently using all GPUs.

docker-publish.yml: studio cache scope moves to mode=min; a mode=max
cache of a ~24GB image would evict everything else in the 10GB GHA
quota for no hit-rate gain.
2026-06-12 05:31:24 +00:00
danielhanchen
9e9877e11e docker: pin the llama.cpp bake by target arch, add docker_confirm.sh
The first bake attempt reused studio/install_llama_prebuilt.py, but that
resolver selects a bundle for the CURRENT host: on a GPU build host
/proc/driver/nvidia leaks into docker build and the resolver goes down the
CUDA path with no readable driver runtime (chosen_asset=none, exit 2),
while on a GPU-less CI runner it would resolve a CPU bundle instead. Both
violate the image's build-host-independence rule.

fetch_llama_prebuilt.py pins by build target only: amd64 takes the
linux-x64-cuda12-portable bundle, arm64 the linux-arm64-cuda13-portable
bundle (DGX Spark / Grace), both sha256-verified against the release's
llama-prebuilt-sha256.json. convert_hf_to_gguf.py plus gguf-py/ are
hydrated from the same release's source tarball so the converter's tensor
mappings match the binaries, mirroring unsloth_zoo's
_hydrate_converter_sources layout. LLAMA_PREBUILT_TAG build-arg overrides
the pinned release.

docker_confirm.sh: one-command confirmation script for any machine
(Linux / WSL2 / macOS) following the staging confirm-script conventions:
host + docker + GPU detection with CPU-mode auto-fallback, image pulls,
in-container torch.cuda check, 5-step LoRA training smoke, baked llama.cpp
verification, full-image boot probing Studio /api/health and JupyterLab
/api, PASS/WARN/FAIL summary with RESULT line.
2026-06-12 05:06:51 +00:00
danielhanchen
f1a63db6fa docker: ship Jupyter, Studio and prebuilt llama.cpp out of the box
Base image (docker/Dockerfile):
- Install JupyterLab + notebook + ipywidgets in a separate pure-Python uv
  pass so the cu128 pin set cannot move; EXPOSE 8888.
- Bake the prebuilt llama.cpp bundle into /opt/unsloth/llama.cpp at the
  runtime stage using studio/install_llama_prebuilt.py from the same
  UNSLOTH_REF (sha256-verified, portable CUDA bundle since the build host
  has no GPU; arm64 resolves the linux-arm64-cuda13 bundle). Export
  UNSLOTH_LLAMA_CPP_PATH so unsloth_zoo's save_pretrained_gguf finds it
  and never reaches the interactive install prompt or a source build.
- Optional github_token BuildKit secret for the resolver's API calls on
  shared CI runner IPs.

Entrypoint: UNSLOTH_ALLOW_CPU=1 degrades a missing GPU to a warning so
Docker Desktop on macOS / Windows-without-WSL2-GPU and plain CPU hosts can
run Jupyter, GGUF tooling and Studio chat; with a GPU visible the normal
pre-flight still runs.

Full image (docker/Dockerfile.studio): now mirrors the production service
set under supervisord - Studio on 8000, JupyterLab on 8888, key-only sshd
on 22 (enabled only when PUBLIC_KEY/SSH_KEY is set). Points Studio's
llama.cpp dir at the baked bundle to skip a duplicate download, accepts
any git ref via fetch+checkout (CI passes commit SHAs), and FROMs a
digest-pinned BASE_IMAGE.

Publish workflow: base image moves to the base-* tag namespace; new
build-studio/merge-studio jobs publish the full image as :latest (hub
parity with the previous production image, which shipped Studio + Jupyter
+ SSH). Studio builds FROM the exact base manifest digest published by the
same run. GPU smoke job now also boots the full image and probes Studio
/api/health and Jupyter /api.

run.sh: UNSLOTH_GPUS=none, UNSLOTH_ALLOW_CPU forwarding, UNSLOTH_PORTS
publish flags, CPU-mode and Jupyter usage examples.
2026-06-12 05:06:51 +00:00
danielhanchen
f34a4cd73d Merge branch 'main' into docker-blackwell-build 2026-06-12 04:33:21 +00:00
Daniel Han
d24ee77f17
Fix Llama 3.1+ rope scaling dropped on the FastLanguageModel path (long inputs become gibberish past ~29K tokens) (#6197)
* Fix config.rope_scaling being dropped by the replaced rotary embedding (#2405)

On modern transformers, LlamaModel builds its rotary embedding from config
using unsloth's replacement LlamaRotaryEmbedding class, whose config path
computed vanilla inv_freq and ignored config.rope_scaling entirely. The
llama3/linear/longrope dispatch in patch_llama_rope_scaling rewrites
LlamaAttention.__init__, which no longer constructs rotary embeddings, so it
never fires; the model-level rotary is then copied onto every attention
layer. Result: Llama-3.1/3.2/3.3 ran with unscaled RoPE on the
FastLanguageModel path and collapsed into repetition loops past roughly 29K
tokens (PASS at 28867, FAIL at 31767 in needle retrieval). FastModel was
unaffected because vision.py keeps transformers' own rotary. qwen2, qwen3,
qwen3_moe, mistral and cohere assign the same base class, so any rope-scaled
config of those families was equally exposed.

The fix makes the base class config path compute inv_freq and
attention_scaling via transformers' ROPE_INIT_FUNCTIONS (covers llama3,
linear, dynamic, yarn, longrope), with an inline llama3 fallback reading
factors from config for older transformers, degrading to prior behavior on
any failure. attention_scaling is applied in _set_cos_sin_cache (1.0 default,
exact no-op for unscaled paths) and persists across extend_rope_embedding.
A type(self) guard prevents double-scaling via the legacy scaled subclasses.

Adds tests/utils/test_rope_scaling_drift.py (AST tripwire + behavioral
inv_freq/cos-cache/extension checks, validated to fail 4 of 5 on the unfixed
code) and wires it into the existing consolidated CI HARD GATE step.

Verified on GPU: 48K-token needle retrieval flips FAIL to PASS for
FastLanguageModel in bf16 and 4bit, 20K stays PASS, scaled inv_freq matches
transformers exactly, and the left-padded batch generation guard still gets
exact solo-vs-batched token matches.

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Address review: normalize object-style rope_scaling, vectorize llama3 fallback

config.rope_scaling can be a config object rather than a dict on newer
transformers; _rope_scaling_as_dict normalizes it (to_dict/dict/vars
fallbacks) before any .get() access, with a regression test using a
dataclass stand-in. The inline llama3 fallback now uses torch.where instead
of a per-frequency Python loop; verified bit-for-bit equal to transformers
ROPE_INIT_FUNCTIONS for factor 8 (Llama-3.1) and factor 32 (Llama-3.2).

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Address review: CPU-safe rope guard tests, normalized config for delegation

The rotary constructor builds per-device CUDA caches, so the behavioral tests
that instantiate it cannot run on GPU-less CI. Restructured into three layers:
the AST tripwire now also asserts the constructor stays wired to
_compute_config_rope_inv_freq; the CPU layer tests that pure helper directly
(llama3 dict, llama3 object, linear object, default type) with no
instantiation; the instantiation and cache tests are gated behind a real CUDA
probe (actual tensor allocation, so import-time CUDA spoofs cannot fool the
gate). Verified: 9 passed with GPU; 5 passed 4 skipped with CUDA hidden; 5
failed 4 skipped on the unfixed code in CPU mode.

Delegation to ROPE_INIT_FUNCTIONS now retries with a shallow config copy
carrying the normalized rope_scaling dict when the original was an object the
installed transformers cannot read; covered by a linear-object test, which has
no inline fallback and passes only through that retry path.

* Tighten comments in rope scaling fix and guard test

Comment and docstring reduction only; verified code-identical with
scripts/comment_tools.py check --strip-docstrings (AST signature match on
both Python files). All guard tests unchanged: 20 passed with GPU, 5 passed
4 skipped with CUDA hidden.

* Apply repo kwarg-spacing format

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2026-06-11 07:48:21 -07:00
Daniel Han
2e29363ad9
Studio CI: stop HF 429 rate limits from sinking the llama.cpp prebuilt path (#6199)
* Stop HF 429 rate limits from sinking the llama.cpp prebuilt path in Studio CI

The Windows Studio API smoke job failed when anonymous huggingface.co
fetches of the tiny GGUF validation model (stories260K.gguf) hit HTTP 429
on the shared runner IP. The installer correctly refused the unvalidated
prebuilt and fell back to a source build, which the prebuilt assert then
flags. Three layers fix this:

1. Installer: auth_headers sends HF_TOKEN (or HUGGING_FACE_HUB_TOKEN) to
   huggingface.co hosts, mirroring the existing GH_TOKEN handling for the
   GitHub API rate limit. A redirect handler strips Authorization when a
   download is redirected off-host (CDN signed URLs reject foreign auth;
   urllib forwards headers on redirect, unlike requests/huggingface_hub).

2. Workflows: the HF_HOME prime steps also prefetch the validation model
   so the install's hf_hub_download resolves from the local cache even
   when the Hub is rate limiting; cache keys bumped v1 to v2 to repopulate.
   This also covers fork PRs, which cannot see secrets.

3. Workflows: every Install Studio / update step that already passes
   GH_TOKEN now also passes HF_TOKEN, so both the huggingface_hub path and
   the direct URL fallback are authenticated.

Tests: tests/studio/install/test_hf_auth.py covers token-to-host routing,
the cross-host redirect strip, and the download_bytes wiring (offline).
Verified live: authenticated download of the validation model through the
new opener (CDN redirect exercised, pinned sha matches) and an offline
hf_hub_download cache hit against an HF_HOME primed by the new step.

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2026-06-11 06:57:48 -07:00
Daniel Han
28fe978549
Lint CI: diff import-hoist check against the PR merge-base, not the base tip (#6190)
The import-hoist safety step built its changed-file list with a two-dot
diff against the base branch tip on a shallow clone. Once the base
branch moves past a PR's branch point, that diff includes every file
the base changed since, and the verifier compares newer base code
(BEFORE) against the PR's older snapshot (AFTER). This time-reversed
comparison reports the base branch's own refactors as blockers on PRs
that never touched those files, forcing branch updates or admin merges.

Resolve the true merge-base through the compare API, fetch that single
commit by SHA (clone stays shallow), and use it for both the file list
and --before. Verified against the PR 6137 failure: with the base tip
the step flags studio files from a later main commit; with the
merge-base the file list contains only the PR's own files and the
verifier passes.
2026-06-11 04:23:41 -07:00
Daniel Han
27d43a31f4
MLX CI: drop removed --simple-policy and stale ggml-org pin from the prebuilt step (#6189)
#5963 folded the manifest resolver into the simple-path resolver,
removed the installer's --simple-policy flag, and moved macOS prebuilts
to the unslothai/llama.cpp fork. The MLX CI prebuilt step still passed
the deleted flag, so argparse exits 2 and the workflow has been red on
every main push since. Point the step at the fork with no extra flags,
exactly like studio/setup.sh on macOS.
2026-06-11 00:00:50 -07:00
Daniel Han
52aa2482bb Update CODEOWNERS 2026-06-10 11:09:16 -07:00
Daniel Han
184141db99
Tests + CI guard: batched left-padded generation can never silently regress again (#1066, #3699) (#6145)
* Add regression guard for batched left-padded generation (#1066, #3699)

Three layers of tests plus a path-filtered CI workflow so the left-padding
position_ids / attention-mask bug class cannot silently return:

- tests/utils/test_prepare_inputs_ast_guard.py: import-free AST checks on
  _fast_prepare_inputs_for_generation (cumsum-from-mask branch present,
  cache_position only as fallback, no mask truncation, model families wired)
- tests/utils/test_prepare_inputs_leftpad.py: CPU behavioral unit test with
  synthetic left-padded masks and fake caches; exact expected position_ids
  for prefill and cached decode
- tests/utils/test_batched_leftpad_generation_gpu.py: optional GPU e2e,
  solo vs batched prefix match, skipped without CUDA
- .github/workflows/batch-inference-guard.yml: ubuntu-latest CPU job running
  the two deterministic layers on PRs touching unsloth/models/**

Validated: all pass on main; both CPU layers fail at 6d0f8643~1 (pre #4100)
and at 332eabf3~1 (pre #2216), reproducing the historical bug signatures.

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Cite staging proof in batch-inference-guard header (staging-2 PRs 170/171)

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Fold left-padding guard into consolidated Core CI; merge AST + behavioral tests

No new workflow and no new CI job: the guard now runs as one HARD GATE step
inside consolidated-tests-ci.yml, right after the callback signature drift
detector, where the CPU torch stack is already installed. The AST structural
checks and the behavioral unit tests live in a single file
(tests/utils/test_prepare_inputs_leftpad.py); the AST layer stays stdlib-only
with unsloth imported lazily inside the behavioral tests, so import breakage
cannot mask the structural checks.

Revalidated after the merge: 11 assertions pass on main, 8 fail at
6d0f8643~1 (pre #4100).

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Update staging proof reference for consolidated gate (PRs 170/172)

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2026-06-10 08:00:28 -07:00
Daniel Han
f3001159f9
Frontend CI: hard-fail unreviewed npm install scripts (#6139)
Completes the allowScripts rollout: upgrade CI npm to the 11.x line
(node 22 bundles 10.x, which predates the gate) with a loud version
guard so the flag can never silently degrade into a warning, then run
npm ci --strict-allow-scripts. A dependency that introduces install
scripts not covered by the committed policy now fails the job with
npm's approve-scripts/deny-scripts instructions; the pre-commit sync
hook keeps existing pins fresh after bumps.
2026-06-10 06:10:16 -07:00
Daniel Han
7cbf428b0a
Fix JSON-mode smoke on Linux: bump quant, keep the hard Paris assert (#6138)
At UD-IQ2_XXS the temp-0 greedy answer to the capital-of-France probe is
hardware dependent: GitHub ubuntu runners deterministically answer
city=France while other CPUs answer Paris, because the 2-bit argmax
flips with the SIMD kernel path. Seeds do not rescue it: a staging-fork
sweep on the affected runners measured 1/5 Paris at temp 0.7 and 1/5 at
temp 1.0 (different winning seeds on different hardware), so a
retry-across-seeds assert would still flake about a third of runs.

The same sweep on UD-Q4_K_XL answered Paris 13/13 across temp 0, 0.7
and 1.0 with 5 seeds each, including 3x deterministic greedy. Bump the
job's quant (roughly 570 MiB to 1.1 GiB, cache key already includes the
variant) and leave the assertion exactly as it was.
2026-06-10 06:10:03 -07:00
Daniel Han
f41617ad9f
Studio: auto-sync allowScripts pins after dependency bumps (#6136)
* Studio: npm v12 readiness for install-script gating

npm 12 (July 2026) stops running dependency install scripts unless they
are approved via allowScripts, and npm 11.16 already warns. Studio has
no git or remote URL deps anywhere, so script gating is the only
exposure:

- commit the allowScripts policy that npm approve-scripts writes for
  @biomejs/biome and msw, plus a manual fsevents entry: the tooling
  cannot match a darwin-only optional dep from Linux, but the strict
  check walks the platform independent ideal tree and flags it anyway
- drop the minimum-release-age npmrc alias; npm >=11.16 flags it as an
  unknown project config that stops working in npm 12
- approve bun's postinstall in the setup.sh / setup.ps1 bun bootstrap;
  under npm 12 defaults npm install -g bun otherwise leaves a broken
  stub and setup falls back to the slower npm install path
- fix the stale esbuild comment in studio-frontend-ci.yml: the vite 8
  chain ships napi binaries with no install scripts

* Studio: auto-sync allowScripts pins after dependency bumps

The allowScripts entries from #6128 are version pinned, so a biome or
msw bump strands the pin and the approval silently stops matching.
Dependabot cannot maintain the field, so:

- scripts/sync_allow_scripts_pins.py re-pins existing entries from the
  versions package-lock.json actually resolves. It never adds or
  removes entries, so approving a new script-bearing package stays a
  human decision. Bare names and non-exact specs are left alone.
- a pre-commit hook runs it with --fix; pre-commit.ci pushes the fix
  commit to PR branches, Dependabot's included, so stale pins heal
  without a human in the loop
- a Frontend CI step runs --check plus the offline unit tests as the
  backstop when pre-commit.ci is skipped

No dependabot.yml change needed: the /studio/frontend entry already
suppresses version PRs (security only) behind a 7 day cooldown.

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Make the sync hook robust to lost executable bits

The pre-commit.ci autofix commit dropped the script's exec bit, which
breaks a shebang-style entry. Invoke via python instead and restore
the bit.

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2026-06-10 02:35:37 -07:00
Daniel Han
3307561f85
Studio: npm v12 readiness for install-script gating (#6128)
npm 12 (July 2026) stops running dependency install scripts unless they
are approved via allowScripts, and npm 11.16 already warns. Studio has
no git or remote URL deps anywhere, so script gating is the only
exposure:

- commit the allowScripts policy that npm approve-scripts writes for
  @biomejs/biome and msw, plus a manual fsevents entry: the tooling
  cannot match a darwin-only optional dep from Linux, but the strict
  check walks the platform independent ideal tree and flags it anyway
- drop the minimum-release-age npmrc alias; npm >=11.16 flags it as an
  unknown project config that stops working in npm 12
- approve bun's postinstall in the setup.sh / setup.ps1 bun bootstrap;
  under npm 12 defaults npm install -g bun otherwise leaves a broken
  stub and setup falls back to the slower npm install path
- fix the stale esbuild comment in studio-frontend-ci.yml: the vite 8
  chain ships napi binaries with no install scripts
2026-06-10 02:20:27 -07:00
Matt Van Horn
03349d1e05
feat: support text-only loading of Gemma 3 27B via FastLanguageModel (skip SiglipVisionModel) (#5816)
* feat: support text-only loading of Gemma 3 27B via FastLanguageModel (skip SiglipVisionModel)

* test: instantiate text-only Gemma3 model and assert no vision tower

Existing tests were AST source-introspection plus a config-resolves-to-
text-config check; none actually instantiated a model from the
text-only config. Add a small integration test that builds a shrunken
Gemma3TextConfig (CPU-cheap), instantiates the matching CausalLM
class, and asserts the resulting model exposes the LM head and has no
vision_tower or multi_modal_projector attribute.

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Deduplicate _get_text_only_config into _utils for PR #5816

* Fall back to full model when a VLM has no text-only class for PR #5816

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Preserve quantization_config and clarify warning for text-only loading for PR #5816

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Only take text-only path when the VLM has its own text decoder for PR #5816

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Convert source string-match assertions to AST checks per Gemini review

* Load real VLM text weights on transformers 5.x for text-only mode in PR #5816

transformers >=5 changed Gemma3ForCausalLM base_model_prefix from language_model to model, so a VLM checkpoint's text weights (gemma3: language_model.model.*, gemma3n: model.language_model.*) no longer auto-strip onto the text decoder and were silently initialized random. Add a version-gated key_mapping that remaps them onto the text keys, returning None on transformers <5 where the prefix still strips and a mapping would break the load.

Apply the same family-guarded remap on the load_in_fp8 offline path and for direct FastBaseModel callers, and remap quantization llm_int8_skip_modules off the wrapper prefix after stripping.

Add a regression test that loads real VLM checkpoint weights (the prior tests only instantiated a fresh model so they missed this) and drop the bitsandbytes dependency from the quantization-config test.

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Separate FP8 text-only cache and hoist the text-only guard for PR #5816

Address review of the text-only changes: (1) _offline_quantize_to_fp8 produced different artifacts for text-only vs full VLM but reused the same <name>-fp8-<mode> cache dir, so one mode could load the other's saved model; decide text-only before the cache name and add a -text-only suffix. (2) FastBaseModel.from_pretrained rewrote the VLM auto class to AutoModelForCausalLM before loading auto_config and before the family check, leaving is_vlm wrong for the fast_inference/vLLM block; hoist the family-guarded text-only decision above those checks and drop the redundant later block. (3) Wire the text-only regression test into the curated CPU pytest job so it runs in CI across the transformers matrix.

* Trim text-only code comments for PR #5816

Shorten and de-duplicate the comments added for the text-only loading work; keep the non-obvious rationale (the transformers >=5 base_model_prefix change) and drop the obvious parts. Comments only, no code changes; AST-based tests still pass on transformers 4.57.6 and 5.4.0.

* Make text-only loading opt-in via a public text_only argument for PR #5816

Rename the internal _force_text_only flag to a public text_only parameter on FastLanguageModel, FastModel and FastBaseModel (and the fp8 helper), defaulting False on all three. Text-only loading is now opt-in (text_only=True) instead of forced on by FastLanguageModel; the family guard and key remap are unchanged. Updated the AST tests for the new parameter and forwarding.

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Trim text-only code comments for clarity

---------

Co-authored-by: Matt Van Horn <455140+mvanhorn@users.noreply.github.com>
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: Daniel Han <danielhanchen@gmail.com>
2026-06-09 22:52:39 -07:00
Datta Nimmaturi
6f27ecc66e
Merge moe-lora-target-fix CI fixes
Merged latest main, resolved _utils.py and KTO test conflicts
2026-06-08 20:20:00 +05:30
Daniel Han
b2b4e4c376
CI: allowlist deepseek_ocr2 in the compiler full-model-sweep (#6085)
transformers-latest ships a new deepseek_ocr2 model whose source-rewriter
compile exceeds the 60s per-model budget on the CI runner, same as the
existing beit/sam/sam_hq entries. Add it to KNOWN_BROKEN_COMPILE Category F
so HF=latest Core stops failing on a new upstream model. The slow compile
path itself remains a follow-up for unsloth_zoo.
2026-06-07 21:43:09 -07:00
Daniel Han
b1ee492982 Revert "CI: mark deepseek_ocr2 as known-broken compile timeout (#5995)"
This reverts commit 4eac527247.
2026-06-04 07:17:55 +00:00
Daniel Han
4eac527247
CI: mark deepseek_ocr2 as known-broken compile timeout (#5995) 2026-06-04 00:08:19 -07:00
Daniel Han
52612d6ed3
Studio CI: tolerate transient runner crashes in the llama-server log collection step (#5925)
The 'Collect llama-server logs' diagnostic step copies llama-server stdout into
the workspace for debugging. On the Windows runners it intermittently dies with
exit code -1073741502 (0xC0000142, STATUS_DLL_INIT_FAILED) when spawning the
copy, which failed the whole Tool calling Tests job even though the tests had
already run.

It is a diagnostic-only step, so mark it continue-on-error in all three jobs, the
same treatment #5913 gave the artifact-upload steps. A transient log-collection
crash no longer turns a green job red.

Refs #5913.
2026-06-01 07:24:09 -07:00
Daniel Han
ef43b5f450
Studio CI: run the Resolve-CudaToolkit unit test in the Windows GGUF job (#5921)
The deferred Windows CUDA Toolkit check (Resolve-CudaToolkit in setup.ps1) has a
pwsh unit test but nothing ran it. Rather than add a separate workflow, fold a
fast GPU-free gate into the existing Windows GGUF CI: parse setup.ps1 and run
tests/studio/test_resolve_cuda_toolkit.ps1 right after checkout, before the heavy
GGUF smoke, so a setup.ps1 regression fails fast on the runner that is already
spun up.

Refs #5912.
2026-06-01 06:35:58 -07:00
Daniel Han
0091b72a90
Studio CI: tolerate transient artifact-upload flakes on diagnostic log steps (#5913) 2026-06-01 02:55:54 -07:00
Daniel Han
f213663d5b
ci(security-audit): make package installs network-resilient (#5853)
Make the network-touching install and download steps in the security-audit workflow resilient to transient failures without relaxing any integrity check.

- Add top-level retry and backoff env knobs for pip, cargo, and npm.
- Wrap the pip-audit + cargo install and npm ci steps in an exponential-backoff retry helper, preserving --locked and --ignore-scripts.
- Re-pin swatinem/rust-cache to the v2.9.1 commit so the SHA matches its comment.
- Split the OSV-Scanner download and SHA-256 verification into a hard-gated step: a checksum mismatch fails the job, while a transient download failure skips the scan; the advisory scan stays non-blocking.
2026-05-31 01:46:55 -07:00
Daniel Han
366937de44
studio: pick a macOS llama.cpp prebuilt that loads on the host OS (#5883)
Make macOS llama.cpp prebuilt selection host-OS-version aware: skip a prebuilt whose minimum-OS exceeds the host and walk back to the newest release that loads (macOS 26 keeps latest; 14/15 land on a compatible older release). Source-build fallback pins CMAKE_OSX_DEPLOYMENT_TARGET=13.3. CI: binary-load assertion plus a macos-14/15/26 install matrix. No change to Linux/Windows or CUDA selection.
2026-05-31 00:59:25 -07:00
Daniel Han
8ec9a74fd3
studio: ROCm cleanups follow-up to #5301 (#5874)
Follow-up cleanups to the merged AMD ROCm support PR #5301:

1. De-duplicate the torchao Windows-ROCm import stub into a single shared
   module (studio/backend/core/_torchao_stub.py); both workers call one
   install_torchao_windows_rocm_stub() entrypoint.
2. Align the gfx name/arch comment columns in setup.sh and setup.ps1.
3. Isolate the float16 dtype fallback to AMD without native bf16; NVIDIA
   keeps dtype=None so unsloth's own bf16/fp16/FORCE_FLOAT32 detection is
   honored.
4. Hoist unconditional stdlib imports (gc, glob, re, subprocess, copy,
   types, sys, importlib.metadata) from function bodies to module top
   across the PR #5301-touched files; heavy/optional/relative imports stay
   lazy.
5. bitsandbytes Windows-ROCm install now uses plain pip (force_pip=True)
   instead of UV_SKIP_WHEEL_FILENAME_CHECK, per the AMD hackathon docs.

Also adds scripts/verify_import_hoist.py (a scope-aware LEGB AST resolver
that catches dangling-alias and rename-clash bugs in import-hoist
refactors) and wires it into the Lint CI source-lint job as a self-test
plus a pull_request compare gate.
2026-05-30 03:06:47 -07:00