Merge branch 'main' into pip
This commit is contained in:
commit
da81c94510
6 changed files with 183 additions and 32 deletions
66
install.ps1
66
install.ps1
|
|
@ -44,14 +44,45 @@ function Install-UnslothStudio {
|
|||
# Uses try-catch + stderr redirection so that App Execution Alias stubs
|
||||
# (WindowsApps) and other non-functional executables are probed safely
|
||||
# without triggering $ErrorActionPreference = "Stop".
|
||||
#
|
||||
# Skips Anaconda/Miniconda Python: conda-bundled CPython ships modified
|
||||
# DLL search paths that break torch's c10.dll loading on Windows.
|
||||
# Standalone CPython (python.org, winget, uv) does not have this issue.
|
||||
#
|
||||
# NOTE: A venv created from conda Python inherits conda's base_prefix
|
||||
# even if the venv path does not contain "conda". We check both the
|
||||
# executable path AND sys.base_prefix to catch this.
|
||||
$script:CondaSkipPattern = '(?i)(conda|miniconda|anaconda|miniforge|mambaforge)'
|
||||
|
||||
function Test-IsCondaPython {
|
||||
param([string]$Exe)
|
||||
if ($Exe -match $script:CondaSkipPattern) { return $true }
|
||||
try {
|
||||
$basePrefix = (& $Exe -c "import sys; print(sys.base_prefix)" 2>$null | Out-String).Trim()
|
||||
if ($basePrefix -match $script:CondaSkipPattern) { return $true }
|
||||
} catch { }
|
||||
return $false
|
||||
}
|
||||
|
||||
# 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 {
|
||||
# Try the Python Launcher first (most reliable on Windows)
|
||||
# py.exe resolves to the standard CPython install, not conda.
|
||||
$pyLauncher = Get-Command py -CommandType Application -ErrorAction SilentlyContinue
|
||||
if ($pyLauncher) {
|
||||
if ($pyLauncher -and $pyLauncher.Source -notmatch $script:CondaSkipPattern) {
|
||||
foreach ($minor in @("3.13", "3.12", "3.11")) {
|
||||
try {
|
||||
$out = & $pyLauncher.Source "-$minor" --version 2>&1 | Out-String
|
||||
if ($out -match "Python (3\.1[1-3])\.\d+") { return $Matches[1] }
|
||||
if ($out -match "Python (3\.1[1-3])\.\d+") {
|
||||
$ver = $Matches[1]
|
||||
# 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)) {
|
||||
return @{ Version = $ver; Path = $resolvedExe }
|
||||
}
|
||||
}
|
||||
} catch {}
|
||||
}
|
||||
}
|
||||
|
|
@ -61,25 +92,30 @@ function Install-UnslothStudio {
|
|||
# and can open the Microsoft Store as a side effect. Legitimate Store
|
||||
# Python is already detected via the py launcher above (Store packages
|
||||
# include py since Python 3.11).
|
||||
# Skip Anaconda/Miniconda: check both path and sys.base_prefix.
|
||||
foreach ($name in @("python3", "python")) {
|
||||
foreach ($cmd in @(Get-Command $name -All -ErrorAction SilentlyContinue)) {
|
||||
if (-not $cmd.Source) { continue }
|
||||
if ($cmd.Source -like "*\WindowsApps\*") { continue }
|
||||
if (Test-IsCondaPython $cmd.Source) { continue }
|
||||
try {
|
||||
$out = & $cmd.Source --version 2>&1 | Out-String
|
||||
if ($out -match "Python (3\.1[1-3])\.\d+") { return $Matches[1] }
|
||||
if ($out -match "Python (3\.1[1-3])\.\d+") {
|
||||
return @{ Version = $Matches[1]; Path = $cmd.Source }
|
||||
}
|
||||
} catch {}
|
||||
}
|
||||
}
|
||||
return ""
|
||||
return $null
|
||||
}
|
||||
|
||||
# ── Install Python if no compatible version (3.11-3.13) found ──
|
||||
$DetectedPythonVersion = Find-CompatiblePython
|
||||
if ($DetectedPythonVersion) {
|
||||
Write-Host "==> Python already installed: Python $DetectedPythonVersion"
|
||||
# Find-CompatiblePython returns @{ Version = "3.13"; Path = "C:\...\python.exe" } or $null.
|
||||
$DetectedPython = Find-CompatiblePython
|
||||
if ($DetectedPython) {
|
||||
Write-Host "==> Python already installed: Python $($DetectedPython.Version)"
|
||||
}
|
||||
if (-not $DetectedPythonVersion) {
|
||||
if (-not $DetectedPython) {
|
||||
Write-Host "==> Installing Python ${PythonVersion}..."
|
||||
$pythonPackageId = "Python.Python.$PythonVersion"
|
||||
# Temporarily lower ErrorActionPreference so that winget stderr
|
||||
|
|
@ -95,9 +131,9 @@ function Install-UnslothStudio {
|
|||
Refresh-SessionPath
|
||||
|
||||
# Re-detect after install (PATH may have changed)
|
||||
$DetectedPythonVersion = Find-CompatiblePython
|
||||
$DetectedPython = Find-CompatiblePython
|
||||
|
||||
if (-not $DetectedPythonVersion) {
|
||||
if (-not $DetectedPython) {
|
||||
# Python still not functional after winget -- force reinstall.
|
||||
# This handles both real failures AND "already installed" codes where
|
||||
# winget thinks Python is present but it's not actually on PATH
|
||||
|
|
@ -110,10 +146,10 @@ function Install-UnslothStudio {
|
|||
} catch { $wingetExit = 1 }
|
||||
$ErrorActionPreference = $prevEAP
|
||||
Refresh-SessionPath
|
||||
$DetectedPythonVersion = Find-CompatiblePython
|
||||
$DetectedPython = Find-CompatiblePython
|
||||
}
|
||||
|
||||
if (-not $DetectedPythonVersion) {
|
||||
if (-not $DetectedPython) {
|
||||
Write-Host "[ERROR] Python installation failed (exit code $wingetExit)" -ForegroundColor Red
|
||||
Write-Host " Please install Python $PythonVersion manually from https://www.python.org/downloads/" -ForegroundColor Yellow
|
||||
Write-Host " Make sure to check 'Add Python to PATH' during installation." -ForegroundColor Yellow
|
||||
|
|
@ -145,11 +181,13 @@ function Install-UnslothStudio {
|
|||
}
|
||||
|
||||
# ── Create venv (skip if it already exists and has a valid interpreter) ──
|
||||
# Pass the resolved executable path to uv so it does not re-resolve
|
||||
# a version string back to a conda interpreter.
|
||||
$VenvPython = Join-Path $VenvName "Scripts\python.exe"
|
||||
if (-not (Test-Path $VenvPython)) {
|
||||
if (Test-Path $VenvName) { Remove-Item -Recurse -Force $VenvName }
|
||||
Write-Host "==> Creating Python ${DetectedPythonVersion} virtual environment (${VenvName})..."
|
||||
uv venv $VenvName --python $DetectedPythonVersion
|
||||
Write-Host "==> Creating Python $($DetectedPython.Version) virtual environment (${VenvName})..."
|
||||
uv venv $VenvName --python "$($DetectedPython.Path)"
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
Write-Host "[ERROR] Failed to create virtual environment (exit code $LASTEXITCODE)" -ForegroundColor Red
|
||||
return
|
||||
|
|
|
|||
|
|
@ -15,11 +15,19 @@ from typing import Any
|
|||
from uuid import uuid4
|
||||
|
||||
from fastapi import APIRouter, HTTPException, UploadFile, File as FastAPIFile, Form
|
||||
from data_designer_unstructured_seed.chunking import (
|
||||
build_unstructured_preview_rows,
|
||||
normalize_unstructured_text,
|
||||
resolve_chunking,
|
||||
)
|
||||
|
||||
try:
|
||||
from data_designer_unstructured_seed.chunking import (
|
||||
build_multi_file_preview_rows,
|
||||
build_unstructured_preview_rows,
|
||||
normalize_unstructured_text,
|
||||
resolve_chunking,
|
||||
)
|
||||
except ImportError:
|
||||
build_multi_file_preview_rows = None
|
||||
build_unstructured_preview_rows = None
|
||||
normalize_unstructured_text = None
|
||||
resolve_chunking = None
|
||||
from core.data_recipe.jsonable import to_preview_jsonable
|
||||
from utils.paths import ensure_dir, seed_uploads_root, unstructured_uploads_root
|
||||
|
||||
|
|
@ -232,6 +240,11 @@ def _read_preview_rows_from_unstructured_file(
|
|||
chunk_size: int | None,
|
||||
chunk_overlap: int | None,
|
||||
) -> list[dict[str, Any]]:
|
||||
if resolve_chunking is None or build_unstructured_preview_rows is None:
|
||||
raise HTTPException(
|
||||
500,
|
||||
"Unstructured seed support not available (missing data_designer_unstructured_seed)",
|
||||
)
|
||||
size, overlap = resolve_chunking(chunk_size, chunk_overlap)
|
||||
try:
|
||||
rows = build_unstructured_preview_rows(
|
||||
|
|
@ -256,7 +269,11 @@ def _read_preview_rows_from_multi_files(
|
|||
chunk_size: int | None,
|
||||
chunk_overlap: int | None,
|
||||
) -> list[dict[str, str]]:
|
||||
from data_designer_unstructured_seed.chunking import build_multi_file_preview_rows
|
||||
if build_multi_file_preview_rows is None:
|
||||
raise HTTPException(
|
||||
500,
|
||||
"Unstructured seed support not available (missing data_designer_unstructured_seed)",
|
||||
)
|
||||
|
||||
_validate_safe_id(block_id, "block_id")
|
||||
block_dir = UNSTRUCTURED_UPLOAD_ROOT / block_id
|
||||
|
|
@ -382,6 +399,8 @@ def _extract_text_from_file(file_path: Path, ext: str) -> str:
|
|||
else:
|
||||
raise ValueError(f"Unsupported file type: {ext}")
|
||||
|
||||
if normalize_unstructured_text is None:
|
||||
return raw
|
||||
return normalize_unstructured_text(raw)
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -256,6 +256,14 @@ def run_server(
|
|||
if __name__ == "__main__":
|
||||
import argparse
|
||||
import signal
|
||||
import traceback
|
||||
|
||||
# Ensure stderr can handle Unicode on Windows (tracebacks with non-ASCII paths)
|
||||
if sys.platform == "win32" and hasattr(sys.stderr, "reconfigure"):
|
||||
try:
|
||||
sys.stderr.reconfigure(encoding = "utf-8", errors = "replace")
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
parser = argparse.ArgumentParser(description = "Run Unsloth UI Backend server")
|
||||
parser.add_argument("--host", default = "0.0.0.0", help = "Host to bind to")
|
||||
|
|
@ -273,7 +281,21 @@ if __name__ == "__main__":
|
|||
kwargs = dict(host = args.host, port = args.port, silent = args.silent)
|
||||
if args.frontend is not None:
|
||||
kwargs["frontend_path"] = Path(args.frontend)
|
||||
run_server(**kwargs)
|
||||
|
||||
try:
|
||||
run_server(**kwargs)
|
||||
except Exception:
|
||||
sys.stderr.write("\n")
|
||||
sys.stderr.write("=" * 60 + "\n")
|
||||
sys.stderr.write("ERROR: Unsloth Studio failed to start.\n")
|
||||
sys.stderr.write("=" * 60 + "\n")
|
||||
traceback.print_exc(file = sys.stderr)
|
||||
sys.stderr.write("\n")
|
||||
sys.stderr.write(
|
||||
"If a package is missing, try re-running: unsloth studio setup\n"
|
||||
)
|
||||
sys.stderr.flush()
|
||||
sys.exit(1)
|
||||
|
||||
# ── Signal handler — ensures subprocess cleanup on Ctrl+C ────
|
||||
def _signal_handler(signum, frame):
|
||||
|
|
|
|||
|
|
@ -946,23 +946,85 @@ if (Test-Path $OxcValidatorDir) {
|
|||
Write-Host ""
|
||||
Write-Host "Setting up Python environment..." -ForegroundColor Cyan
|
||||
|
||||
# Find Python
|
||||
# Find Python -- skip Anaconda/Miniconda distributions.
|
||||
# Conda-bundled CPython ships modified DLL search paths that break
|
||||
# torch's c10.dll loading on Windows. Standalone CPython (python.org,
|
||||
# winget, uv) does not have this issue.
|
||||
# Uses Get-Command -All to look past conda entries that shadow a valid
|
||||
# standalone Python further down PATH, and probes py.exe (the Python
|
||||
# Launcher) which reliably finds python.org installs.
|
||||
#
|
||||
# NOTE: A venv created from conda Python inherits conda's base_prefix
|
||||
# even though the venv path itself does not contain "conda". We check
|
||||
# both the executable path AND sys.base_prefix to catch this case.
|
||||
$CondaSkipPattern = '(?i)(conda|miniconda|anaconda|miniforge|mambaforge)'
|
||||
$PythonCmd = $null
|
||||
foreach ($candidate in @("python3.13", "python3.12", "python3.11", "python3", "python")) {
|
||||
|
||||
# Helper: check if a Python executable is conda-based by inspecting
|
||||
# both the path and sys.base_prefix (catches venvs created from conda).
|
||||
function Test-IsConda {
|
||||
param([string]$Exe)
|
||||
if ($Exe -match $CondaSkipPattern) { return $true }
|
||||
try {
|
||||
$ver = & $candidate --version 2>&1
|
||||
if ($ver -match 'Python 3\.(\d+)') {
|
||||
$minor = [int]$Matches[1]
|
||||
if ($minor -ge 11 -and $minor -le 13) {
|
||||
$PythonCmd = $candidate
|
||||
break
|
||||
}
|
||||
}
|
||||
$basePrefix = (& $Exe -c "import sys; print(sys.base_prefix)" 2>$null | Out-String).Trim()
|
||||
if ($basePrefix -match $CondaSkipPattern) { return $true }
|
||||
} catch { }
|
||||
return $false
|
||||
}
|
||||
|
||||
# 1. Try the Python Launcher (py.exe) first -- most reliable on Windows.
|
||||
# py.exe is installed by python.org and resolves to standalone CPython.
|
||||
$pyLauncher = Get-Command py -CommandType Application -ErrorAction SilentlyContinue
|
||||
if ($pyLauncher -and $pyLauncher.Source -notmatch $CondaSkipPattern) {
|
||||
foreach ($minor in @("3.13", "3.12", "3.11")) {
|
||||
try {
|
||||
$out = & $pyLauncher.Source "-$minor" --version 2>&1 | Out-String
|
||||
if ($out -match 'Python 3\.(\d+)') {
|
||||
$pyMinor = [int]$Matches[1]
|
||||
if ($pyMinor -ge 11 -and $pyMinor -le 13) {
|
||||
# Resolve the actual executable path so venv creation
|
||||
# does not re-resolve back to a conda interpreter.
|
||||
$resolvedExe = (& $pyLauncher.Source "-$minor" -c "import sys; print(sys.executable)" 2>$null | Out-String).Trim()
|
||||
if ($resolvedExe -and (Test-Path $resolvedExe) -and -not (Test-IsConda $resolvedExe)) {
|
||||
$PythonCmd = $resolvedExe
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch { }
|
||||
}
|
||||
}
|
||||
|
||||
# 2. Fall back to scanning python3.x / python3 / python on PATH.
|
||||
# Use Get-Command -All to look past conda entries.
|
||||
if (-not $PythonCmd) {
|
||||
foreach ($candidate in @("python3.13", "python3.12", "python3.11", "python3", "python")) {
|
||||
foreach ($cmdInfo in @(Get-Command $candidate -All -ErrorAction SilentlyContinue)) {
|
||||
try {
|
||||
if (-not $cmdInfo.Source) { continue }
|
||||
if ($cmdInfo.Source -like "*\WindowsApps\*") { continue }
|
||||
if (Test-IsConda $cmdInfo.Source) {
|
||||
Write-Host " [SKIP] $($cmdInfo.Source) (conda Python breaks torch DLL loading)" -ForegroundColor Yellow
|
||||
continue
|
||||
}
|
||||
$ver = & $cmdInfo.Source --version 2>&1
|
||||
if ($ver -match 'Python 3\.(\d+)') {
|
||||
$minor = [int]$Matches[1]
|
||||
if ($minor -ge 11 -and $minor -le 13) {
|
||||
$PythonCmd = $cmdInfo.Source
|
||||
break
|
||||
}
|
||||
}
|
||||
} catch { }
|
||||
}
|
||||
if ($PythonCmd) { break }
|
||||
}
|
||||
}
|
||||
|
||||
if (-not $PythonCmd) {
|
||||
Write-Host "[ERROR] No Python 3.11-3.13 found." -ForegroundColor Red
|
||||
Write-Host "[ERROR] No standalone Python 3.11-3.13 found (conda Python is not supported)." -ForegroundColor Red
|
||||
Write-Host " Install Python from https://python.org/downloads/ or via:" -ForegroundColor Yellow
|
||||
Write-Host " winget install -e --id Python.Python.3.12" -ForegroundColor Yellow
|
||||
exit 1
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@
|
|||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
__version__ = "2026.3.9"
|
||||
__version__ = "2026.3.10"
|
||||
|
||||
__all__ = [
|
||||
"SUPPORTS_BFLOAT16",
|
||||
|
|
|
|||
|
|
@ -118,6 +118,16 @@ def studio_default(
|
|||
except KeyboardInterrupt:
|
||||
# Child has its own signal handler — let it finish
|
||||
rc = proc.wait()
|
||||
if rc != 0:
|
||||
typer.echo(
|
||||
f"\nError: Studio server exited unexpectedly (code {rc}).",
|
||||
err = True,
|
||||
)
|
||||
typer.echo(
|
||||
"Check the error above. If a package is missing, "
|
||||
"re-run: unsloth studio setup",
|
||||
err = True,
|
||||
)
|
||||
raise typer.Exit(rc)
|
||||
else:
|
||||
os.execvp(str(studio_python), args)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue