Spaces:
Running on Zero
Running on Zero
| """Checkpoint discovery + loading, with the architecture guard wired in. | |
| The studio must never silently generate gibberish from a checkpoint whose text-conditioner doesn't fit | |
| the code (the failure that cost a whole debugging session). :func:`load_model` therefore runs | |
| ``diffu_page.render._assert_conditioner_loaded`` right after loading and re-raises any mismatch as | |
| :class:`CheckpointMismatch`, which the UI/API turn into a clear message instead of confident nonsense. | |
| """ | |
| from __future__ import annotations | |
| from pathlib import Path | |
| from typing import TYPE_CHECKING | |
| from pydantic import BaseModel, ConfigDict | |
| from diffu_studio.device import Device, torch_dtype | |
| if TYPE_CHECKING: | |
| from diffu.config import Config | |
| from diffu.model import Diffu | |
| class ArchConfig(BaseModel): | |
| """The architecture flags a checkpoint was TRAINED with — must match or the conditioner loads wrong. | |
| Defaults track the current best run (``exp_sd35_fast``: ``--glyph-line --no-style-tokens``). ``qk_norm`` | |
| and ``use_unifont`` are NOT here: they are auto-detected per checkpoint from its weights at load time. | |
| """ | |
| model_config = ConfigDict(frozen=True) | |
| glyph_line: bool = True | |
| style_in_context: bool = False | |
| glyph_concat: bool = False | |
| line_height: int = 64 # crop height the checkpoint was trained at (64px runs; 128 for exp_sd35_128) | |
| class Checkpoint(BaseModel): | |
| model_config = ConfigDict(frozen=True) | |
| label: str # "<run>/<step>" | |
| path: str | |
| step: int | |
| class LoadedModel(BaseModel): | |
| """A built + loaded model ready to sample, plus the config it was built with and a short arch tag.""" | |
| model_config = ConfigDict(arbitrary_types_allowed=True, frozen=True) | |
| model: Diffu | |
| cfg: Config | |
| path: str | |
| tag: str # "SD3.5" / "SD3.0" | |
| class CheckpointMismatch(RuntimeError): | |
| """A checkpoint's architecture doesn't fit this code — surfaced to the user, never silently ignored.""" | |
| def infer_qk_norm(ckpt: str) -> str | None: | |
| """``"rms_norm"`` if the checkpoint was trained with SD3.5 qk-norm (has ``...attn.norm_q.*``), else None.""" | |
| try: | |
| from safetensors import safe_open | |
| with safe_open(ckpt, framework="pt") as handle: | |
| return "rms_norm" if any(".norm_q." in k for k in handle.keys()) else None # noqa: SIM118 | |
| except Exception: # noqa: BLE001 — a .pt file or unreadable header: fall back to no qk-norm | |
| return None | |
| def discover_checkpoints(root: str = "checkpoints") -> list[Checkpoint]: | |
| """All ``<run>/step_<N>`` (and ``final``) checkpoints under ``root``, MOST-RECENTLY-TRAINED first, so the | |
| default is the active run's latest — not a stale run that merely has a higher step count. Prefers the EMA | |
| export (what sampling used) over the raw weights.""" | |
| base = Path(root) | |
| found: list[Checkpoint] = [] | |
| for step_dir in base.glob("*/step_*"): | |
| tail = step_dir.name.partition("_")[2] | |
| path = _prefer_ema(step_dir) | |
| if tail.isdigit() and path: | |
| found.append( | |
| Checkpoint(label=f"{step_dir.parent.name}/{step_dir.name}", path=path, step=int(tail)) | |
| ) | |
| for final_dir in base.glob("*/final"): | |
| path = _prefer_ema(final_dir) | |
| if path: | |
| found.append(Checkpoint(label=f"{final_dir.parent.name}/final", path=path, step=10**12)) | |
| found.sort(key=lambda c: Path(c.path).stat().st_mtime, reverse=True) # newest-trained first (across runs) | |
| return found | |
| def latest_checkpoint(root: str = "checkpoints") -> Checkpoint | None: | |
| """The newest checkpoint under ``root`` (the default the studio boots with), or None if there are none.""" | |
| found = discover_checkpoints(root) | |
| return found[0] if found else None | |
| def _prefer_ema(step_dir: Path) -> str | None: | |
| for name in ("ema_model.safetensors", "model.safetensors"): | |
| if (step_dir / name).exists(): | |
| return str(step_dir / name) | |
| return None | |
| def resolve_weights(path: str) -> str: | |
| """Accept a ``step_*`` DIR or a ``.safetensors`` file → the weights file (EMA preferred for a dir).""" | |
| p = Path(path) | |
| if p.is_dir(): | |
| return _prefer_ema(p) or str(p / "model.safetensors") | |
| return path | |
| def load_model(path: str, arch: ArchConfig, device: Device) -> LoadedModel: | |
| """Build Diffu with ``arch`` (+ auto-detected qk-norm/unifont), load ``path``, and verify the conditioner. | |
| Raises :class:`CheckpointMismatch` if the checkpoint's text-conditioner doesn't fit the code. | |
| """ | |
| from diffu_page.render import _assert_conditioner_loaded | |
| from diffu.config import Config | |
| from diffu.generate import load_checkpoint | |
| from diffu.model import Diffu | |
| weights = resolve_weights(path) | |
| cfg = Config() | |
| cfg.data.line_height = arch.line_height # config.py default may differ from the checkpoint's train res | |
| cfg.cond.glyph_line = arch.glyph_line | |
| cfg.cond.style_in_context = arch.style_in_context | |
| cfg.cond.glyph_concat = arch.glyph_concat | |
| qk = infer_qk_norm(weights) # the content-stencil font is keyed to the same signal (see app-era note) | |
| cfg.backbone.qk_norm = qk | |
| cfg.cond.use_unifont = qk is not None | |
| model = Diffu(cfg).to(device.torch_device, dtype=torch_dtype(device)).eval() | |
| load_checkpoint(model, weights) | |
| try: | |
| _assert_conditioner_loaded(model, weights) | |
| except RuntimeError as exc: | |
| raise CheckpointMismatch(str(exc)) from exc | |
| return LoadedModel(model=model, cfg=cfg, path=weights, tag=f"SD3.{'5' if qk else '0'}") | |