Instructions to use echoforest/naver-psy-classifier-gemma4-12b with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- PEFT
How to use echoforest/naver-psy-classifier-gemma4-12b with PEFT:
Task type is invalid.
- Notebooks
- Google Colab
- Kaggle
File size: 8,577 Bytes
210f9af | 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 | """Package a trained run for the private HF repo and (only on request) push it (classifier-plan §3.5).
Run on this host: ``python -m training.export_hf RUN_DIR --out DIR [--metrics F ...] [--push --yes]``.
The package holds: ``adapter/`` (LoRA; ``base_model_name_or_path`` rewritten to the hub id), ``heads.safetensors``,
``taxonomy.json``, ``temperatures.json``, ``config.json`` (training settings without local paths), the metrics and
``REPORT.md`` files given, a generated model card ``README.md`` and ``code/`` (the training package and its
requirements). Never data, predictions or the ledger: the package is checked for ``*.jsonl`` and ``ledger*`` files
before anything is written or pushed. The push needs both ``--push`` and ``--yes``, reads ``HF_TOKEN`` from the
environment (never printed) and creates the repo private; the HF commit becomes ``model_revision`` (§6.3).
"""
from __future__ import annotations
import argparse
import json
import os
import shutil
from pathlib import Path
from typing import Any, Callable
DEFAULT_REPO = "echoforest/naver-psy-classifier-gemma4-12b"
BASES = {"base": "google/gemma-4-12B", "it": "google/gemma-4-12B-it"}
PATH_KEYS = ("data", "model", "out", "resume_from")
FORBIDDEN_SUFFIXES = (".jsonl", ".jsonl.tmp", ".pt", ".tar", ".age")
HERE = Path(__file__).resolve().parent
def base_model_id(cfg: dict) -> str:
if cfg.get("base_model"):
return cfg["base_model"]
return BASES[cfg.get("checkpoint", "base")]
def clean_config(cfg: dict) -> dict:
"""Training settings without local paths (keys named in PATH_KEYS or absolute-path string values)."""
out = {}
for k, v in cfg.items():
if k in PATH_KEYS or (isinstance(v, str) and (v.startswith("/") or v.startswith("~"))):
continue
out[k] = v
out["base_model"] = base_model_id(cfg)
return out
def forbidden(folder: Path) -> list[str]:
"""Files that must never be in a package: data, predictions, optimizer state, the ledger."""
bad = []
for p in folder.rglob("*"):
name = p.name.lower()
ledger = name.startswith("ledger") and not name.endswith(".py") # the ledger file, not code/ledger.py
if p.is_file() and (name.endswith(FORBIDDEN_SUFFIXES) or ledger or "predictions" in p.relative_to(folder).parts):
bad.append(str(p.relative_to(folder)))
return sorted(bad)
def model_card(cfg: dict, metrics: dict[str, Any], repo: str) -> str:
lines = [
"---", "license: apache-2.0", f"base_model: {base_model_id(cfg)}", "library_name: peft", "private: true", "---",
"", f"# {repo.split('/')[-1]}", "",
"LoRA adapter and per-field linear heads on the Gemma 4 12B text decoder for the "
f"`{cfg.get('taxonomy_version') or 'nsb-v1'}` taxonomy of",
"Naver 종목토론실 posts (naver-psy, docs/classifier-plan.md). Private; not for redistribution.", "",
"- Agreement figures are agreement with the teacher labels, not accuracy against ground truth.",
"- Input: `nsb-input-v1` render, segments joined with newlines, suffix `### LABELS`, one BOS.",
"- Output: raw logits per field; calibrated with `temperatures.json`, then gated (training/taxonomy.py).",
"- No training data, predictions or post text are in this repository.", "",
"| Setting | Value |", "|---|---|",
]
for k in ("arm", "checkpoint", "pool", "epochs", "max_len", "lora_r", "lora_alpha", "lr_lora", "lr_heads", "seed",
"data_version", "data_manifest_sha256"):
if k in cfg:
lines.append(f"| {k} | `{cfg[k]}` |")
if metrics:
lines += ["", "## Metrics", ""]
for name, value in metrics.items():
lines += [f"### {name}", "", "```json", json.dumps(value, indent=1, ensure_ascii=False)[:4000], "```", ""]
return "\n".join(lines) + "\n"
def copy_code(dst: Path) -> None:
dst.mkdir(parents=True)
for p in sorted(HERE.glob("*.py")) + sorted(HERE.glob("requirements*.txt")):
shutil.copy(p, dst / p.name)
runpod = HERE / "runpod"
if runpod.is_dir():
(dst / "runpod").mkdir()
for p in sorted(runpod.iterdir()):
if p.is_file() and not p.name.endswith(".pyc"):
shutil.copy(p, dst / "runpod" / p.name)
def package(run: Path, out: Path, metrics: list[Path] = (), which: str = "best", repo: str = DEFAULT_REPO) -> Path:
run, out = Path(run), Path(out)
if out.exists():
raise SystemExit(f"refusing to overwrite {out}")
src = run / which
cfg = json.loads((run / "config.json").read_text())
for need in (src / "adapter" / "adapter_config.json", src / "adapter" / "adapter_model.safetensors",
src / "heads.safetensors", run / "taxonomy.json", run / "temperatures.json"):
if not need.exists():
raise SystemExit(f"missing {need}")
tmp = out.with_name(out.name + ".tmp")
shutil.rmtree(tmp, ignore_errors=True)
(tmp / "adapter").mkdir(parents=True)
shutil.copy(src / "adapter" / "adapter_model.safetensors", tmp / "adapter" / "adapter_model.safetensors")
adapter_cfg = json.loads((src / "adapter" / "adapter_config.json").read_text())
adapter_cfg["base_model_name_or_path"] = base_model_id(cfg)
(tmp / "adapter" / "adapter_config.json").write_text(json.dumps(adapter_cfg, indent=2) + "\n")
shutil.copy(src / "heads.safetensors", tmp / "heads.safetensors")
shutil.copy(run / "taxonomy.json", tmp / "taxonomy.json")
shutil.copy(run / "temperatures.json", tmp / "temperatures.json")
settings = clean_config(cfg)
state = src / "state.json"
if state.exists():
settings["checkpoint_step"] = json.loads(state.read_text()).get("step")
(tmp / "config.json").write_text(json.dumps(settings, indent=1, ensure_ascii=False) + "\n")
loaded = {}
for m in [Path(x) for x in metrics]:
if m.suffix == ".json":
loaded[m.stem] = json.loads(m.read_text())
shutil.copy(m, tmp / m.name)
if (run / "REPORT.md").exists() and not (tmp / "REPORT.md").exists():
shutil.copy(run / "REPORT.md", tmp / "REPORT.md")
(tmp / "README.md").write_text(model_card(settings, loaded, repo))
copy_code(tmp / "code")
bad = forbidden(tmp)
if bad:
shutil.rmtree(tmp)
raise SystemExit(f"refusing to package data/predictions/ledger files: {bad[:5]}")
tmp.rename(out)
return out
def push(folder: Path, repo: str = DEFAULT_REPO, api_factory: Callable[..., Any] | None = None) -> str:
"""Upload to the private repo; returns ``repo@<commit>`` (the ``model_revision``)."""
folder = Path(folder)
bad = forbidden(folder)
if bad:
raise SystemExit(f"refusing to push data/predictions/ledger files: {bad[:5]}")
token = os.environ.get("HF_TOKEN")
if not token:
raise SystemExit("HF_TOKEN is not set")
if api_factory is None:
from huggingface_hub import HfApi as api_factory
api = api_factory(token=token)
api.create_repo(repo, private=True, exist_ok=True, repo_type="model")
info = api.upload_folder(folder_path=str(folder), repo_id=repo, repo_type="model",
commit_message="Upload naver-psy classifier (adapter, heads, calibration)")
oid = getattr(info, "oid", None) or getattr(info, "commit_id", None)
if not oid or len(oid) != 40:
raise SystemExit("upload returned no 40-hex commit id")
return f"{repo}@{oid}"
def main(argv=None) -> int:
p = argparse.ArgumentParser(description=__doc__.splitlines()[0])
p.add_argument("run")
p.add_argument("--out", required=True)
p.add_argument("--which", default="best", choices=["best", "last"])
p.add_argument("--metrics", nargs="*", default=[])
p.add_argument("--repo", default=DEFAULT_REPO)
p.add_argument("--push", action="store_true", help="Upload to the private HF repo (also needs --yes)")
p.add_argument("--yes", action="store_true")
args = p.parse_args(argv)
if args.push and not args.yes:
p.error("--push also needs --yes")
out = package(Path(args.run), Path(args.out), [Path(m) for m in args.metrics], args.which, args.repo)
print(json.dumps({"packaged": str(out), "files": sorted(str(x.relative_to(out)) for x in out.rglob("*")
if x.is_file() and "code" not in x.parts)}))
if args.push:
revision = push(out, args.repo)
print(json.dumps({"model_revision": revision}))
return 0
if __name__ == "__main__":
raise SystemExit(main())
|