Buckets:
bbkdevops/unicosys-hypergraph-bucket / tinymind-native-colab-handoff /bundle /evaluation /self_dialogue_evidence.py
| """Train/eval evidence for self-dialogue TinyMind data.""" | |
| from __future__ import annotations | |
| import json | |
| import math | |
| import random | |
| from pathlib import Path | |
| import torch | |
| import torch.nn.functional as F | |
| from data.self_dialogue_forge import SelfDialogueForge | |
| from evaluation.claims import build_claim_dossier | |
| from evaluation.local_evidence import _collate, _encode, _make_config, _save_json | |
| from model.architecture import OmegaModel | |
| from model.sparse_int4 import export_sparse_int4_model | |
| SYSTEM_PROMPT = "TinyMind solves by self-dialogue: plan, act, verify, then final." | |
| def _seed_everything(seed: int) -> None: | |
| random.seed(seed) | |
| torch.manual_seed(seed) | |
| def _load_jsonl(path: str | Path) -> list[dict]: | |
| return [json.loads(line) for line in Path(path).read_text(encoding="utf-8").splitlines() if line.strip()] | |
| def _dialogue_text(row: dict) -> str: | |
| return ( | |
| f"<bos><system>{SYSTEM_PROMPT}</system>\n" | |
| f"<user>{row['prompt']}</user>\n" | |
| f"<assistant>{row['target']}<eos>" | |
| ) | |
| def _prompt_prefix(row: dict) -> str: | |
| return f"<bos><system>{SYSTEM_PROMPT}</system>\n<user>{row['prompt']}</user>\n<assistant>" | |
| def _encode_rows(rows: list[dict], max_len: int, vocab_size: int) -> list[torch.Tensor]: | |
| return [_encode(_dialogue_text(row), max_len, vocab_size) for row in rows] | |
| def _decode_ids(ids: list[int]) -> str: | |
| data = bytes(max(0, min(255, token - 4)) for token in ids if token >= 4) | |
| return data.decode("utf-8", errors="ignore") | |
| def _corrupt_row(row: dict) -> dict: | |
| corrupt = dict(row) | |
| oracle = str(row.get("oracle", "")) | |
| wrong = str(int(oracle) + 7) if oracle.isdigit() else "wrong" | |
| corrupt["target"] = ( | |
| "<plan>Trust the first draft without checking.</plan>\n" | |
| "<act>Skip recomputation and keep the inconsistent value.</act>\n" | |
| "<verify>failed_check=true; oracle_mismatch=true</verify>\n" | |
| f"<final>{wrong}</final>" | |
| ) | |
| return corrupt | |
| def _loss(model: OmegaModel, sequences: list[torch.Tensor]) -> float: | |
| model.eval() | |
| input_ids, labels = _collate(sequences) | |
| return float(model(input_ids, labels=labels)["loss"].item()) | |
| def _train(model: OmegaModel, sequences: list[torch.Tensor], steps: int) -> tuple[list[float], float]: | |
| optimizer = torch.optim.AdamW(model.parameters(), lr=1.5e-3, weight_decay=0.01) | |
| losses: list[float] = [] | |
| grad_norm = 0.0 | |
| model.train() | |
| for step in range(max(1, int(steps))): | |
| batch = [sequences[(step + j) % len(sequences)] for j in range(min(4, len(sequences)))] | |
| input_ids, labels = _collate(batch) | |
| out = model(input_ids, labels=labels) | |
| loss = out["loss"] | |
| optimizer.zero_grad() | |
| loss.backward() | |
| norm = torch.nn.utils.clip_grad_norm_(model.parameters(), 1.0) | |
| optimizer.step() | |
| losses.append(float(loss.item())) | |
| grad_norm = float(norm.item() if hasattr(norm, "item") else norm) | |
| return losses, grad_norm | |
| def _sequence_nll(model: OmegaModel, sequences: list[torch.Tensor]) -> torch.Tensor: | |
| input_ids, labels = _collate(sequences) | |
| out = model(input_ids) | |
| logits = out["logits"][..., :-1, :].contiguous() | |
| targets = labels[..., 1:].contiguous() | |
| token_loss = F.cross_entropy( | |
| logits.view(-1, model.cfg.vocab_size), | |
| targets.view(-1), | |
| ignore_index=-100, | |
| reduction="none", | |
| ).view(targets.shape) | |
| mask = (targets != -100).float() | |
| return (token_loss * mask).sum(dim=-1) / mask.sum(dim=-1).clamp(min=1.0) | |
| def _train_with_preference( | |
| model: OmegaModel, | |
| rows: list[dict], | |
| max_len: int, | |
| vocab_size: int, | |
| steps: int, | |
| beta: float = 2.0, | |
| margin: float = 0.25, | |
| ) -> dict: | |
| if steps <= 0: | |
| return {"enabled": False, "steps": 0, "losses": [], "final_loss": None, "final_margin": None} | |
| optimizer = torch.optim.AdamW(model.parameters(), lr=2e-3, weight_decay=0.01) | |
| losses: list[float] = [] | |
| margins: list[float] = [] | |
| model.train() | |
| for step in range(int(steps)): | |
| batch_rows = [rows[(step + j) % len(rows)] for j in range(min(4, len(rows)))] | |
| chosen = [_encode(_dialogue_text(row), max_len, vocab_size) for row in batch_rows] | |
| rejected = [_encode(_dialogue_text(_corrupt_row(row)), max_len, vocab_size) for row in batch_rows] | |
| chosen_nll = _sequence_nll(model, chosen) | |
| rejected_nll = _sequence_nll(model, rejected) | |
| pref_margin = rejected_nll - chosen_nll | |
| preference_loss = F.softplus(-beta * (pref_margin - margin)).mean() | |
| lm_loss = chosen_nll.mean() | |
| loss = lm_loss + preference_loss | |
| optimizer.zero_grad() | |
| loss.backward() | |
| torch.nn.utils.clip_grad_norm_(model.parameters(), 1.0) | |
| optimizer.step() | |
| losses.append(float(loss.item())) | |
| margins.append(float(pref_margin.mean().item())) | |
| return { | |
| "enabled": True, | |
| "steps": int(steps), | |
| "losses": losses, | |
| "final_loss": losses[-1], | |
| "final_margin": margins[-1], | |
| "beta": beta, | |
| "target_margin": margin, | |
| } | |
| def _contains_self_dialogue_markers(rows: list[dict]) -> dict: | |
| required = ("<plan>", "<act>", "<verify>", "<final>") | |
| passed = all(all(marker in row["target"] for marker in required) for row in rows) | |
| return {"passed": passed, "required_markers": list(required), "records_checked": len(rows)} | |
| def _ngram_repetition_rate(tokens: list[str], n: int = 4) -> float: | |
| if len(tokens) < n: | |
| return 0.0 | |
| grams = [tuple(tokens[i : i + n]) for i in range(len(tokens) - n + 1)] | |
| return 1.0 - (len(set(grams)) / max(len(grams), 1)) | |
| def _distinct_ratio(tokens: list[str], n: int = 1) -> float: | |
| if len(tokens) < n: | |
| return 0.0 | |
| grams = [tuple(tokens[i : i + n]) for i in range(len(tokens) - n + 1)] | |
| return len(set(grams)) / max(len(grams), 1) | |
| def score_generation_quality(samples: list[dict]) -> dict: | |
| repetitions: list[float] = [] | |
| distinct_1: list[float] = [] | |
| distinct_2: list[float] = [] | |
| final_hits = 0 | |
| for sample in samples: | |
| text = str(sample.get("generated_text", "")) | |
| tokens = text.split() | |
| if not tokens and text: | |
| tokens = list(text) | |
| repetitions.append(_ngram_repetition_rate(tokens, 4)) | |
| distinct_1.append(_distinct_ratio(tokens, 1)) | |
| distinct_2.append(_distinct_ratio(tokens, 2)) | |
| oracle = str(sample.get("oracle", "")) | |
| final_hits += int(bool(oracle) and oracle in text) | |
| count = max(len(samples), 1) | |
| avg_repetition = sum(repetitions) / count | |
| avg_distinct_1 = sum(distinct_1) / count | |
| avg_distinct_2 = sum(distinct_2) / count | |
| final_accuracy = final_hits / count | |
| collapse_detected = avg_repetition > 0.35 or avg_distinct_1 < 0.20 | |
| passed = not collapse_detected and final_accuracy >= 0.50 | |
| return { | |
| "passed": passed, | |
| "collapse_detected": collapse_detected, | |
| "avg_repetition_4gram_rate": avg_repetition, | |
| "avg_distinct_1": avg_distinct_1, | |
| "avg_distinct_2": avg_distinct_2, | |
| "final_answer_accuracy": final_accuracy, | |
| "sample_count": len(samples), | |
| "samples": samples, | |
| } | |
| def _greedy_generate_text(model: OmegaModel, row: dict, max_new_tokens: int = 80) -> dict: | |
| model.eval() | |
| prompt = _prompt_prefix(row) | |
| ids = _encode(prompt, model.cfg.max_seq_len, model.cfg.vocab_size).tolist() | |
| generated: list[int] = [] | |
| for _ in range(max_new_tokens): | |
| context = torch.tensor([ids[-model.cfg.max_seq_len :]], dtype=torch.long) | |
| logits = model(context)["logits"][0, -1] | |
| next_id = int(torch.argmax(logits).item()) | |
| if next_id == model.cfg.eos_token_id: | |
| break | |
| ids.append(next_id) | |
| generated.append(next_id) | |
| text = _decode_ids(generated) | |
| return { | |
| "prompt": row["prompt"], | |
| "oracle": str(row.get("oracle", "")), | |
| "generated_text": text, | |
| "generated_token_count": len(generated), | |
| } | |
| def _generation_quality_eval(model: OmegaModel, rows: list[dict], limit: int = 3) -> dict: | |
| samples = [_greedy_generate_text(model, row) for row in rows[: min(limit, len(rows))]] | |
| return score_generation_quality(samples) | |
| def _template_memorization_guard(train_rows: list[dict], eval_rows: list[dict]) -> dict: | |
| train_ids = {row["id"] for row in train_rows} | |
| eval_ids = {row["id"] for row in eval_rows} | |
| train_prompts = {row["prompt"] for row in train_rows} | |
| eval_prompts = {row["prompt"] for row in eval_rows} | |
| return { | |
| "passed": not (train_ids & eval_ids) and not (train_prompts & eval_prompts), | |
| "id_overlap": len(train_ids & eval_ids), | |
| "prompt_overlap": len(train_prompts & eval_prompts), | |
| "train_records": len(train_rows), | |
| "eval_records": len(eval_rows), | |
| } | |
| def _candidate_preference_eval(model: OmegaModel, rows: list[dict], max_len: int, vocab_size: int) -> dict: | |
| """Check whether the trained model prefers oracle traces over corrupted traces.""" | |
| wins = 0 | |
| total = 0 | |
| margins: list[float] = [] | |
| examples: list[dict] = [] | |
| for row in rows: | |
| corrupt = _corrupt_row(row) | |
| correct_seq = _encode(_dialogue_text(row), max_len, vocab_size) | |
| corrupt_seq = _encode(_dialogue_text(corrupt), max_len, vocab_size) | |
| correct_loss = _loss(model, [correct_seq]) | |
| corrupt_loss = _loss(model, [corrupt_seq]) | |
| preferred = correct_loss <= corrupt_loss | |
| margin = corrupt_loss - correct_loss | |
| margins.append(margin) | |
| wins += int(preferred) | |
| total += 1 | |
| if len(examples) < 3: | |
| examples.append( | |
| { | |
| "prompt": row["prompt"], | |
| "correct_loss": correct_loss, | |
| "corrupt_loss": corrupt_loss, | |
| "margin": margin, | |
| "preferred_oracle": preferred, | |
| } | |
| ) | |
| return { | |
| "passed": wins >= max(1, total // 2), | |
| "oracle_preference_accuracy": wins / max(total, 1), | |
| "avg_margin": sum(margins) / max(len(margins), 1), | |
| "examples": examples, | |
| } | |
| def run_self_dialogue_train_eval_bundle( | |
| out_dir: str | Path, | |
| train_steps: int = 12, | |
| preference_steps: int = 0, | |
| train_size: int = 48, | |
| eval_size: int = 12, | |
| preference_eval_limit: int = 6, | |
| seed: int = 20260523, | |
| ) -> dict: | |
| _seed_everything(seed) | |
| out = Path(out_dir) | |
| out.mkdir(parents=True, exist_ok=True) | |
| manifest = SelfDialogueForge(train_size=train_size, eval_size=eval_size, seed=seed).write_jsonl(out) | |
| train_rows = _load_jsonl(manifest["train_path"]) | |
| eval_rows = _load_jsonl(manifest["eval_path"]) | |
| cfg = _make_config() | |
| cfg.max_seq_len = 320 | |
| train_sequences = _encode_rows(train_rows, cfg.max_seq_len, cfg.vocab_size) | |
| eval_sequences = _encode_rows(eval_rows, cfg.max_seq_len, cfg.vocab_size) | |
| model = OmegaModel(cfg) | |
| initial_eval_loss = _loss(model, eval_sequences) | |
| train_losses, grad_norm = _train(model, train_sequences, train_steps) | |
| preference_optimization = _train_with_preference( | |
| model, | |
| train_rows, | |
| cfg.max_seq_len, | |
| cfg.vocab_size, | |
| preference_steps, | |
| ) | |
| eval_loss = _loss(model, eval_sequences) | |
| perplexity = float(math.exp(min(eval_loss, 20.0))) | |
| checkpoint_path = out / "self_dialogue_purefield.pt" | |
| torch.save( | |
| { | |
| "step": int(train_steps), | |
| "model_state": model.state_dict(), | |
| "model_cfg": cfg, | |
| "train_losses": train_losses, | |
| "eval_loss": eval_loss, | |
| "dataset_manifest": str(out / "self_dialogue_manifest.json"), | |
| "training_mode": "self_dialogue_oracle_trace", | |
| }, | |
| checkpoint_path, | |
| ) | |
| int4_artifact = export_sparse_int4_model(model, quality_gate_delta=cfg.quality_gate_delta) | |
| int4_path = out / "self_dialogue_int4_sparse.pt" | |
| torch.save(int4_artifact, int4_path) | |
| marker_eval = _contains_self_dialogue_markers(train_rows + eval_rows) | |
| memorization_guard = _template_memorization_guard(train_rows, eval_rows) | |
| preference = _candidate_preference_eval( | |
| model, | |
| eval_rows[: min(int(preference_eval_limit), len(eval_rows))], | |
| cfg.max_seq_len, | |
| cfg.vocab_size, | |
| ) | |
| generation_quality = _generation_quality_eval(model, eval_rows, limit=min(3, int(preference_eval_limit))) | |
| loss_improved = eval_loss <= initial_eval_loss | |
| train_eval = { | |
| "steps": int(train_steps), | |
| "preference_steps": int(preference_steps), | |
| "initial_eval_loss": initial_eval_loss, | |
| "eval_loss": eval_loss, | |
| "perplexity": perplexity, | |
| "loss_delta": initial_eval_loss - eval_loss, | |
| "final_train_loss": train_losses[-1], | |
| "grad_norm": grad_norm, | |
| } | |
| measurements = { | |
| "quality": { | |
| "passed": bool(torch.isfinite(torch.tensor(eval_loss)).item()) and perplexity < cfg.vocab_size * 2, | |
| "score": eval_loss, | |
| "artifact": str(checkpoint_path), | |
| "notes": f"Self-dialogue eval loss={eval_loss:.4f}, perplexity={perplexity:.2f}.", | |
| }, | |
| "self_dialogue": { | |
| "passed": marker_eval["passed"] and memorization_guard["passed"], | |
| "score": preference["oracle_preference_accuracy"], | |
| "artifact": str(out / "self_dialogue_eval.jsonl"), | |
| "notes": "Targets include plan/act/verify/final and eval prompts are disjoint from train prompts.", | |
| }, | |
| "anti_memorization": { | |
| "passed": memorization_guard["passed"], | |
| "score": 1.0 - min(1.0, memorization_guard["prompt_overlap"] / max(1, len(eval_rows))), | |
| "artifact": str(out / "self_dialogue_manifest.json"), | |
| "notes": "Train/eval IDs and prompts are disjoint; records are generated from held-out oracle seeds.", | |
| }, | |
| "stability": { | |
| "passed": bool(torch.isfinite(torch.tensor(train_losses + [eval_loss, grad_norm])).all().item()), | |
| "score": grad_norm, | |
| "artifact": str(checkpoint_path), | |
| "notes": "Training losses, eval loss, and gradient norm were finite.", | |
| }, | |
| "quantization": { | |
| "passed": len(int4_artifact.get("layers", [])) > 0, | |
| "score": len(int4_artifact.get("layers", [])), | |
| "artifact": str(int4_path), | |
| "notes": "INT4 sparse artifact was exported from the self-dialogue trained model.", | |
| }, | |
| "oracle_preference": { | |
| "passed": preference["oracle_preference_accuracy"] >= 0.999 and preference["avg_margin"] > 0, | |
| "score": preference["oracle_preference_accuracy"], | |
| "artifact": str(out / "self_dialogue_evidence.json"), | |
| "notes": ( | |
| "Chosen oracle traces are compared against corrupted traces; " | |
| f"avg_margin={preference['avg_margin']:.4f}." | |
| ), | |
| }, | |
| "generation_quality": { | |
| "passed": generation_quality["passed"], | |
| "score": generation_quality["final_answer_accuracy"], | |
| "artifact": str(out / "self_dialogue_evidence.json"), | |
| "notes": ( | |
| "Greedy generation must avoid repetition collapse and include the oracle final answer. " | |
| f"collapse_detected={generation_quality['collapse_detected']}, " | |
| f"repetition_4gram={generation_quality['avg_repetition_4gram_rate']:.4f}." | |
| ), | |
| }, | |
| } | |
| evidence = { | |
| "schema_version": "tinymind-self-dialogue-train-eval-v1", | |
| "model_name": "TinyMind PureField ReGenesis self-dialogue local trainer", | |
| "claim_scope": "self_dialogue_non_memorized_local_capability", | |
| "as_of": "2026-05-23", | |
| "local_evidence_complete": True, | |
| "training_mode": "oracle_generated_self_dialogue_not_answer_copying", | |
| "artifacts": { | |
| "checkpoint": str(checkpoint_path), | |
| "int4_artifact": str(int4_path), | |
| "dataset_manifest": str(out / "self_dialogue_manifest.json"), | |
| "objective_report": str(out / "self_dialogue_evidence.json"), | |
| }, | |
| "dataset_manifest": manifest, | |
| "train_eval": train_eval, | |
| "marker_eval": marker_eval, | |
| "memorization_guard": memorization_guard, | |
| "candidate_preference": preference, | |
| "generation_quality": generation_quality, | |
| "preference_optimization": preference_optimization, | |
| "generated_prompt_prefix_example": _prompt_prefix(eval_rows[0]) if eval_rows else "", | |
| "measurements": measurements, | |
| "comparisons": [], | |
| "limitations": [ | |
| "This proves local self-dialogue training/eval mechanics, not world-best external rank.", | |
| "The tiny CPU run is intentionally small; larger training is required for strong open-ended conversation.", | |
| ], | |
| "loss_improved": loss_improved, | |
| } | |
| evidence["claim_dossier"] = build_claim_dossier(evidence) | |
| evidence_path = out / "self_dialogue_evidence.json" | |
| evidence["evidence_path"] = str(evidence_path) | |
| _save_json(evidence_path, evidence) | |
| return evidence | |
Xet Storage Details
- Size:
- 17.2 kB
- Xet hash:
- 5c6ce0688a78f20d10c5ff4959785c213be3c6689dc6a32c73c884dfee2c10ed
·
Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.