Spaces:
Running on Zero
Running on Zero
File size: 8,621 Bytes
bd97ee9 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 | """
Frox AI Morph 1.1 β Utilities
Small, dependency-light helpers shared across training, inference, and
scripts. Morph 1.0 had no equivalent module β every script rolled its
own seeding / device-detection logic, which is how subtle
non-reproducibility bugs creep in.
"""
from __future__ import annotations
import os
import random
import sys
import time
from contextlib import contextmanager
from typing import Optional
import numpy as np
import torch
# ββ Reproducibility βββββββββββββββββββββββββββββββββββββββββββββββ
def set_seed(seed: int = 1337, deterministic: bool = False):
"""
Seed every RNG Morph touches: Python, NumPy, PyTorch (CPU + all CUDA
devices). `deterministic=True` additionally forces cuDNN into
deterministic mode β slower, but bit-exact reruns for debugging.
"""
random.seed(seed)
np.random.seed(seed)
torch.manual_seed(seed)
if torch.cuda.is_available():
torch.cuda.manual_seed_all(seed)
if deterministic:
torch.backends.cudnn.deterministic = True
torch.backends.cudnn.benchmark = False
os.environ["CUBLAS_WORKSPACE_CONFIG"] = ":4096:8"
else:
torch.backends.cudnn.benchmark = True
print(f"β Seed set to {seed} (deterministic={deterministic})")
# ββ Device detection βββββββββββββββββββββββββββββββββββββββββββββββ
def get_device(prefer: Optional[str] = None) -> torch.device:
"""
Pick the best available device.
prefer: force "cuda" | "mps" | "cpu" if given and available.
"""
if prefer:
if prefer == "cuda" and torch.cuda.is_available():
return torch.device("cuda")
if prefer == "mps" and torch.backends.mps.is_available():
return torch.device("mps")
if prefer == "cpu":
return torch.device("cpu")
print(f"β Requested device '{prefer}' unavailable, auto-detecting instead")
if torch.cuda.is_available():
return torch.device("cuda")
if getattr(torch.backends, "mps", None) is not None and torch.backends.mps.is_available():
return torch.device("mps")
return torch.device("cpu")
def describe_device(device: torch.device) -> str:
"""Human-readable device description for logs."""
if device.type == "cuda":
idx = device.index or 0
name = torch.cuda.get_device_name(idx)
total_gb = torch.cuda.get_device_properties(idx).total_memory / (1024 ** 3)
cc = torch.cuda.get_device_capability(idx)
return f"{name} ({total_gb:.1f}GB, compute capability {cc[0]}.{cc[1]})"
if device.type == "mps":
return "Apple Silicon (MPS)"
return "CPU"
def recommended_dtype(device: torch.device) -> torch.dtype:
"""bfloat16 on Ampere+ (A100/H100/RTX 30xx+), float16 on older CUDA, float32 on CPU/MPS."""
if device.type != "cuda":
return torch.float32
cc = torch.cuda.get_device_capability(device)
return torch.bfloat16 if cc[0] >= 8 else torch.float16
# ββ Environment detection βββββββββββββββββββββββββββββββββββββββββ
def detect_environment() -> str:
"""Return 'kaggle' | 'colab' | 'local' for auto-configuring save paths."""
if os.path.exists("/kaggle/working"):
return "kaggle"
if os.path.exists("/content"):
return "colab"
return "local"
def require_checkpoint_dir(path: str) -> "Path":
"""
Confirm `path` is a checkpoint directory containing config.json before
any loader tries to open it, and fail with an actionable message instead
of a bare FileNotFoundError pointing at the config.json open() call.
The most common cause: a phase (pretrain/sft) was skipped or never
finished, so the checkpoint directory --from-checkpoint / --model points
at was never written.
"""
from pathlib import Path
p = Path(path)
if not p.exists():
parent = p.parent
siblings = sorted(d.name for d in parent.iterdir() if d.is_dir()) if parent.exists() else []
hint = (
f"\n Checkpoints found in {parent}: {', '.join(siblings)}"
if siblings else
f"\n {parent} doesn't exist yet or is empty β no training phase has saved a checkpoint there."
)
raise FileNotFoundError(
f"Checkpoint directory not found: {p}{hint}\n"
f" If you skipped an earlier phase (e.g. pretrain), either run that phase first "
f"or drop --from-checkpoint / --model to start from fresh weights."
)
if not (p / "config.json").exists():
contents = sorted(f.name for f in p.iterdir())
raise FileNotFoundError(
f"{p} exists but has no config.json (found: {', '.join(contents) or 'nothing'}).\n"
f" This usually means the save that was supposed to write here didn't complete β "
f"check the log for the run that was meant to produce this checkpoint."
)
return p
# ββ Timing ββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
@contextmanager
def timer(label: str = "block"):
"""Context manager that prints elapsed wall-clock time on exit."""
t0 = time.perf_counter()
yield
elapsed = time.perf_counter() - t0
print(f"β± {label}: {elapsed:.2f}s")
# ββ Logging setup βββββββββββββββββββββββββββββββββββββββββββββββββ
def setup_logging(level: str = "INFO"):
"""Configure structlog if available, else fall back to stdlib logging."""
try:
import structlog
structlog.configure(
processors=[
structlog.processors.TimeStamper(fmt="iso"),
structlog.processors.add_log_level,
structlog.dev.ConsoleRenderer(),
],
)
return structlog.get_logger()
except ImportError:
import logging
logging.basicConfig(
level=getattr(logging, level),
format="%(asctime)s [%(levelname)s] %(message)s",
stream=sys.stdout,
)
return logging.getLogger("morph")
# ββ Misc ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
def human_readable_bytes(n: float) -> str:
for unit in ("B", "KB", "MB", "GB", "TB"):
if abs(n) < 1024.0:
return f"{n:.1f}{unit}"
n /= 1024.0
return f"{n:.1f}PB"
# ββ Model family loading βββββββββββββββββββββββββββββββββββββββββββ
FAMILY_TIERS = ("nano", "mini", "classic", "pro", "code")
_LEGACY_SCALE_ALIASES = {"1.5b": "mini", "1b": "mini", "3b": "classic", "8b": "code", "7b": "code"}
def load_family_config(name: str):
"""
Load exactly one Morph model-family tier config, on demand.
Uses importlib so requesting "nano" never imports mini/classic/
pro/code's modules β each tier stays independently loadable, which
matters if you're shipping only one tier's files in a deployment.
"""
import importlib
key = name.strip().lower()
key = _LEGACY_SCALE_ALIASES.get(key, key)
if key not in FAMILY_TIERS:
raise ValueError(
f"Unknown Morph family '{name}'. Choose from: {', '.join(FAMILY_TIERS)} "
f"(legacy aliases also work: {', '.join(_LEGACY_SCALE_ALIASES)})"
)
module = importlib.import_module(f"config.family.{key}")
return module.get_config(), module
def print_banner(version: str = "1.1.0"):
print(r"""
βββββββββββββββ βββββββ βββ βββ
βββββββββββββββββββββββββββββββββ
ββββββ βββββββββββ βββ ββββββ
ββββββ βββββββββββ βββ ββββββ
βββ βββ ββββββββββββββββ βββ
βββ βββ βββ βββββββ βββ βββ
""")
print(f" Frox AI β Morph {version}\n")
|