JEBS-4B / src /mdtrain /evaluate.py
the-Xy's picture
JEBS-4B v3: sealed artifact, runtime, model card
6592c2d verified
Raw History Blame Contribute Delete
7.05 kB
"""Basic grouped scoring and optional scalar-temperature finalization. Policy stays disabled."""
import contextlib
import json
import os
import tempfile
from pathlib import Path
from mdtrain.budget import BudgetLedger
from mdtrain.data import DatasetView, EVALUATION_SPLITS
from mdtrain.engine import DecisionModel
from mdtrain.fixtures import read_requests
from mdtrain.metrics import fit_temperature, score_predictions
from mdtrain.storage import atomic_json, report_json, verify_seal
def prediction_file(path: Path, rows: list[dict]) -> None:
path.parent.mkdir(parents=True, exist_ok=True)
descriptor, name = tempfile.mkstemp(prefix=f".{path.name}.", dir=path.parent)
temporary = Path(name)
try:
with os.fdopen(descriptor, "w") as stream:
for row in rows:
stream.write(json.dumps(row, ensure_ascii=False, allow_nan=False) + "\n")
stream.flush()
os.fsync(stream.fileno())
os.replace(temporary, path)
finally:
temporary.unlink(missing_ok=True)
def gpu_allowance(device: str, path: Path | None):
# Rented pods pass a ledger to account paid time; a user's own GPU needs none.
if device == "cuda" and path is not None:
return BudgetLedger(path)
return contextlib.nullcontext(None)
def evaluate_model(model: DecisionModel, view: DatasetView, *, ledger: BudgetLedger | None = None) -> tuple[list[dict], dict]:
rows = []
for example, canonical in zip(view.examples, view.rows):
if ledger is not None:
ledger.admit(60)
batch = model.compiler.compile((example.input,))
prediction = model.predict((example.input,))[0]
expected_keys = tuple(candidate.key for candidate in example.input.candidates)
if prediction["id"] != example.input.id or tuple(prediction["probabilities"]) != expected_keys:
raise ValueError("Prediction identity/candidate alignment differs from requested view")
rows.append({**prediction, "group_id": example.group_id,
"target": expected_keys[example.target_index], "source": example.source,
"origin": example.origin, "modality": canonical["modality"],
"primitive": example.input.kind, "family": example.family, "ood_axis": example.ood_axis,
"position_count": int(batch.expanded_lengths[0])})
if ledger is not None:
ledger.commit()
metrics = score_predictions(rows)
real_rows = [row for row in rows if row["origin"] == "real_human_label"]
generated_rows = [row for row in rows if row["origin"] != "real_human_label"]
return rows, {"all": metrics, "real": score_predictions(real_rows) if real_rows else None,
"generated": score_predictions(generated_rows) if generated_rows else None}
def predict(root: Path, *, artifact: Path, input_path: Path, media_root: Path, output: Path,
device: str, budget_ledger: Path | None = None) -> dict:
requests = read_requests(input_path)
with gpu_allowance(device, budget_ledger) as ledger:
if ledger:
ledger.admit(300)
model = DecisionModel.load(artifact, root=root, device=device, media_root=media_root)
rows = []
for item in requests:
if ledger:
ledger.admit(60)
rows.extend(model.predict((item,)))
prediction_file(output, rows)
return {"status": "completed", "questions": len(rows), "artifact_id": model.artifact_id,
"output": str(output), "policy": "disabled"}
def evaluate(root: Path, *, artifact: Path, splits: tuple[str, ...], output: Path,
device: str, budget_ledger: Path | None = None) -> dict:
if not splits or len(set(splits)) != len(splits) or any(split not in EVALUATION_SPLITS for split in splits):
raise ValueError("Evaluation requires distinct declared evaluation splits")
with gpu_allowance(device, budget_ledger) as ledger:
if ledger:
ledger.admit(300)
model = DecisionModel.load(artifact, root=root, device=device)
if any(split.startswith("test_") for split in splits) and not model.finalized:
raise ValueError("Locked final-test evaluation requires a finalized paired artifact")
result = {"status": "completed", "artifact_id": model.artifact_id,
"temperature": model.temperature, "policy": "disabled", "splits": {}}
for split in splits:
view = DatasetView.open(root, split=split, recipe="mixed")
rows, metrics = evaluate_model(model, view, ledger=ledger)
prediction_file(output.with_name(output.stem + f"-{split}-predictions.jsonl"), rows)
result["splits"][split] = {"view_identity": view.identity, "metrics": metrics}
result = report_json(result)
atomic_json(output, result)
return result
def finalize(root: Path, *, checkpoint: Path, output: Path, device: str,
fit_scalar_temperature: bool = False, budget_ledger: Path | None = None) -> dict:
checkpoint = checkpoint.resolve(strict=True)
checkpoint_id = verify_seal(checkpoint)
state = json.loads((checkpoint / "state.json").read_text())
if state["training"]["status"] != "completed" or state["spec"]["stage"] != "main":
raise ValueError("Only a completed main checkpoint may be finalized")
with gpu_allowance(device, budget_ledger) as ledger:
if ledger:
ledger.admit(300)
if state["ledger_id"] != ledger.record["id"]:
raise ValueError("Finalization must use the same durable budget ledger")
model = DecisionModel.load(checkpoint / "inference", root=root, device=device)
calibration_identity = None
if fit_scalar_temperature:
view = DatasetView.open(root, split="calibration", recipe="mixed")
logits, targets = [], []
for example in view.examples:
if ledger:
ledger.admit(60)
logits.append(model.logits((example.input,))[0])
targets.append(example.target_index)
model.temperature = fit_temperature(logits, targets)
model.temperature_fitted = True
calibration_identity = view.identity
model.finalized = True
model.save(output, metadata={"checkpoint_id": checkpoint_id, "training_status": "completed",
"calibration_view_id": calibration_identity, "policy": "disabled"})
receipt = {"status": "completed", "artifact": str(output), "artifact_id": model.artifact_id,
"checkpoint_id": checkpoint_id, "temperature": model.temperature,
"temperature_fitted": fit_scalar_temperature, "calibration_view_id": calibration_identity,
"policy": "disabled", "weights_selected_using_final_tests": False}
atomic_json(output.with_name(output.name + "-freeze-receipt.json"), receipt)
return receipt