Compare commits
No commits in common. "main" and "v0.1.501-beta" have entirely different histories.
main
...
v0.1.501-b
701 changed files with 6226 additions and 90792 deletions
|
|
@ -17,8 +17,7 @@ if [ -n "${STUDIO_PERMISSION_FRONTEND:-}" ]; then
|
|||
fi
|
||||
|
||||
mkdir -p "$artifact_dir"
|
||||
# Wipe (not reset-password): the boot below must re-seed a fresh .bootstrap_password.
|
||||
rm -rf "$studio_home/auth"
|
||||
unsloth studio reset-password
|
||||
UNSLOTH_API_ONLY=1 unsloth studio -H 127.0.0.1 -p "$port" "$@" \
|
||||
>"$server_log" 2>&1 &
|
||||
studio_pid=$!
|
||||
|
|
|
|||
15
.github/workflows/consolidated-tests-ci.yml
vendored
15
.github/workflows/consolidated-tests-ci.yml
vendored
|
|
@ -7,7 +7,7 @@
|
|||
#
|
||||
# Why a separate workflow:
|
||||
# - studio-backend-ci.yml's "Repo tests (CPU)" job already auto-discovers
|
||||
# tests/ minus tests/qlora, tests/saving, tests/utils, tests/sh. The 17
|
||||
# tests/ minus tests/qlora, tests/saving, tests/utils, tests/sh. The 16
|
||||
# Bucket-A tests below live inside those --ignore dirs (CPU-runnable but
|
||||
# historically excluded with their GPU siblings); pulling them out into
|
||||
# a sibling job keeps the existing 760-passed baseline stable while we
|
||||
|
|
@ -274,7 +274,6 @@ jobs:
|
|||
tests/saving/test_export_dispatch.py \
|
||||
tests/saving/test_imatrix_export.py \
|
||||
tests/saving/test_gguf_single_pass_export.py \
|
||||
tests/saving/test_offline_gguf_vlm_tokenizer_7481.py \
|
||||
tests/utils/test_attention_masks.py \
|
||||
tests/utils/test_trunc_normal_patch.py \
|
||||
tests/python/test_fast_language_model_text_only.py
|
||||
|
|
@ -366,17 +365,17 @@ jobs:
|
|||
tests/saving/test_export_dispatch.py \
|
||||
tests/saving/test_imatrix_export.py \
|
||||
tests/saving/test_gguf_single_pass_export.py \
|
||||
tests/saving/test_offline_gguf_vlm_tokenizer_7481.py \
|
||||
tests/utils/test_attention_masks.py \
|
||||
tests/utils/test_trunc_normal_patch.py \
|
||||
tests/python/test_fast_language_model_text_only.py \
|
||||
tests/test_bad_mappings_redirect.py \
|
||||
tests/test_prefetch_snapshot_scope.py \
|
||||
tests/test_gemma_2b_mapper_key.py \
|
||||
tests/test_raw_text_json_loading.py
|
||||
# test_run_attention_flash_varlen_receives_window_and_softcap was deselected
|
||||
# until attention_dispatch.py predefined flash_attn_varlen_func as None; it
|
||||
# monkeypatches that name, so it no longer needs flash_attn on this runner.
|
||||
--deselect 'tests/utils/test_attention_masks.py::test_run_attention_flash_varlen_receives_window_and_softcap'
|
||||
# The deselected test monkeypatches flash_attn_varlen_func, which is
|
||||
# only bound on the module when `flash_attn` is importable. flash_attn
|
||||
# requires CUDA + dev toolchain, which the CPU-only ubuntu-latest
|
||||
# runner does not have. The other Bucket-A tests pass cleanly.
|
||||
|
||||
- name: unsloth_zoo @ ${{ env.UNSLOTH_ZOO_REF }} — full pytest (CPU)
|
||||
# 106 of 111 test_* in unsloth_zoo are CPU-only. The two CUDA-skip
|
||||
|
|
@ -2130,7 +2129,7 @@ jobs:
|
|||
pip show unsloth_zoo
|
||||
echo "::endgroup::"
|
||||
echo "Consolidated job done. Coverage:"
|
||||
echo " - 17 unsloth Bucket-A tests under tests/saving/ + tests/utils/"
|
||||
echo " - 16 unsloth Bucket-A tests under tests/saving/ + tests/utils/"
|
||||
echo " - unsloth_zoo @ ${UNSLOTH_ZOO_REF} pytest tests/ (5 GPU cases deselected)"
|
||||
echo " - unsloth_zoo.compiler.test_apply_fused_lm_head"
|
||||
|
||||
|
|
|
|||
10
.github/workflows/local-agent-guides-ci.yml
vendored
10
.github/workflows/local-agent-guides-ci.yml
vendored
|
|
@ -167,9 +167,7 @@ jobs:
|
|||
# ── boot the server under test (factored helper) ──────────────────
|
||||
- name: Serve unsloth run --disable-tools (gemma-4-E4B)
|
||||
run: |
|
||||
# Wipe, not reset-password: since #7573 the reset rotates in place and
|
||||
# prints the new passphrase, which would land unmasked in the job log.
|
||||
rm -rf ~/.unsloth/studio/auth
|
||||
unsloth studio reset-password
|
||||
bash .github/scripts/serve-unsloth-run.sh \
|
||||
--gguf-file "$GITHUB_WORKSPACE/gguf-cache/${GGUF_FILE}" \
|
||||
--port "$STUDIO_PORT" --log-dir logs \
|
||||
|
|
@ -373,7 +371,7 @@ jobs:
|
|||
|
||||
- name: Serve unsloth run --disable-tools (gemma-4-E4B)
|
||||
run: |
|
||||
rm -rf ~/.unsloth/studio/auth
|
||||
unsloth studio reset-password
|
||||
bash .github/scripts/serve-unsloth-run.sh \
|
||||
--gguf-file "$GITHUB_WORKSPACE/gguf-cache/${GGUF_FILE}" \
|
||||
--port "$STUDIO_PORT" --log-dir logs \
|
||||
|
|
@ -556,7 +554,7 @@ jobs:
|
|||
|
||||
- name: Serve unsloth run --disable-tools (gemma-4-E4B)
|
||||
run: |
|
||||
rm -rf ~/.unsloth/studio/auth
|
||||
unsloth studio reset-password
|
||||
bash .github/scripts/serve-unsloth-run.sh \
|
||||
--gguf-file "$GITHUB_WORKSPACE/gguf-cache/${GGUF_FILE}" \
|
||||
--port "$STUDIO_PORT" --log-dir logs \
|
||||
|
|
@ -720,7 +718,7 @@ jobs:
|
|||
|
||||
- name: Serve unsloth run --disable-tools (gemma-3-270m)
|
||||
run: |
|
||||
rm -rf ~/.unsloth/studio/auth
|
||||
unsloth studio reset-password
|
||||
bash .github/scripts/serve-unsloth-run.sh \
|
||||
--model "$GGUF_REPO" --gguf-variant "$GGUF_VARIANT" \
|
||||
--port "$STUDIO_PORT" --log-dir logs \
|
||||
|
|
|
|||
3
.github/workflows/release-desktop.yml
vendored
3
.github/workflows/release-desktop.yml
vendored
|
|
@ -766,7 +766,6 @@ jobs:
|
|||
env:
|
||||
GH_REPO: ${{ github.repository }}
|
||||
APP_VERSION: ${{ needs.prepare-version.outputs.app_version }}
|
||||
PYPI_VERSION: ${{ needs.prepare-version.outputs.pypi_version }}
|
||||
STUDIO_VERSION: ${{ needs.prepare-version.outputs.studio_version }}
|
||||
DESKTOP_RELEASE_TAG: ${{ needs.prepare-version.outputs.desktop_release_tag }}
|
||||
DESKTOP_PRERELEASE: ${{ needs.prepare-version.outputs.prerelease }}
|
||||
|
|
@ -912,8 +911,6 @@ jobs:
|
|||
notes = pathlib.Path(os.environ['RUNNER_TEMP'], 'desktop-release-notes.md').read_text()
|
||||
metadata = {
|
||||
'version': os.environ['APP_VERSION'],
|
||||
# App version is SemVer; CHANGELOG.md is keyed by the backend release.
|
||||
'pypi_version': os.environ['PYPI_VERSION'],
|
||||
'notes': notes,
|
||||
'pub_date': datetime.datetime.now(datetime.timezone.utc).isoformat(timespec='milliseconds').replace('+00:00', 'Z'),
|
||||
'platforms': {
|
||||
|
|
|
|||
156
.github/workflows/startup-profile-ci.yml
vendored
156
.github/workflows/startup-profile-ci.yml
vendored
|
|
@ -1,156 +0,0 @@
|
|||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
# Copyright 2026-present the Unsloth AI Inc. team. All rights reserved.
|
||||
|
||||
# Measures where Studio's startup time goes, on each platform.
|
||||
#
|
||||
# Nothing recorded a number before: main.py logs "lifespan startup completed in X ms"
|
||||
# and studio_test_kit polls /healthz, but both throw the elapsed time away. A first
|
||||
# local run (Linux, warm cache, 18-core server) put `import main` at 5.7-6.6s BEFORE
|
||||
# the server can bind, dominated by eager module-level imports pulled in by routes:
|
||||
# torch ~1.9s self, unsloth_zoo ~0.8s, routes ~0.6s, transformers ~0.5s.
|
||||
#
|
||||
# Not a gate yet: --max-healthz-seconds exists, but a budget should come from
|
||||
# observed numbers rather than a guess.
|
||||
|
||||
name: Startup profile
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
paths:
|
||||
# The measured import graph is the whole backend tree: main.py imports auth,
|
||||
# core, hub, loggers, models, picker, routes and utils at module scope.
|
||||
- 'studio/backend/**'
|
||||
- '!studio/backend/tests/**'
|
||||
# The launch phase spawns `unsloth studio --api-only`, so the CLI counts too.
|
||||
- 'unsloth_cli/**'
|
||||
- 'studio/src-tauri/src/preflight**'
|
||||
# The profiler hardcodes the desktop argv that process.rs::backend_args builds,
|
||||
# so a change there must schedule a run or the two silently diverge.
|
||||
- 'studio/src-tauri/src/process.rs'
|
||||
- 'scripts/profile_startup.py'
|
||||
- '.github/workflows/startup-profile-ci.yml'
|
||||
# The job profiles whatever `install.sh --local` built: the installers pick the
|
||||
# venv's Python and the dependency specs, and pyproject's include list is what
|
||||
# makes --local overlay studio.backend*.
|
||||
- 'install.sh'
|
||||
- 'install.ps1'
|
||||
- 'pyproject.toml'
|
||||
# --local also runs the checkout's setup scripts (install.sh picks
|
||||
# $_REPO_ROOT/studio/setup.sh, the editable install resolves setup.ps1 to the
|
||||
# repo), and both call install_python_stack.py, which picks the dependencies.
|
||||
- 'studio/setup.sh'
|
||||
- 'studio/setup.ps1'
|
||||
- 'studio/install_python_stack.py'
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
repeats:
|
||||
description: 'launch repeats per OS (median reported)'
|
||||
type: string
|
||||
default: '3'
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
profile:
|
||||
name: startup ${{ matrix.os }}
|
||||
runs-on: ${{ matrix.os }}
|
||||
timeout-minutes: 60
|
||||
continue-on-error: true
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [ubuntu-latest, macos-14, windows-latest]
|
||||
|
||||
env:
|
||||
UNSLOTH_STUDIO_HOME: ${{ github.workspace }}/.studio-home
|
||||
# A wildcard bind calls ifconfig.me on the startup path; loopback times our code.
|
||||
UNSLOTH_STUDIO_DISABLE_PUBLIC_CHECK: '1'
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Install Studio
|
||||
shell: bash
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
set -o pipefail
|
||||
mkdir -p logs
|
||||
# --local is load-bearing: it overlays the checkout, so the profiled server
|
||||
# is this diff. Without it install.sh resolves unsloth from PyPI.
|
||||
if [ "${{ runner.os }}" = "Windows" ]; then
|
||||
pwsh -NoProfile -File ./install.ps1 --local 2>&1 | tee logs/install.log
|
||||
else
|
||||
bash install.sh --local 2>&1 | tee logs/install.log
|
||||
fi
|
||||
|
||||
- name: Profile startup
|
||||
shell: bash
|
||||
run: |
|
||||
BIN="$UNSLOTH_STUDIO_HOME/unsloth_studio/bin/unsloth"
|
||||
[ -x "$BIN" ] || BIN="$UNSLOTH_STUDIO_HOME/unsloth_studio/Scripts/unsloth.exe"
|
||||
[ -x "$BIN" ] || BIN=""
|
||||
# Profile imports with the INSTALLED interpreter: that venv is what launches.
|
||||
PY="$UNSLOTH_STUDIO_HOME/unsloth_studio/bin/python"
|
||||
[ -x "$PY" ] || PY="$UNSLOTH_STUDIO_HOME/unsloth_studio/Scripts/python.exe"
|
||||
[ -x "$PY" ] || PY="$(command -v python3 || command -v python)"
|
||||
python3 scripts/profile_startup.py \
|
||||
--python "$PY" \
|
||||
${BIN:+--bin "$BIN"} \
|
||||
--repeats "${{ inputs.repeats || '3' }}" \
|
||||
--json "startup-${{ matrix.os }}.json" 2>&1 | tee logs/profile.log
|
||||
|
||||
- name: Summary
|
||||
if: always()
|
||||
shell: bash
|
||||
run: |
|
||||
f="startup-${{ matrix.os }}.json"
|
||||
[ -f "$f" ] || { echo "no profile produced"; exit 0; }
|
||||
python3 - "$f" >> "$GITHUB_STEP_SUMMARY" <<'PY'
|
||||
import json, sys
|
||||
d = json.load(open(sys.argv[1]))
|
||||
print(f"### {d['platform']} / {d['machine']} (py {d['python']}, {d['cpu_count']} cpu)\n")
|
||||
imp = d.get("imports", {})
|
||||
# Gate on ok: a failed `import main` still leaves rows, so a total can lie.
|
||||
if imp.get("ok"):
|
||||
print(f"**`import main`: {imp['total_seconds']}s**\n")
|
||||
print("| package | self ms |")
|
||||
print("|---|---:|")
|
||||
for k, v in list(imp.get("self_by_package_ms", {}).items())[:8]:
|
||||
print(f"| {k} | {v} |")
|
||||
print()
|
||||
else:
|
||||
print("**`import main` failed - no valid import profile**\n")
|
||||
print("```\n" + (imp.get("error") or "")[-1500:] + "\n```\n")
|
||||
lau = d.get("launch") or {}
|
||||
runs = len(lau.get("runs") or [])
|
||||
failed = lau.get("failed_runs") or 0
|
||||
if lau.get("healthz_median_seconds") is not None:
|
||||
# The aggregates cover only the runs that reached healthz, so flag the
|
||||
# failures: bare numbers would read as a normal fast startup.
|
||||
note = f" _({runs - failed} of {runs} launches; {failed} never became healthy)_" if failed else ""
|
||||
print(f"**time to a healthy port: {lau['healthz_median_seconds']}s median, "
|
||||
f"{lau['healthz_max_seconds']}s max**{note}\n")
|
||||
elif lau.get("skipped"):
|
||||
print(f"_launch phase skipped: {lau['skipped']}_\n")
|
||||
elif runs:
|
||||
print(f"**no launch measurement: all {runs} launches failed to become healthy**\n")
|
||||
PY
|
||||
|
||||
- name: Upload profile
|
||||
if: always()
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
with:
|
||||
name: startup-profile-${{ matrix.os }}
|
||||
path: |
|
||||
startup-*.json
|
||||
logs/
|
||||
retention-days: 14
|
||||
if-no-files-found: warn
|
||||
3
.github/workflows/studio-api-smoke.yml
vendored
3
.github/workflows/studio-api-smoke.yml
vendored
|
|
@ -113,8 +113,7 @@ jobs:
|
|||
|
||||
- name: Reset auth + boot Unsloth (API-only)
|
||||
run: |
|
||||
# Wipe (not reset-password): the boot below must re-seed a fresh .bootstrap_password.
|
||||
rm -rf ~/.unsloth/studio/auth
|
||||
unsloth studio reset-password
|
||||
mkdir -p logs
|
||||
UNSLOTH_API_ONLY=1 unsloth studio -H 127.0.0.1 -p "$STUDIO_PORT" \
|
||||
> logs/studio.log 2>&1 &
|
||||
|
|
|
|||
67
.github/workflows/studio-backend-ci.yml
vendored
67
.github/workflows/studio-backend-ci.yml
vendored
|
|
@ -30,13 +30,6 @@ on:
|
|||
- 'unsloth/**'
|
||||
- 'unsloth_cli/**'
|
||||
- 'tests/**'
|
||||
# The root installers: tests/sh/*.sh and tests/studio/install/* assert
|
||||
# against these two files, so a change here must run the suite that
|
||||
# covers it. Without them an install-only edit (the shape most AMD/ROCm
|
||||
# routing fixes take) skipped Backend CI entirely.
|
||||
- 'install.sh'
|
||||
- 'install.ps1'
|
||||
- 'scripts/**'
|
||||
- 'pyproject.toml'
|
||||
- '.github/workflows/studio-backend-ci.yml'
|
||||
push:
|
||||
|
|
@ -200,7 +193,6 @@ jobs:
|
|||
--ignore=tests/sh \
|
||||
--ignore=tests/studio/test_hardware_dispatch_matrix.py \
|
||||
--ignore=tests/studio/test_is_mlx_dispatch_gate.py \
|
||||
--ignore=tests/studio/test_xpu_spoof_pipeline.py \
|
||||
--ignore=tests/vllm_compat \
|
||||
--ignore=tests/version_compat \
|
||||
-m 'not server and not e2e' \
|
||||
|
|
@ -213,53 +205,36 @@ jobs:
|
|||
env:
|
||||
PYTHONPATH: ${{ github.workspace }}/studio
|
||||
UNSLOTH_COMPILE_DISABLE: '1'
|
||||
# These files mutate hardware.py module globals at runtime via the
|
||||
# spoof fixtures (CUDA/ROCm/XPU/MLX/CPU), which leaks state into any
|
||||
# other test that imports hardware. Run them in their own pytest
|
||||
# invocation so the leak does not cross file boundaries.
|
||||
# These two files mutate hardware.py module globals at runtime
|
||||
# via the spoof fixtures, which leaks state into any other test
|
||||
# that imports hardware. Run them in their own pytest invocation
|
||||
# so the leak does not cross file boundaries.
|
||||
run: |
|
||||
python -m pytest -q --tb=short \
|
||||
tests/studio/test_hardware_dispatch_matrix.py \
|
||||
tests/studio/test_is_mlx_dispatch_gate.py \
|
||||
tests/studio/test_xpu_spoof_pipeline.py
|
||||
|
||||
- name: CLI tests (unsloth_cli)
|
||||
# unsloth_cli/tests had no CI at all: `unsloth_cli/**` was only a paths
|
||||
# trigger and a ruff target, so 673 tests covering the studio launcher,
|
||||
# the pre-exposure gate and the auth secret writers ran nowhere, and
|
||||
# four of them had been failing on main unnoticed.
|
||||
# Own step, not folded into the tests/ discovery above: pyproject's
|
||||
# testpaths is tests/, and this suite needs no PYTHONPATH or CUDA spoof
|
||||
# (it self-bootstraps sys.path and imports neither unsloth nor torch).
|
||||
run: python -m pytest unsloth_cli/tests -q --tb=short
|
||||
tests/studio/test_is_mlx_dispatch_gate.py
|
||||
|
||||
- name: Shell installer tests
|
||||
# Auto-discovered rather than allowlisted. The old hardcoded list had
|
||||
# silently fallen seven files behind tests/run_all.sh, including
|
||||
# test_strixhalo_wsl_reroute.sh -- the only shell coverage of the ROCm
|
||||
# WSL reroute -- so that suite never ran on a PR. Skips are explicit,
|
||||
# each with a reason, and tests/studio/test_ci_shell_suite_coverage.py
|
||||
# fails if this step stops discovering the directory or the skip list
|
||||
# grows without one.
|
||||
#
|
||||
# Skipped:
|
||||
# test_install_host_defaults.sh: asserts an install.ps1 layout that
|
||||
# has drifted (separate followup).
|
||||
# test_install_rollback_lifecycle.sh: already runs on both platforms
|
||||
# in cross-platform-parity-ci.yml.
|
||||
# Subset that does not depend on a writable / pristine install.sh
|
||||
# tree; test_install_host_defaults.sh checks install.ps1 layout
|
||||
# which has drifted (separate followup).
|
||||
run: |
|
||||
set -e
|
||||
skip="test_install_host_defaults.sh test_install_rollback_lifecycle.sh"
|
||||
found=0
|
||||
for s in tests/sh/test_*.sh; do
|
||||
case " $skip " in
|
||||
*" $(basename "$s") "*) echo "skipping $s (see workflow comment)"; continue ;;
|
||||
esac
|
||||
found=$((found + 1))
|
||||
for s in \
|
||||
tests/sh/test_get_torch_index_url.sh \
|
||||
tests/sh/test_mac_intel_compat.sh \
|
||||
tests/sh/test_node_decision.sh \
|
||||
tests/sh/test_studio_home_node_dir.sh \
|
||||
tests/sh/test_system_node_readonly.sh \
|
||||
tests/sh/test_nvcc_meets_llama_minimum.sh \
|
||||
tests/sh/test_resolve_cuda_archs.sh \
|
||||
tests/sh/test_tauri_install_exit_order.sh \
|
||||
tests/sh/test_torch_constraint.sh \
|
||||
tests/sh/test_torch_flavor.sh \
|
||||
tests/sh/test_with_llama_cpp_dir_flag.sh \
|
||||
tests/sh/test_with_llama_cpp_dir_link_behavior.sh; do
|
||||
echo "::group::$s"
|
||||
bash "$s"
|
||||
echo "::endgroup::"
|
||||
done
|
||||
[ "$found" -gt 0 ] || { echo "::error::no shell tests discovered under tests/sh"; exit 1; }
|
||||
echo "ran $found shell installer test files"
|
||||
|
||||
|
|
|
|||
3
.github/workflows/studio-frontend-ci.yml
vendored
3
.github/workflows/studio-frontend-ci.yml
vendored
|
|
@ -133,9 +133,6 @@ jobs:
|
|||
- name: Typecheck
|
||||
run: npm run typecheck
|
||||
|
||||
- name: Unit tests
|
||||
run: npm test
|
||||
|
||||
- name: Build
|
||||
run: npm run build
|
||||
|
||||
|
|
|
|||
7
.github/workflows/studio-inference-smoke.yml
vendored
7
.github/workflows/studio-inference-smoke.yml
vendored
|
|
@ -127,8 +127,7 @@ jobs:
|
|||
|
||||
- name: Reset auth + boot Unsloth (API-only)
|
||||
run: |
|
||||
# Wipe (not reset-password): the boot below must re-seed a fresh .bootstrap_password.
|
||||
rm -rf ~/.unsloth/studio/auth
|
||||
unsloth studio reset-password
|
||||
mkdir -p logs
|
||||
UNSLOTH_API_ONLY=1 unsloth studio -H 127.0.0.1 -p "$STUDIO_PORT" \
|
||||
> logs/studio.log 2>&1 &
|
||||
|
|
@ -401,7 +400,7 @@ jobs:
|
|||
# tool_policy=None so each request's `enable_tools` field is
|
||||
# honoured.
|
||||
run: |
|
||||
rm -rf ~/.unsloth/studio/auth
|
||||
unsloth studio reset-password
|
||||
mkdir -p logs
|
||||
UNSLOTH_API_ONLY=1 unsloth studio -H 127.0.0.1 -p "$STUDIO_PORT" \
|
||||
> logs/studio.log 2>&1 &
|
||||
|
|
@ -979,7 +978,7 @@ jobs:
|
|||
# response_format requests aren't routed through the agentic
|
||||
# tool loop.
|
||||
run: |
|
||||
rm -rf ~/.unsloth/studio/auth
|
||||
unsloth studio reset-password
|
||||
mkdir -p logs
|
||||
UNSLOTH_API_ONLY=1 unsloth studio -H 127.0.0.1 -p "$STUDIO_PORT" \
|
||||
> logs/studio.log 2>&1 &
|
||||
|
|
|
|||
3
.github/workflows/studio-mac-api-smoke.yml
vendored
3
.github/workflows/studio-mac-api-smoke.yml
vendored
|
|
@ -101,8 +101,7 @@ jobs:
|
|||
|
||||
- name: Reset auth + boot Unsloth (API-only)
|
||||
run: |
|
||||
# Wipe (not reset-password): the boot below must re-seed a fresh .bootstrap_password.
|
||||
rm -rf ~/.unsloth/studio/auth
|
||||
unsloth studio reset-password
|
||||
mkdir -p logs
|
||||
UNSLOTH_API_ONLY=1 unsloth studio -H 127.0.0.1 -p "$STUDIO_PORT" \
|
||||
> logs/studio.log 2>&1 &
|
||||
|
|
|
|||
|
|
@ -126,8 +126,7 @@ jobs:
|
|||
|
||||
- name: Reset auth + boot Unsloth (API-only)
|
||||
run: |
|
||||
# Wipe (not reset-password): the boot below must re-seed a fresh .bootstrap_password.
|
||||
rm -rf ~/.unsloth/studio/auth
|
||||
unsloth studio reset-password
|
||||
mkdir -p logs
|
||||
UNSLOTH_API_ONLY=1 unsloth studio -H 127.0.0.1 -p "$STUDIO_PORT" \
|
||||
> logs/studio.log 2>&1 &
|
||||
|
|
@ -387,7 +386,7 @@ jobs:
|
|||
# tool_policy=None so each request's `enable_tools` field is
|
||||
# honoured.
|
||||
run: |
|
||||
rm -rf ~/.unsloth/studio/auth
|
||||
unsloth studio reset-password
|
||||
mkdir -p logs
|
||||
UNSLOTH_API_ONLY=1 unsloth studio -H 127.0.0.1 -p "$STUDIO_PORT" \
|
||||
> logs/studio.log 2>&1 &
|
||||
|
|
@ -832,7 +831,7 @@ jobs:
|
|||
# response_format requests aren't routed through the agentic
|
||||
# tool loop.
|
||||
run: |
|
||||
rm -rf ~/.unsloth/studio/auth
|
||||
unsloth studio reset-password
|
||||
mkdir -p logs
|
||||
UNSLOTH_API_ONLY=1 unsloth studio -H 127.0.0.1 -p "$STUDIO_PORT" \
|
||||
> logs/studio.log 2>&1 &
|
||||
|
|
|
|||
11
.github/workflows/studio-mac-ui-smoke.yml
vendored
11
.github/workflows/studio-mac-ui-smoke.yml
vendored
|
|
@ -146,8 +146,7 @@ jobs:
|
|||
|
||||
- name: Reset auth + boot Unsloth
|
||||
run: |
|
||||
# Wipe (not reset-password): the boot below must re-seed a fresh .bootstrap_password.
|
||||
rm -rf ~/.unsloth/studio/auth
|
||||
unsloth studio reset-password
|
||||
mkdir -p logs
|
||||
UNSLOTH_API_ONLY=1 unsloth studio -H 127.0.0.1 -p "$STUDIO_PORT" \
|
||||
> logs/studio.log 2>&1 &
|
||||
|
|
@ -191,7 +190,7 @@ jobs:
|
|||
# runner's kernel briefly runs out of socket buffers, and (3) a
|
||||
# goto 'interrupted by another navigation' when the SPA auth
|
||||
# guard redirects mid-navigation. The retry FULLY resets Unsloth
|
||||
# (kill, wipe auth, reboot, wait /api/health, re-export
|
||||
# (kill, reset-password, reboot, wait /api/health, re-export
|
||||
# bootstrap pw) before re-running the script. A real test failure
|
||||
# (assertion / timeout) does NOT match any pattern so it bypasses
|
||||
# retry and surfaces immediately.
|
||||
|
|
@ -214,7 +213,7 @@ jobs:
|
|||
echo "::warning::Playwright flake on attempt ${attempt}; resetting Unsloth and retrying..."
|
||||
kill "${STUDIO_PID}" 2>/dev/null || true
|
||||
sleep 2
|
||||
rm -rf ~/.unsloth/studio/auth
|
||||
unsloth studio reset-password
|
||||
UNSLOTH_API_ONLY=1 unsloth studio -H 127.0.0.1 -p "$STUDIO_PORT" \
|
||||
> "logs/studio_retry_${attempt}.log" 2>&1 &
|
||||
STUDIO_PID=$!
|
||||
|
|
@ -252,7 +251,7 @@ jobs:
|
|||
|
||||
- name: Reset auth + boot Unsloth for extra UI tests (port 18897)
|
||||
run: |
|
||||
rm -rf ~/.unsloth/studio/auth
|
||||
unsloth studio reset-password
|
||||
mkdir -p logs
|
||||
UNSLOTH_API_ONLY=1 unsloth studio -H 127.0.0.1 -p 18897 \
|
||||
> logs/studio_extra.log 2>&1 &
|
||||
|
|
@ -309,7 +308,7 @@ jobs:
|
|||
echo "::warning::Playwright flake on attempt ${attempt}; resetting Unsloth and retrying..."
|
||||
kill "${STUDIO_EXTRA_PID}" 2>/dev/null || true
|
||||
sleep 2
|
||||
rm -rf ~/.unsloth/studio/auth
|
||||
unsloth studio reset-password
|
||||
UNSLOTH_API_ONLY=1 unsloth studio -H 127.0.0.1 -p 18897 \
|
||||
> "logs/studio_extra_retry_${attempt}.log" 2>&1 &
|
||||
STUDIO_EXTRA_PID=$!
|
||||
|
|
|
|||
10
.github/workflows/studio-tauri-smoke.yml
vendored
10
.github/workflows/studio-tauri-smoke.yml
vendored
|
|
@ -91,16 +91,6 @@ jobs:
|
|||
npm run build
|
||||
test -f dist/index.html
|
||||
|
||||
# The crate carries ~100 unit tests (native_file_dialogs, preflight,
|
||||
# install, desktop_auth, ...) that nothing ran until now: this workflow
|
||||
# only ever built. Run them here, where the toolchain and the WebKit dev
|
||||
# packages are already installed, so a broken assertion fails the PR
|
||||
# instead of sitting unnoticed. `--no-fail-fast` reports every failing
|
||||
# test in one run rather than stopping at the first.
|
||||
- name: Rust unit tests (studio/src-tauri)
|
||||
working-directory: studio/src-tauri
|
||||
run: cargo test --no-fail-fast
|
||||
|
||||
- name: Tauri debug build (Linux, no bundle, no codesign)
|
||||
# `--debug` + `--no-bundle` keeps this lean: compiles the Rust crate,
|
||||
# confirms the frontend dist is wired into Tauri, but skips the AppImage
|
||||
|
|
|
|||
9
.github/workflows/studio-ui-smoke.yml
vendored
9
.github/workflows/studio-ui-smoke.yml
vendored
|
|
@ -115,8 +115,7 @@ jobs:
|
|||
|
||||
- name: Reset auth + boot Unsloth
|
||||
run: |
|
||||
# Wipe (not reset-password): the boot below must re-seed a fresh .bootstrap_password.
|
||||
rm -rf ~/.unsloth/studio/auth
|
||||
unsloth studio reset-password
|
||||
mkdir -p logs
|
||||
UNSLOTH_API_ONLY=1 unsloth studio -H 127.0.0.1 -p "$STUDIO_PORT" \
|
||||
> logs/studio.log 2>&1 &
|
||||
|
|
@ -194,7 +193,7 @@ jobs:
|
|||
# warm install we already did) so this adds little wall time.
|
||||
- name: Reset auth + boot Unsloth for extra UI tests (port 18894)
|
||||
run: |
|
||||
rm -rf ~/.unsloth/studio/auth
|
||||
unsloth studio reset-password
|
||||
mkdir -p logs
|
||||
UNSLOTH_API_ONLY=1 unsloth studio -H 127.0.0.1 -p 18894 \
|
||||
> logs/studio_extra.log 2>&1 &
|
||||
|
|
@ -254,7 +253,7 @@ jobs:
|
|||
# (RAG embedder + llama.cpp probe) stay hidden from the picker.
|
||||
- name: Reset auth + boot Unsloth for model-config tests (port 18898)
|
||||
run: |
|
||||
rm -rf ~/.unsloth/studio/auth
|
||||
unsloth studio reset-password
|
||||
mkdir -p logs
|
||||
UNSLOTH_API_ONLY=1 unsloth studio -H 127.0.0.1 -p 18898 \
|
||||
> logs/studio_modelcfg.log 2>&1 &
|
||||
|
|
@ -300,7 +299,7 @@ jobs:
|
|||
# earlier UI tests. No GGUF -- the bug surface is the composer.
|
||||
- name: Reset auth + boot Unsloth for IME / i18n tests (port 18896)
|
||||
run: |
|
||||
rm -rf ~/.unsloth/studio/auth
|
||||
unsloth studio reset-password
|
||||
mkdir -p logs
|
||||
UNSLOTH_API_ONLY=1 unsloth studio -H 127.0.0.1 -p 18896 \
|
||||
> logs/studio_ime.log 2>&1 &
|
||||
|
|
|
|||
40
.github/workflows/studio-update-smoke.yml
vendored
40
.github/workflows/studio-update-smoke.yml
vendored
|
|
@ -146,46 +146,6 @@ jobs:
|
|||
kill "$PID" 2>/dev/null || true
|
||||
echo "post-update Unsloth /api/health OK"
|
||||
|
||||
- name: A complete install reports itself complete
|
||||
run: |
|
||||
set -o pipefail
|
||||
unsloth studio verify-install
|
||||
unsloth studio desktop-capabilities --json | tee /tmp/caps.json
|
||||
jq -e '.studio_install_ok == true' /tmp/caps.json
|
||||
jq -e '.desktop_manageability_version >= 2' /tmp/caps.json
|
||||
|
||||
- name: An incomplete install must not report itself ready
|
||||
# An installer killed part-way leaves a working CLI but no studio.txt
|
||||
# deps, which the old preflight called ManagedReady. The manifest is
|
||||
# written last, so removing it reproduces that state.
|
||||
run: |
|
||||
set -o pipefail
|
||||
# install.sh's default root, resolved explicitly: `python` on PATH
|
||||
# here is setup-python's, not the managed venv.
|
||||
MANIFEST="$HOME/.unsloth/studio/unsloth_studio/unsloth_install_manifest.json"
|
||||
test -f "$MANIFEST" || { echo "::error::installer never wrote $MANIFEST"; exit 1; }
|
||||
rm -f "$MANIFEST"
|
||||
unsloth studio desktop-capabilities --json | tee /tmp/caps_bad.json
|
||||
jq -e '.studio_install_ok == false' /tmp/caps_bad.json
|
||||
if unsloth studio verify-install; then
|
||||
echo "::error::verify-install passed on an install with no manifest"
|
||||
exit 1
|
||||
fi
|
||||
echo "incomplete install correctly reported not-ready"
|
||||
|
||||
- name: Update repairs an incomplete install
|
||||
# `--local` bypasses setup.sh's PyPI version compare, so this asserts
|
||||
# the repair OUTCOME. The non-local fast path the desktop Repair button
|
||||
# uses is covered by tests/studio/install/test_setup_fast_path_guard.py.
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
set -o pipefail
|
||||
unsloth studio update --local 2>&1 | tee logs/update_repair.log
|
||||
unsloth studio verify-install
|
||||
unsloth studio desktop-capabilities --json | jq -e '.studio_install_ok == true'
|
||||
echo "update repaired the incomplete install"
|
||||
|
||||
- name: Uninstall and verify clean
|
||||
# Round-trip the installer through scripts/uninstall.sh: confirms the
|
||||
# uninstaller actually finds and removes everything install.sh +
|
||||
|
|
|
|||
|
|
@ -179,8 +179,7 @@ jobs:
|
|||
|
||||
- name: Reset auth + boot Unsloth (API-only)
|
||||
run: |
|
||||
# Wipe (not reset-password): the boot below must re-seed a fresh .bootstrap_password.
|
||||
rm -rf ~/.unsloth/studio/auth
|
||||
unsloth studio reset-password
|
||||
mkdir -p logs
|
||||
UNSLOTH_API_ONLY=1 unsloth studio -H 127.0.0.1 -p "$STUDIO_PORT" \
|
||||
> logs/studio.log 2>&1 &
|
||||
|
|
|
|||
|
|
@ -229,8 +229,7 @@ jobs:
|
|||
|
||||
- name: Reset auth + boot Unsloth (API-only)
|
||||
run: |
|
||||
# Wipe (not reset-password): the boot below must re-seed a fresh .bootstrap_password.
|
||||
rm -rf ~/.unsloth/studio/auth
|
||||
unsloth studio reset-password
|
||||
mkdir -p logs
|
||||
UNSLOTH_API_ONLY=1 unsloth studio -H 127.0.0.1 -p "$STUDIO_PORT" \
|
||||
> logs/studio.log 2>&1 &
|
||||
|
|
@ -574,7 +573,7 @@ jobs:
|
|||
|
||||
- name: Reset auth + boot Unsloth (API-only, default tool policy)
|
||||
run: |
|
||||
rm -rf ~/.unsloth/studio/auth
|
||||
unsloth studio reset-password
|
||||
mkdir -p logs
|
||||
UNSLOTH_API_ONLY=1 unsloth studio -H 127.0.0.1 -p "$STUDIO_PORT" \
|
||||
> logs/studio.log 2>&1 &
|
||||
|
|
@ -1075,7 +1074,7 @@ jobs:
|
|||
|
||||
- name: Reset auth + boot Unsloth (API-only)
|
||||
run: |
|
||||
rm -rf ~/.unsloth/studio/auth
|
||||
unsloth studio reset-password
|
||||
mkdir -p logs
|
||||
UNSLOTH_API_ONLY=1 unsloth studio -H 127.0.0.1 -p "$STUDIO_PORT" \
|
||||
> logs/studio.log 2>&1 &
|
||||
|
|
@ -1547,7 +1546,7 @@ jobs:
|
|||
|
||||
- name: Reset auth + boot Unsloth (API-only)
|
||||
run: |
|
||||
rm -rf ~/.unsloth/studio/auth
|
||||
unsloth studio reset-password
|
||||
mkdir -p logs
|
||||
UNSLOTH_API_ONLY=1 unsloth studio -H 127.0.0.1 -p "$STUDIO_PORT" \
|
||||
> logs/studio.log 2>&1 &
|
||||
|
|
@ -1889,11 +1888,8 @@ jobs:
|
|||
# (step/substep -> Write-StudioStdoutMirror / Get-StudioAnsi).
|
||||
$script:StudioVtOk = $false
|
||||
$script:UnslothVerbose = $false
|
||||
# Get-HostMachineArch is reached only on the absent path, where
|
||||
# Test-VCRedistInstalled consults it before trusting the System32 DLL, so
|
||||
# part A passes without it and only the clean-box part fails.
|
||||
foreach ($fn in @('Get-StudioAnsi', 'Write-StudioStdoutMirror', 'step', 'substep',
|
||||
'Invoke-SetupCommand', 'Refresh-Environment', 'Get-HostMachineArch',
|
||||
'Invoke-SetupCommand', 'Refresh-Environment',
|
||||
'Test-VCRedistInstalled', 'Ensure-VCRedist')) {
|
||||
$src = Get-FunctionSource -Path $setup -Name $fn
|
||||
if (-not $src) { throw "Function '$fn' not found in setup.ps1" }
|
||||
|
|
|
|||
|
|
@ -297,8 +297,7 @@ jobs:
|
|||
|
||||
- name: Reset auth + boot Unsloth
|
||||
run: |
|
||||
# Wipe (not reset-password): the boot below must re-seed a fresh .bootstrap_password.
|
||||
rm -rf ~/.unsloth/studio/auth
|
||||
unsloth studio reset-password
|
||||
mkdir -p logs
|
||||
UNSLOTH_API_ONLY=1 unsloth studio -H 127.0.0.1 -p "$STUDIO_PORT" \
|
||||
> logs/studio.log 2>&1 &
|
||||
|
|
@ -353,7 +352,7 @@ jobs:
|
|||
|
||||
- name: Reset auth + boot Unsloth for extra UI tests (port 18897)
|
||||
run: |
|
||||
rm -rf ~/.unsloth/studio/auth
|
||||
unsloth studio reset-password
|
||||
mkdir -p logs
|
||||
UNSLOTH_API_ONLY=1 unsloth studio -H 127.0.0.1 -p 18897 \
|
||||
> logs/studio_extra.log 2>&1 &
|
||||
|
|
|
|||
|
|
@ -198,31 +198,6 @@ jobs:
|
|||
fi
|
||||
echo "update path took the prebuilt fast path"
|
||||
|
||||
- name: Update must keep the --no-torch install GGUF-only
|
||||
run: |
|
||||
# `unsloth studio update` exports no UNSLOTH_NO_TORCH, so setup.ps1 has
|
||||
# to recover the mode from the install manifest. Without that it reads
|
||||
# the missing torch as a stale venv and tries to delete the venv it is
|
||||
# running out of, and the shared dependency pass pulls torch back in.
|
||||
# The skip line only prints when the dependency pass actually runs, so
|
||||
# don't demand it if the fast path short-circuited that pass.
|
||||
if grep -q "running ordered dependency installation" logs/update.log \
|
||||
&& ! grep -q "skipping direct PyTorch and Triton installation (no-torch mode)" logs/update.log; then
|
||||
echo "::error::studio update left no-torch mode; it would reinstall PyTorch."
|
||||
grep -iE "no-torch|stale venv|PyTorch" logs/update.log | tail -40
|
||||
exit 1
|
||||
fi
|
||||
PY="$HOME/.unsloth/studio/unsloth_studio/Scripts/python.exe"
|
||||
if [ ! -f "$PY" ]; then
|
||||
echo "::error::studio venv interpreter missing at $PY"
|
||||
exit 1
|
||||
fi
|
||||
if "$PY" -c "import torch" 2>/dev/null; then
|
||||
echo "::error::torch was reinstalled into the --no-torch venv."
|
||||
exit 1
|
||||
fi
|
||||
echo "update preserved no-torch mode"
|
||||
|
||||
- name: Second update must also be a no-op
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
|
|
|||
25
.github/workflows/wheel-smoke.yml
vendored
25
.github/workflows/wheel-smoke.yml
vendored
|
|
@ -127,31 +127,6 @@ jobs:
|
|||
cd /tmp
|
||||
/tmp/v/bin/python -c "from studio.backend.main import app; print('Unsloth backend OK:', app.title)"
|
||||
|
||||
- name: CLI without the Studio stack guides instead of tracebacking
|
||||
# The smoke above installs studio.txt first, so it cannot catch a wheel
|
||||
# that ships studio/ without declaring what it imports (#4701, #5260,
|
||||
# #7147). Drop only structlog to reuse that venv without a re-download.
|
||||
run: |
|
||||
set -eu
|
||||
/tmp/v/bin/pip uninstall -y structlog >/dev/null
|
||||
cd /tmp
|
||||
status=0
|
||||
for args in "export ./nope ./out" "list-checkpoints"; do
|
||||
echo "--- unsloth $args"
|
||||
out=$(/tmp/v/bin/unsloth $args 2>&1 || true)
|
||||
printf '%s\n' "$out"
|
||||
case "$out" in
|
||||
*Traceback*)
|
||||
echo "FAIL: raw traceback instead of guidance"; status=1 ;;
|
||||
esac
|
||||
case "$out" in
|
||||
*'unsloth studio update'*) ;;
|
||||
*) echo "FAIL: no remediation in the message"; status=1 ;;
|
||||
esac
|
||||
done
|
||||
/tmp/v/bin/pip install -q structlog >/dev/null
|
||||
exit "$status"
|
||||
|
||||
- name: Upload wheel on failure
|
||||
if: failure()
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
|
|
|
|||
6
.gitignore
vendored
6
.gitignore
vendored
|
|
@ -208,9 +208,6 @@ tmp/
|
|||
**/node_modules/
|
||||
auth.db
|
||||
|
||||
# Packaging snapshot of the root CHANGELOG.md (written by build.sh)
|
||||
studio/CHANGELOG.md
|
||||
|
||||
# Tauri local build/generated output
|
||||
studio/src-tauri/target/
|
||||
studio/src-tauri/gen/
|
||||
|
|
@ -241,5 +238,4 @@ package-lock.json
|
|||
!studio/package-lock.json
|
||||
llama.cpp/
|
||||
# Stray "~" dir some tools create from a literal ~ TMPDIR; never part of the repo.
|
||||
~/
|
||||
/temp/
|
||||
/~/
|
||||
|
|
|
|||
88
CHANGELOG.md
88
CHANGELOG.md
|
|
@ -1,88 +0,0 @@
|
|||
# Changelog
|
||||
|
||||
Release notes for Unsloth and Unsloth Studio.
|
||||
|
||||
Unsloth Studio reads this file to show release notes inside the "New Unsloth
|
||||
version" update popup. Edit it here and the popup picks the change up on the
|
||||
next update check, with no release or rebuild required.
|
||||
|
||||
## Format
|
||||
|
||||
Every release is a level-2 heading whose first token is the version, optionally
|
||||
followed by a date:
|
||||
|
||||
```md
|
||||
## 2026.7.6 - 2026-07-22
|
||||
```
|
||||
|
||||
`## [2026.7.6] - 2026-07-22` and `## v2026.7.6` also work. Everything under a
|
||||
heading, up to the next level-2 heading, is that release's notes and renders as
|
||||
Markdown in the popup.
|
||||
|
||||
Notes are matched to one exact version. When Studio offers an update to
|
||||
`2026.7.6` it renders the `2026.7.6` section and nothing else. If that section
|
||||
is missing, the popup links out to the online changelog rather than showing
|
||||
notes from an unrelated release, so a new version needs its own section here
|
||||
before its notes can appear.
|
||||
|
||||
Keep the newest release at the top. Lead each bullet with the change itself:
|
||||
the collapsed popup highlights the first sentence and dims the rest.
|
||||
`## Unreleased` is ignored by the popup, so it is safe to stage notes there and
|
||||
rename the heading at release time.
|
||||
|
||||
<!-- Add new releases directly below this line. -->
|
||||
|
||||
## Unreleased
|
||||
|
||||
## 2026.7.5
|
||||
|
||||
### What's Changed
|
||||
|
||||
- AMD support is here. Train, run RL, chat with and deploy 500+ models on
|
||||
Radeon, Instinct, Ryzen and data center GPUs across Windows, WSL and Linux,
|
||||
up to 2x faster with 70% less VRAM and no accuracy loss.
|
||||
- Intel XPU support lands in Studio, so Arc and Data Center GPUs run chat and
|
||||
training alongside the NVIDIA, AMD and Apple paths.
|
||||
- Local speech to text dictation runs fully offline, with slim Whisper bundles
|
||||
and a picker for custom models.
|
||||
- DoRA training is available in Studio, selectable next to LoRA and full
|
||||
fine-tuning in the training tab.
|
||||
- The update popup previews release notes inline, pulled from this file and
|
||||
matched to the exact version being offered.
|
||||
|
||||
### AMD, 23 July update
|
||||
|
||||
Our AMD collaboration, custom Triton kernels and math algorithms bring local
|
||||
training and inference to AMD hardware. The 23 July update builds on the
|
||||
[AMD release](https://github.com/unslothai/unsloth/releases/tag/v0.1.501-beta):
|
||||
|
||||
- RDNA2 and Gorgon Halo are supported, and the installer no longer fails to
|
||||
detect GPUs on Strix Halo and other AMD cards.
|
||||
- RDNA4 handling is better, and HIP and ROCm failures are caught and fixed
|
||||
automatically instead of stopping the install.
|
||||
- Unified memory safetensors loading is 2x faster, with much faster gradient
|
||||
checkpointing on unified memory devices.
|
||||
- Voice dictation through whisper.cpp has preliminary support.
|
||||
- Rollback environments left by installs no longer eat 5GB of disk. They are
|
||||
cleaned up automatically.
|
||||
|
||||
Optimized ROCm builds cover GGUF and safetensors inference, and ROCm
|
||||
compatibility is improved for MI300X and MI325X. Full guide:
|
||||
[unsloth.ai/docs/basics/amd](https://unsloth.ai/docs/basics/amd).
|
||||
|
||||
### Running larger models
|
||||
|
||||
- Automatic GPU placement, or pick exactly which GPUs and layers to use.
|
||||
- Move MoE expert layers into system memory so larger models fit.
|
||||
- Split a model across several GPUs, or use tensor parallelism.
|
||||
- Hardware settings are saved per model and quant.
|
||||
|
||||
### Also in this release
|
||||
|
||||
- Remote access with `unsloth studio --secure` over free HTTPS via Cloudflare.
|
||||
- Web search reads PDF papers and manuals, and parallel tool calls, reasoning
|
||||
output and tool retries are more reliable.
|
||||
- The model download location is configurable, so weights can live on a second
|
||||
drive instead of the default cache.
|
||||
- Stalled Hugging Face XET downloads retry over standard HTTP, and existing
|
||||
GGUF files are reused instead of downloaded again.
|
||||
|
|
@ -1,2 +0,0 @@
|
|||
include _changelog_build.py
|
||||
include CHANGELOG.md
|
||||
20
README.md
20
README.md
|
|
@ -103,7 +103,7 @@ Unsloth Studio (Beta) works on **Windows, Linux, WSL** and **macOS**.
|
|||
* **NVIDIA:** Training works on RTX 30/40/50, Blackwell, DGX Spark, Station and more
|
||||
* **macOS:** Training, MLX and GGUF inference are ALL supported.
|
||||
* **AMD:** Training, RL, chat and deployment work on Windows, WSL and Linux. [Read the AMD guide](https://unsloth.ai/docs/basics/amd).
|
||||
* **Vulkan:** GGUF inference is supported on [compatible GPUs, including Intel GPUs](https://github.com/unslothai/unsloth/pull/5819). Vulkan accelerates GGUF inference only; training still requires a supported PyTorch or MLX backend.
|
||||
* **Vulkan:** GGUF inference is supported on [compatible GPUs, including Intel GPUs](https://github.com/unslothai/unsloth/pull/5819).
|
||||
* **Multi-GPU:** Available now, with a major upgrade on the way
|
||||
|
||||
#### macOS, Linux, WSL:
|
||||
|
|
@ -112,28 +112,12 @@ curl -fsSL https://unsloth.ai/install.sh | sh
|
|||
```
|
||||
Use the same command to update.
|
||||
|
||||
To force the Vulkan llama.cpp backend, set `UNSLOTH_FORCE_VULKAN=1` **before installing or updating**. The setting selects the llama.cpp binary bundle, so setting it only when launching Studio cannot replace an existing CPU bundle:
|
||||
|
||||
```bash
|
||||
export UNSLOTH_FORCE_VULKAN=1
|
||||
curl -fsSL https://unsloth.ai/install.sh | sh
|
||||
```
|
||||
|
||||
#### Windows:
|
||||
```powershell
|
||||
irm https://unsloth.ai/install.ps1 | iex
|
||||
```
|
||||
Use the same command to update.
|
||||
|
||||
To force the Vulkan llama.cpp backend, set the environment variable before running the installer or updater:
|
||||
|
||||
```powershell
|
||||
$env:UNSLOTH_FORCE_VULKAN=1
|
||||
irm https://unsloth.ai/install.ps1 | iex
|
||||
```
|
||||
|
||||
Re-running the current installer replaces a previously selected CPU bundle when the backend differs. A separate Vulkan SDK is not required; the GPU driver must provide a working Vulkan runtime.
|
||||
|
||||
#### Launch
|
||||
```bash
|
||||
unsloth studio -p 8888
|
||||
|
|
@ -279,8 +263,6 @@ unsloth studio -H 0.0.0.0 -p 8888
|
|||
```
|
||||
The Cloudflare tunnel is **off by default**: `-H 0.0.0.0` exposes the raw port only, not a public internet URL. Pair the wildcard bind with `--cloudflare` (`unsloth studio -H 0.0.0.0 --cloudflare`) to also publish a public `https://*.trycloudflare.com` link, or prefer `--secure` (above), which keeps the raw port private. `--cloudflare` has no effect on a loopback bind.
|
||||
|
||||
On a wildcard bind Unsloth works out the address to share by asking `ifconfig.me` for the public IP, then asks `check-host.net` whether that port is reachable so it can tell you if a firewall is in the way. Both contact a third party. Set `UNSLOTH_STUDIO_DISABLE_PUBLIC_CHECK=1` to skip them; the banner then shows the LAN address and no reachability line.
|
||||
|
||||
The first time Unsloth is published on a public URL (`--secure` or `--cloudflare`) with the auto-generated admin password still in place, it asks for a new admin password in the terminal (masked input with confirmation) before the public link goes up. Without an attached terminal it warns instead and keeps the bootstrap deadline: Unsloth shuts down after `UNSLOTH_STUDIO_BOOTSTRAP_TIMEOUT` (default 1 hour) unless the password is changed in the web UI.
|
||||
|
||||
For headless setups that cannot answer that prompt, set the initial admin password non-interactively with `--password` (only takes effect when no password is set yet; if one already exists it is a hard error, so rotate later with `unsloth studio reset-password`):
|
||||
|
|
|
|||
|
|
@ -1,36 +0,0 @@
|
|||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
# Copyright 2026-present the Unsloth AI Inc. team. All rights reserved.
|
||||
|
||||
"""Snapshot CHANGELOG.md into the studio package at build time.
|
||||
|
||||
CHANGELOG.md at the repo root stays the one file to edit. Copying it here,
|
||||
rather than in build.sh, means every packaging path ships it, so release notes
|
||||
still render when the popup cannot reach GitHub."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import shutil
|
||||
from pathlib import Path
|
||||
|
||||
from setuptools.command.build_py import build_py as _build_py
|
||||
|
||||
ROOT = Path(__file__).resolve().parent
|
||||
SOURCE = ROOT / "CHANGELOG.md"
|
||||
SNAPSHOT = ROOT / "studio" / "CHANGELOG.md"
|
||||
|
||||
|
||||
class build_py(_build_py):
|
||||
def run(self) -> None:
|
||||
# Beside the sources only if writable (PEP 517 may build an immutable
|
||||
# checkout); into the staging directory always.
|
||||
if SOURCE.is_file():
|
||||
try:
|
||||
shutil.copyfile(SOURCE, SNAPSHOT)
|
||||
except OSError:
|
||||
pass
|
||||
super().run()
|
||||
if not SOURCE.is_file():
|
||||
return
|
||||
staged = Path(self.build_lib) / "studio" / "CHANGELOG.md"
|
||||
staged.parent.mkdir(parents = True, exist_ok = True)
|
||||
shutil.copyfile(SOURCE, staged)
|
||||
6
build.sh
6
build.sh
|
|
@ -103,13 +103,9 @@ else
|
|||
STUDIO_STAMPED_VERSION="$(python scripts/stamp_studio_release.py)"
|
||||
fi
|
||||
|
||||
# 4. Build wheel/sdist. _changelog_build.py snapshots CHANGELOG.md into the studio
|
||||
# package so release notes render offline.
|
||||
# 4. Build wheel/sdist
|
||||
python -m build
|
||||
|
||||
# Drop the snapshot so a source checkout never serves a stale copy.
|
||||
rm -f studio/CHANGELOG.md
|
||||
|
||||
if [ "${1:-}" = "publish" ]; then
|
||||
python scripts/stamp_studio_release.py --verify-dist dist --expected "$STUDIO_STAMPED_VERSION"
|
||||
fi
|
||||
|
|
|
|||
223
install.ps1
223
install.ps1
|
|
@ -28,14 +28,6 @@ function Install-UnslothStudio {
|
|||
}
|
||||
}
|
||||
|
||||
function Clear-TauriInstallError {
|
||||
param([string]$Message)
|
||||
if ($TauriMode) {
|
||||
Write-TauriLog "ERROR_CLEAR" $Message
|
||||
[Console]::Error.WriteLine("[TAURI:ERROR_CLEAR] $Message")
|
||||
}
|
||||
}
|
||||
|
||||
function Format-TauriDiagBool {
|
||||
param([bool]$Value)
|
||||
if ($Value) { return "true" }
|
||||
|
|
@ -57,26 +49,6 @@ function Install-UnslothStudio {
|
|||
}
|
||||
}
|
||||
|
||||
# Machine arch; Get-TauriDiagArch above reports the process. An emulated x64 shell on
|
||||
# ARM64 reports AMD64, but PROCESSOR_ARCHITEW6432 is ARM64 in exactly that case.
|
||||
function Get-HostMachineArch {
|
||||
$osArch = ""
|
||||
try { $osArch = [System.Runtime.InteropServices.RuntimeInformation]::OSArchitecture.ToString() } catch { $osArch = "" }
|
||||
$signals = @([string]$env:PROCESSOR_ARCHITEW6432, [string]$env:PROCESSOR_ARCHITECTURE, $osArch)
|
||||
foreach ($s in $signals) {
|
||||
if ($s.ToLowerInvariant() -eq "arm64") { return "arm64" }
|
||||
}
|
||||
foreach ($s in $signals) {
|
||||
if ([string]::IsNullOrWhiteSpace($s)) { continue }
|
||||
switch ($s.ToLowerInvariant()) {
|
||||
"amd64" { return "x86_64" }
|
||||
"x64" { return "x86_64" }
|
||||
"x86" { return "x86" }
|
||||
}
|
||||
}
|
||||
return "unknown"
|
||||
}
|
||||
|
||||
function Get-TauriTorchIndexFamily {
|
||||
param([string]$TorchIndexUrl)
|
||||
if ($SkipTorch) { return "none" }
|
||||
|
|
@ -114,7 +86,7 @@ function Install-UnslothStudio {
|
|||
[int]$Code = 1
|
||||
)
|
||||
if ($Code -eq 0) { $Code = 1 }
|
||||
Write-TauriLog "ERROR_DEFAULT" $Message
|
||||
Write-TauriLog "ERROR" $Message
|
||||
if (Get-Command Restore-StudioVenvRollback -CommandType Function -ErrorAction SilentlyContinue) {
|
||||
Restore-StudioVenvRollback
|
||||
}
|
||||
|
|
@ -513,8 +485,7 @@ function Install-UnslothStudio {
|
|||
# Full command output is shown only when --verbose / UNSLOTH_VERBOSE=1.
|
||||
function Invoke-InstallCommand {
|
||||
param(
|
||||
[Parameter(Mandatory = $true)][ScriptBlock]$Command,
|
||||
[string]$Label = "install command"
|
||||
[Parameter(Mandatory = $true)][ScriptBlock]$Command
|
||||
)
|
||||
# Installer-pinned index installs (torch) must beat an inherited uv mirror (#6898):
|
||||
# for --default-index, clear the uv index env vars (restore in finally) and set
|
||||
|
|
@ -533,7 +504,6 @@ function Install-UnslothStudio {
|
|||
try {
|
||||
# Reset to avoid stale values from prior native commands.
|
||||
$global:LASTEXITCODE = 0
|
||||
Write-TauriLog "OUTPUT_CLEAR" $Label
|
||||
if ($script:UnslothVerbose) {
|
||||
# Merge stderr into stdout so progress/warning output stays visible
|
||||
# without flipping $? on successful native commands (PS 5.1 treats
|
||||
|
|
@ -548,13 +518,7 @@ function Install-UnslothStudio {
|
|||
Write-Host (Redact-InstallOutput $output) -ForegroundColor Red
|
||||
}
|
||||
}
|
||||
$exitCode = [int]$LASTEXITCODE
|
||||
if ($exitCode -eq 0) {
|
||||
Clear-TauriInstallError "$Label recovered"
|
||||
} else {
|
||||
Write-TauriLog "ERROR_OUTPUT" "$Label failed (exit code $exitCode)"
|
||||
}
|
||||
return $exitCode
|
||||
return [int]$LASTEXITCODE
|
||||
} finally {
|
||||
$ErrorActionPreference = $prevEap
|
||||
if ($savedUvIndex) {
|
||||
|
|
@ -585,7 +549,7 @@ function Install-UnslothStudio {
|
|||
}
|
||||
$attempt = 1
|
||||
while ($true) {
|
||||
$code = Invoke-InstallCommand -Command $Command -Label $Label
|
||||
$code = Invoke-InstallCommand $Command
|
||||
if ($code -eq 0) { return 0 }
|
||||
if ($attempt -ge $maxAttempts) { return $code }
|
||||
substep ("retrying ""$Label"" after transient failure (attempt $($attempt + 1)/$maxAttempts, waiting ${delay}s)...") "Yellow"
|
||||
|
|
@ -1144,27 +1108,10 @@ exit 0
|
|||
return $false
|
||||
}
|
||||
|
||||
# The interpreter's own arch, asked of it: win-amd64|win-arm64|win32|"".
|
||||
function Get-PythonPlatformTag {
|
||||
param([string]$Exe)
|
||||
try {
|
||||
return (& $Exe -c "import sysconfig; print(sysconfig.get_platform())" 2>$null | Out-String).Trim().ToLowerInvariant()
|
||||
} catch { return "" }
|
||||
}
|
||||
|
||||
# Returns @{ Version = "3.13"; Path = "C:\...\python.exe" } or $null.
|
||||
# The resolved Path is passed to `uv venv --python` to prevent uv from
|
||||
# re-resolving the version string back to a conda interpreter.
|
||||
function Find-CompatiblePython {
|
||||
# -X64Only: best installed x64 interpreter or $null, never ARM64. Last resort for
|
||||
# Install-X64Python, where x64 of a lower-priority minor beats ARM64.
|
||||
param([switch]$X64Only)
|
||||
# Windows on ARM: prefer x64. pyarrow (via datasets) and hf-transfer ship no
|
||||
# win_arm64 wheel, so a native ARM64 Python source-builds both and dies on CMake /
|
||||
# Rust minutes in; x64 runs fine emulated. ARM64 is still returned when it is all
|
||||
# there is, and the caller then bootstraps x64 or warns.
|
||||
$preferX64 = $X64Only -or ((Get-HostMachineArch) -eq "arm64")
|
||||
$candidates = @()
|
||||
# Try the Python Launcher first (most reliable on Windows)
|
||||
# py.exe resolves to the standard CPython install, not conda.
|
||||
# Prefer the requested $PythonVersion, then newest-first fallback.
|
||||
|
|
@ -1182,8 +1129,7 @@ exit 0
|
|||
# Resolve the actual executable path and verify it is not conda-based
|
||||
$resolvedExe = (& $pyLauncher.Source "-$minor" -c "import sys; print(sys.executable)" 2>$null | Out-String).Trim()
|
||||
if ($resolvedExe -and (Test-Path $resolvedExe) -and -not (Test-IsCondaPython $resolvedExe)) {
|
||||
if (-not $preferX64) { return @{ Version = $ver; Path = $resolvedExe; Arch = "" } }
|
||||
$candidates += @{ Version = $ver; Path = $resolvedExe }
|
||||
return @{ Version = $ver; Path = $resolvedExe }
|
||||
}
|
||||
}
|
||||
} catch {}
|
||||
|
|
@ -1204,53 +1150,11 @@ exit 0
|
|||
try {
|
||||
$out = & $cmd.Source --version 2>&1 | Out-String
|
||||
if ($out -match "Python (3\.1[1-3])\.\d+") {
|
||||
if (-not $preferX64) { return @{ Version = $Matches[1]; Path = $cmd.Source; Arch = "" } }
|
||||
$candidates += @{ Version = $Matches[1]; Path = $cmd.Source }
|
||||
return @{ Version = $Matches[1]; Path = $cmd.Source }
|
||||
}
|
||||
} catch {}
|
||||
}
|
||||
}
|
||||
# `py -3.12` runs the launcher's preferred build, normally the native ARM64 one, so
|
||||
# a same-minor x64 install that is neither preferred nor on PATH never becomes a
|
||||
# candidate. `-3.12-64` cannot disambiguate (deprecated, it only means "not
|
||||
# 32-bit"), so enumerate every registration with -0p and probe each path.
|
||||
if ($preferX64) {
|
||||
foreach ($pyLauncher in @(Get-Command py -All -CommandType Application -ErrorAction SilentlyContinue)) {
|
||||
if ($pyLauncher.Source -match $script:CondaSkipPattern) { continue }
|
||||
$listed = @()
|
||||
try { $listed = @(& $pyLauncher.Source "-0p" 2>$null) } catch {}
|
||||
foreach ($line in $listed) {
|
||||
# " -V:3.12 * C:\...\python.exe": tag, optional default marker, path.
|
||||
$m = [regex]::Match([string]$line, '(?i)^\s*-\S+\s+\*?\s*"?(?<p>\S.*?\.exe)"?\s*$')
|
||||
if (-not $m.Success) { continue }
|
||||
$exe = $m.Groups['p'].Value.Trim()
|
||||
if ($candidates | Where-Object { $_.Path -eq $exe }) { continue }
|
||||
if (-not (Test-Path -LiteralPath $exe)) { continue }
|
||||
if (Test-IsCondaPython $exe) { continue }
|
||||
try {
|
||||
$out = & $exe --version 2>&1 | Out-String
|
||||
if ($out -match "Python (3\.1[1-3])\.\d+") {
|
||||
$candidates += @{ Version = $Matches[1]; Path = $exe }
|
||||
}
|
||||
} catch {}
|
||||
}
|
||||
}
|
||||
}
|
||||
# Prefer x64, but only within one minor: $minors is the caller's version preference,
|
||||
# so ranking on arch alone would answer UNSLOTH_PYTHON=3.12 with an x64 3.13 and
|
||||
# never bootstrap x64 3.12. Probing costs a subprocess, so non-ARM returned above.
|
||||
foreach ($c in $candidates) {
|
||||
$tag = Get-PythonPlatformTag $c.Path
|
||||
$c.Arch = if ($tag -eq "win-amd64") { "x86_64" } elseif ($tag -eq "win-arm64") { "arm64" } else { "unknown" }
|
||||
}
|
||||
foreach ($minor in $minors) {
|
||||
$sameMinor = @($candidates | Where-Object { $_.Version -eq $minor })
|
||||
if ($sameMinor.Count -eq 0) { continue }
|
||||
$x64 = $sameMinor | Where-Object { $_.Arch -eq "x86_64" } | Select-Object -First 1
|
||||
if ($x64) { return $x64 }
|
||||
if (-not $X64Only) { return $sameMinor[0] }
|
||||
}
|
||||
if (-not $X64Only -and $candidates.Count -gt 0) { return $candidates[0] }
|
||||
return $null
|
||||
}
|
||||
|
||||
|
|
@ -1261,11 +1165,8 @@ exit 0
|
|||
# (no UAC), putting python.exe + the py launcher on PATH. Mirrors the uv ->
|
||||
# astral.sh fallback below. Returns @{ Version; Path } or $null.
|
||||
function Install-PythonFromPythonOrg {
|
||||
# $Arch overrides the host arch, to pull x64 onto an ARM64 box.
|
||||
param([string]$Arch = "")
|
||||
# python.org ships one installer per architecture.
|
||||
$targetArch = if ($Arch) { $Arch } else { Get-TauriDiagArch }
|
||||
$archSuffix = switch ($targetArch) {
|
||||
$archSuffix = switch (Get-TauriDiagArch) {
|
||||
"x86_64" { "-amd64" }
|
||||
"arm64" { "-arm64" }
|
||||
"x86" { "" }
|
||||
|
|
@ -1330,28 +1231,6 @@ exit 0
|
|||
return (Find-CompatiblePython)
|
||||
}
|
||||
|
||||
# ── Windows on ARM: get an x64 CPython ──
|
||||
# --architecture x64 forces winget off the ARM64 build; python.org takes the same override.
|
||||
function Install-X64Python {
|
||||
if ($script:WingetAvailable) {
|
||||
$prevEAP = $ErrorActionPreference
|
||||
$ErrorActionPreference = "Continue"
|
||||
try {
|
||||
winget install -e --id "Python.Python.$PythonVersion" --source winget --architecture x64 --accept-package-agreements --accept-source-agreements
|
||||
} catch { }
|
||||
$ErrorActionPreference = $prevEAP
|
||||
Refresh-SessionPath
|
||||
$found = Find-CompatiblePython
|
||||
if ($found -and $found.Arch -eq "x86_64") { return $found }
|
||||
substep "winget could not provide an x64 Python -- trying python.org..." "Yellow"
|
||||
}
|
||||
$found = Install-PythonFromPythonOrg -Arch "x86_64"
|
||||
if ($found -and $found.Arch -eq "x86_64") { return $found }
|
||||
# Nothing installable (offline / no winget): an x64 build of another supported minor
|
||||
# still runs the wheels ARM64 cannot, so take it over the native interpreter.
|
||||
return (Find-CompatiblePython -X64Only)
|
||||
}
|
||||
|
||||
# ── Install Python if no compatible version (3.11-3.13) found ──
|
||||
# Find-CompatiblePython returns @{ Version = "3.13"; Path = "C:\...\python.exe" } or $null.
|
||||
Write-TauriLog "STEP" "Installing Python"
|
||||
|
|
@ -1423,26 +1302,6 @@ exit 0
|
|||
return (Exit-InstallFailure "Python installation failed")
|
||||
}
|
||||
}
|
||||
# ── Windows on ARM: swap a native ARM64 interpreter for x64 ──
|
||||
# pyarrow and hf-transfer publish no win_arm64 wheel, so an ARM64 Python source-builds
|
||||
# both and fails deep into the run. Warn up front if x64 is unobtainable.
|
||||
if ($DetectedPython -and (Get-HostMachineArch) -eq "arm64" -and $DetectedPython.Arch -ne "x86_64") {
|
||||
substep "windows on arm: only a native ARM64 Python $($DetectedPython.Version) was found." "Yellow"
|
||||
substep "pyarrow and hf-transfer publish no win_arm64 wheels, so installing x64 Python..." "Yellow"
|
||||
$X64Python = Install-X64Python
|
||||
if ($X64Python) {
|
||||
$DetectedPython = $X64Python
|
||||
step "python" "using x64 Python $($DetectedPython.Version) under emulation"
|
||||
} else {
|
||||
Write-Host "[WARN] Could not install an x64 Python on this ARM64 machine." -ForegroundColor Yellow
|
||||
Write-Host " Continuing with ARM64 Python $($DetectedPython.Version), but the install is likely to fail:" -ForegroundColor Yellow
|
||||
Write-Host " pyarrow (via datasets) and hf-transfer ship no win_arm64 wheels and will be" -ForegroundColor Yellow
|
||||
Write-Host " built from source, which needs CMake plus the MSVC and Rust toolchains." -ForegroundColor Yellow
|
||||
Write-Host " Fix: install x64 Python from https://www.python.org/downloads/windows/" -ForegroundColor Yellow
|
||||
Write-Host " (choose 'Windows installer (64-bit)', not ARM64), then re-run this installer." -ForegroundColor Yellow
|
||||
}
|
||||
}
|
||||
|
||||
$DiagPythonVersion = $PythonVersion
|
||||
if ($DetectedPython) { $DiagPythonVersion = $DetectedPython.Version }
|
||||
$InitialGpuBranch = "unknown"
|
||||
|
|
@ -1744,7 +1603,7 @@ exit 0
|
|||
if (-not (Test-Path -LiteralPath $VenvPython)) {
|
||||
step "venv" "creating Python $($DetectedPython.Version) virtual environment"
|
||||
substep "$VenvDir"
|
||||
$venvExit = Invoke-InstallCommand -Label "create virtual environment" { uv venv $VenvDir --python "$($DetectedPython.Path)" }
|
||||
$venvExit = Invoke-InstallCommand { uv venv $VenvDir --python "$($DetectedPython.Path)" }
|
||||
if ($venvExit -ne 0) {
|
||||
Write-Host "[ERROR] Failed to create virtual environment (exit code $venvExit)" -ForegroundColor Red
|
||||
return (Exit-InstallFailure "Failed to create virtual environment (exit code $venvExit)" $venvExit)
|
||||
|
|
@ -2058,14 +1917,12 @@ exit 0
|
|||
# (gfx120X/110X/1151/1150/103X); unknown names fall back to CPU.
|
||||
elseif ($ROCmGpuLabel) {
|
||||
$nameArchTable = @(
|
||||
@{ P = "9070|9080"; A = "gfx1201" } # RDNA 4 (Navi 48: RX 9070 XT / 9070 GRE / 9070 / 9080)
|
||||
@{ P = "9060"; A = "gfx1200" } # RDNA 4 (Navi 44: RX 9060 XT / 9060)
|
||||
@{ P = "9070 XT|9080"; A = "gfx1201" } # RDNA 4 (RX 9070 XT / 9080)
|
||||
@{ P = "9070|9060"; A = "gfx1200" } # RDNA 4 (RX 9070 / 9060)
|
||||
@{ P = "8065S|8060S|8050S|8040S|Strix Halo|Ryzen AI Max|AI Max"; A = "gfx1151" } # RDNA 3.5 (Strix Halo + Gorgon Halo: Radeon 8065S/8060S/8050S/8040S iGPU, Ryzen AI Max / Max+)
|
||||
@{ P = "890M|880M|Strix Point|HX 37[05]|AI 9 HX|AI 9 36[05]"; A = "gfx1150" } # RDNA 3.5 (Strix Point: Radeon 890M/880M, Ryzen AI 9 HX 370/375)
|
||||
@{ P = "860M|840M|Krackan|AI 7 35[05]|AI 5 34[05]|AI 7 PRO 35|AI 5 33"; A = "gfx1152" } # RDNA 3.5 (Krackan Point: Radeon 860M/840M, Ryzen AI 7 350 / AI 5 340)
|
||||
@{ P = "RX 7900|PRO W7900|PRO W7800"; A = "gfx1100" } # RDNA 3 desktop/workstation (Navi 31)
|
||||
@{ P = "RX 7800|RX 7700(?!S)|PRO W7700|PRO V710"; A = "gfx1101" } # RDNA 3 (Navi 32)
|
||||
@{ P = "RX 7600|RX 7700S|RX 7650|PRO W7600|PRO W7500"; A = "gfx1102" } # RDNA 3 (Navi 33)
|
||||
@{ P = "890M|880M|860M|840M|Strix Point|Krackan|HX 37[05]|AI 9 HX|AI 9 36[05]|AI 7 35[05]|AI 5 34[05]|AI 7 PRO 35|AI 5 33"; A = "gfx1150" } # RDNA 3.5 (Strix/Krackan Point: Radeon 890M/880M iGPU, Ryzen AI 9 HX 370/375)
|
||||
@{ P = "RX 7900|RX 7800|RX 7700(?!S)|PRO W7900|PRO W7800|PRO W7700"; A = "gfx1100" } # RDNA 3 desktop/workstation (Navi 31)
|
||||
@{ P = "RX 7600|RX 7700S|RX 7650|PRO W7600|PRO W7500|PRO V710"; A = "gfx1102" } # RDNA 3 (Navi 33)
|
||||
@{ P = "780M|760M|740M|Phoenix|Hawk Point|Z1 Extreme|Z2 Extreme"; A = "gfx1103" } # RDNA 3 iGPU (Phoenix / Hawk Point)
|
||||
@{ P = "RX 6900|RX 6800|RX 6750|RX 6700|PRO W6800|PRO W6900"; A = "gfx1030" } # RDNA 2 (Navi 21) -- gfx103X family
|
||||
@{ P = "RX 6650|RX 6600|PRO W6600|PRO W6650"; A = "gfx1032" } # RDNA 2 (Navi 23) -- gfx103X family
|
||||
|
|
@ -2346,7 +2203,6 @@ exit 0
|
|||
$archFamilyMap = @{
|
||||
"gfx1201" = "gfx120X-all"; "gfx1200" = "gfx120X-all" # RDNA 4
|
||||
"gfx1151" = "gfx1151"; "gfx1150" = "gfx1150" # RDNA 3.5 (Strix Halo/Point)
|
||||
"gfx1152" = "gfx1152" # RDNA 3.5 (Krackan Point)
|
||||
"gfx1103" = "gfx110X-all"; "gfx1102" = "gfx110X-all" # RDNA 3
|
||||
"gfx1101" = "gfx110X-all"; "gfx1100" = "gfx110X-all"
|
||||
"gfx1036" = "gfx103X-all"; "gfx1035" = "gfx103X-all" # RDNA 2 (RX 6000)
|
||||
|
|
@ -2368,7 +2224,6 @@ exit 0
|
|||
$torchFloorMap = @{
|
||||
"gfx1201" = "torch>=2.11.0,<2.12.0"; "gfx1200" = "torch>=2.11.0,<2.12.0"
|
||||
"gfx1151" = "torch>=2.11.0,<2.12.0"; "gfx1150" = "torch>=2.11.0,<2.12.0"
|
||||
"gfx1152" = "torch>=2.11.0,<2.12.0"
|
||||
}
|
||||
# Companion ranges track the torch ceiling so pip resolves a consistent
|
||||
# trio on AMD's per-arch index (each published independently). Mirrors
|
||||
|
|
@ -2376,12 +2231,10 @@ exit 0
|
|||
$torchvisionFloorMap = @{
|
||||
"gfx1201" = "torchvision>=0.26.0,<0.27.0"; "gfx1200" = "torchvision>=0.26.0,<0.27.0"
|
||||
"gfx1151" = "torchvision>=0.26.0,<0.27.0"; "gfx1150" = "torchvision>=0.26.0,<0.27.0"
|
||||
"gfx1152" = "torchvision>=0.26.0,<0.27.0"
|
||||
}
|
||||
$torchaudioFloorMap = @{
|
||||
"gfx1201" = "torchaudio>=2.11.0,<2.12.0"; "gfx1200" = "torchaudio>=2.11.0,<2.12.0"
|
||||
"gfx1151" = "torchaudio>=2.11.0,<2.12.0"; "gfx1150" = "torchaudio>=2.11.0,<2.12.0"
|
||||
"gfx1152" = "torchaudio>=2.11.0,<2.12.0"
|
||||
}
|
||||
$archFamily = if ($ROCmGfxArch -and $archFamilyMap.ContainsKey($ROCmGfxArch)) { $archFamilyMap[$ROCmGfxArch] } else { $null }
|
||||
if ($archFamily) {
|
||||
|
|
@ -2411,7 +2264,7 @@ exit 0
|
|||
$_pinRocm211 = ([int]$Matches[1] -eq 7 -and [int]$Matches[2] -eq 2)
|
||||
}
|
||||
# Only the 2.11-allowlist gfx arches need the floor; others publish <2.11 and stay bare.
|
||||
$_pinGfx211 = @('gfx120x-all', 'gfx1151', 'gfx1150', 'gfx1152') -contains $_pinLeaf
|
||||
$_pinGfx211 = @('gfx120x-all', 'gfx1151', 'gfx1150') -contains $_pinLeaf
|
||||
if ($_pinGfx211 -or $_pinRocm211) {
|
||||
$ROCmIndexUrl = $TorchIndexUrl
|
||||
$ROCmTorchFloor = "torch>=2.11.0,<2.12.0"
|
||||
|
|
@ -2494,7 +2347,7 @@ exit 0
|
|||
if ($SkipTorch) {
|
||||
# No-torch: install unsloth + unsloth-zoo with --no-deps, then
|
||||
# runtime deps (typer, safetensors, transformers, etc.) with --no-deps.
|
||||
$baseInstallExit = Invoke-InstallCommandRetry -Label "install unsloth (migrated no-torch)" { uv pip install --python $VenvPython --no-deps --reinstall-package unsloth --reinstall-package unsloth-zoo "unsloth>=2026.7.5" "unsloth-zoo>=2026.7.6" }
|
||||
$baseInstallExit = Invoke-InstallCommandRetry -Label "install unsloth (migrated no-torch)" { uv pip install --python $VenvPython --no-deps --reinstall-package unsloth --reinstall-package unsloth-zoo "unsloth>=2026.7.4" "unsloth-zoo>=2026.7.4" }
|
||||
if ($baseInstallExit -eq 0) {
|
||||
# Resolve pydantic WITH deps so pip pins pydantic-core
|
||||
# to the matching version (no-torch-runtime.txt below
|
||||
|
|
@ -2508,7 +2361,7 @@ exit 0
|
|||
}
|
||||
}
|
||||
} else {
|
||||
$baseInstallExit = Invoke-InstallCommandRetry -Label "install unsloth (migrated)" { uv pip install --python $VenvPython --reinstall-package unsloth --reinstall-package unsloth-zoo "unsloth>=2026.7.5" "unsloth-zoo>=2026.7.6" }
|
||||
$baseInstallExit = Invoke-InstallCommandRetry -Label "install unsloth (migrated)" { uv pip install --python $VenvPython --reinstall-package unsloth --reinstall-package unsloth-zoo "unsloth>=2026.7.4" "unsloth-zoo>=2026.7.4" }
|
||||
}
|
||||
if ($baseInstallExit -ne 0) {
|
||||
Write-Host "[ERROR] Failed to install unsloth (exit code $baseInstallExit)" -ForegroundColor Red
|
||||
|
|
@ -2516,7 +2369,7 @@ exit 0
|
|||
}
|
||||
if ($StudioLocalInstall) {
|
||||
substep "overlaying local repo (editable)..."
|
||||
$overlayExit = Invoke-InstallCommand -Label "overlay local repo" { uv pip install --python $VenvPython -e $RepoRoot --no-deps }
|
||||
$overlayExit = Invoke-InstallCommand { uv pip install --python $VenvPython -e $RepoRoot --no-deps }
|
||||
if ($overlayExit -ne 0) {
|
||||
Write-Host "[ERROR] Failed to overlay local repo (exit code $overlayExit)" -ForegroundColor Red
|
||||
return (Exit-InstallFailure "Failed to overlay local repo (exit code $overlayExit)" $overlayExit)
|
||||
|
|
@ -2563,13 +2416,6 @@ exit 0
|
|||
}
|
||||
} else {
|
||||
Write-TauriLog "STEP" "Installing PyTorch"
|
||||
# Windows on ARM lacks only torchaudio (whl/cpu win_arm64: torch 42,
|
||||
# torchvision 60, torchaudio 0), so drop that pin instead of aborting. Ask the
|
||||
# interpreter, not PROCESSOR_ARCHITECTURE; reached when no x64 Python exists.
|
||||
$VenvPlatform = ""
|
||||
try {
|
||||
$VenvPlatform = (& $VenvPython -c "import sysconfig; print(sysconfig.get_platform())" 2>$null | Out-String).Trim().ToLowerInvariant()
|
||||
} catch { $VenvPlatform = "" }
|
||||
substep "installing PyTorch ($(Remove-IndexUrlCredentials $TorchIndexUrl))..."
|
||||
# Bound the companions to the capped torch on EVERY index, cu<digits>
|
||||
# families included: torchaudio 2.11 dropped its exact torch pin from
|
||||
|
|
@ -2577,13 +2423,7 @@ exit 0
|
|||
# resolve a mismatched 2.11.0 build. Mirrors install.sh.
|
||||
$_pinVisionSpec = "torchvision>=0.19,<0.26.0"
|
||||
$_pinAudioSpec = "torchaudio>=2.4,<2.11.0"
|
||||
$_torchSpecs = @("torch>=2.4,<2.11.0", $_pinVisionSpec, $_pinAudioSpec)
|
||||
if ($VenvPlatform -eq "win-arm64") {
|
||||
substep "windows on arm: skipping torchaudio (upstream publishes no"
|
||||
substep "win_arm64 wheel); torch and torchvision install normally."
|
||||
$_torchSpecs = @("torch>=2.4,<2.11.0", $_pinVisionSpec)
|
||||
}
|
||||
$torchInstallExit = Invoke-InstallCommandRetry -Label "install PyTorch" { uv pip install --python $VenvPython @_torchSpecs --default-index $TorchIndexUrl }
|
||||
$torchInstallExit = Invoke-InstallCommandRetry -Label "install PyTorch" { uv pip install --python $VenvPython "torch>=2.4,<2.11.0" $_pinVisionSpec $_pinAudioSpec --default-index $TorchIndexUrl }
|
||||
if ($torchInstallExit -ne 0) {
|
||||
Write-Host "[ERROR] Failed to install PyTorch (exit code $torchInstallExit)" -ForegroundColor Red
|
||||
return (Exit-InstallFailure "Failed to install PyTorch (exit code $torchInstallExit)" $torchInstallExit)
|
||||
|
|
@ -2595,7 +2435,7 @@ exit 0
|
|||
if ($SkipTorch) {
|
||||
# No-torch: install unsloth + unsloth-zoo with --no-deps, then
|
||||
# runtime deps (typer, safetensors, transformers, etc.) with --no-deps.
|
||||
$baseInstallExit = Invoke-InstallCommandRetry -Label "install unsloth (no-torch)" { uv pip install --python $VenvPython --no-deps --upgrade-package unsloth --upgrade-package unsloth-zoo "unsloth>=2026.7.5" "unsloth-zoo>=2026.7.6" }
|
||||
$baseInstallExit = Invoke-InstallCommandRetry -Label "install unsloth (no-torch)" { uv pip install --python $VenvPython --no-deps --upgrade-package unsloth --upgrade-package unsloth-zoo "unsloth>=2026.7.4" "unsloth-zoo>=2026.7.4" }
|
||||
if ($baseInstallExit -eq 0) {
|
||||
# Same pydantic-with-deps trick as the migrated branch.
|
||||
$baseInstallExit = Invoke-InstallCommandRetry -Label "install pydantic" { uv pip install --python $VenvPython pydantic }
|
||||
|
|
@ -2607,7 +2447,7 @@ exit 0
|
|||
}
|
||||
}
|
||||
} elseif ($StudioLocalInstall) {
|
||||
$baseInstallExit = Invoke-InstallCommandRetry -Label "install unsloth (local)" { uv pip install --python $VenvPython --upgrade-package unsloth "unsloth>=2026.7.5" "unsloth-zoo>=2026.7.6" }
|
||||
$baseInstallExit = Invoke-InstallCommandRetry -Label "install unsloth (local)" { uv pip install --python $VenvPython --upgrade-package unsloth "unsloth>=2026.7.4" "unsloth-zoo>=2026.7.4" }
|
||||
} else {
|
||||
$baseInstallExit = Invoke-InstallCommandRetry -Label "install unsloth" { uv pip install --python $VenvPython --upgrade-package unsloth -- "$PackageName" }
|
||||
}
|
||||
|
|
@ -2618,7 +2458,7 @@ exit 0
|
|||
|
||||
if ($StudioLocalInstall) {
|
||||
substep "overlaying local repo (editable)..."
|
||||
$overlayExit = Invoke-InstallCommand -Label "overlay local repo" { uv pip install --python $VenvPython -e $RepoRoot --no-deps }
|
||||
$overlayExit = Invoke-InstallCommand { uv pip install --python $VenvPython -e $RepoRoot --no-deps }
|
||||
if ($overlayExit -ne 0) {
|
||||
Write-Host "[ERROR] Failed to overlay local repo (exit code $overlayExit)" -ForegroundColor Red
|
||||
return (Exit-InstallFailure "Failed to overlay local repo (exit code $overlayExit)" $overlayExit)
|
||||
|
|
@ -2635,13 +2475,13 @@ exit 0
|
|||
Write-TauriLog "STEP" "Installing unsloth"
|
||||
substep "installing unsloth (this may take a few minutes)..."
|
||||
if ($StudioLocalInstall) {
|
||||
$baseInstallExit = Invoke-InstallCommandRetry -Label "install unsloth (auto torch backend)" { uv pip install --python $VenvPython "unsloth-zoo>=2026.7.6" "unsloth>=2026.7.5" --torch-backend=auto }
|
||||
$baseInstallExit = Invoke-InstallCommandRetry -Label "install unsloth (auto torch backend)" { uv pip install --python $VenvPython "unsloth-zoo>=2026.7.4" "unsloth>=2026.7.4" --torch-backend=auto }
|
||||
if ($baseInstallExit -ne 0) {
|
||||
Write-Host "[ERROR] Failed to install unsloth (exit code $baseInstallExit)" -ForegroundColor Red
|
||||
return (Exit-InstallFailure "Failed to install unsloth (exit code $baseInstallExit)" $baseInstallExit)
|
||||
}
|
||||
substep "overlaying local repo (editable)..."
|
||||
$overlayExit = Invoke-InstallCommand -Label "overlay local repo" { uv pip install --python $VenvPython -e $RepoRoot --no-deps }
|
||||
$overlayExit = Invoke-InstallCommand { uv pip install --python $VenvPython -e $RepoRoot --no-deps }
|
||||
if ($overlayExit -ne 0) {
|
||||
Write-Host "[ERROR] Failed to overlay local repo (exit code $overlayExit)" -ForegroundColor Red
|
||||
return (Exit-InstallFailure "Failed to overlay local repo (exit code $overlayExit)" $overlayExit)
|
||||
|
|
@ -2689,7 +2529,7 @@ exit 0
|
|||
$visionSpec = if ($PinnedRocmVisionSpec) { $PinnedRocmVisionSpec } elseif ($ROCmGfxArch -and $torchvisionFloorMap -and $torchvisionFloorMap.ContainsKey($ROCmGfxArch)) { $torchvisionFloorMap[$ROCmGfxArch] } else { "torchvision" }
|
||||
$audioSpec = if ($PinnedRocmAudioSpec) { $PinnedRocmAudioSpec } elseif ($ROCmGfxArch -and $torchaudioFloorMap -and $torchaudioFloorMap.ContainsKey($ROCmGfxArch)) { $torchaudioFloorMap[$ROCmGfxArch] } else { "torchaudio" }
|
||||
substep "PyTorch flavor mismatch (installed $installedTorchTag, need ROCm) -- reinstalling correct build..." "Yellow"
|
||||
$torchFixExit = Invoke-InstallCommand -Label "reinstall PyTorch (ROCm)" { uv pip install --python $VenvPython --force-reinstall --default-index $ROCmIndexUrl $rocmSpec $visionSpec $audioSpec }
|
||||
$torchFixExit = Invoke-InstallCommand { uv pip install --python $VenvPython --force-reinstall --default-index $ROCmIndexUrl $rocmSpec $visionSpec $audioSpec }
|
||||
if ($torchFixExit -ne 0) {
|
||||
Write-Host "[ERROR] Failed to reinstall PyTorch with the correct ROCm build (exit code $torchFixExit)" -ForegroundColor Red
|
||||
return (Exit-InstallFailure "Failed to reinstall PyTorch (ROCm) (exit code $torchFixExit)" $torchFixExit)
|
||||
|
|
@ -2698,7 +2538,7 @@ exit 0
|
|||
} elseif ($expectedTorchTag -ne 'rocm') {
|
||||
# CUDA: stale +cpu (or wrong cuXXX) against a CUDA index -> reinstall triplet.
|
||||
substep "PyTorch flavor mismatch (installed $installedTorchTag, need $expectedTorchTag) -- reinstalling correct build..." "Yellow"
|
||||
$torchFixExit = Invoke-InstallCommand -Label "reinstall PyTorch ($expectedTorchTag)" { uv pip install --python $VenvPython "torch>=2.4,<2.11.0" "torchvision>=0.19,<0.26.0" "torchaudio>=2.4,<2.11.0" --default-index $TorchIndexUrl --reinstall-package torch --reinstall-package torchvision --reinstall-package torchaudio }
|
||||
$torchFixExit = Invoke-InstallCommand { uv pip install --python $VenvPython "torch>=2.4,<2.11.0" "torchvision>=0.19,<0.26.0" "torchaudio>=2.4,<2.11.0" --default-index $TorchIndexUrl --reinstall-package torch --reinstall-package torchvision --reinstall-package torchaudio }
|
||||
if ($torchFixExit -ne 0) {
|
||||
Write-Host "[ERROR] Failed to reinstall PyTorch with the correct CUDA build (exit code $torchFixExit)" -ForegroundColor Red
|
||||
return (Exit-InstallFailure "Failed to reinstall PyTorch ($expectedTorchTag) (exit code $torchFixExit)" $torchFixExit)
|
||||
|
|
@ -2799,9 +2639,6 @@ exit 0
|
|||
# an inherited value would put llama.cpp in the wrong place.
|
||||
$previousUnslothStudioHome = $env:UNSLOTH_STUDIO_HOME
|
||||
$hadPreviousUnslothStudioHome = ($null -ne $previousUnslothStudioHome)
|
||||
$previousTauriMode = $env:UNSLOTH_TAURI_MODE
|
||||
$hadPreviousTauriMode = ($null -ne $previousTauriMode)
|
||||
$env:UNSLOTH_TAURI_MODE = if ($TauriMode) { "1" } else { "0" }
|
||||
if ($StudioRedirectMode -eq 'env') {
|
||||
$env:UNSLOTH_STUDIO_HOME = $StudioHome
|
||||
} else {
|
||||
|
|
@ -2831,22 +2668,14 @@ exit 0
|
|||
} else {
|
||||
Remove-Item Env:UNSLOTH_STUDIO_HOME -ErrorAction SilentlyContinue
|
||||
}
|
||||
if ($hadPreviousTauriMode) {
|
||||
$env:UNSLOTH_TAURI_MODE = $previousTauriMode
|
||||
} else {
|
||||
Remove-Item Env:UNSLOTH_TAURI_MODE -ErrorAction SilentlyContinue
|
||||
}
|
||||
Remove-Item Env:UNSLOTH_LOCAL_LLAMA_CPP_DIR -ErrorAction SilentlyContinue
|
||||
Remove-Item Env:UNSLOTH_INSTALL_ROLLBACK_MANAGED -ErrorAction SilentlyContinue
|
||||
Remove-Item Env:UNSLOTH_SETUP_PYTHON -ErrorAction SilentlyContinue
|
||||
}
|
||||
if ($setupExit -ne 0) {
|
||||
if (-not $TauriMode) {
|
||||
Write-Host "[ERROR] unsloth studio setup failed (exit code $setupExit)" -ForegroundColor Red
|
||||
}
|
||||
Write-Host "[ERROR] unsloth studio setup failed (exit code $setupExit)" -ForegroundColor Red
|
||||
return (Exit-InstallFailure "unsloth studio setup failed (exit code $setupExit)" $setupExit)
|
||||
}
|
||||
Clear-TauriInstallError "studio setup completed"
|
||||
|
||||
# ── Expose `unsloth` via a shim dir containing only unsloth.exe ──
|
||||
# We do NOT add the venv Scripts dir to PATH (it also holds python.exe
|
||||
|
|
|
|||
612
install.sh
612
install.sh
|
|
@ -19,17 +19,6 @@
|
|||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
# Copyright 2026-present the Unsloth AI Inc. team. All rights reserved. See /studio/LICENSE.AGPL-3.0
|
||||
set -e
|
||||
# ── Why the installer lives in a function ──
|
||||
# Under `curl ... | sh`, sh is the pipe READER. This file is ~150KB, so a top-level
|
||||
# `exit` left most of it unread, the write end failed, and curl tacked
|
||||
# "(56) Failure writing output to destination" onto our own error message. Wrapping
|
||||
# the body forces sh to parse to the closing brace first, so the pipe always drains
|
||||
# (install.ps1 has always had this shape).
|
||||
#
|
||||
# Body is deliberately NOT reindented: reflowing 4000+ lines would bury the change,
|
||||
# and `exit` still exits the shell from inside a function. Do not add
|
||||
# `exec < /dev/null`: for a piped shell that closes the script's own source.
|
||||
_unsloth_main() {
|
||||
|
||||
# ── Output style (aligned with studio/setup.sh) ──
|
||||
RULE=""
|
||||
|
|
@ -218,37 +207,18 @@ run_install_cmd() {
|
|||
# command's exit code across the pipe without relying on pipefail
|
||||
# (this script runs under plain sh).
|
||||
_rcf=$(mktemp)
|
||||
tauri_stream_log stdout "OUTPUT_CLEAR" "$_label"
|
||||
{
|
||||
if "$@" 2>&1; then
|
||||
_cmd_rc=0
|
||||
else
|
||||
_cmd_rc=$?
|
||||
fi
|
||||
printf '%s' "$_cmd_rc" > "$_rcf"
|
||||
} | _redact_install_output
|
||||
{ "$@" 2>&1; printf '%s' "$?" > "$_rcf"; } | _redact_install_output
|
||||
_rc=$(cat "$_rcf" 2>/dev/null || echo 1)
|
||||
rm -f "$_rcf"
|
||||
_rc=${_rc:-1}
|
||||
if [ "$_rc" -eq 0 ] 2>/dev/null; then
|
||||
tauri_clear_install_error "$_label recovered"
|
||||
return 0
|
||||
fi
|
||||
tauri_stream_log stdout "ERROR_OUTPUT" "$_label failed (exit code $_rc)"
|
||||
[ "${_rc:-1}" -eq 0 ] 2>/dev/null && return 0
|
||||
step "error" "$_label failed (exit code $_rc)" "$C_ERR" >&2
|
||||
return "$_rc"
|
||||
fi
|
||||
_log=$(mktemp)
|
||||
tauri_stream_log stderr "OUTPUT_CLEAR" "$_label"
|
||||
"$@" >"$_log" 2>&1 && {
|
||||
rm -f "$_log"
|
||||
tauri_clear_install_error "$_label recovered"
|
||||
return 0
|
||||
}
|
||||
"$@" >"$_log" 2>&1 && { rm -f "$_log"; return 0; }
|
||||
_rc=$?
|
||||
step "error" "$_label failed (exit code $_rc)" "$C_ERR" >&2
|
||||
_redact_install_output "$_log" >&2
|
||||
tauri_stream_log stderr "ERROR_OUTPUT" "$_label failed (exit code $_rc)"
|
||||
rm -f "$_log"
|
||||
return $_rc
|
||||
}
|
||||
|
|
@ -287,70 +257,10 @@ run_install_cmd_retry() {
|
|||
done
|
||||
}
|
||||
|
||||
# True when the runtime target is gfx906 (MI50/Radeon VII): the prebuilt AMD
|
||||
# bitsandbytes wheel carries no gfx906 kernels, and force-reinstalling it would
|
||||
# clobber a user's source-built bnb (the only 4-bit path on this arch) on every
|
||||
# `studio update`. So skip the auto-install and leave whatever bnb is present.
|
||||
# _gfx906_target is set during torch-index resolution; also honor an explicit
|
||||
# UNSLOTH_ROCM_GFX_ARCH so a pinned-index install still skips. The override is
|
||||
# normalized (gfx906:sramecc-:xnack- -> gfx906) so a copied HIP gcnArchName counts.
|
||||
_is_gfx906_bnb_skip() {
|
||||
[ "${_gfx906_target:-false}" = true ] && return 0
|
||||
_bnb_gfx_env=$(printf '%s' "${UNSLOTH_ROCM_GFX_ARCH:-}" | tr '[:upper:]' '[:lower:]' | tr -d '[:space:]')
|
||||
_bnb_gfx_env=${_bnb_gfx_env%%:*}
|
||||
[ "$_bnb_gfx_env" = "gfx906" ] && return 0
|
||||
# A pinned index (UNSLOTH_TORCH_INDEX_URL/_FAMILY) skips the reroute block that
|
||||
# sets _gfx906_target, so a real gfx906 host with a pinned rocm6.3 index and no
|
||||
# UNSLOTH_ROCM_GFX_ARCH would otherwise clobber a source-built bnb. Probe here
|
||||
# in that gap; skip only when gfx906 is the SOLE distinct arch (mixed hosts
|
||||
# opt in via the env var, mirroring the reroute block's de-dup rule).
|
||||
if [ -z "$_bnb_gfx_env" ] && [ "${_torch_index_pinned:-false}" = true ]; then
|
||||
_bnb_gfx_probe=$(_probe_amd_gfx_arch | awk 'NF && !seen[$0]++')
|
||||
[ "$_bnb_gfx_probe" = "gfx906" ] && return 0
|
||||
fi
|
||||
return 1
|
||||
}
|
||||
|
||||
# `pip install unsloth` resolves its unconditional bitsandbytes dep to a generic
|
||||
# CUDA wheel (no gfx906 kernels) once we skip the prebuilt one. Snapshot bnb before
|
||||
# the unsloth install, then drop a freshly pulled wheel afterwards while leaving a
|
||||
# pre-existing source build in place.
|
||||
_gfx906_bnb_installed() {
|
||||
"$_VENV_PY" -c "import importlib.util as u, sys; sys.exit(0 if u.find_spec('bitsandbytes') else 1)" >/dev/null 2>&1
|
||||
}
|
||||
_gfx906_bnb_snapshot() {
|
||||
_gfx906_bnb_absent_before=false
|
||||
_is_gfx906_bnb_skip || return 0
|
||||
_gfx906_bnb_installed || _gfx906_bnb_absent_before=true
|
||||
}
|
||||
_gfx906_bnb_prune() {
|
||||
_is_gfx906_bnb_skip || return 0
|
||||
[ "${_gfx906_bnb_absent_before:-false}" = true ] || return 0
|
||||
_gfx906_bnb_installed || return 0
|
||||
substep "gfx906: removing generic bitsandbytes pulled in as a dependency (no gfx906 kernels; build from source for 4-bit QLoRA)" "$C_WARN"
|
||||
uv pip uninstall --python "$_VENV_PY" bitsandbytes >/dev/null 2>&1 \
|
||||
|| "$_VENV_PY" -m pip uninstall -y bitsandbytes >/dev/null 2>&1 || true
|
||||
}
|
||||
|
||||
# Install bitsandbytes on AMD ROCm hosts. bnb <= 0.49.2 NaNs at 4-bit decode
|
||||
# shape on every AMD GPU; the fix (bnb #1887) ships in continuous-release_main
|
||||
# and, on PyPI, first in 0.50.0. Keep this floor in step with the amd extra in
|
||||
# pyproject.toml and studio/install_python_stack.py.
|
||||
_BNB_ROCM_PYPI_FALLBACK="bitsandbytes>=0.50.0"
|
||||
# bitsandbytes ships no ROCm binary in its aarch64 wheel at any version: the PyPI
|
||||
# 0.50.0 and continuous-release_main aarch64 wheels both carry only
|
||||
# libbitsandbytes_cpu.so plus CUDA variants. So neither install path below gives
|
||||
# aarch64 a 4-bit backend, and the messages must not claim one. Cf. gfx906.
|
||||
_bnb_rocm_arch_has_binary() {
|
||||
case "$_ARCH" in
|
||||
aarch64|arm64) return 1 ;;
|
||||
*) return 0 ;;
|
||||
esac
|
||||
}
|
||||
_warn_bnb_no_rocm_binary() {
|
||||
_bnb_rocm_arch_has_binary && return 0
|
||||
substep "[WARN] aarch64: bitsandbytes ships no ROCm kernels on this arch; 4-bit QLoRA needs a source build -- https://docs.unsloth.ai/get-started/install-and-update/amd" "$C_WARN"
|
||||
}
|
||||
# Install bitsandbytes on AMD ROCm hosts. Uses the continuous-release_main
|
||||
# wheel for the ROCm 4-bit GEMV fix (bnb PR #1887, post-0.49.2); bnb <= 0.49.2
|
||||
# NaNs at decode shape on every AMD GPU. Falls back to PyPI >=0.49.1 if the
|
||||
# pre-release URL is unreachable. Drop the pin once bnb 0.50+ ships on PyPI.
|
||||
_install_bnb_rocm() {
|
||||
_label="$1"
|
||||
_venv_py="$2"
|
||||
|
|
@ -365,8 +275,9 @@ _install_bnb_rocm() {
|
|||
_bnb_whl_url=""
|
||||
;;
|
||||
esac
|
||||
# uv rejects the pre-release wheel: filename version (1.33.7rc0) does not
|
||||
# match metadata (0.50.x.dev0). pip accepts it, so bootstrap pip and use it.
|
||||
# uv rejects the continuous-release_main bitsandbytes wheel because the
|
||||
# filename version (1.33.7rc0) does not match the embedded metadata version
|
||||
# (0.50.0.dev0). pip accepts the mismatch, so bootstrap pip and use it.
|
||||
if ! "$_venv_py" -m pip --version >/dev/null 2>&1; then
|
||||
if ! run_maybe_quiet "$_venv_py" -m ensurepip --upgrade; then
|
||||
run_maybe_quiet uv pip install --python "$_venv_py" pip || \
|
||||
|
|
@ -382,7 +293,6 @@ _install_bnb_rocm() {
|
|||
--retries 8 --timeout 90 \
|
||||
"$_bnb_whl_url" >"$_bnb_log" 2>&1; then
|
||||
rm -f "$_bnb_log"
|
||||
_warn_bnb_no_rocm_binary
|
||||
return 0
|
||||
fi
|
||||
_bnb_rc=$?
|
||||
|
|
@ -391,17 +301,10 @@ _install_bnb_rocm() {
|
|||
fi
|
||||
rm -f "$_bnb_log"
|
||||
step "warning" "$_label (pre-release) failed (exit code $_bnb_rc)" "$C_WARN" >&2
|
||||
if _bnb_rocm_arch_has_binary; then
|
||||
substep "[WARN] bnb pre-release install failed; falling back to PyPI $_BNB_ROCM_PYPI_FALLBACK, which carries the ROCm 4-bit fix" "$C_WARN"
|
||||
else
|
||||
substep "[WARN] bnb pre-release install failed; falling back to PyPI $_BNB_ROCM_PYPI_FALLBACK" "$C_WARN"
|
||||
fi
|
||||
substep "[WARN] bnb pre-release install failed; falling back to PyPI (4-bit decode broken on ROCm)" "$C_WARN"
|
||||
fi
|
||||
run_install_cmd "$_label (pypi fallback)" "$_venv_py" -m pip install \
|
||||
--force-reinstall --no-cache-dir --no-deps "$_BNB_ROCM_PYPI_FALLBACK"
|
||||
_bnb_pypi_rc=$?
|
||||
_warn_bnb_no_rocm_binary
|
||||
return $_bnb_pypi_rc
|
||||
--force-reinstall --no-cache-dir --no-deps "bitsandbytes>=0.49.1"
|
||||
}
|
||||
|
||||
if [ "$_next_is_package" = true ]; then
|
||||
|
|
@ -435,34 +338,6 @@ tauri_log() {
|
|||
fi
|
||||
}
|
||||
|
||||
tauri_stream_log() {
|
||||
_tsl_stream="$1"
|
||||
_tsl_tag="$2"
|
||||
shift 2
|
||||
if [ "$TAURI_MODE" = true ]; then
|
||||
if [ "$_tsl_stream" = stderr ]; then
|
||||
printf '[TAURI:%s] %s\n' "$_tsl_tag" "$*" >&2
|
||||
else
|
||||
printf '[TAURI:%s] %s\n' "$_tsl_tag" "$*"
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
rollback_substep() {
|
||||
if [ "$TAURI_MODE" = true ]; then
|
||||
tauri_log "PROGRESS" "$1"
|
||||
else
|
||||
substep "$@"
|
||||
fi
|
||||
}
|
||||
|
||||
tauri_clear_install_error() {
|
||||
if [ "$TAURI_MODE" = true ]; then
|
||||
tauri_log "ERROR_CLEAR" "$1"
|
||||
printf '[TAURI:ERROR_CLEAR] %s\n' "$1" >&2
|
||||
fi
|
||||
}
|
||||
|
||||
tauri_diag_marker() {
|
||||
_diag_gpu_branch="${1:-unknown}"
|
||||
_diag_torch_index_family="${2:-none}"
|
||||
|
|
@ -623,10 +498,10 @@ _restore_studio_venv_replacement() {
|
|||
_VENV_ROLLBACK_ACTIVE=false
|
||||
return 0
|
||||
}
|
||||
rollback_substep "restoring previous environment after failed install..." "$C_WARN"
|
||||
substep "restoring previous environment after failed install..." "$C_WARN"
|
||||
rm -rf "$_VENV_ROLLBACK_TARGET"
|
||||
if mv "$_VENV_ROLLBACK_DIR" "$_VENV_ROLLBACK_TARGET"; then
|
||||
rollback_substep "restored previous environment"
|
||||
substep "restored previous environment"
|
||||
_VENV_ROLLBACK_ACTIVE=false
|
||||
_VENV_ROLLBACK_DIR=""
|
||||
else
|
||||
|
|
@ -750,45 +625,6 @@ _is_pkg_installed() {
|
|||
esac
|
||||
}
|
||||
|
||||
# ── Helper: human-readable apt distro label for the sudo package prompt (#6207) ──
|
||||
# Reads /etc/os-release so the Accept? prompt can say which distro we detected and
|
||||
# that packages come from that distro's official apt repos (not a tarball).
|
||||
_apt_distro_description() {
|
||||
# Plain ( ... ) subshell — not $() — so case/;; stays bash-3.2-safe on macOS.
|
||||
# Bash 3.2 misparses case arms inside command substitution and errors on `;;`.
|
||||
(
|
||||
if [ ! -r /etc/os-release ]; then
|
||||
printf 'a debian-like system'
|
||||
exit 0
|
||||
fi
|
||||
# shellcheck disable=SC1091
|
||||
. /etc/os-release 2>/dev/null || true
|
||||
if [ -n "${NAME:-}" ] && [ -n "${VERSION_ID:-}" ]; then
|
||||
_ad_label="$NAME $VERSION_ID"
|
||||
elif [ -n "${PRETTY_NAME:-}" ]; then
|
||||
_ad_label="$PRETTY_NAME"
|
||||
elif [ -n "${NAME:-}" ]; then
|
||||
_ad_label="$NAME"
|
||||
else
|
||||
printf 'a debian-like system'
|
||||
exit 0
|
||||
fi
|
||||
case " ${ID:-} ${ID_LIKE:-} " in
|
||||
*" debian "*|*" ubuntu "*) _ad_label="${_ad_label} (debian-like)" ;;
|
||||
esac
|
||||
printf '%s' "$_ad_label"
|
||||
)
|
||||
}
|
||||
|
||||
# ── Helper: can the controlling terminal actually be opened for reading? ──
|
||||
# `test -r` only checks permission bits, which look fine in containers and
|
||||
# systemd units where open() then fails with ENXIO. Probe with a real open.
|
||||
# The subshell is required: in dash a failed redirection on the special
|
||||
# builtin `:` exits the whole script.
|
||||
_can_read_tty() {
|
||||
( : </dev/tty ) >/dev/null 2>&1
|
||||
}
|
||||
|
||||
# ── Helper: install packages via apt, escalating to sudo only if needed ──
|
||||
# Usage: _smart_apt_install pkg1 pkg2 pkg3 ...
|
||||
_smart_apt_install() {
|
||||
|
|
@ -811,90 +647,39 @@ _smart_apt_install() {
|
|||
return 0
|
||||
fi
|
||||
|
||||
# Optional callers never elevate, in any mode: nothing on the consumer path
|
||||
# builds anything, so neither the terminal sudo prompt below nor the Tauri
|
||||
# NEED_SUDO dialog (whose Cancel leaves the user not installed) may gate the
|
||||
# run over unused tools. The caller falls through to prebuilt llama.cpp.
|
||||
# Required packages such as curl still escalate.
|
||||
if [ "${_SMART_APT_OPTIONAL:-false}" = true ]; then
|
||||
return 2
|
||||
fi
|
||||
|
||||
# In Tauri mode, report needed packages and exit — Rust handles elevation
|
||||
if [ "$TAURI_MODE" = true ]; then
|
||||
# Report needed packages and exit — Rust handles elevation.
|
||||
tauri_log "NEED_SUDO" "$_STILL_MISSING"
|
||||
exit 2
|
||||
fi
|
||||
|
||||
# Step 3: Escalate -- need elevated permissions for remaining packages
|
||||
if command -v sudo >/dev/null 2>&1; then
|
||||
_ad_desc="$(_apt_distro_description)"
|
||||
echo ""
|
||||
echo " !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"
|
||||
echo " WARNING: We require sudo elevated permissions to install:"
|
||||
echo " $_STILL_MISSING"
|
||||
echo " Detected ${_ad_desc}."
|
||||
echo " If you accept, we'll run sudo apt-get to install these packages"
|
||||
echo " from your distro's official repositories (not a third-party tarball)."
|
||||
echo " If you accept, we'll run sudo now, and it'll prompt your password."
|
||||
echo " !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"
|
||||
echo ""
|
||||
if _can_read_tty; then
|
||||
printf " Accept? [Y/n] "
|
||||
# The device opened, so a failed read is EOF, not consent: decline,
|
||||
# as the autostart prompt below does. Enter is still yes (a
|
||||
# successful read of an empty line).
|
||||
read -r REPLY </dev/tty || REPLY="n"
|
||||
case "$REPLY" in
|
||||
[nN]*)
|
||||
echo ""
|
||||
echo " Please install these packages first, then re-run Unsloth Studio setup:"
|
||||
echo " sudo apt-get update -y && sudo apt-get install -y $_STILL_MISSING"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
# Mirror the headless branch: on a sudoers denial, a wrong password
|
||||
# or an apt error, say what to run by hand instead of letting set -e
|
||||
# abort on a bare sudo/apt message.
|
||||
if sudo apt-get update -y </dev/null &&
|
||||
sudo apt-get install -y $_STILL_MISSING </dev/null; then
|
||||
:
|
||||
else
|
||||
echo ""
|
||||
echo " Could not install these packages: $_STILL_MISSING"
|
||||
echo " See the error above."
|
||||
echo " Please install them first, then re-run Unsloth Studio setup:"
|
||||
echo " sudo apt-get update -y && sudo apt-get install -y $_STILL_MISSING"
|
||||
exit 1
|
||||
fi
|
||||
printf " Accept? [Y/n] "
|
||||
if [ -r /dev/tty ]; then
|
||||
read -r REPLY </dev/tty || REPLY="y"
|
||||
else
|
||||
# Nobody can answer a prompt or type a password here. -n makes sudo
|
||||
# refuse rather than prompt into a closed stdin, which is how #7307
|
||||
# died. Probe with the real commands: `sudo -l` answers whether they
|
||||
# are *authorized*, not whether running them needs authentication.
|
||||
# -k ignores any cached timestamp, so only a real NOPASSWD rule gets
|
||||
# through, not someone's sudo in another shell minutes ago. Per
|
||||
# sudo(8), -k alongside a command ignores the cached credentials and
|
||||
# "will not update" them, so other sessions keep theirs.
|
||||
echo " No terminal to confirm on; trying passwordless sudo."
|
||||
if sudo -n -k apt-get update -y </dev/null &&
|
||||
sudo -n -k apt-get install -y $_STILL_MISSING </dev/null; then
|
||||
echo " Installed with passwordless sudo."
|
||||
else
|
||||
REPLY="y"
|
||||
fi
|
||||
case "$REPLY" in
|
||||
[nN]*)
|
||||
echo ""
|
||||
echo " Could not install these packages: $_STILL_MISSING"
|
||||
echo " Detected ${_ad_desc}."
|
||||
# Either sudo refused, or apt failed on a bad repo, dpkg lock or
|
||||
# network outage. sudo exits 1 on an auth/config problem and
|
||||
# when the command cannot be executed, but otherwise passes the
|
||||
# command's own status through, so state both causes.
|
||||
echo " Either sudo needs a password here, or apt-get itself"
|
||||
echo " failed; see the error above. With no terminal to"
|
||||
echo " authenticate on, this cannot be done unattended."
|
||||
echo " Please install them first, then re-run Unsloth Studio setup:"
|
||||
echo " Please install these packages first, then re-run Unsloth Studio setup:"
|
||||
echo " sudo apt-get update -y && sudo apt-get install -y $_STILL_MISSING"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
;;
|
||||
*)
|
||||
sudo apt-get update -y </dev/null
|
||||
sudo apt-get install -y $_STILL_MISSING </dev/null
|
||||
;;
|
||||
esac
|
||||
else
|
||||
echo ""
|
||||
echo " sudo is not available on this system."
|
||||
|
|
@ -2018,142 +1803,67 @@ _maybe_reroute_strixhalo_to_2404() {
|
|||
_maybe_reroute_strixhalo_to_2404 || true
|
||||
|
||||
# ── Check system dependencies ──
|
||||
# cmake/git are only needed to *build* llama.cpp from source. Unsloth downloads a
|
||||
# prebuilt by default, and setup.sh self-skips the source build when they're
|
||||
# absent -- so macOS doesn't block on cmake (requiring it would force a manual
|
||||
# Homebrew install). Linux keeps requiring them; its package manager has them.
|
||||
tauri_log "STEP" "Checking system dependencies"
|
||||
|
||||
# Without the Xcode CLT, macOS still ships /usr/bin/git as a stub that errors and pops
|
||||
# a GUI dialog, so `command -v git` is not enough -- only running it tells the truth.
|
||||
_has_working_git() {
|
||||
command -v git >/dev/null 2>&1 || return 1
|
||||
git --version >/dev/null 2>&1
|
||||
}
|
||||
|
||||
# macOS system-dependency check. A function so tests/sh can sed-extract it; the old
|
||||
# inline form was untestable, which is why this gate shipped broken.
|
||||
#
|
||||
# The consumer install needs no developer toolchain: uv is a prebuilt binary, CPython
|
||||
# is uv-managed, llama.cpp/whisper.cpp/Node are prebuilt downloads, and triton is
|
||||
# skipped on macOS. Only `--local` needs git, for the unsloth-zoo git+https URL.
|
||||
_check_macos_deps() {
|
||||
_clt_missing=false
|
||||
xcode-select -p >/dev/null 2>&1 || _clt_missing=true
|
||||
|
||||
if [ "$STUDIO_LOCAL_INSTALL" = true ] && ! _has_working_git; then
|
||||
echo ""
|
||||
step "deps" "git is required for --local installs" "$C_ERR"
|
||||
substep "--local installs unsloth-zoo from git+https://github.com/unslothai/unsloth-zoo,"
|
||||
substep "which needs a working git. Install the Xcode Command Line Tools:"
|
||||
substep " xcode-select --install"
|
||||
substep "Then re-run this script. A normal (non---local) install needs no compiler"
|
||||
substep "and no git -- it uses prebuilt binaries and wheels only."
|
||||
tauri_log "NEED_XCODE_CLT" "git"
|
||||
return 1
|
||||
fi
|
||||
|
||||
if [ "$_clt_missing" = true ]; then
|
||||
# Not fatal, and no GUI dialog: firing xcode-select --install and exiting is
|
||||
# what stranded clean Macs.
|
||||
step "deps" "no Xcode Command Line Tools (not required)" "$C_WARN"
|
||||
substep "Unsloth installs prebuilt binaries and wheels, so no compiler is needed."
|
||||
substep "Install them only for a llama.cpp source build: xcode-select --install"
|
||||
elif command -v cmake >/dev/null 2>&1; then
|
||||
step "deps" "all system dependencies found"
|
||||
else
|
||||
# cmake is only for a source build, so its absence is not fatal.
|
||||
step "deps" "using prebuilt llama.cpp (cmake not found)" "$C_WARN"
|
||||
substep "Install cmake only if you want a source build: brew install cmake"
|
||||
fi
|
||||
return 0
|
||||
}
|
||||
|
||||
# Linux/WSL system-dependency check. Same split as macOS, and a function for the same
|
||||
# reason: tests/sh can extract it.
|
||||
#
|
||||
# Only a download transport is required. cmake, gcc and the libcurl headers exist
|
||||
# solely for a llama.cpp source build the consumer path never does -- unslothai/
|
||||
# llama.cpp publishes linux-x64/arm64 prebuilts for cpu, cuda12, cuda13, rocm and
|
||||
# vulkan. Requiring them turned every non-apt distro into a hard exit 1 over unused
|
||||
# tooling. git follows macOS: --local only.
|
||||
_check_linux_deps() {
|
||||
_transport_missing=false
|
||||
if ! command -v curl >/dev/null 2>&1 && ! command -v wget >/dev/null 2>&1; then
|
||||
_transport_missing=true
|
||||
fi
|
||||
|
||||
# Wanted, never required: git fetches the triton_kernels git+https requirement (a
|
||||
# training speedup), the rest serve the optional source build. Warn, never stop.
|
||||
_optional_missing=""
|
||||
command -v cmake >/dev/null 2>&1 || _optional_missing="$_optional_missing cmake"
|
||||
_has_working_git || _optional_missing="$_optional_missing git"
|
||||
command -v gcc >/dev/null 2>&1 || _optional_missing="$_optional_missing build-essential"
|
||||
command -v curl-config >/dev/null 2>&1 || _optional_missing="$_optional_missing libcurl4-openssl-dev"
|
||||
# Parameter expansion, not `sed`: sed may be absent on a minimal image, and a
|
||||
# failed `$(... | sed ...)` yields "" -- "all found" on a machine that has none.
|
||||
_optional_missing="${_optional_missing# }"
|
||||
|
||||
if [ "$STUDIO_LOCAL_INSTALL" = true ] && ! _has_working_git; then
|
||||
echo ""
|
||||
step "deps" "git is required for --local installs" "$C_ERR"
|
||||
substep "--local installs unsloth-zoo from git+https://github.com/unslothai/unsloth-zoo,"
|
||||
substep "which needs git. Install it with your package manager, then re-run."
|
||||
substep "A normal (non---local) install needs no git and no compiler."
|
||||
return 1
|
||||
fi
|
||||
|
||||
# The one fatal case: nothing can be downloaded. apt is the only distro family we
|
||||
# can drive unattended.
|
||||
if [ "$_transport_missing" = true ]; then
|
||||
if command -v apt-get >/dev/null 2>&1; then
|
||||
echo ""
|
||||
step "deps" "missing: curl" "$C_WARN"
|
||||
substep "Needed to download uv, Python and the prebuilt inference engine."
|
||||
_smart_apt_install curl
|
||||
echo ""
|
||||
else
|
||||
echo ""
|
||||
step "deps" "missing: curl (or wget)" "$C_ERR"
|
||||
substep "Unsloth needs one of them to download uv, Python and the prebuilt"
|
||||
substep "inference engine. Install one, then re-run setup:"
|
||||
substep " Fedora/RHEL: sudo dnf install curl"
|
||||
substep " Arch: sudo pacman -S --needed curl"
|
||||
substep " openSUSE: sudo zypper install curl"
|
||||
return 1
|
||||
fi
|
||||
fi
|
||||
|
||||
# Try apt for the optional set too; failing only costs the features warned about
|
||||
# below.
|
||||
if [ -n "$_optional_missing" ] && command -v apt-get >/dev/null 2>&1; then
|
||||
step "deps" "installing optional build tools: $_optional_missing" "$C_DIM"
|
||||
# Subshell because _smart_apt_install exits rather than returns, so `|| true`
|
||||
# alone would not catch it. _SMART_APT_OPTIONAL suppresses every escalation
|
||||
# path, so no install hinges on a prompt for tools nothing here needs.
|
||||
( _SMART_APT_OPTIONAL=true; _smart_apt_install $_optional_missing ) || true
|
||||
_optional_missing=""
|
||||
command -v cmake >/dev/null 2>&1 || _optional_missing="$_optional_missing cmake"
|
||||
_has_working_git || _optional_missing="$_optional_missing git"
|
||||
command -v gcc >/dev/null 2>&1 || _optional_missing="$_optional_missing build-essential"
|
||||
command -v curl-config >/dev/null 2>&1 || _optional_missing="$_optional_missing libcurl4-openssl-dev"
|
||||
_optional_missing="${_optional_missing# }"
|
||||
fi
|
||||
|
||||
if [ -n "$_optional_missing" ]; then
|
||||
step "deps" "using prebuilt llama.cpp (missing: $_optional_missing)" "$C_WARN"
|
||||
substep "Not required to run: Unsloth downloads a prebuilt inference engine."
|
||||
case " $_optional_missing " in
|
||||
*" git "*) substep "Without git the triton kernels training speedup is skipped." ;;
|
||||
esac
|
||||
else
|
||||
step "deps" "all system dependencies found"
|
||||
fi
|
||||
return 0
|
||||
}
|
||||
|
||||
case "$OS" in
|
||||
macos)
|
||||
_check_macos_deps || exit 1
|
||||
# Xcode Command Line Tools provide the C/C++ compiler and git.
|
||||
if ! xcode-select -p >/dev/null 2>&1; then
|
||||
echo ""
|
||||
echo "==> Xcode Command Line Tools are required."
|
||||
echo " Installing (a system dialog will appear)..."
|
||||
xcode-select --install </dev/null 2>/dev/null || true
|
||||
echo " After the installation completes, please re-run this script."
|
||||
exit 1
|
||||
fi
|
||||
# cmake is only needed for a source build; the default prebuilt path
|
||||
# doesn't use it, so its absence is not fatal -- no Homebrew prerequisite.
|
||||
if command -v cmake >/dev/null 2>&1; then
|
||||
step "deps" "all system dependencies found"
|
||||
else
|
||||
step "deps" "using prebuilt llama.cpp (cmake not found)" "$C_WARN"
|
||||
substep "Install cmake only if you want a source build: brew install cmake"
|
||||
fi
|
||||
;;
|
||||
linux|wsl)
|
||||
_check_linux_deps || exit 1
|
||||
MISSING=""
|
||||
command -v cmake >/dev/null 2>&1 || MISSING="$MISSING cmake"
|
||||
command -v git >/dev/null 2>&1 || MISSING="$MISSING git"
|
||||
# curl or wget is needed for downloads; check both
|
||||
if ! command -v curl >/dev/null 2>&1 && ! command -v wget >/dev/null 2>&1; then
|
||||
MISSING="$MISSING curl"
|
||||
fi
|
||||
command -v gcc >/dev/null 2>&1 || MISSING="$MISSING build-essential"
|
||||
# libcurl dev headers for llama.cpp HTTPS support
|
||||
command -v curl-config >/dev/null 2>&1 || MISSING="$MISSING libcurl4-openssl-dev"
|
||||
|
||||
MISSING=$(echo "$MISSING" | sed 's/^ *//')
|
||||
if [ -n "$MISSING" ]; then
|
||||
echo ""
|
||||
step "deps" "missing: $MISSING" "$C_WARN"
|
||||
substep "These are needed to build the GGUF inference engine."
|
||||
if command -v apt-get >/dev/null 2>&1; then
|
||||
_smart_apt_install $MISSING
|
||||
else
|
||||
echo " Automatic system package installation is supported on apt-based"
|
||||
echo " Linux distributions (Ubuntu/Debian) only. Please install the"
|
||||
echo " missing dependencies with your package manager, then re-run setup:"
|
||||
echo " $MISSING"
|
||||
echo ""
|
||||
echo " Examples:"
|
||||
echo " Fedora/RHEL: sudo dnf install cmake git gcc gcc-c++ make libcurl-devel"
|
||||
echo " Arch: sudo pacman -S --needed cmake git base-devel curl"
|
||||
echo " openSUSE: sudo zypper install cmake git gcc gcc-c++ make libcurl-devel"
|
||||
exit 1
|
||||
fi
|
||||
echo ""
|
||||
else
|
||||
step "deps" "all system dependencies found"
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
|
||||
|
|
@ -2517,7 +2227,6 @@ _amd_arch_index_family_for_gfx() {
|
|||
gfx1201|gfx1200) echo gfx120X-all ;;
|
||||
gfx1151) echo gfx1151 ;;
|
||||
gfx1150) echo gfx1150 ;;
|
||||
gfx1152) echo gfx1152 ;;
|
||||
gfx1103|gfx1102|gfx1101|gfx1100) echo gfx110X-all ;;
|
||||
gfx1036|gfx1035|gfx1034|gfx1033|gfx1032|gfx1031|gfx1030) echo gfx103X-all ;;
|
||||
gfx90a) echo gfx90a ;;
|
||||
|
|
@ -2529,14 +2238,12 @@ _amd_arch_index_family_for_gfx() {
|
|||
# Map a GPU marketing name to gfx arch (kept in sync with install.ps1 nameArchTable).
|
||||
_infer_amd_gfx_arch_from_gpu_name() {
|
||||
case "$1" in
|
||||
*9070*|*9080*) echo gfx1201 ;;
|
||||
*9060*) echo gfx1200 ;;
|
||||
*"9070 XT"*|*9080*) echo gfx1201 ;;
|
||||
*9070*|*9060*) echo gfx1200 ;;
|
||||
*"8065S"*|*"8060S"*|*"8050S"*|*"8040S"*|*"Strix Halo"*|*"Ryzen AI Max"*|*"AI Max"*) echo gfx1151 ;;
|
||||
*"890M"*|*"880M"*|*"Strix Point"*|*"HX 37"*|*"AI 9 HX"*|*"AI 9 36"*) echo gfx1150 ;;
|
||||
*"860M"*|*"840M"*|*"Krackan"*|*"AI 7 35"*|*"AI 5 34"*|*"AI 7 PRO 35"*|*"AI 5 33"*) echo gfx1152 ;;
|
||||
*"RX 7600"*|*"RX 7700S"*|*"RX 7650"*|*"PRO W7600"*|*"PRO W7500"*) echo gfx1102 ;;
|
||||
*"RX 7800"*|*"RX 7700"*|*"PRO W7700"*|*"PRO V710"*) echo gfx1101 ;;
|
||||
*"RX 7900"*|*"PRO W7900"*|*"PRO W7800"*) echo gfx1100 ;;
|
||||
*"890M"*|*"880M"*|*"860M"*|*"840M"*|*"Strix Point"*|*"Krackan"*|*"HX 37"*|*"AI 9 HX"*|*"AI 9 36"*|*"AI 7 35"*|*"AI 5 34"*|*"AI 7 PRO 35"*|*"AI 5 33"*) echo gfx1150 ;;
|
||||
*"RX 7600"*|*"RX 7700S"*|*"RX 7650"*|*"PRO W7600"*|*"PRO W7500"*|*"PRO V710"*) echo gfx1102 ;;
|
||||
*"RX 7900"*|*"RX 7800"*|*"RX 7700"*|*"PRO W7900"*|*"PRO W7800"*|*"PRO W7700"*) echo gfx1100 ;;
|
||||
*"780M"*|*"760M"*|*"740M"*|*"Phoenix"*|*"Hawk Point"*|*"Z1 Extreme"*|*"Z2 Extreme"*) echo gfx1103 ;;
|
||||
*"RX 6900"*|*"RX 6800"*|*"RX 6750"*|*"RX 6700"*|*"PRO W6800"*|*"PRO W6900"*) echo gfx1030 ;;
|
||||
*"RX 6650"*|*"RX 6600"*|*"PRO W6600"*|*"PRO W6650"*) echo gfx1032 ;;
|
||||
|
|
@ -2576,14 +2283,10 @@ _infer_linux_amd_gfx_arch() {
|
|||
echo gfx1151
|
||||
return 0
|
||||
fi
|
||||
if [ -n "$_gpu_evidence" ] && grep -qiE '890M|880M|Strix Point|HX 37[05]|AI 9 HX|AI 9 36[05]' /proc/cpuinfo 2>/dev/null; then
|
||||
if [ -n "$_gpu_evidence" ] && grep -qiE '890M|880M|860M|840M|Strix Point|Krackan|HX 37[05]|AI 9 HX|AI 9 36[05]|AI 7 35[05]|AI 5 34[05]|AI 7 PRO 35|AI 5 33' /proc/cpuinfo 2>/dev/null; then
|
||||
echo gfx1150
|
||||
return 0
|
||||
fi
|
||||
if [ -n "$_gpu_evidence" ] && grep -qiE '860M|840M|Krackan|AI 7 35[05]|AI 5 34[05]|AI 7 PRO 35|AI 5 33' /proc/cpuinfo 2>/dev/null; then
|
||||
echo gfx1152
|
||||
return 0
|
||||
fi
|
||||
if command -v lspci >/dev/null 2>&1; then
|
||||
# A non-AMD controller can enumerate first (Intel/ASPEED before an AMD
|
||||
# dGPU), so scan every display-class line and take the first AMD one
|
||||
|
|
@ -3319,7 +3022,7 @@ if [ "$_torch_index_pinned" = false ] && [ "$SKIP_TORCH" = false ] && \
|
|||
# whole handoff (a user-set override re-exports unchanged).
|
||||
export UNSLOTH_ROCM_GFX_ARCH="$_linux_inferred_gfx"
|
||||
case "$_linux_inferred_gfx" in
|
||||
gfx1201|gfx1200|gfx1151|gfx1150|gfx1152)
|
||||
gfx1201|gfx1200|gfx1151|gfx1150)
|
||||
TORCH_CONSTRAINT="torch>=2.11.0,<2.12.0"
|
||||
TORCHVISION_CONSTRAINT="torchvision>=0.26.0,<0.27.0"
|
||||
TORCHAUDIO_CONSTRAINT="torchaudio>=2.11.0,<2.12.0"
|
||||
|
|
@ -3388,7 +3091,7 @@ fi
|
|||
# and a bare name can resolve a 2.12 ABI-mismatched wheel. Match on the FINAL leaf so a
|
||||
# custom mirror with a gfx/rocm7.2 path segment but a cu*/cpu family isn't forced.
|
||||
case "$_torch_index_leaf" in
|
||||
rocm7.2|gfx120x-all|gfx1151|gfx1150|gfx1152)
|
||||
rocm7.2|gfx120x-all|gfx1151|gfx1150)
|
||||
TORCH_CONSTRAINT="torch>=2.11.0,<2.12.0"
|
||||
TORCHVISION_CONSTRAINT="torchvision>=0.26.0,<0.27.0"
|
||||
TORCHAUDIO_CONSTRAINT="torchaudio>=2.11.0,<2.12.0"
|
||||
|
|
@ -3505,20 +3208,10 @@ case "$_torch_index_leaf" in
|
|||
if (n > 0) print vals[idx]
|
||||
}')
|
||||
fi
|
||||
# An explicit UNSLOTH_ROCM_GFX_ARCH=gfx906 pins the runtime target to the
|
||||
# MI50 / Radeon VII path and must win over Strix probe-order detection on a
|
||||
# mixed Strix + MI50 host, so the Strix reroute is suppressed when it is set.
|
||||
# Normalize a copied HIP gcnArchName (gfx906:sramecc-:xnack- -> gfx906) and
|
||||
# trim whitespace (mirrors the Python .strip()) so the feature-flag suffix or
|
||||
# a stray newline does not defeat the exact gfx906 comparisons below.
|
||||
_gfx906_env=$(printf '%s' "${UNSLOTH_ROCM_GFX_ARCH:-}" | tr '[:upper:]' '[:lower:]' | tr -d '[:space:]')
|
||||
_gfx906_env=${_gfx906_env%%:*}
|
||||
_strix_gfx=""
|
||||
if [ "$_gfx906_env" != "gfx906" ]; then
|
||||
case "$_runtime_gfx" in
|
||||
gfx1151|gfx1150|gfx1152) _strix_gfx="$_runtime_gfx" ;;
|
||||
esac
|
||||
fi
|
||||
case "$_runtime_gfx" in
|
||||
gfx1151|gfx1150) _strix_gfx="$_runtime_gfx" ;;
|
||||
esac
|
||||
# Skip rocm7.13+ generic indexes: they already ship the fixes, so the
|
||||
# arch build (rocm7.13) would be a downgrade rather than a rescue.
|
||||
if [ -n "$_strix_gfx" ] && _rocm_leaf_below "$_torch_index_leaf" 7 13; then
|
||||
|
|
@ -3546,57 +3239,6 @@ case "$_torch_index_leaf" in
|
|||
TORCHAUDIO_CONSTRAINT="torchaudio>=2.11.0,<2.12.0"
|
||||
_amd_gpu_radeon=false
|
||||
fi
|
||||
# ── MI50 / Radeon VII (gfx906, Vega 20): legacy community-supported path ──
|
||||
# Newer rocm wheel families bundle ROCm libraries whose Tensile kernels
|
||||
# dropped gfx906 (rocBLAS "TensileLibrary.dat ... not read for gfx906",
|
||||
# ROCm/TheRock#1844), so a rocm6.4+/7.x index installs a torch that fails
|
||||
# at the first BLAS call. The rocm6.3 index is the last one whose wheels
|
||||
# run on gfx906 (torch 2.7.0 verified on MI50 32GB; up to 2.9 in community
|
||||
# use). Reroute any newer picked index; leave rocm6.0-6.3 alone.
|
||||
#
|
||||
# Target resolution: an explicit UNSLOTH_ROCM_GFX_ARCH wins (lets a host
|
||||
# whose rocminfo/amd-smi emit no gfx token still opt in; _gfx906_env was
|
||||
# lowercased above, before the Strix block it suppresses). Otherwise only
|
||||
# treat gfx906 as the target when it is the SOLE distinct arch present:
|
||||
# _gfx_all is de-duplicated by visible index, which loses per-device
|
||||
# ordinals on a mixed host, so a non-gfx906 selection must never be
|
||||
# downgraded to rocm6.3 -- such hosts set UNSLOTH_ROCM_GFX_ARCH to opt in.
|
||||
_gfx906_target=false
|
||||
if [ -n "$_gfx906_env" ]; then
|
||||
[ "$_gfx906_env" = "gfx906" ] && _gfx906_target=true
|
||||
elif [ -n "$_gfx_all" ]; then
|
||||
_gfx906_uniq=$(printf '%s\n' "$_gfx_all" | awk 'NF && !seen[$0]++')
|
||||
[ "$_gfx906_uniq" = "gfx906" ] && _gfx906_target=true
|
||||
fi
|
||||
# gfx906 always trains from the PyTorch rocm6.3 wheels, never the Radeon repo
|
||||
# (repo.radeon.com wheels carry no gfx906 BLAS kernels). Clear the Radeon
|
||||
# marketing-name flag as soon as gfx906 is the target -- even when the host
|
||||
# already picks rocm6.0-6.3 and the reroute below is a no-op -- so a Radeon VII
|
||||
# does not divert to the radeon branch on those versions.
|
||||
if [ "$_gfx906_target" = true ]; then
|
||||
_amd_gpu_radeon=false
|
||||
fi
|
||||
if [ "$_gfx906_target" = true ] && ! _rocm_leaf_below "$_torch_index_leaf" 6 4; then
|
||||
echo "" >&2
|
||||
echo " [WARN] gfx906 (MI50 / Radeon VII / Vega 20) detected -- routing torch to the" >&2
|
||||
echo " [WARN] rocm6.3 index: it is the last wheel family that runs on gfx906 (newer" >&2
|
||||
echo " [WARN] rocm wheels ship without gfx906 BLAS kernels and fail at first use)." >&2
|
||||
echo " [WARN] gfx906 is a community-maintained legacy path: 16-bit LoRA and full" >&2
|
||||
echo " [WARN] finetuning work out of the box; bitsandbytes 4-bit QLoRA requires a" >&2
|
||||
echo " [WARN] source build of bitsandbytes for gfx906 (see docs.unsloth.ai/amd)." >&2
|
||||
echo "" >&2
|
||||
_amd_gfx906_base="${UNSLOTH_PYTORCH_MIRROR:-https://download.pytorch.org/whl}"
|
||||
while [ "${_amd_gfx906_base%/}" != "$_amd_gfx906_base" ]; do
|
||||
_amd_gfx906_base="${_amd_gfx906_base%/}"
|
||||
done
|
||||
TORCH_INDEX_URL="${_amd_gfx906_base}/rocm6.3"
|
||||
# Reset to the default (<2.11) window: a rocm7.2 pick raised the floor
|
||||
# to 2.11 above, which the rocm6.3 index (torch <= 2.9.x) cannot satisfy.
|
||||
TORCH_CONSTRAINT="torch>=2.4,<2.11.0"
|
||||
TORCHVISION_CONSTRAINT="torchvision>=0.19,<0.26.0"
|
||||
TORCHAUDIO_CONSTRAINT="torchaudio>=2.4,<2.11.0"
|
||||
# (_amd_gpu_radeon already cleared above for every gfx906 target.)
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
fi # _torch_index_pinned guard (Radeon + Strix reroute)
|
||||
|
|
@ -3664,14 +3306,12 @@ elif case "$TORCH_INDEX_URL" in */rocm*|*/gfx*) true ;; *) false ;; esac; then
|
|||
# gfx1102 matched BEFORE gfx1100 so the spaceless "RX 7700S" lands on
|
||||
# gfx1102 (bash case has no negative lookahead like the PS tables).
|
||||
case "$_gpu_disp_mkt" in
|
||||
*9070*|*9080*) _gpu_disp_gfx="gfx1201" ;; # RDNA 4 (Navi 48)
|
||||
*9060*) _gpu_disp_gfx="gfx1200" ;; # RDNA 4 (Navi 44)
|
||||
*"9070 XT"*|*9080*) _gpu_disp_gfx="gfx1201" ;; # RDNA 4
|
||||
*9070*|*9060*) _gpu_disp_gfx="gfx1200" ;; # RDNA 4
|
||||
*"8065S"*|*"8060S"*|*"8050S"*|*"8040S"*|*"Strix Halo"*|*"Ryzen AI Max"*|*"AI Max"*) _gpu_disp_gfx="gfx1151" ;; # RDNA 3.5 (Strix Halo + Gorgon Halo: Radeon 8065S/8060S/8050S/8040S iGPU, Ryzen AI Max / Max+)
|
||||
*"890M"*|*"880M"*|*"Strix Point"*|*"HX 37"*|*"AI 9 HX"*|*"AI 9 36"*) _gpu_disp_gfx="gfx1150" ;; # RDNA 3.5 (Strix Point: Radeon 890M/880M, Ryzen AI 9 HX 370/375)
|
||||
*"860M"*|*"840M"*|*"Krackan"*|*"AI 7 35"*|*"AI 5 34"*|*"AI 7 PRO 35"*|*"AI 5 33"*) _gpu_disp_gfx="gfx1152" ;; # RDNA 3.5 (Krackan Point: Radeon 860M/840M, Ryzen AI 7 350 / AI 5 340)
|
||||
*"RX 7600"*|*"RX 7700S"*|*"RX 7650"*|*"PRO W7600"*|*"PRO W7500"*) _gpu_disp_gfx="gfx1102" ;; # RDNA 3 (Navi 33)
|
||||
*"RX 7800"*|*"RX 7700"*|*"PRO W7700"*|*"PRO V710"*) _gpu_disp_gfx="gfx1101" ;; # RDNA 3 (Navi 32)
|
||||
*"RX 7900"*|*"PRO W7900"*|*"PRO W7800"*) _gpu_disp_gfx="gfx1100" ;; # RDNA 3 desktop / workstation (Navi 31)
|
||||
*"890M"*|*"880M"*|*"860M"*|*"840M"*|*"Strix Point"*|*"Krackan"*|*"HX 37"*|*"AI 9 HX"*|*"AI 9 36"*|*"AI 7 35"*|*"AI 5 34"*|*"AI 7 PRO 35"*|*"AI 5 33"*) _gpu_disp_gfx="gfx1150" ;; # RDNA 3.5 (Strix/Krackan Point: Radeon 890M/880M iGPU, Ryzen AI 9 HX 370/375)
|
||||
*"RX 7600"*|*"RX 7700S"*|*"RX 7650"*|*"PRO W7600"*|*"PRO W7500"*|*"PRO V710"*) _gpu_disp_gfx="gfx1102" ;; # RDNA 3 (Navi 33)
|
||||
*"RX 7900"*|*"RX 7800"*|*"RX 7700"*|*"PRO W7900"*|*"PRO W7800"*|*"PRO W7700"*) _gpu_disp_gfx="gfx1100" ;; # RDNA 3 desktop / workstation (Navi 31)
|
||||
*"780M"*|*"760M"*|*"740M"*|*"Phoenix"*|*"Hawk Point"*|*"Z1 Extreme"*|*"Z2 Extreme"*) _gpu_disp_gfx="gfx1103" ;; # RDNA 3 iGPU (Phoenix / Hawk Point)
|
||||
*"RX 6900"*|*"RX 6800"*|*"RX 6750"*|*"RX 6700"*|*"PRO W6800"*|*"PRO W6900"*) _gpu_disp_gfx="gfx1030" ;; # RDNA 2 (Navi 21)
|
||||
*"RX 6650"*|*"RX 6600"*|*"PRO W6600"*|*"PRO W6650"*) _gpu_disp_gfx="gfx1032" ;; # RDNA 2 (Navi 23)
|
||||
|
|
@ -3823,7 +3463,6 @@ for _p in ('torch', 'torchvision', 'torchaudio'):
|
|||
if [ "$_MIGRATED" = true ]; then
|
||||
# Migrated env: force-reinstall unsloth+unsloth-zoo for a clean state, preserving
|
||||
# existing torch/CUDA unless the ROCm repair below fires.
|
||||
_gfx906_bnb_snapshot
|
||||
substep "upgrading unsloth in migrated environment..."
|
||||
if [ "$SKIP_TORCH" = true ]; then
|
||||
# No-torch: install unsloth + unsloth-zoo with --no-deps (current
|
||||
|
|
@ -3832,7 +3471,7 @@ if [ "$_MIGRATED" = true ]; then
|
|||
# to prevent transitive torch resolution.
|
||||
run_install_cmd_retry "install unsloth (migrated no-torch)" uv pip install --python "$_VENV_PY" --no-deps \
|
||||
--reinstall-package unsloth --reinstall-package unsloth-zoo \
|
||||
"unsloth>=2026.7.5" "unsloth-zoo>=2026.7.6"
|
||||
"unsloth>=2026.7.4" "unsloth-zoo>=2026.7.4"
|
||||
# Resolve pydantic WITH deps so pip pins pydantic-core to the
|
||||
# matching version (no-torch-runtime.txt below is --no-deps).
|
||||
# All transitive deps are torch-free.
|
||||
|
|
@ -3849,7 +3488,7 @@ if [ "$_MIGRATED" = true ]; then
|
|||
run_install_cmd_retry "install unsloth (migrated)" uv pip install --python "$_VENV_PY" \
|
||||
${_UNSLOTH_TORCH_OVERRIDES:+--overrides "$_UNSLOTH_TORCH_OVERRIDES"} \
|
||||
--reinstall-package unsloth --reinstall-package unsloth-zoo \
|
||||
"unsloth>=2026.7.5" "unsloth-zoo>=2026.7.6" ${_MLX_LM_EXCLUDE_ARG:-}
|
||||
"unsloth>=2026.7.4" "unsloth-zoo>=2026.7.4" ${_MLX_LM_EXCLUDE_ARG:-}
|
||||
[ -n "$_UNSLOTH_TORCH_OVERRIDES" ] && rm -f "$_UNSLOTH_TORCH_OVERRIDES"
|
||||
_UNSLOTH_TORCH_OVERRIDES=""
|
||||
fi
|
||||
|
|
@ -3865,18 +3504,13 @@ if [ "$_MIGRATED" = true ]; then
|
|||
# existing ROCm installs gain the AMD bitsandbytes build without a
|
||||
# fresh reinstall.
|
||||
if [ "$SKIP_TORCH" = false ] && [ "$_torch_index_is_rocm_family" = true ]; then
|
||||
if _is_gfx906_bnb_skip; then
|
||||
substep "gfx906: skipping prebuilt bitsandbytes (no gfx906 kernels); build from source for 4-bit QLoRA -- https://docs.unsloth.ai/get-started/install-and-update/amd" "$C_WARN"
|
||||
else
|
||||
_install_bnb_rocm "install bitsandbytes (AMD)" "$_VENV_PY"
|
||||
fi
|
||||
_install_bnb_rocm "install bitsandbytes (AMD)" "$_VENV_PY"
|
||||
# Repair ROCm torch if overwritten during migrated install
|
||||
_has_hip=$("$_VENV_PY" -c "import torch; print(getattr(torch.version,'hip','') or '')" 2>/dev/null || true)
|
||||
if [ -z "$_has_hip" ]; then
|
||||
substep "repairing ROCm torch (overwritten by dependency resolution)..."
|
||||
_install_torch_default_index --force-reinstall
|
||||
fi
|
||||
_gfx906_bnb_prune
|
||||
fi
|
||||
elif [ -n "$TORCH_INDEX_URL" ]; then
|
||||
# Fresh: Step 1 - install torch from explicit index (skip when --no-torch or Intel Mac)
|
||||
|
|
@ -4067,13 +3701,8 @@ elif [ -n "$TORCH_INDEX_URL" ]; then
|
|||
# host stays in GGUF-only mode rather than pulling in bitsandbytes,
|
||||
# which is only useful once torch is present for training.
|
||||
if [ "$SKIP_TORCH" = false ] && [ "$_torch_index_is_rocm_family" = true ]; then
|
||||
if _is_gfx906_bnb_skip; then
|
||||
substep "gfx906: skipping prebuilt bitsandbytes (no gfx906 kernels); build from source for 4-bit QLoRA -- https://docs.unsloth.ai/get-started/install-and-update/amd" "$C_WARN"
|
||||
else
|
||||
_install_bnb_rocm "install bitsandbytes (AMD)" "$_VENV_PY"
|
||||
fi
|
||||
_install_bnb_rocm "install bitsandbytes (AMD)" "$_VENV_PY"
|
||||
fi
|
||||
_gfx906_bnb_snapshot
|
||||
# Fresh: Step 2 - install unsloth, preserving the torch Step 1 installed
|
||||
tauri_log "STEP" "Installing Unsloth"
|
||||
substep "installing unsloth (this may take a few minutes)..."
|
||||
|
|
@ -4083,7 +3712,7 @@ elif [ -n "$TORCH_INDEX_URL" ]; then
|
|||
# runtime deps (typer, safetensors, transformers, etc.) with --no-deps.
|
||||
run_install_cmd_retry "install unsloth (no-torch)" uv pip install --python "$_VENV_PY" --no-deps \
|
||||
--upgrade-package unsloth --upgrade-package unsloth-zoo \
|
||||
"unsloth>=2026.7.5" "unsloth-zoo>=2026.7.6"
|
||||
"unsloth>=2026.7.4" "unsloth-zoo>=2026.7.4"
|
||||
# Same pydantic-with-deps trick as the migrated branch.
|
||||
run_install_cmd_retry "install pydantic (with deps for compatible core)" \
|
||||
uv pip install --python "$_VENV_PY" pydantic
|
||||
|
|
@ -4102,7 +3731,7 @@ elif [ -n "$TORCH_INDEX_URL" ]; then
|
|||
elif [ "$STUDIO_LOCAL_INSTALL" = true ]; then
|
||||
run_install_cmd_retry "install unsloth (local)" uv pip install --python "$_VENV_PY" \
|
||||
${_UNSLOTH_TORCH_OVERRIDES:+--overrides "$_UNSLOTH_TORCH_OVERRIDES"} \
|
||||
--upgrade-package unsloth "unsloth>=2026.7.5" "unsloth-zoo>=2026.7.6"
|
||||
--upgrade-package unsloth "unsloth>=2026.7.4" "unsloth-zoo>=2026.7.4"
|
||||
substep "overlaying local repo (editable)..."
|
||||
run_install_cmd "overlay local repo" uv pip install --python "$_VENV_PY" -e "$_REPO_ROOT" --no-deps
|
||||
substep "overlaying unsloth-zoo from git main..."
|
||||
|
|
@ -4124,14 +3753,13 @@ elif [ -n "$TORCH_INDEX_URL" ]; then
|
|||
substep "repairing ROCm torch (overwritten by dependency resolution)..."
|
||||
_install_torch_default_index --force-reinstall
|
||||
fi
|
||||
_gfx906_bnb_prune
|
||||
fi
|
||||
else
|
||||
# Fallback: GPU detection failed to produce a URL -- let uv resolve torch
|
||||
tauri_log "STEP" "Installing Unsloth"
|
||||
substep "installing unsloth (this may take a few minutes)..."
|
||||
if [ "$STUDIO_LOCAL_INSTALL" = true ]; then
|
||||
run_install_cmd_retry "install unsloth (auto torch backend)" uv pip install --python "$_VENV_PY" "unsloth-zoo>=2026.7.6" "unsloth>=2026.7.5" --torch-backend=auto
|
||||
run_install_cmd_retry "install unsloth (auto torch backend)" uv pip install --python "$_VENV_PY" "unsloth-zoo>=2026.7.4" "unsloth>=2026.7.4" --torch-backend=auto
|
||||
substep "overlaying local repo (editable)..."
|
||||
run_install_cmd "overlay local repo" uv pip install --python "$_VENV_PY" -e "$_REPO_ROOT" --no-deps
|
||||
substep "overlaying unsloth-zoo from git main..."
|
||||
|
|
@ -4219,7 +3847,6 @@ if [ -n "$VENV_ABS_BIN" ]; then
|
|||
fi
|
||||
|
||||
if ! command -v bash >/dev/null 2>&1; then
|
||||
tauri_log "ERROR" "bash is required to run studio setup"
|
||||
step "setup" "bash is required to run studio setup" "$C_ERR"
|
||||
substep "Please install bash and re-run install.sh"
|
||||
exit 1
|
||||
|
|
@ -4258,7 +3885,6 @@ if [ "$STUDIO_LOCAL_INSTALL" = true ]; then
|
|||
STUDIO_LOCAL_REPO="$_REPO_ROOT" \
|
||||
UNSLOTH_NO_TORCH="$SKIP_TORCH" \
|
||||
UNSLOTH_LOCAL_LLAMA_CPP_DIR="$_WITH_LLAMA_CPP_DIR" \
|
||||
UNSLOTH_TAURI_MODE="$TAURI_MODE" \
|
||||
bash "$SETUP_SH" </dev/null || _SETUP_EXIT=$?
|
||||
else
|
||||
# Explicitly reset STUDIO_LOCAL_INSTALL / STUDIO_LOCAL_REPO so a stale
|
||||
|
|
@ -4274,14 +3900,9 @@ else
|
|||
STUDIO_LOCAL_REPO= \
|
||||
UNSLOTH_NO_TORCH="$SKIP_TORCH" \
|
||||
UNSLOTH_LOCAL_LLAMA_CPP_DIR="$_WITH_LLAMA_CPP_DIR" \
|
||||
UNSLOTH_TAURI_MODE="$TAURI_MODE" \
|
||||
bash "$SETUP_SH" </dev/null || _SETUP_EXIT=$?
|
||||
fi
|
||||
|
||||
if [ "$_SETUP_EXIT" -eq 0 ]; then
|
||||
tauri_clear_install_error "studio setup completed"
|
||||
fi
|
||||
|
||||
# ── Make 'unsloth' available via $_LOCAL_BIN (resolved earlier) ──
|
||||
# Env-mode: $_LOCAL_BIN is $STUDIO_HOME/bin; skip shell-rc PATH append so we
|
||||
# don't pollute the user's profile with a workspace-scoped path.
|
||||
|
|
@ -4337,11 +3958,7 @@ fi
|
|||
# PATH and shortcuts are already set up so the user can fix and retry.
|
||||
if [ "$_SETUP_EXIT" -ne 0 ]; then
|
||||
echo ""
|
||||
if [ "$TAURI_MODE" = true ]; then
|
||||
tauri_log "ERROR_DEFAULT" "studio setup failed (exit code $_SETUP_EXIT)"
|
||||
else
|
||||
step "error" "studio setup failed (exit code $_SETUP_EXIT)" "$C_ERR"
|
||||
fi
|
||||
step "error" "studio setup failed (exit code $_SETUP_EXIT)" "$C_ERR"
|
||||
echo ""
|
||||
exit "$_SETUP_EXIT"
|
||||
fi
|
||||
|
|
@ -4396,11 +4013,9 @@ echo ""
|
|||
# In non-interactive environments (Docker, CI, cloud-init) just print instructions.
|
||||
if [ "$_SKIP_AUTOSTART" != true ] && [ -t 1 ]; then
|
||||
echo ""
|
||||
printf " Start Unsloth Studio now? [Y/n] "
|
||||
# No readable answer (closed/EOF tty) defaults to no; Enter is still yes.
|
||||
# Prompt only when something can answer: `test -r` passes on the unopenable
|
||||
# /dev/tty found in containers, leaving a dangling question in the log.
|
||||
if _can_read_tty; then
|
||||
printf " Start Unsloth Studio now? [Y/n] "
|
||||
if [ -r /dev/tty ]; then
|
||||
read -r _reply </dev/tty || _reply="n"
|
||||
else
|
||||
_reply="n"
|
||||
|
|
@ -4458,8 +4073,3 @@ else
|
|||
substep "(add -H 0.0.0.0 --cloudflare for a public Cloudflare HTTPS link, or --secure to keep the raw port private; anyone with the API key can run code)"
|
||||
echo ""
|
||||
fi
|
||||
|
||||
}
|
||||
|
||||
# Every byte above is parsed before this line runs, which is the point.
|
||||
_unsloth_main "$@"
|
||||
|
|
|
|||
|
|
@ -30,12 +30,6 @@ dependencies = [
|
|||
"pydantic",
|
||||
"pyyaml",
|
||||
"nest-asyncio",
|
||||
# Every CLI command imports studio.backend.*, which reaches structlog at
|
||||
# module level. The rest of the server stack lives in the studio extra.
|
||||
"structlog>=24.1.0",
|
||||
# unsloth_cli/__init__.py reaches click via commands/start.py, so every
|
||||
# command needs it. typer supplied it until 0.27 dropped the dependency.
|
||||
"click>=8.0",
|
||||
]
|
||||
|
||||
[project.scripts]
|
||||
|
|
@ -47,14 +41,9 @@ version = {attr = "unsloth.models._utils.__version__"}
|
|||
[tool.setuptools]
|
||||
include-package-data = true
|
||||
|
||||
[tool.setuptools.cmdclass]
|
||||
# Snapshots CHANGELOG.md into studio/ so every build path ships it.
|
||||
build_py = "_changelog_build.build_py"
|
||||
|
||||
[tool.setuptools.package-data]
|
||||
unsloth_cli = ["codex_fallback_prompt.md", "pi_subagent.ts"]
|
||||
studio = [
|
||||
"CHANGELOG.md",
|
||||
"*.sh",
|
||||
"*.ps1",
|
||||
"*.bat",
|
||||
|
|
@ -79,33 +68,6 @@ include = ["unsloth*", "unsloth_cli*", "studio", "studio.backend*"]
|
|||
exclude = ["images*", "tests*", "*.node_modules", "*.node_modules.*"]
|
||||
|
||||
[project.optional-dependencies]
|
||||
# Studio's server stack, mirroring studio/backend/requirements/studio.txt.
|
||||
# test_studio_extra_matches_requirements.py catches drift.
|
||||
studio = [
|
||||
"typer",
|
||||
"fastapi",
|
||||
"uvicorn",
|
||||
"pydantic",
|
||||
"packaging",
|
||||
"matplotlib==3.10.9",
|
||||
"pandas",
|
||||
"nest_asyncio",
|
||||
"datasets==4.3.0",
|
||||
"pyjwt",
|
||||
"huggingface-hub==0.36.2",
|
||||
"structlog>=24.1.0",
|
||||
"diceware",
|
||||
"ddgs",
|
||||
"cryptography>=42.0.0",
|
||||
"boto3>=1.34.0",
|
||||
"httpx>=0.27.0",
|
||||
"fastmcp>=3.0.2",
|
||||
"sqlite-vec==0.1.9",
|
||||
"pymupdf==1.27.2.3",
|
||||
"pymupdf4llm==0.3.4",
|
||||
"python-docx==1.2.0",
|
||||
]
|
||||
|
||||
triton = [
|
||||
"triton>=3.0.0 ; ('linux' in sys_platform)",
|
||||
"triton-windows ; (sys_platform == 'win32') and (platform_machine == 'AMD64' or platform_machine == 'x86_64')",
|
||||
|
|
@ -131,22 +93,6 @@ huggingfacenotorch = [
|
|||
"trl>=0.18.2,!=0.19.0,<=0.24.0",
|
||||
"sentence-transformers",
|
||||
]
|
||||
# torchcodec backend for Gemma audio / datasets>=4 (#7225).
|
||||
# Pick the audio-torch* pin matching your torch minor (see TORCH_TORCHCODEC).
|
||||
# torchcodec publishes no sdist and only manylinux_2_28_x86_64, macosx_*_arm64
|
||||
# and win_amd64 wheels, so Linux aarch64, Windows ARM64 and Intel Mac have
|
||||
# nothing to resolve and pip fails the whole install rather than skipping audio.
|
||||
# Gate on the platforms that have a wheel, matching
|
||||
# PLATFORM_LACKS_TORCHCODEC_WHEEL in studio/install_python_stack.py.
|
||||
audio-torch210 = [
|
||||
"torchcodec>=0.10.0,<0.11.0 ; python_version >= '3.10' and (((sys_platform == 'linux' or sys_platform == 'win32') and (platform_machine == 'x86_64' or platform_machine == 'AMD64')) or (sys_platform == 'darwin' and platform_machine == 'arm64'))",
|
||||
]
|
||||
audio-torch290 = [
|
||||
"torchcodec>=0.8.0,<0.10.0 ; python_version >= '3.10' and (((sys_platform == 'linux' or sys_platform == 'win32') and (platform_machine == 'x86_64' or platform_machine == 'AMD64')) or (sys_platform == 'darwin' and platform_machine == 'arm64'))",
|
||||
]
|
||||
audio-torch280 = [
|
||||
"torchcodec>=0.6.0,<0.8.0 ; python_version >= '3.9' and (((sys_platform == 'linux' or sys_platform == 'win32') and (platform_machine == 'x86_64' or platform_machine == 'AMD64')) or (sys_platform == 'darwin' and platform_machine == 'arm64'))",
|
||||
]
|
||||
huggingface = [
|
||||
"unsloth[huggingfacenotorch]",
|
||||
"unsloth_zoo>=2026.7.6",
|
||||
|
|
@ -586,19 +532,16 @@ cu126-torch2100 = [
|
|||
"unsloth[huggingface]",
|
||||
"bitsandbytes>=0.45.5,!=0.46.0,!=0.48.0",
|
||||
"unsloth[cu126onlytorch2100]",
|
||||
"unsloth[audio-torch210]",
|
||||
]
|
||||
cu128-torch2100 = [
|
||||
"unsloth[huggingface]",
|
||||
"bitsandbytes>=0.45.5,!=0.46.0,!=0.48.0",
|
||||
"unsloth[cu128onlytorch2100]",
|
||||
"unsloth[audio-torch210]",
|
||||
]
|
||||
cu130-torch2100 = [
|
||||
"unsloth[huggingface]",
|
||||
"bitsandbytes>=0.45.5,!=0.46.0,!=0.48.0",
|
||||
"unsloth[cu130onlytorch2100]",
|
||||
"unsloth[audio-torch210]",
|
||||
]
|
||||
kaggle = [
|
||||
"unsloth[huggingface]",
|
||||
|
|
@ -888,19 +831,16 @@ cu126-ampere-torch2100 = [
|
|||
"unsloth[huggingface]",
|
||||
"bitsandbytes>=0.45.5,!=0.46.0,!=0.48.0",
|
||||
"unsloth[cu126onlytorch2100]",
|
||||
"unsloth[audio-torch210]",
|
||||
]
|
||||
cu128-ampere-torch2100 = [
|
||||
"unsloth[huggingface]",
|
||||
"bitsandbytes>=0.45.5,!=0.46.0,!=0.48.0",
|
||||
"unsloth[cu128onlytorch2100]",
|
||||
"unsloth[audio-torch210]",
|
||||
]
|
||||
cu130-ampere-torch2100 = [
|
||||
"unsloth[huggingface]",
|
||||
"bitsandbytes>=0.45.5,!=0.46.0,!=0.48.0",
|
||||
"unsloth[cu130onlytorch2100]",
|
||||
"unsloth[audio-torch210]",
|
||||
]
|
||||
flashattentiontorch260abiFALSEcu12x = [
|
||||
"flash-attn @ https://github.com/Dao-AILab/flash-attention/releases/download/v2.7.4.post1/flash_attn-2.7.4.post1+cu12torch2.6cxx11abiFALSE-cp39-cp39-linux_x86_64.whl ; ('linux' in sys_platform) and python_version == '3.9'",
|
||||
|
|
@ -1185,8 +1125,7 @@ intelgputorch210 = [
|
|||
"torchvision @ https://download.pytorch.org/whl/xpu/torchvision-0.25.0%2Bxpu-cp313-cp313-win_amd64.whl#sha256=1c4b44b36a557f7381e3076fb8843366742238648441d607c8d049c6da0f8886 ; sys_platform == 'win32' and python_version == '3.13' and (platform_machine == 'AMD64' or platform_machine == 'x86_64')",
|
||||
]
|
||||
intel-gpu-torch210 = [
|
||||
"unsloth[intelgputorch210]",
|
||||
"unsloth[audio-torch210]",
|
||||
"unsloth[intelgputorch210]"
|
||||
]
|
||||
intelgputorch2110 = [
|
||||
"unsloth_zoo[intelgpu]",
|
||||
|
|
@ -1267,11 +1206,8 @@ intel = [
|
|||
]
|
||||
amd = [
|
||||
"unsloth[huggingfacenotorch]",
|
||||
# 4-bit decode is unreliable on ROCm before 0.50.0, the first PyPI release
|
||||
# carrying the full path: blocksize/warp decoupling (bnb #1887), fused SIMT
|
||||
# GEMM on RDNA (#1979), RDNA3/4 workgroup fix (#2012).
|
||||
"bitsandbytes>=0.50.0 ; ('linux' in sys_platform) and (platform_machine == 'AMD64' or platform_machine == 'x86_64' or platform_machine == 'aarch64')",
|
||||
"bitsandbytes>=0.50.0 ; (sys_platform == 'win32') and (platform_machine == 'AMD64' or platform_machine == 'x86_64')",
|
||||
"bitsandbytes>=0.49.1 ; ('linux' in sys_platform) and (platform_machine == 'AMD64' or platform_machine == 'x86_64' or platform_machine == 'aarch64')",
|
||||
"bitsandbytes>=0.49.1 ; (sys_platform == 'win32') and (platform_machine == 'AMD64' or platform_machine == 'x86_64')",
|
||||
]
|
||||
rocm702-torch280 = [
|
||||
"unsloth[amd]",
|
||||
|
|
@ -1343,7 +1279,6 @@ rocm72-torch2100 = [
|
|||
"torchvision @ https://repo.radeon.com/rocm/manylinux/rocm-rel-7.2/torchvision-0.25.0%2Brocm7.2.0.git82df5f59-cp311-cp311-linux_x86_64.whl ; platform_system == 'Linux' and python_version == '3.11' and platform_machine == 'x86_64'",
|
||||
"torchvision @ https://repo.radeon.com/rocm/manylinux/rocm-rel-7.2/torchvision-0.25.0%2Brocm7.2.0.git82df5f59-cp312-cp312-linux_x86_64.whl ; platform_system == 'Linux' and python_version == '3.12' and platform_machine == 'x86_64'",
|
||||
"torchvision @ https://repo.radeon.com/rocm/manylinux/rocm-rel-7.2/torchvision-0.25.0%2Brocm7.2.0.git82df5f59-cp313-cp313-linux_x86_64.whl ; platform_system == 'Linux' and python_version == '3.13' and platform_machine == 'x86_64'",
|
||||
"unsloth[audio-torch210]",
|
||||
]
|
||||
rocm711-torch2100 = [
|
||||
"unsloth[amd]",
|
||||
|
|
@ -1362,7 +1297,6 @@ rocm711-torch2100 = [
|
|||
"torchvision @ https://repo.radeon.com/rocm/manylinux/rocm-rel-7.1.1/torchvision-0.25.0%2Brocm7.1.1.git82df5f59-cp311-cp311-linux_x86_64.whl ; platform_system == 'Linux' and python_version == '3.11' and platform_machine == 'x86_64'",
|
||||
"torchvision @ https://repo.radeon.com/rocm/manylinux/rocm-rel-7.1.1/torchvision-0.25.0%2Brocm7.1.1.git82df5f59-cp312-cp312-linux_x86_64.whl ; platform_system == 'Linux' and python_version == '3.12' and platform_machine == 'x86_64'",
|
||||
"torchvision @ https://repo.radeon.com/rocm/manylinux/rocm-rel-7.1.1/torchvision-0.25.0%2Brocm7.1.1.git82df5f59-cp313-cp313-linux_x86_64.whl ; platform_system == 'Linux' and python_version == '3.13' and platform_machine == 'x86_64'",
|
||||
"unsloth[audio-torch210]",
|
||||
]
|
||||
|
||||
[project.urls]
|
||||
|
|
|
|||
|
|
@ -52,14 +52,14 @@ def _normalise_on(on_field):
|
|||
|
||||
def _load_workflow(path: Path):
|
||||
try:
|
||||
return yaml.safe_load(path.read_text(encoding = "utf-8"))
|
||||
return yaml.safe_load(path.read_text())
|
||||
except Exception as exc:
|
||||
print(f"ERROR: failed to parse {path}: {exc}", file = sys.stderr)
|
||||
sys.exit(2)
|
||||
|
||||
|
||||
def _extract_cache_keys(path: Path) -> list[str]:
|
||||
text = path.read_text(encoding = "utf-8")
|
||||
text = path.read_text()
|
||||
keys: list[str] = []
|
||||
for m in re.finditer(r"(?:^|\n)\s*key:\s*([^\n]+)", text):
|
||||
keys.append(m.group(1).strip())
|
||||
|
|
@ -104,7 +104,7 @@ def main() -> int:
|
|||
|
||||
for t in RESTRICTED_TRIGGERS:
|
||||
if t in triggers:
|
||||
text = path.read_text(encoding = "utf-8")
|
||||
text = path.read_text()
|
||||
if "lint:workflow_triggers-allow-workflow_run" not in text:
|
||||
findings.append(
|
||||
f"{path.name}: RESTRICTED trigger '{t}' requires an "
|
||||
|
|
|
|||
|
|
@ -95,8 +95,8 @@ COLAB_ORACLE_BASE_URL = "https://raw.githubusercontent.com/googlecolab/backend-i
|
|||
# Source: pytorch/torchcodec compatibility matrix on its README.
|
||||
TORCH_TORCHCODEC: dict[str, set[str]] = {
|
||||
"2.10": {"0.10"},
|
||||
"2.9": {"0.8", "0.9"},
|
||||
"2.8": {"0.6", "0.7"},
|
||||
"2.9": {"0.7", "0.8", "0.9"},
|
||||
"2.8": {"0.6"},
|
||||
"2.7": {"0.3", "0.4", "0.5"},
|
||||
"2.6": {"0.2", "0.3"},
|
||||
"2.5": {"0.1", "0.2"},
|
||||
|
|
|
|||
|
|
@ -1,377 +0,0 @@
|
|||
#!/usr/bin/env python3
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
# Copyright 2026-present the Unsloth AI Inc. team. All rights reserved. See /studio/LICENSE.AGPL-3.0
|
||||
|
||||
"""Measure where Unsloth Studio's startup time goes, per platform.
|
||||
|
||||
Nothing measured this before: the backend logs "lifespan startup completed in X ms"
|
||||
but no test or CI job asserted a budget, and studio_test_kit discards the elapsed
|
||||
time of its /healthz poll. A first local run (Linux, warm cache, fast server CPU)
|
||||
found `import main` alone costs 6.6s before the server can bind, dominated by eager
|
||||
module-level imports pulled in by the `routes` package:
|
||||
|
||||
torch 1930 ms self
|
||||
unsloth_zoo 914 ms self
|
||||
routes 779 ms self
|
||||
transformers 524 ms self
|
||||
|
||||
Phases measured:
|
||||
import `python -X importtime -c "import main"`, top cumulative + per-package self
|
||||
spawn process start -> first byte on stdout
|
||||
healthz process start -> /api/health (or /healthz) answers 200
|
||||
lifespan the backend's own "lifespan startup completed in X ms" log line
|
||||
|
||||
Usage:
|
||||
python scripts/profile_startup.py --repeats 3 --json out.json
|
||||
python scripts/profile_startup.py --import-only # no server, no port needed
|
||||
|
||||
Exit code is 0 unless --max-healthz-seconds is given and exceeded.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import math
|
||||
import os
|
||||
import platform
|
||||
import re
|
||||
import shutil
|
||||
import socket
|
||||
import statistics
|
||||
import subprocess
|
||||
import sys
|
||||
import threading
|
||||
import time
|
||||
import urllib.error
|
||||
import urllib.request
|
||||
from pathlib import Path
|
||||
|
||||
REPO_ROOT = Path(__file__).resolve().parents[1]
|
||||
BACKEND = REPO_ROOT / "studio" / "backend"
|
||||
|
||||
_IMPORTTIME_RE = re.compile(r"import time:\s+(\d+)\s+\|\s+(\d+)\s+\|(\s*)(\S.*)")
|
||||
|
||||
|
||||
def _free_port() -> int:
|
||||
with socket.socket() as s:
|
||||
s.bind(("127.0.0.1", 0))
|
||||
return int(s.getsockname()[1])
|
||||
|
||||
|
||||
def profile_imports(python: str, top: int = 15) -> dict:
|
||||
"""Cumulative and self import cost for the backend's module graph.
|
||||
|
||||
Run in a subprocess with -X importtime: the numbers are only meaningful for a
|
||||
cold interpreter, and importing in-process would measure a warm sys.modules.
|
||||
"""
|
||||
proc = subprocess.run(
|
||||
[python, "-X", "importtime", "-c", "import sys; sys.path.insert(0, '.'); import main"],
|
||||
cwd = BACKEND,
|
||||
capture_output = True,
|
||||
text = True,
|
||||
timeout = 900,
|
||||
)
|
||||
rows = []
|
||||
for line in proc.stderr.splitlines():
|
||||
m = _IMPORTTIME_RE.match(line)
|
||||
if m:
|
||||
rows.append((int(m.group(1)), int(m.group(2)), m.group(4).strip()))
|
||||
if not rows:
|
||||
return {"ok": False, "error": (proc.stderr or proc.stdout)[-2000:]}
|
||||
if proc.returncode != 0:
|
||||
# Rows survive up to the failure, so any total from a partial graph is wrong.
|
||||
return {
|
||||
"ok": False,
|
||||
"error": (proc.stderr or proc.stdout)[-2000:],
|
||||
"partial_rows": len(rows),
|
||||
}
|
||||
|
||||
by_cum = sorted(rows, key = lambda r: -r[1])
|
||||
# Total comes from the `main` row, not by_cum[0]: -X importtime also prints the
|
||||
# interpreter's own startup graph (`site`), which can outrank a trivial main.
|
||||
main_row = next((r for r in reversed(rows) if r[2] == "main"), None)
|
||||
if main_row is None:
|
||||
return {
|
||||
"ok": False,
|
||||
"error": "no `import main` row in -X importtime output\n"
|
||||
+ (proc.stderr or proc.stdout)[-2000:],
|
||||
}
|
||||
self_by_pkg: dict[str, int] = {}
|
||||
for self_us, _cum, name in rows:
|
||||
pkg = name.split(".")[0]
|
||||
self_by_pkg[pkg] = self_by_pkg.get(pkg, 0) + self_us
|
||||
|
||||
return {
|
||||
"ok": True,
|
||||
"total_seconds": round(main_row[1] / 1e6, 3),
|
||||
"top_cumulative": [
|
||||
{"module": n, "seconds": round(c / 1e6, 3)} for _s, c, n in by_cum[:top]
|
||||
],
|
||||
"self_by_package_ms": {
|
||||
k: round(v / 1000) for k, v in sorted(self_by_pkg.items(), key = lambda x: -x[1])[:top]
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
def _terminate_tree(proc: subprocess.Popen) -> None:
|
||||
"""Stop the server AND its children, which on Windows are a separate process.
|
||||
|
||||
CI profiles `Scripts/unsloth.exe`, a distlib launcher stub that CreateProcess's
|
||||
the venv python and waits, so terminate() reaps the stub only: the real backend
|
||||
keeps the inherited stdout handle, the reader thread never sees EOF, and
|
||||
--repeats strands one server per iteration on the shared UNSLOTH_STUDIO_HOME.
|
||||
taskkill /T walks the tree, as unsloth_cli/commands/start.py already does.
|
||||
"""
|
||||
if proc.poll() is not None:
|
||||
return
|
||||
if os.name == "nt":
|
||||
try:
|
||||
killed = subprocess.run(
|
||||
["taskkill", "/PID", str(proc.pid), "/T", "/F"],
|
||||
capture_output = True,
|
||||
timeout = 30,
|
||||
check = False,
|
||||
)
|
||||
if killed.returncode == 0:
|
||||
return
|
||||
except Exception:
|
||||
# taskkill missing or timed out; fall through so the stub still dies.
|
||||
pass
|
||||
# check=False: a nonzero taskkill does not raise, so fall through as well.
|
||||
proc.terminate()
|
||||
|
||||
|
||||
def profile_launch(
|
||||
bin_path: str,
|
||||
port: int,
|
||||
timeout_s: int = 300,
|
||||
) -> dict:
|
||||
"""Spawn the backend the way the desktop app does and time it to first 200."""
|
||||
log_lines: list[str] = []
|
||||
first_byte: list[float] = []
|
||||
t0 = time.perf_counter()
|
||||
proc = subprocess.Popen(
|
||||
[bin_path, "studio", "--api-only", "-H", "127.0.0.1", "-p", str(port)],
|
||||
cwd = REPO_ROOT,
|
||||
stdout = subprocess.PIPE,
|
||||
stderr = subprocess.STDOUT,
|
||||
text = True,
|
||||
bufsize = 1,
|
||||
)
|
||||
|
||||
def _drain() -> None:
|
||||
# Runs alongside the health polling: the first read timestamps the spawn
|
||||
# phase, and an undrained pipe blocks the backend before it binds.
|
||||
for line in proc.stdout:
|
||||
if not first_byte:
|
||||
first_byte.append(time.perf_counter() - t0)
|
||||
log_lines.append(line.rstrip("\n"))
|
||||
|
||||
reader = threading.Thread(target = _drain, daemon = True)
|
||||
reader.start()
|
||||
|
||||
t_healthz = None
|
||||
deadline = t0 + timeout_s
|
||||
try:
|
||||
while time.perf_counter() < deadline:
|
||||
if proc.poll() is not None:
|
||||
break
|
||||
if t_healthz is None:
|
||||
for url in (
|
||||
f"http://127.0.0.1:{port}/api/health",
|
||||
f"http://127.0.0.1:{port}/healthz",
|
||||
):
|
||||
try:
|
||||
with urllib.request.urlopen(url, timeout = 2) as r:
|
||||
if r.status == 200:
|
||||
t_healthz = time.perf_counter() - t0
|
||||
break
|
||||
except (urllib.error.URLError, OSError, TimeoutError):
|
||||
pass
|
||||
if t_healthz is not None:
|
||||
break
|
||||
time.sleep(0.25)
|
||||
finally:
|
||||
_terminate_tree(proc)
|
||||
try:
|
||||
# Safe: the reader drains the pipe, so the child cannot block on write().
|
||||
proc.wait(timeout = 30)
|
||||
except subprocess.TimeoutExpired:
|
||||
proc.kill()
|
||||
proc.wait()
|
||||
reader.join(timeout = 10)
|
||||
|
||||
t_first_byte = first_byte[0] if first_byte else None
|
||||
lifespan_ms = None
|
||||
for line in log_lines:
|
||||
m = re.search(r"lifespan startup completed in ([\d.]+)ms", line)
|
||||
if m:
|
||||
lifespan_ms = float(m.group(1))
|
||||
return {
|
||||
"spawn_seconds": round(t_first_byte, 3) if t_first_byte is not None else None,
|
||||
"healthz_seconds": round(t_healthz, 3) if t_healthz is not None else None,
|
||||
"lifespan_ms": lifespan_ms,
|
||||
"reached_healthz": t_healthz is not None,
|
||||
"log_tail": log_lines[-25:],
|
||||
}
|
||||
|
||||
|
||||
def python_version_of(python: str) -> str:
|
||||
"""Version of the interpreter that runs the imports, not the one running us.
|
||||
|
||||
--python points at the installed Studio venv while this script runs under the
|
||||
runner's system python, so platform.python_version() would label it wrong.
|
||||
"""
|
||||
if python == sys.executable:
|
||||
return platform.python_version()
|
||||
try:
|
||||
proc = subprocess.run(
|
||||
[python, "-c", "import platform; print(platform.python_version())"],
|
||||
capture_output = True,
|
||||
text = True,
|
||||
timeout = 60,
|
||||
)
|
||||
if proc.returncode == 0 and proc.stdout.strip():
|
||||
return proc.stdout.strip()
|
||||
except (OSError, subprocess.SubprocessError):
|
||||
pass
|
||||
return "unknown"
|
||||
|
||||
|
||||
def find_bin() -> str | None:
|
||||
home = os.environ.get("UNSLOTH_STUDIO_HOME") or str(Path.home() / ".unsloth" / "studio")
|
||||
names = ["unsloth.exe", "unsloth"] if platform.system() == "Windows" else ["unsloth"]
|
||||
subdirs = ["unsloth_studio/Scripts", "unsloth_studio/bin", "bin", "Scripts"]
|
||||
for sd in subdirs:
|
||||
for n in names:
|
||||
p = Path(home) / sd / n
|
||||
if p.exists():
|
||||
return str(p)
|
||||
return shutil.which("unsloth")
|
||||
|
||||
|
||||
def main(argv: list[str]) -> int:
|
||||
ap = argparse.ArgumentParser(
|
||||
description = __doc__, formatter_class = argparse.RawDescriptionHelpFormatter
|
||||
)
|
||||
ap.add_argument(
|
||||
"--repeats",
|
||||
type = int,
|
||||
default = 1,
|
||||
help = "launch repeats; the median is reported (imports are measured once)",
|
||||
)
|
||||
ap.add_argument(
|
||||
"--python",
|
||||
default = sys.executable,
|
||||
help = "interpreter used for the import profile (default: this one)",
|
||||
)
|
||||
ap.add_argument("--bin", help = "path to the unsloth CLI (default: autodetect)")
|
||||
ap.add_argument(
|
||||
"--import-only",
|
||||
action = "store_true",
|
||||
help = "skip the server phases (no install needed beyond the deps)",
|
||||
)
|
||||
ap.add_argument(
|
||||
"--max-healthz-seconds",
|
||||
type = float,
|
||||
help = "fail if the median time to a healthy port exceeds this",
|
||||
)
|
||||
ap.add_argument("--json", help = "write the full report here")
|
||||
a = ap.parse_args(argv)
|
||||
# range(0) launches nothing, leaving the budget check with nothing to fail on.
|
||||
if a.repeats < 1:
|
||||
ap.error("--repeats must be at least 1")
|
||||
# Same reason: --import-only never launches anything.
|
||||
if a.import_only and a.max_healthz_seconds is not None:
|
||||
ap.error("--max-healthz-seconds cannot be combined with --import-only")
|
||||
# nan and inf parse fine as floats but `med > budget` is then always False,
|
||||
# so the gate would report success without ever bounding anything.
|
||||
if a.max_healthz_seconds is not None and not math.isfinite(a.max_healthz_seconds):
|
||||
ap.error("--max-healthz-seconds must be a finite number")
|
||||
|
||||
report: dict = {
|
||||
"platform": platform.system().lower(),
|
||||
"machine": platform.machine(),
|
||||
"python": python_version_of(a.python),
|
||||
"cpu_count": os.cpu_count(),
|
||||
}
|
||||
|
||||
print("== import graph ==")
|
||||
report["imports"] = profile_imports(a.python)
|
||||
imp = report["imports"]
|
||||
if imp.get("ok"):
|
||||
print(f" import main: {imp['total_seconds']}s")
|
||||
for row in imp["top_cumulative"][:8]:
|
||||
print(f" {row['seconds']:7.3f}s {row['module']}")
|
||||
print(" self time by package (ms):")
|
||||
for k, v in list(imp["self_by_package_ms"].items())[:8]:
|
||||
print(f" {v:8} ms {k}")
|
||||
else:
|
||||
print(f" FAILED: {imp.get('error', '')[:400]}")
|
||||
|
||||
if not a.import_only:
|
||||
bin_path = a.bin or find_bin()
|
||||
if not bin_path:
|
||||
print(
|
||||
"== launch == skipped: no unsloth CLI found "
|
||||
"(set UNSLOTH_STUDIO_HOME or pass --bin)"
|
||||
)
|
||||
report["launch"] = {"skipped": "no unsloth CLI found"}
|
||||
else:
|
||||
print(f"== launch == {bin_path}")
|
||||
runs = []
|
||||
for i in range(a.repeats):
|
||||
r = profile_launch(bin_path, _free_port())
|
||||
runs.append(r)
|
||||
print(
|
||||
f" run {i + 1}: healthz={r['healthz_seconds']}s "
|
||||
f"lifespan={r['lifespan_ms']}ms reached={r['reached_healthz']}"
|
||||
)
|
||||
got = [r["healthz_seconds"] for r in runs if r["healthz_seconds"] is not None]
|
||||
report["launch"] = {
|
||||
"runs": runs,
|
||||
"failed_runs": sum(1 for r in runs if not r["reached_healthz"]),
|
||||
"healthz_median_seconds": round(statistics.median(got), 3) if got else None,
|
||||
"healthz_max_seconds": round(max(got), 3) if got else None,
|
||||
}
|
||||
if got:
|
||||
print(
|
||||
f" median time to healthy port: {report['launch']['healthz_median_seconds']}s"
|
||||
)
|
||||
|
||||
if a.json:
|
||||
Path(a.json).write_text(json.dumps(report, indent = 2), encoding = "utf-8")
|
||||
print(f"\nwrote {a.json}")
|
||||
|
||||
if a.max_healthz_seconds is not None:
|
||||
launch = report.get("launch") or {}
|
||||
med = launch.get("healthz_median_seconds")
|
||||
failed = launch.get("failed_runs") or 0
|
||||
if failed:
|
||||
# Failed launches fail the budget; dropping them would keep only the fast ones.
|
||||
print(
|
||||
f"::error::startup regression: {failed} of {len(launch.get('runs') or [])} "
|
||||
f"launches never became healthy within the timeout"
|
||||
)
|
||||
return 1
|
||||
if med is None:
|
||||
# Nothing measured: exiting 0 would pass a requested budget without a
|
||||
# single health request, so fail closed.
|
||||
print(
|
||||
"::error::startup regression: no healthz measurement, so the "
|
||||
f"{a.max_healthz_seconds}s budget was never checked "
|
||||
f"({launch.get('skipped') or 'launch phase produced no runs'})"
|
||||
)
|
||||
return 1
|
||||
elif med > a.max_healthz_seconds:
|
||||
print(
|
||||
f"::error::startup regression: {med}s median to a healthy port "
|
||||
f"exceeds the {a.max_healthz_seconds}s budget"
|
||||
)
|
||||
return 1
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main(sys.argv[1:]))
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"_comment": "scan_packages.py allowlist (reviewed). Each entry is a CRITICAL/HIGH finding manually judged benign. Matched on (package, package-relative file, check, evidence_hash); evidence_hash is over the matched code with L<NN>: markers stripped, so version bumps and line shifts do not reopen an entry but changed code does. severity and evidence are for review only. Regenerate with --write-baseline AFTER reviewing every line.",
|
||||
"_comment": "scan_packages.py allowlist. Each entry is a CRITICAL/HIGH finding manually judged benign. Matched on (package, package-relative file, check, evidence_hash); evidence_hash is over the matched code with L<NN>: markers stripped, so version bumps and line shifts do not reopen an entry but changed code does. severity and evidence are for review only. Regenerate with --write-baseline AFTER reviewing every line.",
|
||||
"version": 1,
|
||||
"entries": [
|
||||
{
|
||||
|
|
@ -98,14 +98,6 @@
|
|||
"evidence": "L587: while True: sha256:06c2c7f15d73bf192e5e3272c5ff5fcaeff7f6774fef5f4eca6ef473ae50e2b3",
|
||||
"evidence_hash": "57acd497f404c203e4450d0580ad85aa8a33406e8d64ad06fbac6cf47d97b24d"
|
||||
},
|
||||
{
|
||||
"package": "fastapi",
|
||||
"file": "fastapi/routing.py",
|
||||
"check": "C2 polling/beaconing loop detected",
|
||||
"severity": "CRITICAL",
|
||||
"evidence": "L592: while True: sha256:84283c09277ded3296998b2a6a838744457b606829cf5ab5d0da6f222ff020a0",
|
||||
"evidence_hash": "a7295004315e26a8f3c64fb837521e9fdd7268219bb43e000fb0236ab0259223"
|
||||
},
|
||||
{
|
||||
"package": "fastmcp-slim",
|
||||
"file": "fastmcp/cli/apps_dev.py",
|
||||
|
|
@ -311,8 +303,8 @@
|
|||
"file": "openai/_base_client.py",
|
||||
"check": "C2 polling/beaconing loop detected",
|
||||
"severity": "CRITICAL",
|
||||
"evidence": "L274: while True: sha256:90a38e5c1e26893c7c273354143612640e9a9c0f079d3e2b60612d79f24e80a6",
|
||||
"evidence_hash": "1022e8e8649436ec64a98a9d9141d085452c49549fd2157b0278fc369a83ac66"
|
||||
"evidence": "L264: while True: sha256:95ca67e46d42354ae650abbdc5b0d97df8b0ed43187800bf40f5690c3901b94b",
|
||||
"evidence_hash": "a57d8d15fed0bf04f9967dcc18a18b80bb19f4095675bccbb78ac0450d7fce14"
|
||||
},
|
||||
{
|
||||
"package": "openai",
|
||||
|
|
@ -327,8 +319,8 @@
|
|||
"file": "openai/auth/_workload.py",
|
||||
"check": "Accesses cloud metadata/IMDS AND makes network calls",
|
||||
"severity": "CRITICAL",
|
||||
"evidence": "IMDS: L97: url = \"http://169.254.169.254/metadata/identity/oauth2/token\" | L150: url = \"http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/identity\"\nNetwork: L78: http_client: httpx.Client | None = None, | L109: with httpx.Client() as client: | L134: http_client: httpx.Client | None = None, | L156: with httpx.Client() as client: | L251: exchange_client = DefaultHttpx2Client(follow_redirects=False) if self._use_httpx2 else httpx.Client()",
|
||||
"evidence_hash": "9717e51cb961dc14c458955d91a1e48e3753997346ecea0106bded3a8d64bfe0"
|
||||
"evidence": "IMDS: L96: url = \"http://169.254.169.254/metadata/identity/oauth2/token\" | L149: url = \"http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/identity\"\nNetwork: L77: http_client: httpx.Client | None = None, | L108: with httpx.Client() as client: | L133: http_client: httpx.Client | None = None, | L155: with httpx.Client() as client: | L248: with httpx.Client() as client:",
|
||||
"evidence_hash": "1581d9f4a23393e9af23fbe5ef9f66807b22c5b5a3f1fe167254c9ebee108567"
|
||||
},
|
||||
{
|
||||
"package": "openai",
|
||||
|
|
@ -351,8 +343,8 @@
|
|||
"file": "openai/resources/beta/responses/responses.py",
|
||||
"check": "C2 polling/beaconing loop detected",
|
||||
"severity": "CRITICAL",
|
||||
"evidence": "L4000: while True: sha256:f8ab538118daba9ec06e27399dbdc90a4521c3390e6a47a6348a1f180a83effd",
|
||||
"evidence_hash": "31481ea83c687acc27144d72d3832d4fb98dd1c79fb5e0ddd85080de95997b9f"
|
||||
"evidence": "L3999: while True: sha256:df298b6eaf3416589b79f4ef283f8fb76e54d505bfda8840673f8e6419117e2e",
|
||||
"evidence_hash": "10ce5cb5a7097fcff4042ddcfb4802edda60aa4b7b113c8b926a52ddb76f78c2"
|
||||
},
|
||||
{
|
||||
"package": "openai",
|
||||
|
|
@ -367,16 +359,16 @@
|
|||
"file": "openai/resources/realtime/realtime.py",
|
||||
"check": "C2 polling/beaconing loop detected",
|
||||
"severity": "CRITICAL",
|
||||
"evidence": "L311: while True: sha256:5b63313072aae9ca28677e03426513ccf12221e4f4e0ea6c31efbe09790633b5",
|
||||
"evidence_hash": "05e1af469d651b51673763a7c4cdf759af9472fb627b7b470adc28cc237bd650"
|
||||
"evidence": "L310: while True: sha256:458198ff3d3f05870bf98c9564cbfd68c739e57b9bbe4120ed81e3eb6af74a05",
|
||||
"evidence_hash": "a3165d21e46b3ce553795daeae53e8f80e8e89c5cb228e68e6dcaff54bca5a89"
|
||||
},
|
||||
{
|
||||
"package": "openai",
|
||||
"file": "openai/resources/responses/responses.py",
|
||||
"check": "C2 polling/beaconing loop detected",
|
||||
"severity": "CRITICAL",
|
||||
"evidence": "L3951: while True: sha256:d68ef896bf0743ca430cfacb9a3353da1f3b9c51c3a21b6450a07a32b55aa2ac",
|
||||
"evidence_hash": "160eecdd79b521bffbe8476f782b69a0724c35d1b19376a7600807165fd54f9f"
|
||||
"evidence": "L3950: while True: sha256:1ce0b5a388c747945cdfda1a71b77afdfd03ae840d7aa9fa62f02eb00aa5e29f",
|
||||
"evidence_hash": "6de300ebb5e6e17cb51c89cbcdf08515a44655182f0776f0908a9d1043ebbcd7"
|
||||
},
|
||||
{
|
||||
"package": "openai",
|
||||
|
|
|
|||
|
|
@ -1,145 +1,134 @@
|
|||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "view-in-github",
|
||||
"colab_type": "text"
|
||||
},
|
||||
"source": [
|
||||
"<a href=\"https://colab.research.google.com/github/unslothai/unsloth/blob/main/studio/Unsloth_Studio_Colab.ipynb\" target=\"_parent\"><img src=\"https://colab.research.google.com/assets/colab-badge.svg\" alt=\"Open In Colab\"/></a>"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "6b87de59"
|
||||
},
|
||||
"source": [
|
||||
"To run this, press \"*Runtime*\" and press \"*Run all*\" on a **free** Tesla T4 Google Colab instance!\n",
|
||||
"<div class=\"align-center\">\n",
|
||||
"<a href=\"https://unsloth.ai/\"><img src=\"https://github.com/unslothai/unsloth/raw/main/images/unsloth%20new%20logo.png\" width=\"115\"></a>\n",
|
||||
"<a href=\"https://discord.gg/unsloth\"><img src=\"https://github.com/unslothai/unsloth/raw/main/images/Discord button.png\" width=\"145\"></a>\n",
|
||||
"<a href=\"https://unsloth.ai/docs/\"><img src=\"https://github.com/unslothai/unsloth/blob/main/images/documentation%20green%20button.png?raw=true\" width=\"125\"></a> Join Discord if you need help + ⭐ <i>Star us on <a href=\"https://github.com/unslothai/unsloth\">Github</a> </i> ⭐\n",
|
||||
"</div>\n",
|
||||
"\n",
|
||||
"To install Unsloth Studio on your local device, follow [our guide](https://unsloth.ai/docs/new/unsloth-studio/install). Unsloth Studio is licensed [AGPL-3.0](https://github.com/unslothai/unsloth/blob/main/studio/LICENSE.AGPL-3.0).\n",
|
||||
"\n",
|
||||
"### Unsloth Studio\n",
|
||||
"\n",
|
||||
"Train and run open models with [**Unsloth Studio**](https://unsloth.ai/docs/new/unsloth-studio/start). NEW! Installation should now only take 2 mins!\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"We are actively working on making Unsloth Studio install on Colab T4 GPUs faster.\n",
|
||||
"\n",
|
||||
"[Features](https://unsloth.ai/docs/new/unsloth-studio#features) • [Quickstart](https://unsloth.ai/docs/new/unsloth-studio/start) • [Data Recipes](https://unsloth.ai/docs/new/unsloth-studio/data-recipe) • [Unsloth Chat](https://unsloth.ai/docs/new/unsloth-studio/chat) • [Export](https://unsloth.ai/docs/new/unsloth-studio/export)"
|
||||
],
|
||||
"id": "6b87de59"
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "e4206349"
|
||||
},
|
||||
"source": [
|
||||
"<p align=\"left\"><img src=\"https://github.com/unslothai/unsloth/raw/main/studio/frontend/public/studio%20github%20landscape%20colab%20display.png\" width=\"600\"></p>"
|
||||
],
|
||||
"id": "e4206349"
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "27da2957"
|
||||
},
|
||||
"source": [
|
||||
"### Setup: Clone repo and run setup"
|
||||
],
|
||||
"id": "27da2957"
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"metadata": {
|
||||
"id": "27e68f91"
|
||||
},
|
||||
"source": "!git clone --depth 1 --branch main https://github.com/unslothai/unsloth.git\n%cd /content/unsloth\n!chmod +x studio/setup.sh && ./studio/setup.sh --local",
|
||||
"execution_count": null,
|
||||
"outputs": [],
|
||||
"id": "27e68f91"
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "3e1771a9"
|
||||
},
|
||||
"source": [
|
||||
"### Start Unsloth Studio"
|
||||
],
|
||||
"id": "3e1771a9"
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"metadata": {
|
||||
"id": "277e431e"
|
||||
},
|
||||
"source": [
|
||||
"import sys\n",
|
||||
"sys.path.insert(0, \"/content/unsloth/studio/backend\")\n",
|
||||
"from colab import start\n",
|
||||
"\n",
|
||||
"# On Colab, start() auto-opens a Cloudflare link and prints admin login credentials.\n",
|
||||
"# Use the Cloudflare link above the ready card to open Studio (in-cell iframes often stay blank).\n",
|
||||
"start()\n",
|
||||
"\n",
|
||||
"# To skip the Cloudflare tunnel and try the in-notebook proxy iframe only:\n",
|
||||
"# start(cloudflare=False)"
|
||||
],
|
||||
"execution_count": null,
|
||||
"outputs": [],
|
||||
"id": "277e431e"
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "f2b0c6a1"
|
||||
},
|
||||
"source": [
|
||||
"And we're done! If you have any questions on Unsloth, we have a [Discord](https://discord.gg/unsloth) channel! If you find any bugs or want to keep updated with the latest LLM stuff, or need help, join projects etc, feel free to join our Discord!\n",
|
||||
"\n",
|
||||
"Some other resources:\n",
|
||||
"1. Looking to use Unsloth locally? Read our [Installation Guide](https://unsloth.ai/docs/get-started/install) for details on installing Unsloth on Windows, Docker, AMD, Intel GPUs.\n",
|
||||
"2. Learn how to do Reinforcement Learning with our [RL Guide and notebooks](https://unsloth.ai/docs/get-started/reinforcement-learning-rl-guide).\n",
|
||||
"3. Read our guides and notebooks for [Text-to-speech (TTS)](https://unsloth.ai/docs/basics/text-to-speech-tts-fine-tuning) and [vision](https://unsloth.ai/docs/basics/vision-fine-tuning) model support.\n",
|
||||
"4. Explore our [LLM Tutorials Directory](https://unsloth.ai/docs/models/tutorials-how-to-fine-tune-and-run-llms) to find dedicated guides for each model.\n",
|
||||
"5. Need help with Inference? Read our [Inference & Deployment page](https://unsloth.ai/docs/basics/inference-and-deployment) for details on using vLLM, llama.cpp, Ollama etc.\n",
|
||||
"\n",
|
||||
"<div class=\"align-center\">\n",
|
||||
" <a href=\"https://unsloth.ai\"><img src=\"https://github.com/unslothai/unsloth/raw/main/images/unsloth%20new%20logo.png\" width=\"115\"></a>\n",
|
||||
" <a href=\"https://discord.gg/unsloth\"><img src=\"https://github.com/unslothai/unsloth/raw/main/images/Discord.png\" width=\"145\"></a>\n",
|
||||
" <a href=\"https://unsloth.ai/docs/\"><img src=\"https://github.com/unslothai/unsloth/blob/main/images/documentation%20green%20button.png?raw=true\" width=\"125\"></a>\n",
|
||||
"\n",
|
||||
" Join Discord if you need help + ⭐️ <i>Star us on <a href=\"https://github.com/unslothai/unsloth\">Github</a> </i> ⭐️\n",
|
||||
"\n",
|
||||
" <b>This notebook is licensed <a href=\"https://github.com/unslothai/unsloth/blob/main/studio/LICENSE.AGPL-3.0\">AGPL-3.0</a></b>\n",
|
||||
"</div>"
|
||||
],
|
||||
"id": "f2b0c6a1"
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"accelerator": "GPU",
|
||||
"colab": {
|
||||
"gpuType": "T4",
|
||||
"provenance": [],
|
||||
"include_colab_link": true
|
||||
},
|
||||
"kernelspec": {
|
||||
"display_name": "Python 3",
|
||||
"name": "python3"
|
||||
},
|
||||
"language_info": {
|
||||
"name": "python"
|
||||
}
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "view-in-github",
|
||||
"colab_type": "text"
|
||||
},
|
||||
"source": [
|
||||
"<a href=\"https://colab.research.google.com/github/unslothai/unsloth/blob/main/studio/Unsloth_Studio_Colab.ipynb\" target=\"_parent\"><img src=\"https://colab.research.google.com/assets/colab-badge.svg\" alt=\"Open In Colab\"/></a>"
|
||||
]
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 5
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "6b87de59",
|
||||
"metadata": {
|
||||
"id": "6b87de59"
|
||||
},
|
||||
"source": [
|
||||
"To run this, press \"*Runtime*\" and press \"*Run all*\" on a **free** Tesla T4 Google Colab instance!\n",
|
||||
"<div class=\"align-center\">\n",
|
||||
"<a href=\"https://unsloth.ai/\"><img src=\"https://github.com/unslothai/unsloth/raw/main/images/unsloth%20new%20logo.png\" width=\"115\"></a>\n",
|
||||
"<a href=\"https://discord.gg/unsloth\"><img src=\"https://github.com/unslothai/unsloth/raw/main/images/Discord button.png\" width=\"145\"></a>\n",
|
||||
"<a href=\"https://unsloth.ai/docs/\"><img src=\"https://github.com/unslothai/unsloth/blob/main/images/documentation%20green%20button.png?raw=true\" width=\"125\"></a> Join Discord if you need help + ⭐ <i>Star us on <a href=\"https://github.com/unslothai/unsloth\">Github</a> </i> ⭐\n",
|
||||
"</div>\n",
|
||||
"\n",
|
||||
"To install Unsloth Studio on your local device, follow [our guide](https://unsloth.ai/docs/new/unsloth-studio/install). Unsloth Studio is licensed [AGPL-3.0](https://github.com/unslothai/unsloth/blob/main/studio/LICENSE.AGPL-3.0).\n",
|
||||
"\n",
|
||||
"### Unsloth Studio\n",
|
||||
"\n",
|
||||
"Train and run open models with [**Unsloth Studio**](https://unsloth.ai/docs/new/unsloth-studio/start). NEW! Installation should now only take 2 mins!\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"We are actively working on making Unsloth Studio install on Colab T4 GPUs faster.\n",
|
||||
"\n",
|
||||
"[Features](https://unsloth.ai/docs/new/unsloth-studio#features) • [Quickstart](https://unsloth.ai/docs/new/unsloth-studio/start) • [Data Recipes](https://unsloth.ai/docs/new/unsloth-studio/data-recipe) • [Unsloth Chat](https://unsloth.ai/docs/new/unsloth-studio/chat) • [Export](https://unsloth.ai/docs/new/unsloth-studio/export)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "e4206349",
|
||||
"metadata": {
|
||||
"id": "e4206349"
|
||||
},
|
||||
"source": [
|
||||
"<p align=\"left\"><img src=\"https://github.com/unslothai/unsloth/raw/main/studio/frontend/public/studio%20github%20landscape%20colab%20display.png\" width=\"600\"></p>"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "27da2957",
|
||||
"metadata": {
|
||||
"id": "27da2957"
|
||||
},
|
||||
"source": [
|
||||
"### Setup: Clone repo and run setup"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "27e68f91",
|
||||
"metadata": {
|
||||
"id": "27e68f91"
|
||||
},
|
||||
"outputs": [],
|
||||
"source": "!git clone --depth 1 --branch main https://github.com/unslothai/unsloth.git\n%cd /content/unsloth\n!chmod +x studio/setup.sh && ./studio/setup.sh --local"
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "3e1771a9",
|
||||
"metadata": {
|
||||
"id": "3e1771a9"
|
||||
},
|
||||
"source": [
|
||||
"### Start Unsloth Studio"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "277e431e",
|
||||
"metadata": {
|
||||
"id": "277e431e"
|
||||
},
|
||||
"outputs": [],
|
||||
"source": "import sys\nsys.path.insert(0, \"/content/unsloth/studio/backend\")\nfrom colab import start\n\n# Default: in-tab iframe only. start() blocks to keep the kernel alive.\nstart()\n\n# For a shareable Cloudflare link, replace start() above with:\n# start(cloudflare=True)"
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "f2b0c6a1",
|
||||
"metadata": {
|
||||
"id": "f2b0c6a1"
|
||||
},
|
||||
"source": [
|
||||
"And we're done! If you have any questions on Unsloth, we have a [Discord](https://discord.gg/unsloth) channel! If you find any bugs or want to keep updated with the latest LLM stuff, or need help, join projects etc, feel free to join our Discord!\n",
|
||||
"\n",
|
||||
"Some other resources:\n",
|
||||
"1. Looking to use Unsloth locally? Read our [Installation Guide](https://unsloth.ai/docs/get-started/install) for details on installing Unsloth on Windows, Docker, AMD, Intel GPUs.\n",
|
||||
"2. Learn how to do Reinforcement Learning with our [RL Guide and notebooks](https://unsloth.ai/docs/get-started/reinforcement-learning-rl-guide).\n",
|
||||
"3. Read our guides and notebooks for [Text-to-speech (TTS)](https://unsloth.ai/docs/basics/text-to-speech-tts-fine-tuning) and [vision](https://unsloth.ai/docs/basics/vision-fine-tuning) model support.\n",
|
||||
"4. Explore our [LLM Tutorials Directory](https://unsloth.ai/docs/models/tutorials-how-to-fine-tune-and-run-llms) to find dedicated guides for each model.\n",
|
||||
"5. Need help with Inference? Read our [Inference & Deployment page](https://unsloth.ai/docs/basics/inference-and-deployment) for details on using vLLM, llama.cpp, Ollama etc.\n",
|
||||
"\n",
|
||||
"<div class=\"align-center\">\n",
|
||||
" <a href=\"https://unsloth.ai\"><img src=\"https://github.com/unslothai/unsloth/raw/main/images/unsloth%20new%20logo.png\" width=\"115\"></a>\n",
|
||||
" <a href=\"https://discord.gg/unsloth\"><img src=\"https://github.com/unslothai/unsloth/raw/main/images/Discord.png\" width=\"145\"></a>\n",
|
||||
" <a href=\"https://unsloth.ai/docs/\"><img src=\"https://github.com/unslothai/unsloth/blob/main/images/documentation%20green%20button.png?raw=true\" width=\"125\"></a>\n",
|
||||
"\n",
|
||||
" Join Discord if you need help + ⭐️ <i>Star us on <a href=\"https://github.com/unslothai/unsloth\">Github</a> </i> ⭐️\n",
|
||||
"\n",
|
||||
" <b>This notebook is licensed <a href=\"https://github.com/unslothai/unsloth/blob/main/studio/LICENSE.AGPL-3.0\">AGPL-3.0</a></b>\n",
|
||||
"</div>"
|
||||
]
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"accelerator": "GPU",
|
||||
"colab": {
|
||||
"gpuType": "T4",
|
||||
"provenance": [],
|
||||
"include_colab_link": true
|
||||
},
|
||||
"kernelspec": {
|
||||
"display_name": "Python 3",
|
||||
"name": "python3"
|
||||
},
|
||||
"language_info": {
|
||||
"name": "python"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 5
|
||||
}
|
||||
|
|
@ -30,7 +30,6 @@ lora:
|
|||
vision_all_linear: false
|
||||
use_rslora: false
|
||||
use_loftq: false
|
||||
use_dora: false
|
||||
finetune_vision_layers: true
|
||||
finetune_language_layers: true
|
||||
finetune_attention_modules: true
|
||||
|
|
|
|||
|
|
@ -30,7 +30,6 @@ lora:
|
|||
vision_all_linear: false
|
||||
use_rslora: false
|
||||
use_loftq: false
|
||||
use_dora: false
|
||||
finetune_vision_layers: true
|
||||
finetune_language_layers: true
|
||||
finetune_attention_modules: true
|
||||
|
|
|
|||
|
|
@ -33,7 +33,6 @@ lora:
|
|||
- "down_proj"
|
||||
use_rslora: false
|
||||
use_loftq: false
|
||||
use_dora: false
|
||||
finetune_vision_layers: true
|
||||
finetune_language_layers: true
|
||||
finetune_attention_modules: true
|
||||
|
|
|
|||
|
|
@ -34,7 +34,6 @@ lora:
|
|||
- "down_proj"
|
||||
use_rslora: false
|
||||
use_loftq: false
|
||||
use_dora: false
|
||||
|
||||
logging:
|
||||
enable_wandb: false
|
||||
|
|
|
|||
|
|
@ -30,7 +30,6 @@ lora:
|
|||
- "query"
|
||||
use_rslora: false
|
||||
use_loftq: false
|
||||
use_dora: false
|
||||
|
||||
logging:
|
||||
enable_wandb: false
|
||||
|
|
|
|||
|
|
@ -30,7 +30,6 @@ lora:
|
|||
- "value"
|
||||
use_rslora: false
|
||||
use_loftq: false
|
||||
use_dora: false
|
||||
|
||||
logging:
|
||||
enable_wandb: false
|
||||
|
|
|
|||
|
|
@ -33,7 +33,6 @@ lora:
|
|||
- "down_proj"
|
||||
use_rslora: false
|
||||
use_loftq: false
|
||||
use_dora: false
|
||||
|
||||
logging:
|
||||
enable_wandb: false
|
||||
|
|
|
|||
|
|
@ -29,7 +29,6 @@ lora:
|
|||
- "Wqkv"
|
||||
use_rslora: false
|
||||
use_loftq: false
|
||||
use_dora: false
|
||||
|
||||
logging:
|
||||
enable_wandb: false
|
||||
|
|
|
|||
|
|
@ -34,7 +34,6 @@ lora:
|
|||
- "down_proj"
|
||||
use_rslora: false
|
||||
use_loftq: false
|
||||
use_dora: false
|
||||
|
||||
logging:
|
||||
enable_wandb: false
|
||||
|
|
|
|||
|
|
@ -35,7 +35,6 @@ lora:
|
|||
- "down_proj"
|
||||
use_rslora: false
|
||||
use_loftq: false
|
||||
use_dora: false
|
||||
finetune_vision_layers: true
|
||||
finetune_language_layers: true
|
||||
finetune_attention_modules: true
|
||||
|
|
|
|||
|
|
@ -34,7 +34,6 @@ lora:
|
|||
- "down_proj"
|
||||
use_rslora: false
|
||||
use_loftq: false
|
||||
use_dora: false
|
||||
|
||||
logging:
|
||||
enable_wandb: false
|
||||
|
|
|
|||
|
|
@ -35,7 +35,6 @@ lora:
|
|||
- "down_proj"
|
||||
use_rslora: false
|
||||
use_loftq: false
|
||||
use_dora: false
|
||||
|
||||
logging:
|
||||
enable_wandb: false
|
||||
|
|
|
|||
|
|
@ -35,7 +35,6 @@ lora:
|
|||
- "down_proj"
|
||||
use_rslora: false
|
||||
use_loftq: false
|
||||
use_dora: false
|
||||
|
||||
logging:
|
||||
enable_wandb: false
|
||||
|
|
|
|||
|
|
@ -33,7 +33,6 @@ lora:
|
|||
- "down_proj"
|
||||
use_rslora: false
|
||||
use_loftq: false
|
||||
use_dora: false
|
||||
|
||||
logging:
|
||||
enable_wandb: false
|
||||
|
|
|
|||
|
|
@ -34,7 +34,6 @@ lora:
|
|||
- "down_proj"
|
||||
use_rslora: false
|
||||
use_loftq: false
|
||||
use_dora: false
|
||||
|
||||
logging:
|
||||
enable_wandb: false
|
||||
|
|
|
|||
|
|
@ -35,7 +35,6 @@ lora:
|
|||
- "down_proj"
|
||||
use_rslora: false
|
||||
use_loftq: false
|
||||
use_dora: false
|
||||
|
||||
logging:
|
||||
enable_wandb: false
|
||||
|
|
|
|||
|
|
@ -29,7 +29,6 @@ lora:
|
|||
- "all-linear"
|
||||
use_rslora: false
|
||||
use_loftq: false
|
||||
use_dora: false
|
||||
finetune_vision_layers: true
|
||||
finetune_language_layers: true
|
||||
finetune_attention_modules: true
|
||||
|
|
|
|||
|
|
@ -29,7 +29,6 @@ lora:
|
|||
- "all-linear"
|
||||
use_rslora: false
|
||||
use_loftq: false
|
||||
use_dora: false
|
||||
finetune_vision_layers: true
|
||||
finetune_language_layers: true
|
||||
finetune_attention_modules: true
|
||||
|
|
|
|||
|
|
@ -29,7 +29,6 @@ lora:
|
|||
- "all-linear"
|
||||
use_rslora: false
|
||||
use_loftq: false
|
||||
use_dora: false
|
||||
finetune_vision_layers: true
|
||||
finetune_language_layers: true
|
||||
finetune_attention_modules: true
|
||||
|
|
|
|||
|
|
@ -29,7 +29,6 @@ lora:
|
|||
- "all-linear"
|
||||
use_rslora: false
|
||||
use_loftq: false
|
||||
use_dora: false
|
||||
finetune_vision_layers: true
|
||||
finetune_language_layers: true
|
||||
finetune_attention_modules: true
|
||||
|
|
|
|||
|
|
@ -29,7 +29,6 @@ lora:
|
|||
- "all-linear"
|
||||
use_rslora: false
|
||||
use_loftq: false
|
||||
use_dora: false
|
||||
finetune_vision_layers: true
|
||||
finetune_language_layers: true
|
||||
finetune_attention_modules: true
|
||||
|
|
|
|||
|
|
@ -26,7 +26,6 @@ lora:
|
|||
- "all-linear"
|
||||
use_rslora: false
|
||||
use_loftq: false
|
||||
use_dora: false
|
||||
finetune_vision_layers: true
|
||||
finetune_language_layers: true
|
||||
finetune_attention_modules: true
|
||||
|
|
|
|||
|
|
@ -26,7 +26,6 @@ lora:
|
|||
- "all-linear"
|
||||
use_rslora: false
|
||||
use_loftq: false
|
||||
use_dora: false
|
||||
finetune_vision_layers: true
|
||||
finetune_language_layers: true
|
||||
finetune_attention_modules: true
|
||||
|
|
|
|||
|
|
@ -26,7 +26,6 @@ lora:
|
|||
- "all-linear"
|
||||
use_rslora: false
|
||||
use_loftq: false
|
||||
use_dora: false
|
||||
finetune_vision_layers: true
|
||||
finetune_language_layers: true
|
||||
finetune_attention_modules: true
|
||||
|
|
|
|||
|
|
@ -26,7 +26,6 @@ lora:
|
|||
- "all-linear"
|
||||
use_rslora: false
|
||||
use_loftq: false
|
||||
use_dora: false
|
||||
finetune_vision_layers: true
|
||||
finetune_language_layers: true
|
||||
finetune_attention_modules: true
|
||||
|
|
|
|||
|
|
@ -26,7 +26,6 @@ lora:
|
|||
- "all-linear"
|
||||
use_rslora: false
|
||||
use_loftq: false
|
||||
use_dora: false
|
||||
finetune_vision_layers: true
|
||||
finetune_language_layers: true
|
||||
finetune_attention_modules: true
|
||||
|
|
|
|||
|
|
@ -26,7 +26,6 @@ lora:
|
|||
- "all-linear"
|
||||
use_rslora: false
|
||||
use_loftq: false
|
||||
use_dora: false
|
||||
finetune_vision_layers: true
|
||||
finetune_language_layers: true
|
||||
finetune_attention_modules: true
|
||||
|
|
|
|||
|
|
@ -26,7 +26,6 @@ lora:
|
|||
- "all-linear"
|
||||
use_rslora: false
|
||||
use_loftq: false
|
||||
use_dora: false
|
||||
finetune_vision_layers: true
|
||||
finetune_language_layers: true
|
||||
finetune_attention_modules: true
|
||||
|
|
|
|||
|
|
@ -26,7 +26,6 @@ lora:
|
|||
- "all-linear"
|
||||
use_rslora: false
|
||||
use_loftq: false
|
||||
use_dora: false
|
||||
finetune_vision_layers: true
|
||||
finetune_language_layers: true
|
||||
finetune_attention_modules: true
|
||||
|
|
|
|||
|
|
@ -35,7 +35,6 @@ lora:
|
|||
- "down_proj"
|
||||
use_rslora: false
|
||||
use_loftq: false
|
||||
use_dora: false
|
||||
|
||||
logging:
|
||||
enable_wandb: false
|
||||
|
|
|
|||
|
|
@ -35,7 +35,6 @@ lora:
|
|||
- "down_proj"
|
||||
use_rslora: false
|
||||
use_loftq: false
|
||||
use_dora: false
|
||||
|
||||
logging:
|
||||
enable_wandb: false
|
||||
|
|
|
|||
|
|
@ -37,7 +37,6 @@ lora:
|
|||
- "shared_mlp.output_linear"
|
||||
use_rslora: false
|
||||
use_loftq: false
|
||||
use_dora: false
|
||||
|
||||
logging:
|
||||
enable_wandb: false
|
||||
|
|
|
|||
|
|
@ -37,7 +37,6 @@ lora:
|
|||
- "shared_mlp.output_linear"
|
||||
use_rslora: false
|
||||
use_loftq: false
|
||||
use_dora: false
|
||||
|
||||
logging:
|
||||
enable_wandb: false
|
||||
|
|
|
|||
|
|
@ -29,7 +29,6 @@ lora:
|
|||
- "all-linear"
|
||||
use_rslora: false
|
||||
use_loftq: false
|
||||
use_dora: false
|
||||
finetune_vision_layers: true
|
||||
finetune_language_layers: true
|
||||
finetune_attention_modules: true
|
||||
|
|
|
|||
|
|
@ -34,7 +34,6 @@ lora:
|
|||
- "down_proj"
|
||||
use_rslora: false
|
||||
use_loftq: false
|
||||
use_dora: false
|
||||
|
||||
logging:
|
||||
enable_wandb: false
|
||||
|
|
|
|||
|
|
@ -35,7 +35,6 @@ lora:
|
|||
- "down_proj"
|
||||
use_rslora: false
|
||||
use_loftq: false
|
||||
use_dora: false
|
||||
|
||||
logging:
|
||||
enable_wandb: false
|
||||
|
|
|
|||
|
|
@ -35,7 +35,6 @@ lora:
|
|||
- "down_proj"
|
||||
use_rslora: false
|
||||
use_loftq: false
|
||||
use_dora: false
|
||||
|
||||
logging:
|
||||
enable_wandb: false
|
||||
|
|
|
|||
|
|
@ -34,7 +34,6 @@ lora:
|
|||
- "down_proj"
|
||||
use_rslora: false
|
||||
use_loftq: false
|
||||
use_dora: false
|
||||
|
||||
logging:
|
||||
enable_wandb: false
|
||||
|
|
|
|||
|
|
@ -34,7 +34,6 @@ lora:
|
|||
- "down_proj"
|
||||
use_rslora: false
|
||||
use_loftq: false
|
||||
use_dora: false
|
||||
|
||||
logging:
|
||||
enable_wandb: false
|
||||
|
|
|
|||
|
|
@ -34,7 +34,6 @@ lora:
|
|||
- "down_proj"
|
||||
use_rslora: false
|
||||
use_loftq: false
|
||||
use_dora: false
|
||||
|
||||
logging:
|
||||
enable_wandb: false
|
||||
|
|
|
|||
|
|
@ -34,7 +34,6 @@ lora:
|
|||
- "down_proj"
|
||||
use_rslora: false
|
||||
use_loftq: false
|
||||
use_dora: false
|
||||
|
||||
logging:
|
||||
enable_wandb: false
|
||||
|
|
|
|||
|
|
@ -30,7 +30,6 @@ lora:
|
|||
- "v_proj"
|
||||
use_rslora: false
|
||||
use_loftq: false
|
||||
use_dora: false
|
||||
|
||||
logging:
|
||||
enable_wandb: false
|
||||
|
|
|
|||
|
|
@ -35,7 +35,6 @@ lora:
|
|||
- "down_proj"
|
||||
use_rslora: false
|
||||
use_loftq: false
|
||||
use_dora: false
|
||||
finetune_vision_layers: true
|
||||
finetune_language_layers: true
|
||||
finetune_attention_modules: true
|
||||
|
|
|
|||
|
|
@ -35,7 +35,6 @@ lora:
|
|||
- "down_proj"
|
||||
use_rslora: false
|
||||
use_loftq: false
|
||||
use_dora: false
|
||||
finetune_vision_layers: true
|
||||
finetune_language_layers: true
|
||||
finetune_attention_modules: true
|
||||
|
|
|
|||
|
|
@ -34,7 +34,6 @@ lora:
|
|||
- "down_proj"
|
||||
use_rslora: false
|
||||
use_loftq: false
|
||||
use_dora: false
|
||||
|
||||
logging:
|
||||
enable_wandb: false
|
||||
|
|
|
|||
|
|
@ -34,7 +34,6 @@ lora:
|
|||
- "down_proj"
|
||||
use_rslora: false
|
||||
use_loftq: false
|
||||
use_dora: false
|
||||
|
||||
logging:
|
||||
enable_wandb: false
|
||||
|
|
|
|||
|
|
@ -29,7 +29,6 @@ lora:
|
|||
- "all-linear"
|
||||
use_rslora: false
|
||||
use_loftq: false
|
||||
use_dora: false
|
||||
finetune_vision_layers: true
|
||||
finetune_language_layers: true
|
||||
finetune_attention_modules: false
|
||||
|
|
|
|||
|
|
@ -34,7 +34,6 @@ lora:
|
|||
- "down_proj"
|
||||
use_rslora: false
|
||||
use_loftq: false
|
||||
use_dora: false
|
||||
|
||||
logging:
|
||||
enable_wandb: false
|
||||
|
|
|
|||
|
|
@ -33,7 +33,6 @@ lora:
|
|||
- "down_proj"
|
||||
use_rslora: false
|
||||
use_loftq: false
|
||||
use_dora: false
|
||||
|
||||
logging:
|
||||
enable_wandb: false
|
||||
|
|
|
|||
|
|
@ -33,7 +33,6 @@ lora:
|
|||
- "v_proj"
|
||||
use_rslora: false
|
||||
use_loftq: false
|
||||
use_dora: false
|
||||
|
||||
logging:
|
||||
enable_wandb: false
|
||||
|
|
|
|||
|
|
@ -38,7 +38,6 @@ lora:
|
|||
- "down_proj"
|
||||
use_rslora: false
|
||||
use_loftq: false
|
||||
use_dora: false
|
||||
|
||||
logging:
|
||||
enable_wandb: false
|
||||
|
|
|
|||
|
|
@ -37,7 +37,6 @@ lora:
|
|||
- "down_proj"
|
||||
use_rslora: false
|
||||
use_loftq: false
|
||||
use_dora: false
|
||||
|
||||
logging:
|
||||
enable_wandb: false
|
||||
|
|
|
|||
|
|
@ -35,7 +35,6 @@ lora:
|
|||
- "out_proj"
|
||||
use_rslora: false
|
||||
use_loftq: false
|
||||
use_dora: false
|
||||
|
||||
logging:
|
||||
enable_wandb: false
|
||||
|
|
|
|||
|
|
@ -29,7 +29,6 @@ lora:
|
|||
- "all-linear"
|
||||
use_rslora: false
|
||||
use_loftq: false
|
||||
use_dora: false
|
||||
|
||||
logging:
|
||||
enable_wandb: false
|
||||
|
|
|
|||
|
|
@ -37,7 +37,6 @@ lora:
|
|||
- "out_proj"
|
||||
use_rslora: false
|
||||
use_loftq: false
|
||||
use_dora: false
|
||||
|
||||
logging:
|
||||
enable_wandb: false
|
||||
|
|
|
|||
|
|
@ -35,7 +35,6 @@ lora:
|
|||
- "down_proj"
|
||||
use_rslora: false
|
||||
use_loftq: false
|
||||
use_dora: false
|
||||
finetune_vision_layers: true
|
||||
finetune_language_layers: true
|
||||
finetune_attention_modules: true
|
||||
|
|
|
|||
|
|
@ -33,7 +33,6 @@ lora:
|
|||
- "down_proj"
|
||||
use_rslora: false
|
||||
use_loftq: false
|
||||
use_dora: false
|
||||
|
||||
logging:
|
||||
enable_wandb: false
|
||||
|
|
|
|||
|
|
@ -38,7 +38,6 @@ lora:
|
|||
- "down_proj"
|
||||
use_rslora: false
|
||||
use_loftq: false
|
||||
use_dora: false
|
||||
|
||||
logging:
|
||||
enable_wandb: false
|
||||
|
|
|
|||
|
|
@ -34,7 +34,6 @@ lora:
|
|||
- "down_proj"
|
||||
use_rslora: false
|
||||
use_loftq: false
|
||||
use_dora: false
|
||||
|
||||
logging:
|
||||
enable_wandb: false
|
||||
|
|
|
|||
|
|
@ -33,7 +33,6 @@ lora:
|
|||
- "v_proj"
|
||||
use_rslora: false
|
||||
use_loftq: false
|
||||
use_dora: false
|
||||
|
||||
logging:
|
||||
enable_wandb: false
|
||||
|
|
|
|||
|
|
@ -34,7 +34,6 @@ lora:
|
|||
- "down_proj"
|
||||
use_rslora: false
|
||||
use_loftq: false
|
||||
use_dora: false
|
||||
|
||||
logging:
|
||||
enable_wandb: false
|
||||
|
|
|
|||
|
|
@ -34,7 +34,6 @@ lora:
|
|||
- "down_proj"
|
||||
use_rslora: false
|
||||
use_loftq: false
|
||||
use_dora: false
|
||||
|
||||
logging:
|
||||
enable_wandb: false
|
||||
|
|
|
|||
|
|
@ -35,7 +35,6 @@ lora:
|
|||
- "down_proj"
|
||||
use_rslora: false
|
||||
use_loftq: false
|
||||
use_dora: false
|
||||
|
||||
logging:
|
||||
enable_wandb: false
|
||||
|
|
|
|||
|
|
@ -36,7 +36,6 @@ lora:
|
|||
- "gate_up_proj"
|
||||
use_rslora: false
|
||||
use_loftq: false
|
||||
use_dora: false
|
||||
|
||||
logging:
|
||||
enable_wandb: false
|
||||
|
|
|
|||
|
|
@ -34,7 +34,6 @@ lora:
|
|||
- "down_proj"
|
||||
use_rslora: false
|
||||
use_loftq: false
|
||||
use_dora: false
|
||||
|
||||
logging:
|
||||
enable_wandb: false
|
||||
|
|
|
|||
|
|
@ -29,7 +29,6 @@ lora:
|
|||
- "all-linear"
|
||||
use_rslora: false
|
||||
use_loftq: false
|
||||
use_dora: false
|
||||
finetune_vision_layers: true
|
||||
finetune_language_layers: true
|
||||
finetune_attention_modules: true
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue