mirror of
https://github.com/elder-plinius/OBLITERATUS.git
synced 2026-08-18 00:47:23 +02:00
Retain the unsuperseded macOS RAM-detection contribution from PR #13 while making every optional probe fail closed. The shared MPS device, loader, cache, and dtype work is already present on current main and is intentionally not duplicated.
347 lines
12 KiB
Python
347 lines
12 KiB
Python
"""Local UI launcher for OBLITERATUS — beautiful terminal experience + Gradio web UI.
|
|
|
|
Usage:
|
|
obliteratus ui # launch with defaults
|
|
obliteratus ui --port 8080 --share # public link on custom port
|
|
obliteratus ui --no-browser # don't auto-open browser
|
|
obliteratus ui --auth user:pass # basic auth
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import os
|
|
import pathlib
|
|
import platform
|
|
import shutil
|
|
import sys
|
|
import tempfile
|
|
import time
|
|
|
|
from rich.console import Console
|
|
from rich.panel import Panel
|
|
from rich.table import Table
|
|
|
|
console = Console()
|
|
|
|
# ── ASCII banner ────────────────────────────────────────────────────────────
|
|
|
|
_BANNER = r"""
|
|
[bold green]
|
|
░▒█▀▀▀█ ░▒█▀▀▄ ░▒█░░░ ▀█▀ ▀▀█▀▀ ░▒█▀▀▀ ░▒█▀▀█ ▒█▀▀█ ▀▀█▀▀ ░▒█░░▒█ ░▒█▀▀▀█
|
|
░▒█░░▒█ ░▒█▀▀▄ ░▒█░░░ ░█░ ░░█░░ ░▒█▀▀▀ ░▒█▄▄▀ ▒█▄▄█ ░░█░░ ░▒█░░▒█ ░░▀▀▀▄▄
|
|
░▒█▄▄▄█ ░▒█▄▄▀ ░▒█▄▄█ ▄█▄ ░░▀░░ ░▒█▄▄▄ ░▒█░▒█ ▒█░▒█ ░░▀░░ ░░▒█▄▄█ ░▒█▄▄▄█
|
|
[/bold green]
|
|
[dim] ════════════════════════════════════════════════════════════════════[/dim]
|
|
[bold white] MASTER ABLATION SUITE — LOCAL EDITION[/bold white] [dim]//[/dim] [bold green]Free the mind.[/bold green]
|
|
[dim] ════════════════════════════════════════════════════════════════════[/dim]
|
|
"""
|
|
|
|
_ASCII_BANNER = """
|
|
[bold green]OBLITERATUS[/bold green]
|
|
[dim]====================================================================[/dim]
|
|
[bold white]MASTER ABLATION SUITE - LOCAL EDITION[/bold white] [dim]//[/dim] [bold green]Free the mind.[/bold green]
|
|
[dim]====================================================================[/dim]
|
|
"""
|
|
|
|
|
|
def _banner_for_console() -> str:
|
|
"""Return a startup banner supported by the active stdout encoding."""
|
|
encoding = getattr(sys.stdout, "encoding", None) or "utf-8"
|
|
try:
|
|
_BANNER.encode(encoding)
|
|
except (LookupError, UnicodeEncodeError):
|
|
return _ASCII_BANNER
|
|
return _BANNER
|
|
|
|
|
|
def _detect_gpu() -> list[dict]:
|
|
"""Detect available GPUs and return info dicts."""
|
|
gpus = []
|
|
try:
|
|
import torch
|
|
|
|
if torch.cuda.is_available():
|
|
for i in range(torch.cuda.device_count()):
|
|
props = torch.cuda.get_device_properties(i)
|
|
gpus.append(
|
|
{
|
|
"index": i,
|
|
"name": props.name,
|
|
"vram_gb": round(props.total_memory / 1024**3, 1),
|
|
"compute": f"{props.major}.{props.minor}",
|
|
}
|
|
)
|
|
elif hasattr(torch.backends, "mps") and torch.backends.mps.is_available():
|
|
gpus.append(
|
|
{
|
|
"index": 0,
|
|
"name": "Apple Silicon (MPS)",
|
|
"vram_gb": 0, # shared memory
|
|
"compute": "mps",
|
|
}
|
|
)
|
|
except ImportError:
|
|
pass
|
|
return gpus
|
|
|
|
|
|
def _get_disk_free_gb(path: str = "/") -> float:
|
|
"""Return free disk space in GB."""
|
|
try:
|
|
usage = shutil.disk_usage(path)
|
|
return round(usage.free / 1024**3, 1)
|
|
except Exception:
|
|
return 0.0
|
|
|
|
|
|
def _get_ram_gb() -> float:
|
|
"""Return total system RAM in GB."""
|
|
try:
|
|
import psutil
|
|
|
|
return round(psutil.virtual_memory().total / 1024**3, 1)
|
|
except (AttributeError, ImportError, OSError, RuntimeError, ValueError):
|
|
pass
|
|
|
|
# Fallback: read from /proc/meminfo on Linux.
|
|
try:
|
|
with open("/proc/meminfo") as f:
|
|
for line in f:
|
|
if line.startswith("MemTotal:"):
|
|
kb = int(line.split()[1])
|
|
return round(kb / 1024**2, 1)
|
|
except (OSError, ValueError):
|
|
pass
|
|
|
|
# macOS and other POSIX systems generally expose physical RAM via sysconf.
|
|
try:
|
|
pages = os.sysconf("SC_PHYS_PAGES")
|
|
page_size = os.sysconf("SC_PAGE_SIZE")
|
|
if pages > 0 and page_size > 0:
|
|
return round(pages * page_size / 1024**3, 1)
|
|
except (AttributeError, OSError, ValueError):
|
|
pass
|
|
return 0.0
|
|
|
|
|
|
def _print_system_info(gpus: list[dict]) -> None:
|
|
"""Print a beautiful system info panel."""
|
|
table = Table(show_header=False, border_style="green", expand=True, padding=(0, 2))
|
|
table.add_column("Key", style="dim", width=18)
|
|
table.add_column("Value", style="cyan")
|
|
|
|
table.add_row("Platform", f"{platform.system()} {platform.release()}")
|
|
table.add_row("Python", f"{platform.python_version()}")
|
|
|
|
ram = _get_ram_gb()
|
|
if ram > 0:
|
|
table.add_row("System RAM", f"{ram} GB")
|
|
|
|
temp_dir = tempfile.gettempdir()
|
|
disk = _get_disk_free_gb(temp_dir)
|
|
if disk > 0:
|
|
table.add_row(f"Disk Free ({temp_dir})", f"{disk} GB")
|
|
|
|
# PyTorch version
|
|
try:
|
|
import torch
|
|
|
|
table.add_row("PyTorch", torch.__version__)
|
|
except ImportError:
|
|
table.add_row("PyTorch", "[red]NOT INSTALLED[/red]")
|
|
|
|
# Transformers version
|
|
try:
|
|
import transformers
|
|
|
|
table.add_row("Transformers", transformers.__version__)
|
|
except ImportError:
|
|
table.add_row("Transformers", "[red]NOT INSTALLED[/red]")
|
|
|
|
# Gradio version
|
|
try:
|
|
import gradio
|
|
|
|
table.add_row("Gradio", gradio.__version__)
|
|
except ImportError:
|
|
table.add_row("Gradio", "[red]NOT INSTALLED[/red]")
|
|
|
|
# GPU info
|
|
if gpus:
|
|
for gpu in gpus:
|
|
vram = f"{gpu['vram_gb']} GB" if gpu["vram_gb"] > 0 else "shared"
|
|
table.add_row(
|
|
f"GPU {gpu['index']}",
|
|
f"[bold green]{gpu['name']}[/bold green] ({vram}, compute {gpu['compute']})",
|
|
)
|
|
else:
|
|
table.add_row("GPU", "[yellow]None detected — CPU mode[/yellow]")
|
|
|
|
# HF Token
|
|
hf_token = os.environ.get("HF_TOKEN", "")
|
|
if hf_token:
|
|
table.add_row("HF Token", "[green]set[/green]")
|
|
else:
|
|
table.add_row("HF Token", "[dim]not set (gated models won't work)[/dim]")
|
|
|
|
console.print(Panel(table, title="[bold green]System Info[/bold green]", border_style="green"))
|
|
|
|
|
|
def _compute_tier(gpus: list[dict]) -> str:
|
|
"""Determine the compute tier based on available hardware."""
|
|
if not gpus:
|
|
return "cpu"
|
|
max_vram = max(g["vram_gb"] for g in gpus)
|
|
if max_vram >= 80:
|
|
return "frontier"
|
|
elif max_vram >= 24:
|
|
return "large"
|
|
elif max_vram >= 8:
|
|
return "medium"
|
|
elif max_vram > 0:
|
|
return "small"
|
|
else:
|
|
return "mps" # Apple Silicon
|
|
|
|
|
|
def _print_recommendations(tier: str) -> None:
|
|
"""Print model recommendations based on detected hardware."""
|
|
recs = {
|
|
"cpu": [
|
|
("distilbert/distilgpt2", "Tiny — 82M params, runs on anything"),
|
|
("TinyLlama/TinyLlama-1.1B-Chat-v1.0", "Small — 1.1B params, workable on CPU"),
|
|
],
|
|
"small": [
|
|
("TinyLlama/TinyLlama-1.1B-Chat-v1.0", "1.1B — fits in 4GB"),
|
|
("Qwen/Qwen2.5-0.5B-Instruct", "0.5B — very fast on small GPUs"),
|
|
("Qwen/Qwen2.5-3B-Instruct", "3B — good quality, ~6GB VRAM"),
|
|
],
|
|
"mps": [
|
|
("TinyLlama/TinyLlama-1.1B-Chat-v1.0", "1.1B — fast on Apple Silicon"),
|
|
("Qwen/Qwen2.5-3B-Instruct", "3B — good with shared memory"),
|
|
("meta-llama/Llama-3.2-3B-Instruct", "3B — great quality on MPS"),
|
|
],
|
|
"medium": [
|
|
("Qwen/Qwen2.5-7B-Instruct", "7B — excellent quality at 16GB"),
|
|
("meta-llama/Llama-3.1-8B-Instruct", "8B — the standard benchmark"),
|
|
("mistralai/Mistral-7B-Instruct-v0.3", "7B — fast and capable"),
|
|
],
|
|
"large": [
|
|
("openai/gpt-oss-20b", "20B MoE — flagship, try surgical/nuclear"),
|
|
("meta-llama/Llama-3.1-70B-Instruct", "70B — with 4bit quant"),
|
|
("Qwen/Qwen3-30B-A3B", "30B MoE — only 3B active"),
|
|
],
|
|
"frontier": [
|
|
("deepseek-ai/DeepSeek-V3", "671B MoE — the frontier"),
|
|
("meta-llama/Llama-3.1-70B-Instruct", "70B — full precision"),
|
|
("Qwen/Qwen3.5-397B-A17B", "397B MoE — massive"),
|
|
],
|
|
}
|
|
|
|
tier_recs = recs.get(tier, recs["cpu"])
|
|
|
|
table = Table(
|
|
title=f"Recommended Models for Your Hardware ({tier.upper()})",
|
|
border_style="green",
|
|
)
|
|
table.add_column("Model", style="cyan", min_width=40)
|
|
table.add_column("Notes", style="dim")
|
|
|
|
for model, notes in tier_recs:
|
|
table.add_row(model, notes)
|
|
|
|
console.print(table)
|
|
|
|
|
|
def _print_launch_info(
|
|
host: str,
|
|
port: int,
|
|
share: bool,
|
|
auth: tuple[str, str] | None,
|
|
) -> None:
|
|
"""Print launch configuration."""
|
|
parts = []
|
|
url = f"http://{'localhost' if host == '0.0.0.0' else host}:{port}"
|
|
parts.append(f"[bold green]Local URL:[/bold green] {url}")
|
|
if host == "0.0.0.0":
|
|
parts.append(f"[bold green]Network URL:[/bold green] http://<your-ip>:{port}")
|
|
if share:
|
|
parts.append("[bold green]Share link:[/bold green] [yellow]generating...[/yellow]")
|
|
if auth:
|
|
parts.append(f"[bold green]Auth:[/bold green] {auth[0]}:{'*' * len(auth[1])}")
|
|
parts.append("")
|
|
parts.append("[dim]Press Ctrl+C to stop the server[/dim]")
|
|
|
|
console.print(
|
|
Panel(
|
|
"\n".join(parts),
|
|
title="[bold green]Launching OBLITERATUS UI[/bold green]",
|
|
border_style="green",
|
|
)
|
|
)
|
|
|
|
|
|
def launch_local_ui(
|
|
host: str = "0.0.0.0",
|
|
port: int = 7860,
|
|
share: bool = False,
|
|
open_browser: bool = True,
|
|
auth: tuple[str, str] | None = None,
|
|
quiet: bool = False,
|
|
) -> None:
|
|
"""Launch the OBLITERATUS Gradio UI with a beautiful local experience.
|
|
|
|
This is the main entry point for ``obliteratus ui``.
|
|
"""
|
|
# ── Beautiful startup ──────────────────────────────────────────────
|
|
if not quiet:
|
|
console.print(_banner_for_console())
|
|
|
|
gpus = _detect_gpu()
|
|
_print_system_info(gpus)
|
|
console.print()
|
|
|
|
tier = _compute_tier(gpus)
|
|
_print_recommendations(tier)
|
|
console.print()
|
|
|
|
_print_launch_info(host, port, share, auth)
|
|
console.print()
|
|
|
|
# ── Check dependencies ─────────────────────────────────────────────
|
|
try:
|
|
import gradio # noqa: F401
|
|
except ImportError:
|
|
console.print(
|
|
"[bold red]Error:[/bold red] Gradio is not installed.\n"
|
|
"Install it with: [cyan]pip install -e '.[spaces]'[/cyan]\n"
|
|
"Or: [cyan]pip install gradio>=5.0[/cyan]"
|
|
)
|
|
sys.exit(1)
|
|
|
|
# ── Import and launch the app ──────────────────────────────────────
|
|
console.print("[dim]Loading OBLITERATUS UI (this may take a moment on first run)...[/dim]")
|
|
start = time.time()
|
|
|
|
# app.py lives at the project root, one level above this package.
|
|
# When installed via pip the root isn't on sys.path, so add it.
|
|
_project_root = str(pathlib.Path(__file__).resolve().parent.parent)
|
|
if _project_root not in sys.path:
|
|
sys.path.insert(0, _project_root)
|
|
|
|
from app import launch as app_launch
|
|
|
|
elapsed = time.time() - start
|
|
if not quiet:
|
|
console.print(f"[green]UI loaded in {elapsed:.1f}s[/green]")
|
|
console.print()
|
|
|
|
app_launch(
|
|
server_name=host,
|
|
server_port=port,
|
|
share=share,
|
|
inbrowser=open_browser,
|
|
auth=auth,
|
|
quiet=quiet,
|
|
)
|