Merge branch 'main' into pip

This commit is contained in:
Daniel Han 2026-03-22 08:23:44 -07:00
commit da81c94510
6 changed files with 183 additions and 32 deletions

View file

@ -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

View file

@ -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)

View file

@ -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):

View file

@ -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
}

View file

@ -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",

View file

@ -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)