allow install from source

This commit is contained in:
Roland Tannous 2026-03-13 18:18:24 +00:00
commit 8ce2b64df7
2 changed files with 10 additions and 243 deletions

View file

@ -19,34 +19,6 @@ STUDIO_HOME = Path.home() / ".unsloth" / "studio"
_PACKAGE_ROOT = Path(__file__).resolve().parent.parent.parent
def _is_repo_root(path: Path) -> bool:
"""Check if a directory looks like the repo root (actual git clone, not site-packages)."""
return (
(path / ".git").exists()
and (path / "pyproject.toml").is_file()
and (
(path / "studio" / "setup.sh").is_file()
or (path / "studio" / "setup.ps1").is_file()
)
)
def _get_repo_root() -> Optional[Path]:
"""Find the git clone repo root.
Used only by setup() checks __file__ first (editable install),
then walks CWD parents (wheel install, user is inside the clone).
"""
# Check 1: __file__ is in the repo (editable install)
if _is_repo_root(_PACKAGE_ROOT):
return _PACKAGE_ROOT
# Check 2: CWD or any parent is the repo
cwd = Path.cwd().resolve()
for parent in (cwd, *cwd.parents):
if _is_repo_root(parent):
return parent
return None
def _studio_venv_python() -> Optional[Path]:
"""Return the studio venv Python binary, or None if not set up."""
@ -78,24 +50,6 @@ def _find_run_py() -> Optional[Path]:
return None
def _find_install_script() -> Optional[Path]:
"""Find studio/install_python_stack.py.
No CWD dependency works from any directory.
"""
# 1. Relative to __file__ (site-packages or editable repo root)
s = _PACKAGE_ROOT / "studio" / "install_python_stack.py"
if s.is_file():
return s
# 2. Studio venv's site-packages
for pattern in (
"lib/python*/site-packages/studio/install_python_stack.py",
"Lib/site-packages/studio/install_python_stack.py",
):
for match in (STUDIO_HOME / ".venv").glob(pattern):
return match
return None
def _find_setup_script() -> Optional[Path]:
"""Find studio/setup.sh or studio/setup.ps1.
@ -187,199 +141,16 @@ def studio_default(
@studio_app.command()
def setup():
"""Run one-time Studio environment setup."""
# If we're inside a git clone, use the full setup script (builds frontend, etc.)
repo = _get_repo_root()
if repo:
_dev_setup(repo)
else:
_pip_setup()
script = _find_setup_script()
if not script:
typer.echo("Error: Could not find setup script (setup.sh / setup.ps1).")
raise typer.Exit(1)
def _dev_setup(repo_root: Path):
"""Git-clone: run setup.sh / setup.ps1."""
studio_dir = repo_root / "studio"
if platform.system() == "Windows":
script = studio_dir / "setup.ps1"
subprocess.run(
["powershell", "-ExecutionPolicy", "Bypass", "-File", str(script)],
check = True,
)
else:
script = studio_dir / "setup.sh"
subprocess.run(["bash", str(script)], check = True)
def _pip_setup():
"""Pip-install: create studio venv, install all deps, build extras."""
import venv as _venv
venv_dir = STUDIO_HOME / ".venv"
venv_t5_dir = STUDIO_HOME / ".venv_t5"
if platform.system() == "Windows":
venv_python = venv_dir / "Scripts" / "python.exe"
venv_pip = venv_dir / "Scripts" / "pip.exe"
else:
venv_python = venv_dir / "bin" / "python"
venv_pip = venv_dir / "bin" / "pip"
typer.echo("Setting up Unsloth Studio...")
# 1. Create venv
if not venv_python.is_file():
typer.echo(f" Creating venv at {venv_dir}...")
STUDIO_HOME.mkdir(parents = True, exist_ok = True)
_venv.create(str(venv_dir), with_pip = True)
# 2. Install all Python deps via install_python_stack.py
install_script = _find_install_script()
if install_script:
typer.echo(" Installing Python dependencies...")
subprocess.run([str(venv_python), str(install_script)], check = True)
else:
typer.echo("Error: Could not find install_python_stack.py")
raise typer.Exit(1)
# 3. Pre-install transformers 5.x overlay
if venv_t5_dir.is_dir() and any(venv_t5_dir.iterdir()):
typer.echo(f" Transformers 5.x overlay already at {venv_t5_dir}")
else:
typer.echo(" Installing transformers 5.x overlay...")
venv_t5_dir.mkdir(parents = True, exist_ok = True)
subprocess.run(
[
str(venv_pip),
"install",
"--target",
str(venv_t5_dir),
"--no-deps",
"transformers==5.2.0",
],
check = True,
)
subprocess.run(
[
str(venv_pip),
"install",
"--target",
str(venv_t5_dir),
"--no-deps",
"huggingface_hub==1.3.0",
],
check = True,
)
typer.echo(f" Installed to {venv_t5_dir}")
# 4. Build llama.cpp
_build_llama_cpp()
typer.echo("")
typer.echo("Setup complete! Run 'unsloth studio' to start.")
def _build_llama_cpp():
"""Build llama.cpp at ~/.unsloth/llama.cpp/."""
import shutil
unsloth_home = Path.home() / ".unsloth"
llama_dir = unsloth_home / "llama.cpp"
if not shutil.which("cmake"):
typer.echo(" cmake not found — skipping llama.cpp build")
return
if not shutil.which("git"):
typer.echo(" git not found — skipping llama.cpp build")
return
typer.echo(" Building llama.cpp for GGUF inference...")
if llama_dir.exists():
# necessary because shutil.rmtree fails on Windows because .git pack files are read-only
def _force_remove_readonly(func, path, exc_info):
"""Clear read-only flag and retry — needed on Windows for .git pack files."""
import stat
os.chmod(path, stat.S_IWRITE)
func(path)
shutil.rmtree(llama_dir, onerror = _force_remove_readonly)
unsloth_home.mkdir(parents = True, exist_ok = True)
result = subprocess.run(
[
"git",
"clone",
"--depth",
"1",
"https://github.com/ggml-org/llama.cpp.git",
str(llama_dir),
],
stdout = subprocess.PIPE,
stderr = subprocess.STDOUT,
)
if result.returncode != 0:
typer.echo(" Failed to clone llama.cpp")
return
cmake_args = []
nvcc_path = shutil.which("nvcc")
if not nvcc_path and Path("/usr/local/cuda/bin/nvcc").is_file():
nvcc_path = "/usr/local/cuda/bin/nvcc"
if nvcc_path:
typer.echo(f" Building with CUDA (nvcc: {nvcc_path})...")
cmake_args.append("-DGGML_CUDA=ON")
else:
typer.echo(" Building CPU-only...")
build_dir = llama_dir / "build"
result = subprocess.run(
["cmake", "-S", str(llama_dir), "-B", str(build_dir)] + cmake_args,
stdout = subprocess.PIPE,
stderr = subprocess.STDOUT,
)
if result.returncode != 0:
typer.echo(" cmake configure failed")
return
ncpu = str(os.cpu_count() or 4)
result = subprocess.run(
[
"cmake",
"--build",
str(build_dir),
"--config",
"Release",
"--target",
"llama-server",
f"-j{ncpu}",
],
stdout = subprocess.PIPE,
stderr = subprocess.STDOUT,
)
if result.returncode != 0:
typer.echo(" llama-server build failed")
return
subprocess.run(
[
"cmake",
"--build",
str(build_dir),
"--config",
"Release",
"--target",
"llama-quantize",
f"-j{ncpu}",
],
stdout = subprocess.PIPE,
stderr = subprocess.STDOUT,
)
if sys.platform == "win32":
server_bin = build_dir / "bin" / "Release" / "llama-server.exe"
else:
server_bin = build_dir / "bin" / "llama-server"
if server_bin.is_file():
typer.echo(f" llama-server built at {server_bin}")
else:
typer.echo(" llama-server binary not found after build")

View file

@ -33,12 +33,6 @@ rm -rf "$REPO_ROOT/unsloth_compiled_cache"
rm -rf "$SCRIPT_DIR/backend/unsloth_compiled_cache"
rm -rf "$SCRIPT_DIR/tmp/unsloth_compiled_cache"
# ── Detect pip install (no frontend source dir → already bundled) ──
IS_PIP_INSTALL=false
if [ ! -f "$SCRIPT_DIR/frontend/package.json" ]; then
IS_PIP_INSTALL=true
fi
# ── Detect Colab (like unsloth does) ──
IS_COLAB=false
keynames=$'\n'$(printenv | cut -d= -f1)
@ -46,9 +40,11 @@ if [[ "$keynames" == *$'\nCOLAB_'* ]]; then
IS_COLAB=true
fi
# ── 1. Check existing Node/npm versions (skip if pip-installed) ──
if [ "$IS_PIP_INSTALL" = true ]; then
echo "✅ Running from pip install — frontend already bundled, skipping Node/npm check."
# ── Detect whether frontend needs building ──
# Just check if dist/ already exists — no need to guess install mode.
# Source is always present (include-package-data = true), only dist/ may be missing.
if [ -d "$SCRIPT_DIR/frontend/dist" ]; then
echo "✅ Frontend already built — skipping Node/npm check."
else
NEED_NODE=true
if command -v node &>/dev/null && command -v npm &>/dev/null; then
@ -129,7 +125,7 @@ run_quiet "npm install (oxc validator runtime)" npm install
cd "$SCRIPT_DIR"
echo "✅ Frontend built to frontend/dist"
fi # end IS_PIP_INSTALL check
fi # end frontend dist check
# ── 6. Python venv + deps ──
echo ""