Spaces:
Sleeping
Sleeping
| """CLI: run the DARAG pipeline end-to-end (or a single stage) for a profile. | |
| # plumbing check (mock ASR, tiny limits, no models needed): | |
| python scribe/training/scripts/run_pipeline.py --profile smoke --stage data | |
| # real ViMedCSS run on a GPU box: | |
| python scribe/training/scripts/run_pipeline.py --profile full --stage all | |
| Stages: ``data`` (datastore + real pairs), ``synth`` (synthetic | |
| transcripts + TTS + synthetic pairs + leakage), ``train`` (augment + QLoRA), and | |
| ``eval`` (LLM/RAG baseline + predict + tables + gate). Each stage is independently | |
| runnable and resumable; ``all`` runs them in order. Paths and run-sizes come from | |
| the versioned JSON run config, while ``gec.paths`` derives its artifact names. | |
| """ | |
| from __future__ import annotations | |
| import argparse | |
| import os | |
| import subprocess | |
| import sys | |
| from pathlib import Path | |
| sys.path.insert(0, str(Path(__file__).resolve().parents[3] / "scribe" / "training")) | |
| sys.path.insert(0, str(Path(__file__).resolve().parents[3] / "scribe")) | |
| from gec.cliutil import configure_stdout # noqa: E402 | |
| configure_stdout() | |
| from gec.data import read_jsonl # noqa: E402 | |
| from gec.manifest import load_manifest, sha256_file # noqa: E402 | |
| from gec.paths import ArtifactPaths # noqa: E402 | |
| from gec.run_config import load_pipeline_config # noqa: E402 | |
| STAGES = ("all", "data", "synth", "train", "eval") | |
| def run(args: list) -> None: | |
| run_env = dict(os.environ) | |
| run_env["PYTHONPATH"] = os.pathsep.join(("scribe/training", "scribe")) | |
| run_env["PYTHONIOENCODING"] = "utf-8" | |
| printable = " ".join(str(a) for a in args) | |
| print("\n>>>", printable, flush=True) | |
| proc = subprocess.run([sys.executable, *map(str, args)], env=run_env) | |
| if proc.returncode != 0: | |
| raise SystemExit(f"step failed ({proc.returncode}): {printable}") | |
| def stage_data(p: ArtifactPaths, prof, dataset: str) -> None: | |
| limit = str(prof.limit_per_split or 0) | |
| run(["scribe/training/scripts/build_datastore.py", "--dataset", dataset, | |
| "--limit-per-split", limit, "--output", str(p.datastore)]) | |
| run(["scribe/training/scripts/make_pairs.py", "--dataset", dataset, "--output", str(p.real_pairs), | |
| "--asr-provider", prof.asr_provider, "--datastore", str(p.datastore), | |
| "--retrieval-backend", prof.retrieval_backend, "--limit-per-split", limit, | |
| "--n-best", str(prof.n_best), "--resume"]) | |
| def stage_synth(p: ArtifactPaths, prof) -> None: | |
| count = prof.synth_count | |
| if count is None: # paper nsyn = n: match the real train size | |
| count = sum(1 for r in read_jsonl(p.real_pairs) if r.get("split") == "train") or 50 | |
| gen = ["scribe/training/scripts/gen_synthetic.py", "--pairs", str(p.real_pairs), | |
| "--output", str(p.synth_clean), "--count", str(count)] | |
| if prof.name != "smoke": | |
| gen.append("--load-in-4bit") | |
| run(gen) | |
| tts = ["scribe/training/scripts/voice_clone_tts.py", "--input", str(p.synth_clean), | |
| "--output", str(p.tts_manifest), "--provider", prof.tts_provider, | |
| "--ref-dataset", "tensorxt/ViMedCSS", "--ref-count", "20", "--resume"] | |
| if prof.synth_tts_limit: | |
| tts += ["--limit", str(prof.synth_tts_limit)] | |
| run(tts) | |
| run(["scribe/training/scripts/make_synth_pairs.py", "--input", str(p.tts_manifest), | |
| "--output", str(p.synth_pairs), "--datastore", str(p.datastore), | |
| "--n-best", str(prof.n_best), "--resume"]) | |
| run(["scribe/training/scripts/check_leakage.py", "--synthetic", str(p.synth_clean), | |
| "--real", str(p.real_pairs), "--output", str(p.leakage)]) | |
| def stage_train(p: ArtifactPaths, prof) -> None: | |
| real_inputs = [str(p.real_pairs)] | |
| # Learn real ASR confusions (paper Limitation #1) into the datastore, then | |
| # refresh every pair's retrieved NEs so the RAC prompt carries the right term. | |
| harvest_pairs = list(real_inputs) | |
| if p.synth_pairs.exists(): | |
| harvest_pairs.append(str(p.synth_pairs)) | |
| run(["scribe/training/scripts/harvest_aliases.py", "--datastore", str(p.datastore), | |
| "--pairs", *harvest_pairs, "--refresh", "--backend", prof.retrieval_backend]) | |
| run(["scribe/training/scripts/augment.py", "--real", *real_inputs, "--synthetic", str(p.synth_pairs), | |
| "--output", str(p.augmented), "--nsyn-factor", str(prof.nsyn_factor)]) | |
| train = ["scribe/training/scripts/train.py", "--pairs", str(p.augmented), "--output-dir", str(p.adapters), | |
| "--max-steps", str(prof.max_steps), "--seeds", *[str(s) for s in prof.seeds]] | |
| train.append("--all-variants" if prof.all_variants else "--variant") | |
| if not prof.all_variants: | |
| train.append("full") | |
| run(train) | |
| def _full_adapter(p: ArtifactPaths, prof) -> str: | |
| """Path of the 'full' adapter under the variant/seed layout train wrote.""" | |
| adir = str(p.adapters) | |
| if prof.all_variants: | |
| adir = f"{adir}/full" | |
| if len(prof.seeds) > 1: | |
| adir = f"{adir}/seed-{prof.seeds[0]}" | |
| return adir | |
| def stage_eval(p: ArtifactPaths, prof, frozen_fixture: Path, frozen_manifest: Path) -> None: | |
| run(["scribe/training/scripts/llm_rag_baseline.py", "--input", str(p.real_pairs), "--output", str(p.llm_rag)]) | |
| run(["scribe/training/scripts/predict.py", "--pairs", str(p.llm_rag), "--adapter-dir", | |
| _full_adapter(p, prof), "--output", str(p.darag_preds), "--column", "gec_pred"]) | |
| run(["scribe/training/scripts/evaluate.py", "--input", str(p.darag_preds), "--prediction-columns", | |
| "raw_asr", "corrected_text", "gec_pred", "--wer-output", str(p.darag_wer), | |
| "--ne-f1-output", str(p.darag_ne_f1), "--stratified-output", str(p.darag_stratified)]) | |
| run(["scribe/training/scripts/gate.py", "--report", str(p.darag_wer)]) | |
| frozen = load_manifest(frozen_manifest) | |
| if sha256_file(frozen_fixture) != frozen["sha256"]: | |
| raise ValueError("frozen evaluation fixture hash does not match its manifest") | |
| run(["scribe/training/scripts/predict.py", "--pairs", str(frozen_fixture), "--adapter-dir", | |
| _full_adapter(p, prof), "--output", str(p.frozen_preds), "--column", "gec_pred"]) | |
| run(["scribe/training/scripts/evaluate.py", "--input", str(p.frozen_preds), "--prediction-columns", | |
| "raw_asr", "gec_pred", "--wer-output", str(p.frozen_wer), "--ne-f1-output", | |
| str(p.frozen_ne_f1), "--stratified-output", str(p.frozen_stratified)]) | |
| run(["scribe/training/scripts/gate.py", "--report", str(p.frozen_wer), "--candidate", "gec_pred", | |
| "--baselines", "raw_asr", "--splits", "frozen", "--safety-report", | |
| str(p.frozen_stratified)]) | |
| run(["scribe/training/scripts/export_serve.py", "--adapter-dir", _full_adapter(p, prof), | |
| "--datastore", str(p.datastore), "--output", str(p.serve_bundle), | |
| "--gate-report", str(p.darag_wer)]) | |
| def main() -> None: | |
| parser = argparse.ArgumentParser(description=__doc__) | |
| parser.add_argument("--profile", default="smoke", choices=["smoke", "full"]) | |
| parser.add_argument("--config", type=Path, help="versioned JSON run config") | |
| parser.add_argument("--stage", default="all", choices=list(STAGES)) | |
| args = parser.parse_args() | |
| config_path = args.config or Path("scribe/training/configs") / f"{args.profile}-v1.json" | |
| config = load_pipeline_config(config_path) | |
| prof = config.profile | |
| paths = ArtifactPaths(root=config.artifact_root, suffix=config.suffix) | |
| wanted = STAGES[1:] if args.stage == "all" else [args.stage] | |
| if "train" in wanted: | |
| load_manifest(config.manifest, require_approved=True) | |
| for stage in wanted: | |
| print(f"\n===== STAGE: {stage} (run={config.run_id}, profile={prof.name}) =====") | |
| if stage == "data": | |
| stage_data(paths, prof, config.dataset) | |
| elif stage == "synth": | |
| stage_synth(paths, prof) | |
| elif stage == "train": | |
| stage_train(paths, prof) | |
| elif stage == "eval": | |
| stage_eval(paths, prof, config.frozen_eval_fixture, config.frozen_eval_manifest) | |
| print("\nPipeline stage(s) complete:", ", ".join(wanted)) | |
| if __name__ == "__main__": | |
| main() | |