gate on min uv version and shortcut python candidate search if known (#4489)

* gate on min uv version and shortcut python candidate search if known

* fix sort -V cross compat issue, run_quiet early exit on llamacpp, autolaunch

* update launch message

* Fix PR comments

* auto launch and find open port

* remove dev install

* Fix review findings: major-version guard, non-fatal port fallback, tty comment, restore local

* Remove autolaunch, clean up dead state and debug noise

- Remove find_open_port, TTY-gated autolaunch, and </dev/tty
  redirection from install.sh; just print launch instructions
- Remove unused BEST_MAJOR variable from studio/setup.sh
- Remove stray "finished finding best python" debug echo
- Fix stale comment "below 3.12" to "below 3.11"

* Reject prerelease uv at exact minimum version boundary

* Remove 2>/dev/null from version_ge numeric comparisons

Let non-numeric version parts surface errors on stderr
instead of being silently swallowed.

---------

Co-authored-by: Daniel Han <danielhanchen@gmail.com>
This commit is contained in:
DoubleMathew 2026-03-22 07:20:25 -05:00 committed by GitHub
parent 64f9f389a0
commit 4c1a6cb962
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 99 additions and 30 deletions

View file

@ -171,7 +171,48 @@ else
fi
# ── Install uv ──
if ! command -v uv >/dev/null 2>&1; then
UV_MIN_VERSION="0.7.14"
version_ge() {
# returns 0 if $1 >= $2
_a=$1
_b=$2
while [ -n "$_a" ] || [ -n "$_b" ]; do
_a_part=${_a%%.*}
_b_part=${_b%%.*}
[ "$_a" = "$_a_part" ] && _a="" || _a=${_a#*.}
[ "$_b" = "$_b_part" ] && _b="" || _b=${_b#*.}
[ -z "$_a_part" ] && _a_part=0
[ -z "$_b_part" ] && _b_part=0
if [ "$_a_part" -gt "$_b_part" ]; then
return 0
fi
if [ "$_a_part" -lt "$_b_part" ]; then
return 1
fi
done
return 0
}
_uv_version_ok() {
_raw=$("$1" --version 2>/dev/null | awk '{print $2}') || return 1
[ -n "$_raw" ] || return 1
_ver=${_raw%%[-+]*}
case "$_ver" in
''|*[!0-9.]*) return 1 ;;
esac
version_ge "$_ver" "$UV_MIN_VERSION" || return 1
# Prerelease of the exact minimum (e.g. 0.7.14-rc1) is still below stable 0.7.14
[ "$_ver" = "$UV_MIN_VERSION" ] && [ "$_raw" != "$_ver" ] && return 1
return 0
}
if ! command -v uv >/dev/null 2>&1 || ! _uv_version_ok uv; then
echo "==> Installing uv package manager..."
_uv_tmp=$(mktemp)
download "https://astral.sh/uv/install.sh" "$_uv_tmp"
@ -207,6 +248,7 @@ if [ -n "$VENV_ABS_BIN" ]; then
fi
echo "==> Running unsloth studio setup..."
REQUESTED_PYTHON_VERSION="$(cd "$VENV_NAME/bin" && pwd)/python" \
"$VENV_NAME/bin/unsloth" studio setup </dev/null
echo ""
@ -215,16 +257,8 @@ echo " Unsloth Studio installed!"
echo "========================================="
echo ""
# Launch studio automatically in interactive terminals;
# in non-interactive environments (Docker, CI, cloud-init) just print instructions.
if [ -t 0 ]; then
echo "==> Launching Unsloth Studio..."
echo ""
exec "$VENV_NAME/bin/unsloth" studio -H 0.0.0.0 -p 8888
else
echo " To launch, run:"
echo ""
echo " source ${VENV_NAME}/bin/activate"
echo " unsloth studio -H 0.0.0.0 -p 8888"
echo ""
fi
echo " To launch, run:"
echo ""
echo " source ${VENV_NAME}/bin/activate"
echo " unsloth studio -H 0.0.0.0 -p 8888"
echo ""

View file

@ -8,22 +8,42 @@ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
# ── Helper: run command quietly, show output only on failure ──
run_quiet() {
local label="$1"
shift
_run_quiet() {
local on_fail=$1
local label=$2
shift 2
local tmplog
tmplog=$(mktemp)
if "$@" > "$tmplog" 2>&1; then
tmplog=$(mktemp) || {
printf '%s\n' "Failed to create temporary file" >&2
[ "$on_fail" = "exit" ] && exit 1 || return 1
}
if "$@" >"$tmplog" 2>&1; then
rm -f "$tmplog"
return 0
else
local exit_code=$?
echo "$label failed (exit code $exit_code):"
cat "$tmplog"
printf 'Failed: %s (exit code %s):\n' "$label" "$exit_code" >&2
cat "$tmplog" >&2
rm -f "$tmplog"
exit $exit_code
if [ "$on_fail" = "exit" ]; then
exit "$exit_code"
else
return "$exit_code"
fi
fi
}
run_quiet() {
_run_quiet exit "$@"
}
run_quiet_no_exit() {
_run_quiet return "$@"
}
echo "╔══════════════════════════════════════╗"
echo "║ Unsloth Studio Setup Script ║"
echo "╚══════════════════════════════════════╝"
@ -187,9 +207,24 @@ fi
MIN_PY_MINOR=11 # minimum minor version (>= 3.11)
MAX_PY_MINOR=13 # maximum minor version (< 3.14)
BEST_PY=""
BEST_MAJOR=0
BEST_MINOR=0
# If the caller (e.g. install.sh) already chose a Python, use it directly.
if [ -n "${REQUESTED_PYTHON_VERSION:-}" ] && [ -x "$REQUESTED_PYTHON_VERSION" ]; then
_req_ver=$("$REQUESTED_PYTHON_VERSION" --version 2>&1 | awk '{print $2}')
_req_major=$(echo "$_req_ver" | cut -d. -f1)
_req_minor=$(echo "$_req_ver" | cut -d. -f2)
if [ "$_req_major" -eq 3 ] 2>/dev/null && \
[ "$_req_minor" -ge "$MIN_PY_MINOR" ] 2>/dev/null && \
[ "$_req_minor" -le "$MAX_PY_MINOR" ] 2>/dev/null; then
BEST_PY="$REQUESTED_PYTHON_VERSION"
echo "Using requested Python version: $BEST_PY"
else
echo "Ignoring requested Python $REQUESTED_PYTHON_VERSION ($_req_ver) -- outside supported range"
fi
fi
if [ -z "$BEST_PY" ]; then
# Collect candidate python3 binaries (python3, python3.9, python3.10, …)
for candidate in $(compgen -c python3 2>/dev/null | grep -E '^python3(\.[0-9]+)?$' | sort -u); do
if ! command -v "$candidate" &>/dev/null; then
@ -206,7 +241,7 @@ for candidate in $(compgen -c python3 2>/dev/null | grep -E '^python3(\.[0-9]+)?
continue
fi
# Skip versions below 3.12 (require > 3.11)
# Skip versions below 3.11
if [ "$py_minor" -lt "$MIN_PY_MINOR" ] 2>/dev/null; then
continue
fi
@ -219,11 +254,11 @@ for candidate in $(compgen -c python3 2>/dev/null | grep -E '^python3(\.[0-9]+)?
# Keep the highest qualifying version
if [ "$py_minor" -gt "$BEST_MINOR" ]; then
BEST_PY="$candidate"
BEST_MAJOR="$py_major"
BEST_MINOR="$py_minor"
fi
done
echo "finished finding best python"
fi
if [ -z "$BEST_PY" ]; then
echo "❌ ERROR: No Python version between 3.${MIN_PY_MINOR} and 3.${MAX_PY_MINOR} found on this system."
echo " Detected Python 3 installations:"
@ -402,7 +437,7 @@ rm -rf "$LLAMA_CPP_DIR"
echo "Building llama-server for GGUF inference..."
BUILD_OK=true
run_quiet "clone llama.cpp" git clone --depth 1 https://github.com/ggml-org/llama.cpp.git "$LLAMA_CPP_DIR" || BUILD_OK=false
run_quiet_no_exit "clone llama.cpp" git clone --depth 1 https://github.com/ggml-org/llama.cpp.git "$LLAMA_CPP_DIR" || BUILD_OK=false
if [ "$BUILD_OK" = true ]; then
# Skip tests/examples we don't need (faster build)
@ -474,16 +509,16 @@ rm -rf "$LLAMA_CPP_DIR"
CMAKE_GENERATOR_ARGS="-G Ninja"
fi
run_quiet "cmake llama.cpp" cmake $CMAKE_GENERATOR_ARGS -S "$LLAMA_CPP_DIR" -B "$LLAMA_CPP_DIR/build" $CMAKE_ARGS || BUILD_OK=false
run_quiet_no_exit "cmake llama.cpp" cmake $CMAKE_GENERATOR_ARGS -S "$LLAMA_CPP_DIR" -B "$LLAMA_CPP_DIR/build" $CMAKE_ARGS || BUILD_OK=false
fi
if [ "$BUILD_OK" = true ]; then
run_quiet "build llama-server" cmake --build "$LLAMA_CPP_DIR/build" --config Release --target llama-server -j"$NCPU" || BUILD_OK=false
run_quiet_no_exit "build llama-server" cmake --build "$LLAMA_CPP_DIR/build" --config Release --target llama-server -j"$NCPU" || BUILD_OK=false
fi
# Also build llama-quantize (needed by unsloth-zoo's GGUF export pipeline)
if [ "$BUILD_OK" = true ]; then
run_quiet "build llama-quantize" cmake --build "$LLAMA_CPP_DIR/build" --config Release --target llama-quantize -j"$NCPU" || true
run_quiet_no_exit "build llama-quantize" cmake --build "$LLAMA_CPP_DIR/build" --config Release --target llama-quantize -j"$NCPU" || true
# Symlink to llama.cpp root — check_llama_cpp() looks for the binary there
QUANTIZE_BIN="$LLAMA_CPP_DIR/build/bin/llama-quantize"
if [ -f "$QUANTIZE_BIN" ]; then