carepath-api / scribe /training /gec /notebook.py
tranth3truong's picture
Deploy public Scribe-only CarePath Space
cc678b9
Raw
History Blame Contribute Delete
4.03 kB
"""Thin glue the stage notebooks call so each one is ~5 cells.
``init_stage(profile)`` resolves the run profile, mounts Drive (Colab), and builds
the shared ``ArtifactPaths`` — returning a ``StageContext`` whose ``run_step`` runs
a pipeline CLI with ``PYTHONPATH`` set and ``restore``/``save`` move artifacts
to/from Drive. On Colab the first notebook cell does a minimal locate-or-clone
before importing this module (it can't import the package until ``scribe`` is
on ``sys.path``).
"""
from __future__ import annotations
import os
import subprocess
import sys
from dataclasses import dataclass
from pathlib import Path
from gec import env
from gec.paths import ArtifactPaths
from gec.profiles import RunProfile, get_profile
@dataclass
class StageContext:
profile: RunProfile
paths: ArtifactPaths
backup: Path | None
in_colab: bool
dataset: str = "tensorxt/ViMedCSS"
def run_step(self, args: list[str], env_extra: dict | None = None) -> None:
"""Run a ``scribe/training/scripts/*`` CLI with PYTHONPATH set, raising on failure."""
run_env = dict(os.environ)
run_env["PYTHONPATH"] = os.pathsep.join(("scribe/training", "scribe"))
run_env["PYTHONIOENCODING"] = "utf-8"
if env_extra:
run_env.update(env_extra)
printable = " ".join(str(a) for a in args)
print(">>>", printable, flush=True)
proc = subprocess.run([sys.executable, *map(str, args)], env=run_env)
if proc.returncode != 0:
raise RuntimeError(f"step failed ({proc.returncode}): {printable}")
def restore(self, rel_paths: list[str]) -> None:
env.restore_artifacts(self.backup, rel_paths)
def restore_optional(self, rel_paths: list[str]) -> None:
"""Copy artifacts from Drive *if present*, without failing when they aren't.
Used for inputs that may legitimately be absent (no synthetic pairs, no
labeled export) so a teammate continuing a run on a fresh Colab still pulls
whatever exists on Drive.
"""
if self.backup is None:
return # local: files already on disk (or intentionally absent)
import shutil
for rel in rel_paths:
dst = Path(rel)
src = self.backup / dst.name
if src.exists():
dst.parent.mkdir(parents=True, exist_ok=True)
shutil.copy(src, dst)
print("restored", dst, "from", src)
else:
print("(optional, not on Drive):", dst)
def save(self, rel_paths: list[str]) -> None:
env.save_artifacts(self.backup, rel_paths)
def durable(self, path: str | Path) -> str:
"""Output path that survives a Colab runtime recycle.
Long, resumable stages (ASR pairs, TTS) write here so ``--resume`` can pick
up after a disconnect: on Colab that's the Drive backup, where every flushed
row already persists; locally it's the normal ``artifacts/`` path. Uses the
same basename as ``save``/``restore`` so a downstream stage's ``restore``
finds it unchanged.
"""
p = Path(path)
if self.backup is None:
return str(p)
dst = self.backup / p.name
dst.parent.mkdir(parents=True, exist_ok=True)
return str(dst)
def init_stage(profile: str = "smoke", dataset: str = "tensorxt/ViMedCSS") -> StageContext:
"""Resolve profile + Drive backup + artifact paths for a stage notebook."""
prof = get_profile(profile)
in_colab = env.in_colab()
backup = env.setup_backup(in_colab)
suffix = "" if prof.name == "full" else f"_{prof.name}"
adapters_root = (backup / "gec_lora" / "qwen3") if backup else None
paths = ArtifactPaths(root=Path("artifacts"), suffix=suffix, adapters_root=adapters_root)
print(f"profile={prof.name} | n_best={prof.n_best} | seeds={prof.seeds} | adapters={paths.adapters}")
return StageContext(profile=prof, paths=paths, backup=backup, in_colab=in_colab, dataset=dataset)