| """ |
| Derived from Andrej Karpathy's nanochat project. |
| |
| MIT License |
| |
| Copyright (c) 2025 Andrej Karpathy |
| |
| Permission is hereby granted, free of charge, to any person obtaining a copy |
| of this software and associated documentation files (the "Software"), to deal |
| in the Software without restriction, including without limitation the rights |
| to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
| copies of the Software, and to permit persons to whom the Software is |
| furnished to do so, subject to the following conditions: |
| |
| The above copyright notice and this permission notice shall be included in all |
| copies or substantial portions of the Software. |
| """ |
|
|
| from __future__ import annotations |
|
|
| import argparse |
| import csv |
| from collections import defaultdict |
| import json |
| from pathlib import Path |
| import statistics |
|
|
| from dropout_decay.specs import DropoutCondition, ModelSpec |
|
|
|
|
| SUMMARY_FIELDS = [ |
| "run_mode", |
| "condition", |
| "condition_kind", |
| "stage", |
| "token_limit", |
| "model_name", |
| "n_layer", |
| "n_head", |
| "n_embd", |
| "parameters", |
| "dropout_initial", |
| "dropout_final", |
| "dropout_schedule", |
| "n", |
| "mean_train_eval_loss", |
| "std_train_eval_loss", |
| "mean_val_eval_loss", |
| "std_val_eval_loss", |
| "mean_generalization_gap", |
| "std_generalization_gap", |
| ] |
| SELECTION_FIELDS = [ |
| "run_mode", |
| "token_limit", |
| "model_name", |
| "n_layer", |
| "n_head", |
| "n_embd", |
| "parameters", |
| "n", |
| "best_dropout", |
| "best_val_loss", |
| "best_val_std", |
| "plateau_start_dropout", |
| "plateau_end_dropout", |
| "plateau_delta", |
| "zero_dropout_val_loss", |
| "zero_minus_best", |
| "best_nonzero_dropout", |
| "best_nonzero_val_loss", |
| "zero_minus_best_nonzero", |
| "max_dropout", |
| "max_dropout_val_loss", |
| "max_dropout_minus_best", |
| "has_nonzero_optimum", |
| "meets_target_dropout", |
| "curve_json", |
| ] |
|
|
|
|
| def write_jsonl_row(handle, row: dict) -> None: |
| handle.write(json.dumps(row, sort_keys=True) + "\n") |
| handle.flush() |
|
|
|
|
| def metric_key(row: dict) -> tuple: |
| return ( |
| row["run_mode"], |
| row["condition"], |
| row["condition_kind"], |
| row.get("stage"), |
| int(row["token_limit"]), |
| row["model_name"], |
| int(row["n_layer"]), |
| int(row["n_head"]), |
| int(row["n_embd"]), |
| int(row["seed"]), |
| float(row["dropout_initial"]), |
| float(row["dropout_final"]), |
| row["dropout_schedule"], |
| ) |
|
|
|
|
| def planned_metric_key( |
| *, |
| mode: str, |
| condition: DropoutCondition, |
| model_spec: ModelSpec, |
| seed: int, |
| token_limit: int, |
| stage: int | None = None, |
| ) -> tuple: |
| return ( |
| mode, |
| condition.name, |
| condition.kind, |
| stage, |
| int(token_limit), |
| model_spec.name, |
| int(model_spec.n_layer), |
| int(model_spec.n_head), |
| int(model_spec.n_embd), |
| int(seed), |
| float(condition.initial), |
| float(condition.final), |
| condition.schedule, |
| ) |
|
|
|
|
| def load_metrics(path: Path) -> list[dict]: |
| if not path.exists(): |
| return [] |
| rows = [] |
| for line in path.read_text(encoding="utf-8").splitlines(): |
| if line.strip(): |
| rows.append(json.loads(line)) |
| return rows |
|
|
|
|
| def summarize(rows: list[dict]) -> list[dict]: |
| groups: dict[tuple, list[dict]] = defaultdict(list) |
| for row in rows: |
| key = ( |
| row["run_mode"], |
| row["condition"], |
| row["condition_kind"], |
| row["stage"], |
| row["token_limit"], |
| row["model_name"], |
| row["n_layer"], |
| row["n_head"], |
| row["n_embd"], |
| row["parameters"], |
| row["dropout_initial"], |
| row["dropout_final"], |
| row["dropout_schedule"], |
| ) |
| groups[key].append(row) |
|
|
| summary: list[dict] = [] |
| for group_rows in groups.values(): |
| first = group_rows[0] |
| item = {field: first[field] for field in SUMMARY_FIELDS if field in first} |
| item["n"] = len(group_rows) |
| for source, mean_key, std_key in [ |
| ("train_eval_loss", "mean_train_eval_loss", "std_train_eval_loss"), |
| ("val_eval_loss", "mean_val_eval_loss", "std_val_eval_loss"), |
| ("generalization_gap", "mean_generalization_gap", "std_generalization_gap"), |
| ]: |
| values = [float(row[source]) for row in group_rows] |
| item[mean_key] = statistics.fmean(values) |
| item[std_key] = statistics.stdev(values) if len(values) > 1 else 0.0 |
| summary.append(item) |
| return sorted( |
| summary, |
| key=lambda row: ( |
| row["run_mode"], |
| row["model_name"], |
| row["token_limit"], |
| row["condition"], |
| row["stage"] or -1, |
| ), |
| ) |
|
|
|
|
| def write_csv(path: Path, rows: list[dict], fieldnames: list[str]) -> None: |
| with path.open("w", newline="", encoding="utf-8") as handle: |
| writer = csv.DictWriter(handle, fieldnames=fieldnames, extrasaction="ignore") |
| writer.writeheader() |
| writer.writerows(rows) |
|
|
|
|
| def build_model_selection(summary: list[dict], args: argparse.Namespace) -> list[dict]: |
| groups: dict[tuple, list[dict]] = defaultdict(list) |
| for row in summary: |
| if row["condition_kind"] == "static" and row["stage"] is None: |
| groups[(row["run_mode"], row["token_limit"], row["model_name"])].append(row) |
|
|
| selection: list[dict] = [] |
| for (run_mode, token_limit, model_name), rows in groups.items(): |
| curve = sorted(rows, key=lambda row: row["dropout_initial"]) |
| best = min(curve, key=lambda row: row["mean_val_eval_loss"]) |
| plateau = [ |
| row for row in curve |
| if row["mean_val_eval_loss"] <= best["mean_val_eval_loss"] + args.plateau_delta |
| ] |
| zero = next((row for row in curve if row["dropout_initial"] == 0.0), None) |
| nonzero = [row for row in curve if row["dropout_initial"] > 0.0] |
| best_nonzero = min(nonzero, key=lambda row: row["mean_val_eval_loss"]) if nonzero else None |
| max_dropout = max(curve, key=lambda row: row["dropout_initial"]) |
|
|
| zero_loss = zero["mean_val_eval_loss"] if zero else None |
| zero_minus_best = zero_loss - best["mean_val_eval_loss"] if zero_loss is not None else None |
| zero_minus_best_nonzero = ( |
| zero_loss - best_nonzero["mean_val_eval_loss"] |
| if zero_loss is not None and best_nonzero is not None |
| else None |
| ) |
| max_minus_best = max_dropout["mean_val_eval_loss"] - best["mean_val_eval_loss"] |
| has_nonzero_optimum = ( |
| best["dropout_initial"] > 0.0 |
| and zero_minus_best is not None |
| and zero_minus_best >= args.min_nonzero_margin |
| and max_minus_best >= args.min_high_dropout_margin |
| ) |
| curve_json = json.dumps( |
| [ |
| { |
| "dropout": row["dropout_initial"], |
| "mean_val_loss": row["mean_val_eval_loss"], |
| "std_val_loss": row["std_val_eval_loss"], |
| "mean_train_loss": row["mean_train_eval_loss"], |
| "mean_generalization_gap": row["mean_generalization_gap"], |
| "n": row["n"], |
| } |
| for row in curve |
| ], |
| sort_keys=True, |
| ) |
| selection.append( |
| { |
| "run_mode": run_mode, |
| "token_limit": token_limit, |
| "model_name": model_name, |
| "n_layer": best["n_layer"], |
| "n_head": best["n_head"], |
| "n_embd": best["n_embd"], |
| "parameters": best["parameters"], |
| "n": best["n"], |
| "best_dropout": best["dropout_initial"], |
| "best_val_loss": best["mean_val_eval_loss"], |
| "best_val_std": best["std_val_eval_loss"], |
| "plateau_start_dropout": min(row["dropout_initial"] for row in plateau), |
| "plateau_end_dropout": max(row["dropout_initial"] for row in plateau), |
| "plateau_delta": args.plateau_delta, |
| "zero_dropout_val_loss": zero_loss, |
| "zero_minus_best": zero_minus_best, |
| "best_nonzero_dropout": ( |
| best_nonzero["dropout_initial"] if best_nonzero else None |
| ), |
| "best_nonzero_val_loss": ( |
| best_nonzero["mean_val_eval_loss"] if best_nonzero else None |
| ), |
| "zero_minus_best_nonzero": zero_minus_best_nonzero, |
| "max_dropout": max_dropout["dropout_initial"], |
| "max_dropout_val_loss": max_dropout["mean_val_eval_loss"], |
| "max_dropout_minus_best": max_minus_best, |
| "has_nonzero_optimum": has_nonzero_optimum, |
| "meets_target_dropout": best["dropout_initial"] >= args.target_min_dropout, |
| "curve_json": curve_json, |
| } |
| ) |
| return sorted( |
| selection, |
| key=lambda row: ( |
| not row["has_nonzero_optimum"], |
| not row["meets_target_dropout"], |
| row["best_val_loss"], |
| ), |
| ) |
|
|