Mandeep Sidhu commited on
Commit ·
e7a7275
1
Parent(s): e4b41e9
Refactor experiment pipeline and add regime paper
Browse filesThis view is limited to 50 files because it contains too many changes. See raw diff
- .gitattributes +1 -0
- .gitignore +6 -0
- paper/dropout_decay_regime_specific_pressure_law.bbl +73 -0
- paper/dropout_decay_regime_specific_pressure_law.pdf +3 -0
- paper/dropout_decay_regime_specific_pressure_law.tex +427 -0
- paper/figures/calibration_cell_curves.tex +51 -0
- paper/figures/final_improvements.tex +23 -0
- paper/figures/l20_stage_deltas.tex +47 -0
- paper/figures/static_optima_by_regime.tex +66 -0
- paper/paper_results_summary.json +520 -0
- paper/references.bib +8 -0
- paper/tables/coefficient_table.tex +9 -0
- paper/tables/l20_stage_table.tex +23 -0
- paper/tables/streaming_validation_table.tex +12 -0
- runs/smoke_compat/locked_stream/20260605-224834/RESULT_SUMMARY.md +29 -0
- runs/smoke_compat/locked_stream/20260605-224834/config.json +103 -0
- runs/smoke_compat/locked_stream/20260605-224834/metrics.jsonl +4 -0
- runs/smoke_compat/locked_stream/20260605-224834/summary.csv +5 -0
- runs/smoke_compat/locked_stream/20260605-224834/summary.json +90 -0
- runs/smoke_compat/locked_stream/20260605-224834/trace.jsonl +8 -0
- runs/smoke_compat/screen_static/20260605-224821/RESULT_SUMMARY.md +24 -0
- runs/smoke_compat/screen_static/20260605-224821/config.json +89 -0
- runs/smoke_compat/screen_static/20260605-224821/dropout_curves.svg +24 -0
- runs/smoke_compat/screen_static/20260605-224821/metrics.jsonl +2 -0
- runs/smoke_compat/screen_static/20260605-224821/model_selection.csv +2 -0
- runs/smoke_compat/screen_static/20260605-224821/model_selection.json +29 -0
- runs/smoke_compat/screen_static/20260605-224821/summary.csv +3 -0
- runs/smoke_compat/screen_static/20260605-224821/summary.json +46 -0
- runs/smoke_compat/screen_static/20260605-224821/trace.jsonl +4 -0
- scripts/generate_paper_assets.py +579 -0
- src/dropout_decay/data/__init__.py +45 -0
- src/dropout_decay/datasets/__init__.py +41 -0
- src/dropout_decay/{data.py → datasets/corpus.py} +1 -1
- src/dropout_decay/experiment.py +5 -1686
- src/dropout_decay/experiments/__init__.py +25 -0
- src/dropout_decay/experiments/artifacts.py +281 -0
- src/dropout_decay/experiments/device.py +37 -0
- src/dropout_decay/experiments/parsing.py +148 -0
- src/dropout_decay/experiments/progress.py +66 -0
- src/dropout_decay/experiments/reports.py +448 -0
- src/dropout_decay/experiments/runner.py +555 -0
- src/dropout_decay/experiments/training.py +261 -0
- src/dropout_decay/model.py +21 -209
- src/dropout_decay/models/__init__.py +39 -0
- src/dropout_decay/models/gpt.py +227 -0
- src/dropout_decay/scheduler.py +5 -37
- src/dropout_decay/schedules/__init__.py +24 -0
- src/dropout_decay/schedules/dropout.py +56 -0
- src/dropout_decay/specs/__init__.py +26 -0
- src/dropout_decay/specs/dropout_condition.py +89 -0
.gitattributes
CHANGED
|
@@ -33,3 +33,4 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
|
|
| 33 |
*.zip filter=lfs diff=lfs merge=lfs -text
|
| 34 |
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 35 |
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
|
|
|
|
|
| 33 |
*.zip filter=lfs diff=lfs merge=lfs -text
|
| 34 |
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 35 |
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
| 36 |
+
paper/*.pdf filter=lfs diff=lfs merge=lfs -text
|
.gitignore
CHANGED
|
@@ -2,6 +2,12 @@
|
|
| 2 |
__pycache__/
|
| 3 |
*.py[cod]
|
| 4 |
*.pdf
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 5 |
*.npy
|
| 6 |
.venv/
|
| 7 |
*.egg-info/
|
|
|
|
| 2 |
__pycache__/
|
| 3 |
*.py[cod]
|
| 4 |
*.pdf
|
| 5 |
+
*.aux
|
| 6 |
+
*.blg
|
| 7 |
+
*.fdb_latexmk
|
| 8 |
+
*.fls
|
| 9 |
+
*.log
|
| 10 |
+
*.out
|
| 11 |
*.npy
|
| 12 |
.venv/
|
| 13 |
*.egg-info/
|
paper/dropout_decay_regime_specific_pressure_law.bbl
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
\begin{thebibliography}{8}
|
| 2 |
+
\providecommand{\natexlab}[1]{#1}
|
| 3 |
+
\providecommand{\url}[1]{\texttt{#1}}
|
| 4 |
+
\expandafter\ifx\csname urlstyle\endcsname\relax
|
| 5 |
+
\providecommand{\doi}[1]{doi: #1}\else
|
| 6 |
+
\providecommand{\doi}{doi: \begingroup \urlstyle{rm}\Url}\fi
|
| 7 |
+
|
| 8 |
+
\bibitem[Eldan and Li(2023)]{eldan2023tinystories}
|
| 9 |
+
Ronen Eldan and Yuanzhi Li.
|
| 10 |
+
\newblock Tinystories: How small can language models be and still speak
|
| 11 |
+
coherent english?
|
| 12 |
+
\newblock \emph{arXiv preprint arXiv:2305.07759}, 2023.
|
| 13 |
+
\newblock URL \url{https://arxiv.org/abs/2305.07759}.
|
| 14 |
+
|
| 15 |
+
\bibitem[Gokaslan et~al.(2019)Gokaslan, Cohen, Pavlick, and
|
| 16 |
+
Tellex]{gokaslan2019openwebtext}
|
| 17 |
+
Aaron Gokaslan, Vanya Cohen, Ellie Pavlick, and Stefanie Tellex.
|
| 18 |
+
\newblock Openwebtext corpus.
|
| 19 |
+
\newblock \url{https://zenodo.org/records/3834942}, 2019.
|
| 20 |
+
\newblock Open reproduction of the WebText-style corpus.
|
| 21 |
+
|
| 22 |
+
\bibitem[Hoffmann et~al.(2022)Hoffmann, Borgeaud, Mensch, Buchatskaya, Cai,
|
| 23 |
+
Rutherford, Casas, Hendricks, Welbl, Clark, Hennigan, Noland, Millican,
|
| 24 |
+
van~den Driessche, Damoc, Guy, Osindero, Simonyan, Elsen, Rae, Vinyals, and
|
| 25 |
+
Sifre]{hoffmann2022training}
|
| 26 |
+
Jordan Hoffmann, Sebastian Borgeaud, Arthur Mensch, Elena Buchatskaya, Trevor
|
| 27 |
+
Cai, Eliza Rutherford, Diego de~Las Casas, Lisa~Anne Hendricks, Johannes
|
| 28 |
+
Welbl, Aidan Clark, Tom Hennigan, Eric Noland, Katie Millican, George van~den
|
| 29 |
+
Driessche, Bogdan Damoc, Aurelia Guy, Simon Osindero, Karen Simonyan, Erich
|
| 30 |
+
Elsen, Jack~W. Rae, Oriol Vinyals, and Laurent Sifre.
|
| 31 |
+
\newblock Training compute-optimal large language models.
|
| 32 |
+
\newblock \emph{arXiv preprint arXiv:2203.15556}, 2022.
|
| 33 |
+
\newblock URL \url{https://arxiv.org/abs/2203.15556}.
|
| 34 |
+
|
| 35 |
+
\bibitem[Kaplan et~al.(2020)Kaplan, McCandlish, Henighan, Brown, Chess, Child,
|
| 36 |
+
Gray, Radford, Wu, and Amodei]{kaplan2020scaling}
|
| 37 |
+
Jared Kaplan, Sam McCandlish, Tom Henighan, Tom~B. Brown, Benjamin Chess, Rewon
|
| 38 |
+
Child, Scott Gray, Alec Radford, Jeffrey Wu, and Dario Amodei.
|
| 39 |
+
\newblock Scaling laws for neural language models.
|
| 40 |
+
\newblock \emph{arXiv preprint arXiv:2001.08361}, 2020.
|
| 41 |
+
\newblock URL \url{https://arxiv.org/abs/2001.08361}.
|
| 42 |
+
|
| 43 |
+
\bibitem[Karpathy(2025)]{karpathy_nanochat}
|
| 44 |
+
Andrej Karpathy.
|
| 45 |
+
\newblock nanochat.
|
| 46 |
+
\newblock \url{https://github.com/karpathy/nanochat}, 2025.
|
| 47 |
+
\newblock MIT-licensed repository, accessed 2026-05-29.
|
| 48 |
+
|
| 49 |
+
\bibitem[Merity et~al.(2017)Merity, Xiong, Bradbury, and
|
| 50 |
+
Socher]{merity2017pointer}
|
| 51 |
+
Stephen Merity, Caiming Xiong, James Bradbury, and Richard Socher.
|
| 52 |
+
\newblock Pointer sentinel mixture models.
|
| 53 |
+
\newblock In \emph{International Conference on Learning Representations}, 2017.
|
| 54 |
+
\newblock URL \url{https://arxiv.org/abs/1609.07843}.
|
| 55 |
+
|
| 56 |
+
\bibitem[Srivastava et~al.(2014)Srivastava, Hinton, Krizhevsky, Sutskever, and
|
| 57 |
+
Salakhutdinov]{srivastava2014dropout}
|
| 58 |
+
Nitish Srivastava, Geoffrey Hinton, Alex Krizhevsky, Ilya Sutskever, and Ruslan
|
| 59 |
+
Salakhutdinov.
|
| 60 |
+
\newblock Dropout: A simple way to prevent neural networks from overfitting.
|
| 61 |
+
\newblock \emph{Journal of Machine Learning Research}, 15\penalty0
|
| 62 |
+
(56):\penalty0 1929--1958, 2014.
|
| 63 |
+
\newblock URL \url{https://www.jmlr.org/papers/v15/srivastava14a.html}.
|
| 64 |
+
|
| 65 |
+
\bibitem[Vaswani et~al.(2017)Vaswani, Shazeer, Parmar, Uszkoreit, Jones, Gomez,
|
| 66 |
+
Kaiser, and Polosukhin]{vaswani2017attention}
|
| 67 |
+
Ashish Vaswani, Noam Shazeer, Niki Parmar, Jakob Uszkoreit, Llion Jones,
|
| 68 |
+
Aidan~N. Gomez, Lukasz Kaiser, and Illia Polosukhin.
|
| 69 |
+
\newblock Attention is all you need.
|
| 70 |
+
\newblock In \emph{Advances in Neural Information Processing Systems}, 2017.
|
| 71 |
+
\newblock URL \url{https://arxiv.org/abs/1706.03762}.
|
| 72 |
+
|
| 73 |
+
\end{thebibliography}
|
paper/dropout_decay_regime_specific_pressure_law.pdf
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:46ba08475ba2566bc3733eed5403db061a3ce4145ed6bd79965796719a396bc8
|
| 3 |
+
size 267426
|
paper/dropout_decay_regime_specific_pressure_law.tex
ADDED
|
@@ -0,0 +1,427 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
\documentclass[11pt]{article}
|
| 2 |
+
|
| 3 |
+
\usepackage[margin=1in]{geometry}
|
| 4 |
+
\usepackage{amsmath}
|
| 5 |
+
\usepackage{amssymb}
|
| 6 |
+
\usepackage{booktabs}
|
| 7 |
+
\usepackage{graphicx}
|
| 8 |
+
\usepackage{microtype}
|
| 9 |
+
\usepackage{tikz}
|
| 10 |
+
\usepackage{pgfplots}
|
| 11 |
+
\usepgfplotslibrary{groupplots}
|
| 12 |
+
\pgfplotsset{compat=1.18}
|
| 13 |
+
\usepackage[numbers,sort&compress]{natbib}
|
| 14 |
+
\usepackage[colorlinks=true,linkcolor=blue,citecolor=blue,urlcolor=blue]{hyperref}
|
| 15 |
+
|
| 16 |
+
\title{Regime-Specific Pressure Laws for Dropout Decay in Streaming Language Model Training}
|
| 17 |
+
\author{Anonymous Authors}
|
| 18 |
+
\date{June 2026}
|
| 19 |
+
|
| 20 |
+
\newcommand{\clamp}{\operatorname{clamp}}
|
| 21 |
+
\newcommand{\params}{P}
|
| 22 |
+
\newcommand{\unique}{U_t}
|
| 23 |
+
\newcommand{\sampled}{C_t}
|
| 24 |
+
\newcommand{\dropout}{p_t}
|
| 25 |
+
|
| 26 |
+
\begin{document}
|
| 27 |
+
\maketitle
|
| 28 |
+
|
| 29 |
+
\begin{abstract}
|
| 30 |
+
Dropout is usually selected as a fixed hyperparameter before training, but
|
| 31 |
+
streaming language-model training changes the regularization problem over time:
|
| 32 |
+
early stages expose few unique tokens to a comparatively large model, while
|
| 33 |
+
later stages reveal more data and reduce the need for strong stochastic
|
| 34 |
+
regularization. We study this setting with nanochat-derived causal
|
| 35 |
+
Transformers trained under an expanding-prefix protocol on three text regimes:
|
| 36 |
+
OpenWebText10K, TinyStories, and WikiText-103. Static dropout sweeps show a
|
| 37 |
+
repeatable pattern: for a fixed model, the validation-loss curve over dropout
|
| 38 |
+
is typically unimodal or plateau-shaped, and the optimum falls as unique data
|
| 39 |
+
increases; for a fixed prefix, the optimum rises with model capacity. We turn
|
| 40 |
+
these per-cell optima into a regime-specific pressure law,
|
| 41 |
+
\[
|
| 42 |
+
\dropout=\clamp(p_{\min},p_{\max},
|
| 43 |
+
A\log_{10}(\params/\unique)
|
| 44 |
+
+B\log_{10}(\sampled/\unique)
|
| 45 |
+
+D\log_{10}(\params/\unique)\log_{10}(\sampled/\unique)+C_0),
|
| 46 |
+
\]
|
| 47 |
+
where $\params$ is parameter count, $\unique$ is currently available unique
|
| 48 |
+
tokens, and $\sampled$ is cumulative sampled training tokens. The fitted
|
| 49 |
+
interaction law explains static optimum cells with mean absolute errors of
|
| 50 |
+
0.0158, 0.0180, and 0.0162 dropout on OpenWebText10K, TinyStories, and
|
| 51 |
+
WikiText-103 respectively. In locked streaming validation, fitted or
|
| 52 |
+
formula-derived decay schedules beat the best fixed-dropout baseline on final
|
| 53 |
+
validation loss in all three regimes at 4M-token validation scale and again in
|
| 54 |
+
larger 51.84M-parameter, 8M-token extrapolations. The large extrapolation tier
|
| 55 |
+
wins all paired seeds across all three regimes (9/9 paired wins), but stage-wise
|
| 56 |
+
analysis shows the schedules are final-loss optimized rather than best at every
|
| 57 |
+
intermediate prefix. The results support a practical hypothesis: dropout decay
|
| 58 |
+
should be calibrated as a regime-specific pressure law, not chosen as a
|
| 59 |
+
universal constant or an arbitrary high-to-low schedule.
|
| 60 |
+
\end{abstract}
|
| 61 |
+
|
| 62 |
+
\section{Introduction}
|
| 63 |
+
|
| 64 |
+
Dropout was introduced as a simple and effective regularizer for neural
|
| 65 |
+
networks \citep{srivastava2014dropout}. In language model practice, however,
|
| 66 |
+
dropout is often treated as a fixed scalar selected before a run. That choice
|
| 67 |
+
is mismatched to streaming settings. When a model begins training on a small
|
| 68 |
+
prefix of a stream, its parameter count can be large relative to the number of
|
| 69 |
+
unique tokens currently available; later, the same model receives more unique
|
| 70 |
+
data and can safely expose more functional capacity. A fixed dropout rate must
|
| 71 |
+
therefore compromise between early overfitting risk and late underfitting risk.
|
| 72 |
+
|
| 73 |
+
The motivating hypothesis of this project was intentionally ambitious: a
|
| 74 |
+
high-capacity model could start with strong regularization on small initial
|
| 75 |
+
data and then decay dropout as streaming data accumulates, allowing capacity to
|
| 76 |
+
come online without catastrophic overfitting. The experiments refined that
|
| 77 |
+
hypothesis. Extremely high initial dropout is not automatically useful; early
|
| 78 |
+
tests with dropout near 0.8 underfit badly. The stronger and better-supported
|
| 79 |
+
claim is that useful dropout is a function of training pressure: model capacity
|
| 80 |
+
relative to the current data prefix, and cumulative optimizer sampling relative
|
| 81 |
+
to that prefix.
|
| 82 |
+
|
| 83 |
+
This paper makes four empirical contributions.
|
| 84 |
+
|
| 85 |
+
\begin{enumerate}
|
| 86 |
+
\item We define calibration cells for streaming dropout: one model, one
|
| 87 |
+
unique-token prefix, one sampled-token budget, and a sweep over fixed
|
| 88 |
+
dropout rates.
|
| 89 |
+
\item We show that calibration cells often have a parabolic or plateau-like
|
| 90 |
+
validation-loss curve over dropout, making the local optimum a usable
|
| 91 |
+
supervised target.
|
| 92 |
+
\item We fit a regime-specific interaction pressure law that maps
|
| 93 |
+
$(\params,\unique,\sampled)$ to a dropout rate.
|
| 94 |
+
\item We validate the resulting schedules against broad fixed-dropout
|
| 95 |
+
baselines across three text regimes and a larger-model/larger-stream
|
| 96 |
+
extrapolation tier.
|
| 97 |
+
\end{enumerate}
|
| 98 |
+
|
| 99 |
+
The central result is not that one coefficient vector works everywhere. It does
|
| 100 |
+
not. The central result is that the pressure-law structure is useful, while the
|
| 101 |
+
coefficients are regime-specific.
|
| 102 |
+
|
| 103 |
+
\section{Related Work}
|
| 104 |
+
|
| 105 |
+
\paragraph{Dropout and regularization.}
|
| 106 |
+
Dropout applies multiplicative Bernoulli noise during training and can be
|
| 107 |
+
understood as an approximate ensemble over subnetworks
|
| 108 |
+
\citep{srivastava2014dropout}. With inverted dropout, an activation $h_i$ is
|
| 109 |
+
transformed as $h'_i=h_i m_i/(1-p)$, $m_i\sim\operatorname{Bernoulli}(1-p)$.
|
| 110 |
+
The expectation is preserved, but the variance grows as $h_i^2p/(1-p)$. This
|
| 111 |
+
captures the key tradeoff in our experiments: low dropout can overfit repeated
|
| 112 |
+
small prefixes, but high dropout can remove too much usable capacity.
|
| 113 |
+
|
| 114 |
+
\paragraph{Transformer language models and scaling.}
|
| 115 |
+
Our model family is a decoder-only Transformer following the attention-based
|
| 116 |
+
architecture of \citet{vaswani2017attention}. Scaling-law work shows that loss
|
| 117 |
+
varies systematically with model size, data size, and compute
|
| 118 |
+
\citep{kaplan2020scaling,hoffmann2022training}. Our study is narrower: it does
|
| 119 |
+
not propose a global loss scaling law. Instead, it asks whether a
|
| 120 |
+
regularization hyperparameter can be mapped to local pressure variables during
|
| 121 |
+
streaming.
|
| 122 |
+
|
| 123 |
+
\paragraph{Text regimes.}
|
| 124 |
+
We evaluate on three text regimes. TinyStories is a synthetic simple-English
|
| 125 |
+
story corpus designed for small language-model studies \citep{eldan2023tinystories}.
|
| 126 |
+
WikiText-103 is a standard word-level language-modeling benchmark derived from
|
| 127 |
+
Wikipedia articles \citep{merity2017pointer}. OpenWebText is an open
|
| 128 |
+
WebText-style corpus \citep{gokaslan2019openwebtext}. We use cached/tokenized
|
| 129 |
+
subsets and therefore report results as regime-level evidence rather than
|
| 130 |
+
claims about full-corpus state of the art.
|
| 131 |
+
|
| 132 |
+
\section{Experimental Setting}
|
| 133 |
+
|
| 134 |
+
\subsection{Model and implementation}
|
| 135 |
+
|
| 136 |
+
All experiments use a standalone research codebase derived from Andrej
|
| 137 |
+
Karpathy's MIT-licensed nanochat repository \citep{karpathy_nanochat}. Chat
|
| 138 |
+
interfaces and deployment scripts are removed; the retained components are the
|
| 139 |
+
core BPE tokenizer and causal Transformer template. Derived source files retain
|
| 140 |
+
the original copyright and permission notice.
|
| 141 |
+
|
| 142 |
+
The architecture family keeps the standard Transformer feed-forward expansion
|
| 143 |
+
ratio of four. The central model sizes are L8-H8-D256 (8.39M parameters),
|
| 144 |
+
L12-H8-D320 (17.37M), L16-H8-D384 (31.46M or 32.71M depending on
|
| 145 |
+
vocabulary/run metadata), and L20-H8-D448 (51.84M). The tokenizer vocabulary is
|
| 146 |
+
4096 in the reported runs. Unless noted otherwise, training uses AdamW,
|
| 147 |
+
learning rate $3\times 10^{-4}$, weight decay 0.1, gradient clipping 1.0, batch
|
| 148 |
+
size 16, and block size 128. All Torch training runs are MPS-only; the runner
|
| 149 |
+
refuses CUDA, CPU execution, and MPS fallback.
|
| 150 |
+
|
| 151 |
+
\subsection{Streaming protocol}
|
| 152 |
+
|
| 153 |
+
We simulate streaming by revealing larger token prefixes over time. A locked
|
| 154 |
+
streaming run keeps the same model and optimizer state across stages. At stage
|
| 155 |
+
$t$, minibatches are sampled only from the currently revealed prefix
|
| 156 |
+
$\unique$. The two main protocols are:
|
| 157 |
+
|
| 158 |
+
\begin{itemize}
|
| 159 |
+
\item 4M validation: OpenWebText10K and WikiText-103 use 250k, 500k, 1M,
|
| 160 |
+
2M, and 4M prefixes. TinyStories uses 500k, 1M, 2M, and 4M prefixes. These
|
| 161 |
+
runs use five random seeds.
|
| 162 |
+
\item 8M extrapolation: L20-H8-D448 with an 8M final prefix. These runs use
|
| 163 |
+
three seeds because they are substantially more expensive.
|
| 164 |
+
\end{itemize}
|
| 165 |
+
|
| 166 |
+
Each run compares a dropout schedule against a grid of static-dropout
|
| 167 |
+
baselines. The best static baseline is selected after training from the same
|
| 168 |
+
run family, so the decay schedule competes against a strong fixed-rate oracle
|
| 169 |
+
within the tested grid.
|
| 170 |
+
|
| 171 |
+
\section{From Calibration Cells to a Pressure Law}
|
| 172 |
+
|
| 173 |
+
\subsection{Calibration cell definition}
|
| 174 |
+
|
| 175 |
+
A calibration cell is one static dropout sweep at a fixed tuple:
|
| 176 |
+
\[
|
| 177 |
+
(\text{model architecture}, \params, \unique, \sampled, \text{validation split}).
|
| 178 |
+
\]
|
| 179 |
+
Within that cell, we train/evaluate several fixed dropout values and obtain a
|
| 180 |
+
curve
|
| 181 |
+
\[
|
| 182 |
+
p \mapsto L_{\mathrm{val}}(p \mid \params,\unique,\sampled).
|
| 183 |
+
\]
|
| 184 |
+
The target dropout $p^\star$ is the best observed grid point or, when the best
|
| 185 |
+
point is bracketed by worse neighbors, a local quadratic interpolation of the
|
| 186 |
+
minimum. Boundary optima are retained but downweighted in coefficient fitting
|
| 187 |
+
because the true optimum may lie outside the tested grid.
|
| 188 |
+
|
| 189 |
+
Figure~\ref{fig:cell-curves} shows representative cells. The curves are not
|
| 190 |
+
perfect mathematical parabolas, but they show the practical pattern needed for
|
| 191 |
+
calibration: validation loss improves as dropout moves away from zero, reaches
|
| 192 |
+
a local minimum or plateau, and then rises when dropout becomes too large.
|
| 193 |
+
|
| 194 |
+
\begin{figure}[t]
|
| 195 |
+
\centering
|
| 196 |
+
\input{figures/calibration_cell_curves}
|
| 197 |
+
\caption{Representative static dropout calibration cells. The y-axis is
|
| 198 |
+
validation loss above the best tested dropout in that cell, so each curve is
|
| 199 |
+
normalized to show shape rather than absolute corpus difficulty.}
|
| 200 |
+
\label{fig:cell-curves}
|
| 201 |
+
\end{figure}
|
| 202 |
+
|
| 203 |
+
\subsection{Pressure variables}
|
| 204 |
+
|
| 205 |
+
The formula uses two dimensionless pressure variables:
|
| 206 |
+
\[
|
| 207 |
+
x_t=\log_{10}(\params/\unique), \qquad
|
| 208 |
+
y_t=\log_{10}(\sampled/\unique).
|
| 209 |
+
\]
|
| 210 |
+
The first term measures capacity pressure. Increasing parameters at fixed
|
| 211 |
+
prefix increases memorization capacity and tends to increase useful dropout.
|
| 212 |
+
Increasing unique tokens at fixed model reduces this pressure. The second term
|
| 213 |
+
measures repeated-sampling pressure. If the optimizer consumes many sampled
|
| 214 |
+
tokens while the unique prefix remains small, it repeatedly revisits the same
|
| 215 |
+
data and can benefit from stronger regularization.
|
| 216 |
+
|
| 217 |
+
The fitted interaction family is
|
| 218 |
+
\begin{equation}
|
| 219 |
+
\dropout=\clamp(p_{\min},p_{\max},
|
| 220 |
+
A x_t+B y_t+D x_t y_t+C_0).
|
| 221 |
+
\label{eq:interaction-law}
|
| 222 |
+
\end{equation}
|
| 223 |
+
For schedule generation we use $p_{\min}=0.02$ and $p_{\max}=0.65$ unless a
|
| 224 |
+
run explicitly specifies a different clamp. Static sweeps may still include
|
| 225 |
+
0.0 as an ablation.
|
| 226 |
+
|
| 227 |
+
\subsection{Regime-specific coefficients}
|
| 228 |
+
|
| 229 |
+
We define a regime as the combination of architecture family, tokenizer, corpus
|
| 230 |
+
family, optimizer protocol, dropout placement, stream protocol, and evaluation
|
| 231 |
+
distribution. Model size and prefix size are not regime changes; they are
|
| 232 |
+
inputs to the formula. Corpus family, tokenizer, or optimizer changes can shift
|
| 233 |
+
the coefficients.
|
| 234 |
+
|
| 235 |
+
Table~\ref{tab:coefficients} gives the fitted interaction coefficients. Each
|
| 236 |
+
regime uses 15 or 16 calibration cells. The fitted mean absolute error is about
|
| 237 |
+
0.016--0.018 dropout, meaning the coefficient law typically predicts the
|
| 238 |
+
calibrated static optimum to within roughly two hundredths of dropout.
|
| 239 |
+
|
| 240 |
+
\begin{table}[t]
|
| 241 |
+
\centering
|
| 242 |
+
\caption{Regime-specific interaction coefficient fits. L-model and L-prefix
|
| 243 |
+
are leave-model-out and leave-prefix-out mean absolute errors.}
|
| 244 |
+
\label{tab:coefficients}
|
| 245 |
+
\resizebox{\textwidth}{!}{\input{tables/coefficient_table}}
|
| 246 |
+
\end{table}
|
| 247 |
+
|
| 248 |
+
Figure~\ref{fig:static-optima} shows the fitted target dropout values from the
|
| 249 |
+
calibration cells. Across regimes, useful dropout generally decreases as the
|
| 250 |
+
unique prefix grows. At a fixed prefix, larger models tend to require larger
|
| 251 |
+
dropout. The absolute surface differs by corpus regime, which is why the paper
|
| 252 |
+
does not propose universal coefficients.
|
| 253 |
+
|
| 254 |
+
\begin{figure}[t]
|
| 255 |
+
\centering
|
| 256 |
+
\input{figures/static_optima_by_regime}
|
| 257 |
+
\caption{Calibration targets by regime. At fixed model size, target dropout
|
| 258 |
+
tends to fall as more unique tokens are available; at fixed prefix, larger
|
| 259 |
+
models tend to have higher target dropout. WikiText-103 reaches the dropout
|
| 260 |
+
floor earlier than the other regimes under its calibration protocol.}
|
| 261 |
+
\label{fig:static-optima}
|
| 262 |
+
\end{figure}
|
| 263 |
+
|
| 264 |
+
\section{Streaming Results}
|
| 265 |
+
|
| 266 |
+
\subsection{Main validation}
|
| 267 |
+
|
| 268 |
+
Table~\ref{tab:streaming-validation} summarizes the main streaming results.
|
| 269 |
+
The ``Gain'' column is best static final validation loss minus decay final
|
| 270 |
+
validation loss, so positive values favor decay. The best static baseline is
|
| 271 |
+
chosen from the tested fixed-dropout grid in the same run family.
|
| 272 |
+
|
| 273 |
+
\begin{table}[t]
|
| 274 |
+
\centering
|
| 275 |
+
\caption{Streaming validation against best fixed-dropout baselines.}
|
| 276 |
+
\label{tab:streaming-validation}
|
| 277 |
+
\resizebox{\textwidth}{!}{\input{tables/streaming_validation_table}}
|
| 278 |
+
\end{table}
|
| 279 |
+
|
| 280 |
+
OpenWebText10K provides the cleanest five-seed coefficient-derived validation:
|
| 281 |
+
the interaction schedule improves final validation loss by 0.0473 over the
|
| 282 |
+
best static baseline and wins all five paired seeds. TinyStories shows a
|
| 283 |
+
smaller but consistent final-loss margin: 0.0133 over the best static baseline
|
| 284 |
+
with five paired wins. WikiText-103 is more nuanced. The earlier
|
| 285 |
+
formula-derived L12 schedule wins the five-seed 4M run, but the later
|
| 286 |
+
static-cell-fitted interaction schedule decays too quickly for L12 and is only
|
| 287 |
+
near the best static baseline. This negative result is important: a good static
|
| 288 |
+
cell fit does not by itself guarantee the best streaming schedule unless the
|
| 289 |
+
schedule objective is aligned with final-loss optimization.
|
| 290 |
+
|
| 291 |
+
\subsection{L20 8M extrapolation}
|
| 292 |
+
|
| 293 |
+
The strongest comparable evidence is the L20 extrapolation tier. Here the
|
| 294 |
+
model is fixed to L20-H8-D448 with 51.84M parameters, and each regime uses
|
| 295 |
+
its fitted interaction coefficients to produce a frozen schedule before
|
| 296 |
+
training. No coefficients are refit on the L20 results. Across OpenWebText10K,
|
| 297 |
+
TinyStories, and WikiText-103, the decay schedule beats the best static
|
| 298 |
+
baseline on final validation loss and wins all paired seeds (3/3 in each
|
| 299 |
+
regime, 9/9 total).
|
| 300 |
+
|
| 301 |
+
Figure~\ref{fig:final-improvements} visualizes the final-loss gains. The
|
| 302 |
+
largest L20 gain is OpenWebText10K (0.0363), followed by WikiText-103 (0.0184)
|
| 303 |
+
and TinyStories (0.0100). The TinyStories gain is smaller because a static
|
| 304 |
+
dropout near 0.20 is already a strong fixed compromise for that regime.
|
| 305 |
+
|
| 306 |
+
\begin{figure}[t]
|
| 307 |
+
\centering
|
| 308 |
+
\input{figures/final_improvements}
|
| 309 |
+
\caption{Final validation-loss improvement over the best fixed-dropout
|
| 310 |
+
baseline. Positive values favor the decay schedule.}
|
| 311 |
+
\label{fig:final-improvements}
|
| 312 |
+
\end{figure}
|
| 313 |
+
|
| 314 |
+
\subsection{Stage-wise behavior}
|
| 315 |
+
|
| 316 |
+
Final-loss improvement does not imply per-prefix dominance. Table~\ref{tab:l20-stage}
|
| 317 |
+
and Figure~\ref{fig:l20-stage-deltas} compare the L20 schedule against the best
|
| 318 |
+
static baseline at each stage. OpenWebText10K starts worse than static 0.30 at
|
| 319 |
+
250k but becomes better from 1M onward. TinyStories is nearly tied early,
|
| 320 |
+
slightly worse at 1M/2M, then better at 4M/8M. WikiText-103 is worse through
|
| 321 |
+
the middle of the stream and wins only at the final 8M prefix.
|
| 322 |
+
|
| 323 |
+
\begin{table}[t]
|
| 324 |
+
\centering
|
| 325 |
+
\caption{L20 stage-wise comparison. Delta is decay validation loss minus the
|
| 326 |
+
best static validation loss at the same prefix; negative favors decay.}
|
| 327 |
+
\label{tab:l20-stage}
|
| 328 |
+
\resizebox{\textwidth}{!}{\input{tables/l20_stage_table}}
|
| 329 |
+
\end{table}
|
| 330 |
+
|
| 331 |
+
\begin{figure}[t]
|
| 332 |
+
\centering
|
| 333 |
+
\input{figures/l20_stage_deltas}
|
| 334 |
+
\caption{L20 stage-wise deltas versus the best static dropout at each prefix.
|
| 335 |
+
The schedules are final-loss schedules, not greedy per-prefix optima.}
|
| 336 |
+
\label{fig:l20-stage-deltas}
|
| 337 |
+
\end{figure}
|
| 338 |
+
|
| 339 |
+
\section{Patterns and Interpretation}
|
| 340 |
+
|
| 341 |
+
\paragraph{Moderate high dropout, not massive high dropout.}
|
| 342 |
+
The earliest experiments tested very large dropout and failed. A static 0.8
|
| 343 |
+
dropout stream underfit badly, and a high-dropout decay also lost to moderate
|
| 344 |
+
static dropout. This falsified the naive version of the hypothesis. The
|
| 345 |
+
successful schedules start high only when the pressure law predicts high
|
| 346 |
+
dropout, and high means roughly 0.25--0.45 in the L20 extrapolations, not 0.8.
|
| 347 |
+
|
| 348 |
+
\paragraph{Dropout optima move with pressure.}
|
| 349 |
+
The calibration cells support the core mechanism. For the same model, target
|
| 350 |
+
dropout declines as the unique prefix expands. For the same prefix, target
|
| 351 |
+
dropout generally increases with parameter count. This is consistent with the
|
| 352 |
+
view that dropout is compensating for capacity pressure and repeated sampling.
|
| 353 |
+
|
| 354 |
+
\paragraph{The interaction term matters.}
|
| 355 |
+
The interaction term $xy$ captures the fact that model/data pressure and
|
| 356 |
+
sampled-token pressure are not independent. For example, repeated sampling is
|
| 357 |
+
more dangerous when a large model sees a small prefix than when a smaller model
|
| 358 |
+
sees the same prefix. In WikiText-103, the interaction form reduces calibration
|
| 359 |
+
MAE from 0.0449 for the first-order base formula to 0.0162. Similar interaction
|
| 360 |
+
fits are used for OpenWebText10K and TinyStories.
|
| 361 |
+
|
| 362 |
+
\paragraph{Regime specificity is real.}
|
| 363 |
+
The coefficients differ substantially across regimes. TinyStories needs
|
| 364 |
+
different signs and offsets than OpenWebText10K, and WikiText-103 reaches low
|
| 365 |
+
dropout optima earlier under its calibration protocol. This is not a weakness
|
| 366 |
+
of the pressure-law structure; it is evidence that the corpus/tokenizer/eval
|
| 367 |
+
distribution defines the coefficient regime.
|
| 368 |
+
|
| 369 |
+
\paragraph{Final-loss schedules can sacrifice early local optimality.}
|
| 370 |
+
The L20 stage-wise analysis shows that a schedule can lose to a fixed static
|
| 371 |
+
dropout at early or middle prefixes and still win final loss. This is expected
|
| 372 |
+
if the goal is to train one model continuously through a growing stream, not to
|
| 373 |
+
restart and optimize each prefix independently. Static baselines can be locally
|
| 374 |
+
optimal at one prefix but cannot adapt when the stream changes.
|
| 375 |
+
|
| 376 |
+
\section{Limitations}
|
| 377 |
+
|
| 378 |
+
The experiments are intentionally small compared with frontier language-model
|
| 379 |
+
training. The largest model has 51.84M parameters and the largest stream prefix
|
| 380 |
+
has 8M tokens. All training uses Apple MPS, short sequence length, and a 4096
|
| 381 |
+
vocabulary. We therefore claim a reproducible small-scale phenomenon, not a
|
| 382 |
+
confirmed law at billion-parameter scale.
|
| 383 |
+
|
| 384 |
+
The static coefficient fit optimizes dropout targets extracted from static
|
| 385 |
+
sweeps, while the streaming objective is final validation loss after continued
|
| 386 |
+
training. WikiText-103 shows that these objectives can diverge: the fitted
|
| 387 |
+
interaction schedule is excellent as a static optimum model but decays too
|
| 388 |
+
quickly for L12 streaming. Future work should fit coefficients directly against
|
| 389 |
+
streaming objective functions or include trajectory-aware loss terms.
|
| 390 |
+
|
| 391 |
+
The L20 extrapolation tier uses three seeds for cost reasons. This is balanced
|
| 392 |
+
across all three regimes and supported by five-seed smaller-stream validation,
|
| 393 |
+
but larger seed counts would narrow confidence intervals. We also do not claim
|
| 394 |
+
that the tested fixed-dropout grids contain every possible fixed optimum.
|
| 395 |
+
|
| 396 |
+
\section{Reproducibility}
|
| 397 |
+
|
| 398 |
+
The repository is standalone: cached corpora/tokenizers, saved metrics,
|
| 399 |
+
coefficient files, scripts, and reports are stored in the project. The main
|
| 400 |
+
training entry point is \texttt{scripts/run\_experiments.py}; coefficient
|
| 401 |
+
fitting uses \texttt{scripts/fit\_dropout\_coefficients.py}; this manuscript's
|
| 402 |
+
figures and tables are generated by
|
| 403 |
+
\texttt{scripts/generate\_paper\_assets.py}. Existing result artifacts are read
|
| 404 |
+
from \texttt{runs/}. The experiment runner enforces MPS-only Torch execution.
|
| 405 |
+
|
| 406 |
+
All generated tables in this paper are derived from saved metrics JSONL files
|
| 407 |
+
and coefficient JSON files. The paper-generation summary is written to
|
| 408 |
+
\texttt{paper/paper\_results\_summary.json}.
|
| 409 |
+
|
| 410 |
+
\section{Conclusion}
|
| 411 |
+
|
| 412 |
+
The evidence supports a practical, falsifiable hypothesis: in streaming
|
| 413 |
+
language-model training, dropout should decay according to a regime-specific
|
| 414 |
+
pressure law over model size, currently available unique tokens, and cumulative
|
| 415 |
+
sampled tokens. The hypothesis is stronger than ``larger dropout always helps''
|
| 416 |
+
and weaker than ``one universal formula works everywhere.'' Static calibration
|
| 417 |
+
cells reveal local dropout optima; interaction pressure laws fit those optima
|
| 418 |
+
with low error; and the resulting schedules improve final validation loss over
|
| 419 |
+
the best tested fixed-dropout baselines across three regimes, including a
|
| 420 |
+
larger-model 8M-token extrapolation tier. The next scientific step is to test
|
| 421 |
+
the same fitting procedure at larger scale and to optimize coefficients against
|
| 422 |
+
streaming final-loss objectives directly.
|
| 423 |
+
|
| 424 |
+
\bibliographystyle{plainnat}
|
| 425 |
+
\bibliography{references}
|
| 426 |
+
|
| 427 |
+
\end{document}
|
paper/figures/calibration_cell_curves.tex
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
\begin{tikzpicture}
|
| 2 |
+
\begin{groupplot}[
|
| 3 |
+
group style={group size=3 by 1, horizontal sep=1.0cm},
|
| 4 |
+
width=0.32\textwidth,
|
| 5 |
+
height=0.26\textwidth,
|
| 6 |
+
legend style={font=\scriptsize},
|
| 7 |
+
tick label style={font=\scriptsize},
|
| 8 |
+
label style={font=\scriptsize},
|
| 9 |
+
title style={font=\scriptsize},
|
| 10 |
+
]
|
| 11 |
+
|
| 12 |
+
\nextgroupplot[
|
| 13 |
+
title={OpenWebText10K, L12, 1M},
|
| 14 |
+
xlabel={Dropout},
|
| 15 |
+
ylabel={Validation loss above best},
|
| 16 |
+
ymin=0,
|
| 17 |
+
grid=both,
|
| 18 |
+
]
|
| 19 |
+
\addplot+[thick, mark=*] coordinates {(0,0.64089) (0.02,0.41177) (0.05,0.21561) (0.08,0.10277) (0.1,0.06467) (0.14,0.02993) (0.2,0.00000) (0.3,0.03437) (0.4,0.09347) (0.5,0.21260) (0.6,0.37234) (0.7,0.57061) (0.8,0.88258) (0.9,2.06141)};
|
| 20 |
+
\addlegendentry{static sweep}
|
| 21 |
+
\addplot+[only marks, mark=star, mark size=3pt] coordinates {(0.2,0)};
|
| 22 |
+
\addlegendentry{target $p^\star=0.20$}
|
| 23 |
+
|
| 24 |
+
|
| 25 |
+
\nextgroupplot[
|
| 26 |
+
title={TinyStories, L12, 1M},
|
| 27 |
+
xlabel={Dropout},
|
| 28 |
+
ylabel={Validation loss above best},
|
| 29 |
+
ymin=0,
|
| 30 |
+
grid=both,
|
| 31 |
+
]
|
| 32 |
+
\addplot+[thick, mark=*] coordinates {(0,0.40226) (0.04,0.13035) (0.1,0.02502) (0.2,0.00000) (0.35,0.09066) (0.5,0.22441)};
|
| 33 |
+
\addlegendentry{static sweep}
|
| 34 |
+
\addplot+[only marks, mark=star, mark size=3pt] coordinates {(0.2,0)};
|
| 35 |
+
\addlegendentry{target $p^\star=0.20$}
|
| 36 |
+
|
| 37 |
+
|
| 38 |
+
\nextgroupplot[
|
| 39 |
+
title={WikiText-103, L12, 250k},
|
| 40 |
+
xlabel={Dropout},
|
| 41 |
+
ylabel={Validation loss above best},
|
| 42 |
+
ymin=0,
|
| 43 |
+
grid=both,
|
| 44 |
+
]
|
| 45 |
+
\addplot+[thick, mark=*] coordinates {(0,0.17165) (0.02,0.10888) (0.04,0.07189) (0.06,0.04641) (0.08,0.02649) (0.1,0.00897) (0.14,0.00363) (0.18,0.00000) (0.2,0.01021) (0.26,0.03365) (0.3,0.04794) (0.4,0.13084)};
|
| 46 |
+
\addlegendentry{static sweep}
|
| 47 |
+
\addplot+[only marks, mark=star, mark size=3pt] coordinates {(0.18,0)};
|
| 48 |
+
\addlegendentry{target $p^\star=0.18$}
|
| 49 |
+
|
| 50 |
+
\end{groupplot}
|
| 51 |
+
\end{tikzpicture}
|
paper/figures/final_improvements.tex
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
|
| 2 |
+
\begin{tikzpicture}
|
| 3 |
+
\begin{axis}[
|
| 4 |
+
ybar,
|
| 5 |
+
bar width=8pt,
|
| 6 |
+
width=0.72\textwidth,
|
| 7 |
+
height=0.34\textwidth,
|
| 8 |
+
ymin=0,
|
| 9 |
+
ylabel={Best static final loss $-$ decay final loss},
|
| 10 |
+
symbolic x coords={OWT10K,TinyStories,WikiText},
|
| 11 |
+
xtick=data,
|
| 12 |
+
xtick={OWT10K,TinyStories,WikiText},
|
| 13 |
+
grid=both,
|
| 14 |
+
legend style={font=\scriptsize, at={(0.5,1.04)}, anchor=south, legend columns=2},
|
| 15 |
+
tick label style={font=\scriptsize},
|
| 16 |
+
label style={font=\scriptsize},
|
| 17 |
+
]
|
| 18 |
+
\addplot coordinates {(OWT10K,0.04732) (TinyStories,0.01326) (WikiText,0.02966)};
|
| 19 |
+
\addlegendentry{4M validation}
|
| 20 |
+
\addplot coordinates {(OWT10K,0.03631) (TinyStories,0.01000) (WikiText,0.01838)};
|
| 21 |
+
\addlegendentry{L20 8M extrapolation}
|
| 22 |
+
\end{axis}
|
| 23 |
+
\end{tikzpicture}
|
paper/figures/l20_stage_deltas.tex
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
\begin{tikzpicture}
|
| 2 |
+
\begin{groupplot}[
|
| 3 |
+
group style={group size=3 by 1, horizontal sep=1.0cm},
|
| 4 |
+
width=0.32\textwidth,
|
| 5 |
+
height=0.27\textwidth,
|
| 6 |
+
tick label style={font=\scriptsize},
|
| 7 |
+
label style={font=\scriptsize},
|
| 8 |
+
title style={font=\scriptsize},
|
| 9 |
+
]
|
| 10 |
+
|
| 11 |
+
\nextgroupplot[
|
| 12 |
+
title={OpenWebText10K},
|
| 13 |
+
xlabel={Prefix},
|
| 14 |
+
ylabel={Decay $-$ best static},
|
| 15 |
+
xtick={0,1,2,3,4,5},
|
| 16 |
+
xticklabels={250k,500k,1M,2M,4M,8M},
|
| 17 |
+
grid=both,
|
| 18 |
+
]
|
| 19 |
+
\addplot+[thick, mark=*] coordinates {(0,0.06300) (1,0.00103) (2,-0.01477) (3,-0.02370) (4,-0.02720) (5,-0.03631)};
|
| 20 |
+
\addplot+[black, dashed, mark=none] coordinates {(0,0.00000) (5,0.00000)};
|
| 21 |
+
|
| 22 |
+
|
| 23 |
+
\nextgroupplot[
|
| 24 |
+
title={TinyStories},
|
| 25 |
+
xlabel={Prefix},
|
| 26 |
+
ylabel={Decay $-$ best static},
|
| 27 |
+
xtick={0,1,2,3,4},
|
| 28 |
+
xticklabels={500k,1M,2M,4M,8M},
|
| 29 |
+
grid=both,
|
| 30 |
+
]
|
| 31 |
+
\addplot+[thick, mark=*] coordinates {(0,0.00050) (1,0.00729) (2,0.00347) (3,-0.00570) (4,-0.01000)};
|
| 32 |
+
\addplot+[black, dashed, mark=none] coordinates {(0,0.00000) (4,0.00000)};
|
| 33 |
+
|
| 34 |
+
|
| 35 |
+
\nextgroupplot[
|
| 36 |
+
title={WikiText-103},
|
| 37 |
+
xlabel={Prefix},
|
| 38 |
+
ylabel={Decay $-$ best static},
|
| 39 |
+
xtick={0,1,2,3,4,5},
|
| 40 |
+
xticklabels={250k,500k,1M,2M,4M,8M},
|
| 41 |
+
grid=both,
|
| 42 |
+
]
|
| 43 |
+
\addplot+[thick, mark=*] coordinates {(0,0.00803) (1,0.01591) (2,0.03177) (3,0.03542) (4,0.00371) (5,-0.01838)};
|
| 44 |
+
\addplot+[black, dashed, mark=none] coordinates {(0,0.00000) (5,0.00000)};
|
| 45 |
+
|
| 46 |
+
\end{groupplot}
|
| 47 |
+
\end{tikzpicture}
|
paper/figures/static_optima_by_regime.tex
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
\begin{tikzpicture}
|
| 2 |
+
\begin{groupplot}[
|
| 3 |
+
group style={group size=3 by 1, horizontal sep=1.0cm},
|
| 4 |
+
width=0.32\textwidth,
|
| 5 |
+
height=0.28\textwidth,
|
| 6 |
+
legend style={font=\tiny},
|
| 7 |
+
tick label style={font=\scriptsize},
|
| 8 |
+
label style={font=\scriptsize},
|
| 9 |
+
title style={font=\scriptsize},
|
| 10 |
+
]
|
| 11 |
+
|
| 12 |
+
\nextgroupplot[
|
| 13 |
+
title={OpenWebText10K},
|
| 14 |
+
xlabel={Unique prefix $U$},
|
| 15 |
+
ylabel={Fitted target dropout},
|
| 16 |
+
xtick={0,1,2,3,4},
|
| 17 |
+
xticklabels={250k,500k,1M,2M,4M},
|
| 18 |
+
ymin=0,
|
| 19 |
+
ymax=0.65,
|
| 20 |
+
grid=both,
|
| 21 |
+
]
|
| 22 |
+
\addplot+[thick, mark=*] coordinates {(0,0.5062) (1,0.3559) (2,0.2174) (3,0.1364) (4,0.0224)};
|
| 23 |
+
\addlegendentry{L12\_H8\_D320}
|
| 24 |
+
\addplot+[thick, mark=*] coordinates {(0,0.6039) (1,0.4475) (2,0.2837) (3,0.1388) (4,0.0272)};
|
| 25 |
+
\addlegendentry{L16\_H8\_D384}
|
| 26 |
+
\addplot+[thick, mark=*] coordinates {(0,0.3837) (1,0.2401) (2,0.1463) (3,0.0691) (4,0.0000)};
|
| 27 |
+
\addlegendentry{L8\_H8\_D256}
|
| 28 |
+
|
| 29 |
+
|
| 30 |
+
\nextgroupplot[
|
| 31 |
+
title={TinyStories},
|
| 32 |
+
xlabel={Unique prefix $U$},
|
| 33 |
+
ylabel={Fitted target dropout},
|
| 34 |
+
xtick={0,1,2,3,4},
|
| 35 |
+
xticklabels={250k,500k,1M,2M,4M},
|
| 36 |
+
ymin=0,
|
| 37 |
+
ymax=0.65,
|
| 38 |
+
grid=both,
|
| 39 |
+
]
|
| 40 |
+
\addplot+[thick, mark=*] coordinates {(0,0.4575) (1,0.2947) (2,0.1866) (3,0.0703) (4,0.0303)};
|
| 41 |
+
\addlegendentry{L12\_H8\_D320}
|
| 42 |
+
\addplot+[thick, mark=*] coordinates {(0,0.5699) (2,0.2450) (4,0.0468)};
|
| 43 |
+
\addlegendentry{L16\_H8\_D384}
|
| 44 |
+
\addplot+[thick, mark=*] coordinates {(0,0.3491) (2,0.0977) (4,0.0000)};
|
| 45 |
+
\addlegendentry{L8\_H8\_D256}
|
| 46 |
+
|
| 47 |
+
|
| 48 |
+
\nextgroupplot[
|
| 49 |
+
title={WikiText-103},
|
| 50 |
+
xlabel={Unique prefix $U$},
|
| 51 |
+
ylabel={Fitted target dropout},
|
| 52 |
+
xtick={0,1,2,3,4},
|
| 53 |
+
xticklabels={250k,500k,1M,2M,4M},
|
| 54 |
+
ymin=0,
|
| 55 |
+
ymax=0.65,
|
| 56 |
+
grid=both,
|
| 57 |
+
]
|
| 58 |
+
\addplot+[thick, mark=*] coordinates {(0,0.1645) (1,0.0299) (2,0.0000) (3,0.0000) (4,0.0000)};
|
| 59 |
+
\addlegendentry{L12\_H8\_D320}
|
| 60 |
+
\addplot+[thick, mark=*] coordinates {(0,0.2459) (1,0.0822) (2,0.0225) (3,0.0000) (4,0.0000)};
|
| 61 |
+
\addlegendentry{L16\_H8\_D384}
|
| 62 |
+
\addplot+[thick, mark=*] coordinates {(0,0.0965) (1,0.0114) (2,0.0000) (3,0.0000) (4,0.0000)};
|
| 63 |
+
\addlegendentry{L8\_H8\_D256}
|
| 64 |
+
|
| 65 |
+
\end{groupplot}
|
| 66 |
+
\end{tikzpicture}
|
paper/paper_results_summary.json
ADDED
|
@@ -0,0 +1,520 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"openweb": {
|
| 3 |
+
"coefficients": {
|
| 4 |
+
"regime": "OpenWebText10K",
|
| 5 |
+
"cells": 15,
|
| 6 |
+
"A": 0.08189560629877818,
|
| 7 |
+
"B": -0.023948107975524876,
|
| 8 |
+
"D": 0.1536984646521253,
|
| 9 |
+
"C0": -0.05559686586455526,
|
| 10 |
+
"mae": 0.01579101988615387,
|
| 11 |
+
"rmse": 0.018845563252758567,
|
| 12 |
+
"leave_model_mae": 0.024771721924833498,
|
| 13 |
+
"leave_prefix_mae": 0.030983393290384315,
|
| 14 |
+
"formula": "p = +0.081896 * log10(P / U) -0.023948 * log10(C / U) +0.153698 * log10(P / U) * log10(C / U) -0.055597"
|
| 15 |
+
},
|
| 16 |
+
"small_stream": {
|
| 17 |
+
"n": 5,
|
| 18 |
+
"final_stage": 4,
|
| 19 |
+
"final_prefix": 4000000,
|
| 20 |
+
"condition_final_mean": 4.3981304407119755,
|
| 21 |
+
"condition_final_std": 0.009545784836147743,
|
| 22 |
+
"best_static_condition": "static_dropout_0.14",
|
| 23 |
+
"best_static_mean": 4.44545366615057,
|
| 24 |
+
"best_static_std": 0.012017216742245517,
|
| 25 |
+
"improvement": 0.04732322543859446,
|
| 26 |
+
"paired_deltas": [
|
| 27 |
+
-0.03942108899354935,
|
| 28 |
+
-0.05825117975473404,
|
| 29 |
+
-0.032753005623817444,
|
| 30 |
+
-0.052590467035770416,
|
| 31 |
+
-0.053600385785102844
|
| 32 |
+
],
|
| 33 |
+
"paired_wins": 5,
|
| 34 |
+
"paired_mean_delta": -0.04732322543859482,
|
| 35 |
+
"paired_std_delta": 0.010738969078098962,
|
| 36 |
+
"stage_deltas": [
|
| 37 |
+
{
|
| 38 |
+
"stage": 0,
|
| 39 |
+
"prefix": 250000,
|
| 40 |
+
"condition_dropout": 0.385,
|
| 41 |
+
"condition_mean": 5.4946602284908295,
|
| 42 |
+
"condition_std": 0.01093302726132647,
|
| 43 |
+
"best_static_condition": "static_dropout_0.3",
|
| 44 |
+
"best_static_mean": 5.448330155014991,
|
| 45 |
+
"best_static_std": 0.0138285316736341,
|
| 46 |
+
"delta": 0.04633007347583806
|
| 47 |
+
},
|
| 48 |
+
{
|
| 49 |
+
"stage": 1,
|
| 50 |
+
"prefix": 500000,
|
| 51 |
+
"condition_dropout": 0.319,
|
| 52 |
+
"condition_mean": 5.071460470557213,
|
| 53 |
+
"condition_std": 0.01179360076463939,
|
| 54 |
+
"best_static_condition": "static_dropout_0.3",
|
| 55 |
+
"best_static_mean": 5.066737298667431,
|
| 56 |
+
"best_static_std": 0.017273470277214743,
|
| 57 |
+
"delta": 0.004723171889781774
|
| 58 |
+
},
|
| 59 |
+
{
|
| 60 |
+
"stage": 2,
|
| 61 |
+
"prefix": 1000000,
|
| 62 |
+
"condition_dropout": 0.227,
|
| 63 |
+
"condition_mean": 4.781069016456604,
|
| 64 |
+
"condition_std": 0.008428247627355752,
|
| 65 |
+
"best_static_condition": "static_dropout_0.3",
|
| 66 |
+
"best_static_mean": 4.79825523942709,
|
| 67 |
+
"best_static_std": 0.01441949497608529,
|
| 68 |
+
"delta": -0.01718622297048622
|
| 69 |
+
},
|
| 70 |
+
{
|
| 71 |
+
"stage": 3,
|
| 72 |
+
"prefix": 2000000,
|
| 73 |
+
"condition_dropout": 0.139,
|
| 74 |
+
"condition_mean": 4.558990895748138,
|
| 75 |
+
"condition_std": 0.014177173687439483,
|
| 76 |
+
"best_static_condition": "static_dropout_0.3",
|
| 77 |
+
"best_static_mean": 4.603498187661171,
|
| 78 |
+
"best_static_std": 0.014129740963263297,
|
| 79 |
+
"delta": -0.04450729191303271
|
| 80 |
+
},
|
| 81 |
+
{
|
| 82 |
+
"stage": 4,
|
| 83 |
+
"prefix": 4000000,
|
| 84 |
+
"condition_dropout": 0.066,
|
| 85 |
+
"condition_mean": 4.3981304407119755,
|
| 86 |
+
"condition_std": 0.009545784836147743,
|
| 87 |
+
"best_static_condition": "static_dropout_0.14",
|
| 88 |
+
"best_static_mean": 4.44545366615057,
|
| 89 |
+
"best_static_std": 0.012017216742245517,
|
| 90 |
+
"delta": -0.04732322543859446
|
| 91 |
+
}
|
| 92 |
+
]
|
| 93 |
+
},
|
| 94 |
+
"l20_stream": {
|
| 95 |
+
"n": 3,
|
| 96 |
+
"final_stage": 5,
|
| 97 |
+
"final_prefix": 8000000,
|
| 98 |
+
"condition_final_mean": 4.179318442940712,
|
| 99 |
+
"condition_final_std": 0.01344880214961143,
|
| 100 |
+
"best_static_condition": "static_dropout_0.2",
|
| 101 |
+
"best_static_mean": 4.215624410659075,
|
| 102 |
+
"best_static_std": 0.014242200766109402,
|
| 103 |
+
"improvement": 0.03630596771836281,
|
| 104 |
+
"paired_deltas": [
|
| 105 |
+
-0.03935118764638901,
|
| 106 |
+
-0.025513865053653717,
|
| 107 |
+
-0.03717709332704544
|
| 108 |
+
],
|
| 109 |
+
"paired_wins": 3,
|
| 110 |
+
"paired_mean_delta": -0.03401404867569605,
|
| 111 |
+
"paired_std_delta": 0.007441203715372809,
|
| 112 |
+
"stage_deltas": [
|
| 113 |
+
{
|
| 114 |
+
"stage": 0,
|
| 115 |
+
"prefix": 250000,
|
| 116 |
+
"condition_dropout": 0.433,
|
| 117 |
+
"condition_mean": 5.460076779127121,
|
| 118 |
+
"condition_std": 0.0012588964293079841,
|
| 119 |
+
"best_static_condition": "static_dropout_0.3",
|
| 120 |
+
"best_static_mean": 5.39707263559103,
|
| 121 |
+
"best_static_std": 0.014451385161445679,
|
| 122 |
+
"delta": 0.06300414353609085
|
| 123 |
+
},
|
| 124 |
+
{
|
| 125 |
+
"stage": 1,
|
| 126 |
+
"prefix": 500000,
|
| 127 |
+
"condition_dropout": 0.367,
|
| 128 |
+
"condition_mean": 5.017915904521942,
|
| 129 |
+
"condition_std": 0.005764488447180914,
|
| 130 |
+
"best_static_condition": "static_dropout_0.3",
|
| 131 |
+
"best_static_mean": 5.01688560595115,
|
| 132 |
+
"best_static_std": 0.012061853156578773,
|
| 133 |
+
"delta": 0.0010302985707921763
|
| 134 |
+
},
|
| 135 |
+
{
|
| 136 |
+
"stage": 2,
|
| 137 |
+
"prefix": 1000000,
|
| 138 |
+
"condition_dropout": 0.27,
|
| 139 |
+
"condition_mean": 4.7070309941967325,
|
| 140 |
+
"condition_std": 0.015739562982191573,
|
| 141 |
+
"best_static_condition": "static_dropout_0.3",
|
| 142 |
+
"best_static_mean": 4.72180004666249,
|
| 143 |
+
"best_static_std": 0.02152489745362506,
|
| 144 |
+
"delta": -0.014769052465757326
|
| 145 |
+
},
|
| 146 |
+
{
|
| 147 |
+
"stage": 3,
|
| 148 |
+
"prefix": 2000000,
|
| 149 |
+
"condition_dropout": 0.17600000000000002,
|
| 150 |
+
"condition_mean": 4.487937837839127,
|
| 151 |
+
"condition_std": 0.009026555295310244,
|
| 152 |
+
"best_static_condition": "static_dropout_0.2",
|
| 153 |
+
"best_static_mean": 4.511637444297473,
|
| 154 |
+
"best_static_std": 0.016469397224544248,
|
| 155 |
+
"delta": -0.023699606458346345
|
| 156 |
+
},
|
| 157 |
+
{
|
| 158 |
+
"stage": 4,
|
| 159 |
+
"prefix": 4000000,
|
| 160 |
+
"condition_dropout": 0.09500000000000001,
|
| 161 |
+
"condition_mean": 4.311577834188938,
|
| 162 |
+
"condition_std": 0.015007670903308238,
|
| 163 |
+
"best_static_condition": "static_dropout_0.2",
|
| 164 |
+
"best_static_mean": 4.338780450324218,
|
| 165 |
+
"best_static_std": 0.01698295581756926,
|
| 166 |
+
"delta": -0.027202616135279634
|
| 167 |
+
},
|
| 168 |
+
{
|
| 169 |
+
"stage": 5,
|
| 170 |
+
"prefix": 8000000,
|
| 171 |
+
"condition_dropout": 0.033,
|
| 172 |
+
"condition_mean": 4.179318442940712,
|
| 173 |
+
"condition_std": 0.01344880214961143,
|
| 174 |
+
"best_static_condition": "static_dropout_0.2",
|
| 175 |
+
"best_static_mean": 4.215624410659075,
|
| 176 |
+
"best_static_std": 0.014242200766109402,
|
| 177 |
+
"delta": -0.03630596771836281
|
| 178 |
+
}
|
| 179 |
+
]
|
| 180 |
+
}
|
| 181 |
+
},
|
| 182 |
+
"tinystories": {
|
| 183 |
+
"coefficients": {
|
| 184 |
+
"regime": "TinyStories",
|
| 185 |
+
"cells": 16,
|
| 186 |
+
"A": -0.08926115772929914,
|
| 187 |
+
"B": -0.1297539607354037,
|
| 188 |
+
"D": 0.2550686141985067,
|
| 189 |
+
"C0": 0.08152490344304414,
|
| 190 |
+
"mae": 0.01799638717496229,
|
| 191 |
+
"rmse": 0.023120178816187012,
|
| 192 |
+
"leave_model_mae": 0.029523601005481504,
|
| 193 |
+
"leave_prefix_mae": 0.03253257579449062,
|
| 194 |
+
"formula": "p = -0.089261 * log10(P / U) -0.129754 * log10(C / U) +0.255069 * log10(P / U) * log10(C / U) +0.081525"
|
| 195 |
+
},
|
| 196 |
+
"small_stream": {
|
| 197 |
+
"n": 5,
|
| 198 |
+
"final_stage": 3,
|
| 199 |
+
"final_prefix": 4000000,
|
| 200 |
+
"condition_final_mean": 2.5311126589775084,
|
| 201 |
+
"condition_final_std": 0.021347338722582713,
|
| 202 |
+
"best_static_condition": "static_dropout_0.08",
|
| 203 |
+
"best_static_mean": 2.5443769969046115,
|
| 204 |
+
"best_static_std": 0.021138839821398296,
|
| 205 |
+
"improvement": 0.013264337927103131,
|
| 206 |
+
"paired_deltas": [
|
| 207 |
+
-0.0005062669515609741,
|
| 208 |
+
-0.021110694855451584,
|
| 209 |
+
-0.009244173765182495,
|
| 210 |
+
-0.016586389392614365,
|
| 211 |
+
-0.014097623527050018
|
| 212 |
+
],
|
| 213 |
+
"paired_wins": 5,
|
| 214 |
+
"paired_mean_delta": -0.012309029698371887,
|
| 215 |
+
"paired_std_delta": 0.00786865194496459,
|
| 216 |
+
"stage_deltas": [
|
| 217 |
+
{
|
| 218 |
+
"stage": 0,
|
| 219 |
+
"prefix": 500000,
|
| 220 |
+
"condition_dropout": 0.184,
|
| 221 |
+
"condition_mean": 3.232573334872723,
|
| 222 |
+
"condition_std": 0.012322249630223228,
|
| 223 |
+
"best_static_condition": "static_dropout_0.12",
|
| 224 |
+
"best_static_mean": 3.2225750528275965,
|
| 225 |
+
"best_static_std": 0.014316453554089874,
|
| 226 |
+
"delta": 0.009998282045126317
|
| 227 |
+
},
|
| 228 |
+
{
|
| 229 |
+
"stage": 1,
|
| 230 |
+
"prefix": 1000000,
|
| 231 |
+
"condition_dropout": 0.141,
|
| 232 |
+
"condition_mean": 2.8907706700265408,
|
| 233 |
+
"condition_std": 0.00267614743609897,
|
| 234 |
+
"best_static_condition": "static_dropout_0.12",
|
| 235 |
+
"best_static_mean": 2.893001724779606,
|
| 236 |
+
"best_static_std": 0.012108793042410648,
|
| 237 |
+
"delta": -0.002231054753065287
|
| 238 |
+
},
|
| 239 |
+
{
|
| 240 |
+
"stage": 2,
|
| 241 |
+
"prefix": 2000000,
|
| 242 |
+
"condition_dropout": 0.084,
|
| 243 |
+
"condition_mean": 2.6690264880657195,
|
| 244 |
+
"condition_std": 0.020694473868817478,
|
| 245 |
+
"best_static_condition": "static_dropout_0.12",
|
| 246 |
+
"best_static_mean": 2.6794611223042013,
|
| 247 |
+
"best_static_std": 0.016337678176899628,
|
| 248 |
+
"delta": -0.010434634238481788
|
| 249 |
+
},
|
| 250 |
+
{
|
| 251 |
+
"stage": 3,
|
| 252 |
+
"prefix": 4000000,
|
| 253 |
+
"condition_dropout": 0.045,
|
| 254 |
+
"condition_mean": 2.5311126589775084,
|
| 255 |
+
"condition_std": 0.021347338722582713,
|
| 256 |
+
"best_static_condition": "static_dropout_0.08",
|
| 257 |
+
"best_static_mean": 2.5443769969046115,
|
| 258 |
+
"best_static_std": 0.021138839821398296,
|
| 259 |
+
"delta": -0.013264337927103131
|
| 260 |
+
}
|
| 261 |
+
]
|
| 262 |
+
},
|
| 263 |
+
"l20_stream": {
|
| 264 |
+
"n": 3,
|
| 265 |
+
"final_stage": 4,
|
| 266 |
+
"final_prefix": 8000000,
|
| 267 |
+
"condition_final_mean": 2.208572393283248,
|
| 268 |
+
"condition_final_std": 0.004634657584019386,
|
| 269 |
+
"best_static_condition": "static_dropout_0.2",
|
| 270 |
+
"best_static_mean": 2.218574500953158,
|
| 271 |
+
"best_static_std": 0.003241222983607362,
|
| 272 |
+
"improvement": 0.010002107669909943,
|
| 273 |
+
"paired_deltas": [
|
| 274 |
+
-0.00869341753423214,
|
| 275 |
+
-0.006095590069890022,
|
| 276 |
+
-0.013002954423427582
|
| 277 |
+
],
|
| 278 |
+
"paired_wins": 3,
|
| 279 |
+
"paired_mean_delta": -0.00926398734251658,
|
| 280 |
+
"paired_std_delta": 0.0034888512447114515,
|
| 281 |
+
"stage_deltas": [
|
| 282 |
+
{
|
| 283 |
+
"stage": 0,
|
| 284 |
+
"prefix": 500000,
|
| 285 |
+
"condition_dropout": 0.253,
|
| 286 |
+
"condition_mean": 3.030243801573912,
|
| 287 |
+
"condition_std": 0.006391037258560546,
|
| 288 |
+
"best_static_condition": "static_dropout_0.2",
|
| 289 |
+
"best_static_mean": 3.02974146977067,
|
| 290 |
+
"best_static_std": 0.006865445597812618,
|
| 291 |
+
"delta": 0.0005023318032422175
|
| 292 |
+
},
|
| 293 |
+
{
|
| 294 |
+
"stage": 1,
|
| 295 |
+
"prefix": 1000000,
|
| 296 |
+
"condition_dropout": 0.209,
|
| 297 |
+
"condition_mean": 2.7195058229068914,
|
| 298 |
+
"condition_std": 0.01655123058742941,
|
| 299 |
+
"best_static_condition": "static_dropout_0.26",
|
| 300 |
+
"best_static_mean": 2.712211973965168,
|
| 301 |
+
"best_static_std": 0.01060853933383755,
|
| 302 |
+
"delta": 0.007293848941723358
|
| 303 |
+
},
|
| 304 |
+
{
|
| 305 |
+
"stage": 2,
|
| 306 |
+
"prefix": 2000000,
|
| 307 |
+
"condition_dropout": 0.137,
|
| 308 |
+
"condition_mean": 2.476810533553362,
|
| 309 |
+
"condition_std": 0.006082638165661795,
|
| 310 |
+
"best_static_condition": "static_dropout_0.2",
|
| 311 |
+
"best_static_mean": 2.4733379955093064,
|
| 312 |
+
"best_static_std": 0.009827865290864221,
|
| 313 |
+
"delta": 0.003472538044055451
|
| 314 |
+
},
|
| 315 |
+
{
|
| 316 |
+
"stage": 3,
|
| 317 |
+
"prefix": 4000000,
|
| 318 |
+
"condition_dropout": 0.077,
|
| 319 |
+
"condition_mean": 2.3110335220893226,
|
| 320 |
+
"condition_std": 0.015477651713059048,
|
| 321 |
+
"best_static_condition": "static_dropout_0.2",
|
| 322 |
+
"best_static_mean": 2.3167371824383736,
|
| 323 |
+
"best_static_std": 0.008877543510132386,
|
| 324 |
+
"delta": -0.00570366034905101
|
| 325 |
+
},
|
| 326 |
+
{
|
| 327 |
+
"stage": 4,
|
| 328 |
+
"prefix": 8000000,
|
| 329 |
+
"condition_dropout": 0.041,
|
| 330 |
+
"condition_mean": 2.208572393283248,
|
| 331 |
+
"condition_std": 0.004634657584019386,
|
| 332 |
+
"best_static_condition": "static_dropout_0.2",
|
| 333 |
+
"best_static_mean": 2.218574500953158,
|
| 334 |
+
"best_static_std": 0.003241222983607362,
|
| 335 |
+
"delta": -0.010002107669909943
|
| 336 |
+
}
|
| 337 |
+
]
|
| 338 |
+
}
|
| 339 |
+
},
|
| 340 |
+
"wikitext": {
|
| 341 |
+
"coefficients": {
|
| 342 |
+
"regime": "WikiText-103",
|
| 343 |
+
"cells": 15,
|
| 344 |
+
"A": -0.002674916387321836,
|
| 345 |
+
"B": -0.2836210480242947,
|
| 346 |
+
"D": 0.2686063066585002,
|
| 347 |
+
"C0": -0.01899454503425616,
|
| 348 |
+
"mae": 0.016244092648220347,
|
| 349 |
+
"rmse": 0.019131667586788335,
|
| 350 |
+
"leave_model_mae": 0.030438322968748356,
|
| 351 |
+
"leave_prefix_mae": 0.04435212839791333,
|
| 352 |
+
"formula": "p = -0.002675 * log10(P / U) -0.283621 * log10(C / U) +0.268606 * log10(P / U) * log10(C / U) -0.018995"
|
| 353 |
+
},
|
| 354 |
+
"small_stream": {
|
| 355 |
+
"n": 5,
|
| 356 |
+
"final_stage": 4,
|
| 357 |
+
"final_prefix": 4000000,
|
| 358 |
+
"condition_final_mean": 4.080797865241766,
|
| 359 |
+
"condition_final_std": 0.019458062276034267,
|
| 360 |
+
"best_static_condition": "static_dropout_0.1",
|
| 361 |
+
"best_static_mean": 4.1104619488120075,
|
| 362 |
+
"best_static_std": 0.018806640195540816,
|
| 363 |
+
"improvement": 0.02966408357024175,
|
| 364 |
+
"paired_deltas": [
|
| 365 |
+
-0.018418047577142715,
|
| 366 |
+
-0.018113188445568085,
|
| 367 |
+
-0.027215413749217987,
|
| 368 |
+
-0.02507050335407257,
|
| 369 |
+
-0.037036627531051636
|
| 370 |
+
],
|
| 371 |
+
"paired_wins": 5,
|
| 372 |
+
"paired_mean_delta": -0.0251707561314106,
|
| 373 |
+
"paired_std_delta": 0.007752385341386418,
|
| 374 |
+
"stage_deltas": [
|
| 375 |
+
{
|
| 376 |
+
"stage": 0,
|
| 377 |
+
"prefix": 250000,
|
| 378 |
+
"condition_dropout": 0.3,
|
| 379 |
+
"condition_mean": 5.21483291387558,
|
| 380 |
+
"condition_std": 0.018060268226475495,
|
| 381 |
+
"best_static_condition": "static_dropout_0.18",
|
| 382 |
+
"best_static_mean": 5.16162400841713,
|
| 383 |
+
"best_static_std": 0.014987630048664583,
|
| 384 |
+
"delta": 0.05320890545845014
|
| 385 |
+
},
|
| 386 |
+
{
|
| 387 |
+
"stage": 1,
|
| 388 |
+
"prefix": 500000,
|
| 389 |
+
"condition_dropout": 0.26,
|
| 390 |
+
"condition_mean": 4.80534844994545,
|
| 391 |
+
"condition_std": 0.027829930328897787,
|
| 392 |
+
"best_static_condition": "static_dropout_0.2",
|
| 393 |
+
"best_static_mean": 4.787301687896251,
|
| 394 |
+
"best_static_std": 0.023601504941398347,
|
| 395 |
+
"delta": 0.01804676204919886
|
| 396 |
+
},
|
| 397 |
+
{
|
| 398 |
+
"stage": 2,
|
| 399 |
+
"prefix": 1000000,
|
| 400 |
+
"condition_dropout": 0.18,
|
| 401 |
+
"condition_mean": 4.49377838075161,
|
| 402 |
+
"condition_std": 0.014674108647494532,
|
| 403 |
+
"best_static_condition": "static_dropout_0.14",
|
| 404 |
+
"best_static_mean": 4.500055414438248,
|
| 405 |
+
"best_static_std": 0.014819407090300617,
|
| 406 |
+
"delta": -0.006277033686638234
|
| 407 |
+
},
|
| 408 |
+
{
|
| 409 |
+
"stage": 3,
|
| 410 |
+
"prefix": 2000000,
|
| 411 |
+
"condition_dropout": 0.09,
|
| 412 |
+
"condition_mean": 4.260725708305836,
|
| 413 |
+
"condition_std": 0.018098739272622868,
|
| 414 |
+
"best_static_condition": "static_dropout_0.14",
|
| 415 |
+
"best_static_mean": 4.281563547253609,
|
| 416 |
+
"best_static_std": 0.02138858846966249,
|
| 417 |
+
"delta": -0.020837838947772802
|
| 418 |
+
},
|
| 419 |
+
{
|
| 420 |
+
"stage": 4,
|
| 421 |
+
"prefix": 4000000,
|
| 422 |
+
"condition_dropout": 0.02,
|
| 423 |
+
"condition_mean": 4.080797865241766,
|
| 424 |
+
"condition_std": 0.019458062276034267,
|
| 425 |
+
"best_static_condition": "static_dropout_0.1",
|
| 426 |
+
"best_static_mean": 4.1104619488120075,
|
| 427 |
+
"best_static_std": 0.018806640195540816,
|
| 428 |
+
"delta": -0.02966408357024175
|
| 429 |
+
}
|
| 430 |
+
]
|
| 431 |
+
},
|
| 432 |
+
"l20_stream": {
|
| 433 |
+
"n": 3,
|
| 434 |
+
"final_stage": 5,
|
| 435 |
+
"final_prefix": 8000000,
|
| 436 |
+
"condition_final_mean": 3.957240684578816,
|
| 437 |
+
"condition_final_std": 0.008487626829204101,
|
| 438 |
+
"best_static_condition": "static_dropout_0.14",
|
| 439 |
+
"best_static_mean": 3.9756230898201466,
|
| 440 |
+
"best_static_std": 0.008723729300606414,
|
| 441 |
+
"improvement": 0.018382405241330613,
|
| 442 |
+
"paired_deltas": [
|
| 443 |
+
-0.016518089920282364,
|
| 444 |
+
-0.00015844032168388367,
|
| 445 |
+
-0.027258697897195816
|
| 446 |
+
],
|
| 447 |
+
"paired_wins": 3,
|
| 448 |
+
"paired_mean_delta": -0.014645076046387354,
|
| 449 |
+
"paired_std_delta": 0.013646872385042126,
|
| 450 |
+
"stage_deltas": [
|
| 451 |
+
{
|
| 452 |
+
"stage": 0,
|
| 453 |
+
"prefix": 250000,
|
| 454 |
+
"condition_dropout": 0.284,
|
| 455 |
+
"condition_mean": 5.2075243567427,
|
| 456 |
+
"condition_std": 0.013993230772716539,
|
| 457 |
+
"best_static_condition": "static_dropout_0.26",
|
| 458 |
+
"best_static_mean": 5.1994956483443575,
|
| 459 |
+
"best_static_std": 0.013634536236337126,
|
| 460 |
+
"delta": 0.008028708398342133
|
| 461 |
+
},
|
| 462 |
+
{
|
| 463 |
+
"stage": 1,
|
| 464 |
+
"prefix": 500000,
|
| 465 |
+
"condition_dropout": 0.211,
|
| 466 |
+
"condition_mean": 4.816951217750709,
|
| 467 |
+
"condition_std": 0.0272929978517565,
|
| 468 |
+
"best_static_condition": "static_dropout_0.3",
|
| 469 |
+
"best_static_mean": 4.8010413100322085,
|
| 470 |
+
"best_static_std": 0.023497612891923463,
|
| 471 |
+
"delta": 0.015909907718500094
|
| 472 |
+
},
|
| 473 |
+
{
|
| 474 |
+
"stage": 2,
|
| 475 |
+
"prefix": 1000000,
|
| 476 |
+
"condition_dropout": 0.116,
|
| 477 |
+
"condition_mean": 4.538959090908368,
|
| 478 |
+
"condition_std": 0.018682984974041847,
|
| 479 |
+
"best_static_condition": "static_dropout_0.26",
|
| 480 |
+
"best_static_mean": 4.507193277279536,
|
| 481 |
+
"best_static_std": 0.022063926876841285,
|
| 482 |
+
"delta": 0.03176581362883191
|
| 483 |
+
},
|
| 484 |
+
{
|
| 485 |
+
"stage": 3,
|
| 486 |
+
"prefix": 2000000,
|
| 487 |
+
"condition_dropout": 0.03599999999999999,
|
| 488 |
+
"condition_mean": 4.310809387514989,
|
| 489 |
+
"condition_std": 0.02431455246903313,
|
| 490 |
+
"best_static_condition": "static_dropout_0.26",
|
| 491 |
+
"best_static_mean": 4.275388439496358,
|
| 492 |
+
"best_static_std": 0.021064935851257058,
|
| 493 |
+
"delta": 0.03542094801863094
|
| 494 |
+
},
|
| 495 |
+
{
|
| 496 |
+
"stage": 4,
|
| 497 |
+
"prefix": 4000000,
|
| 498 |
+
"condition_dropout": 0.02,
|
| 499 |
+
"condition_mean": 4.1124994705120725,
|
| 500 |
+
"condition_std": 0.024692308091551114,
|
| 501 |
+
"best_static_condition": "static_dropout_0.18",
|
| 502 |
+
"best_static_mean": 4.108785220732291,
|
| 503 |
+
"best_static_std": 0.016546231012667934,
|
| 504 |
+
"delta": 0.003714249779781298
|
| 505 |
+
},
|
| 506 |
+
{
|
| 507 |
+
"stage": 5,
|
| 508 |
+
"prefix": 8000000,
|
| 509 |
+
"condition_dropout": 0.02,
|
| 510 |
+
"condition_mean": 3.957240684578816,
|
| 511 |
+
"condition_std": 0.008487626829204101,
|
| 512 |
+
"best_static_condition": "static_dropout_0.14",
|
| 513 |
+
"best_static_mean": 3.9756230898201466,
|
| 514 |
+
"best_static_std": 0.008723729300606414,
|
| 515 |
+
"delta": -0.018382405241330613
|
| 516 |
+
}
|
| 517 |
+
]
|
| 518 |
+
}
|
| 519 |
+
}
|
| 520 |
+
}
|
paper/references.bib
CHANGED
|
@@ -33,6 +33,14 @@
|
|
| 33 |
url = {https://arxiv.org/abs/2203.15556}
|
| 34 |
}
|
| 35 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 36 |
@inproceedings{merity2017pointer,
|
| 37 |
title = {Pointer Sentinel Mixture Models},
|
| 38 |
author = {Merity, Stephen and Xiong, Caiming and Bradbury, James and Socher, Richard},
|
|
|
|
| 33 |
url = {https://arxiv.org/abs/2203.15556}
|
| 34 |
}
|
| 35 |
|
| 36 |
+
@misc{gokaslan2019openwebtext,
|
| 37 |
+
title = {OpenWebText Corpus},
|
| 38 |
+
author = {Gokaslan, Aaron and Cohen, Vanya and Pavlick, Ellie and Tellex, Stefanie},
|
| 39 |
+
year = {2019},
|
| 40 |
+
howpublished = {\url{https://zenodo.org/records/3834942}},
|
| 41 |
+
note = {Open reproduction of the WebText-style corpus}
|
| 42 |
+
}
|
| 43 |
+
|
| 44 |
@inproceedings{merity2017pointer,
|
| 45 |
title = {Pointer Sentinel Mixture Models},
|
| 46 |
author = {Merity, Stephen and Xiong, Caiming and Bradbury, James and Socher, Richard},
|
paper/tables/coefficient_table.tex
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
\begin{tabular}{lrrrrrrrr}
|
| 2 |
+
\toprule
|
| 3 |
+
Regime & Cells & A & B & D & C0 & MAE & L-model MAE & L-prefix MAE \\
|
| 4 |
+
\midrule
|
| 5 |
+
OpenWebText10K & 15 & 0.0819 & -0.0239 & 0.1537 & -0.0556 & 0.0158 & 0.0248 & 0.0310 \\
|
| 6 |
+
TinyStories & 16 & -0.0893 & -0.1298 & 0.2551 & 0.0815 & 0.0180 & 0.0295 & 0.0325 \\
|
| 7 |
+
WikiText-103 & 15 & -0.0027 & -0.2836 & 0.2686 & -0.0190 & 0.0162 & 0.0304 & 0.0444 \\
|
| 8 |
+
\bottomrule
|
| 9 |
+
\end{tabular}
|
paper/tables/l20_stage_table.tex
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
\begin{tabular}{lrrrrr}
|
| 2 |
+
\toprule
|
| 3 |
+
Regime & Prefix & Decay dropout & Decay val & Best static val & Delta \\
|
| 4 |
+
\midrule
|
| 5 |
+
OpenWebText10K & 250k & 0.433 & 5.4601 & 5.3971 & +0.0630 \\
|
| 6 |
+
OpenWebText10K & 500k & 0.367 & 5.0179 & 5.0169 & +0.0010 \\
|
| 7 |
+
OpenWebText10K & 1M & 0.270 & 4.7070 & 4.7218 & -0.0148 \\
|
| 8 |
+
OpenWebText10K & 2M & 0.176 & 4.4879 & 4.5116 & -0.0237 \\
|
| 9 |
+
OpenWebText10K & 4M & 0.095 & 4.3116 & 4.3388 & -0.0272 \\
|
| 10 |
+
OpenWebText10K & 8M & 0.033 & 4.1793 & 4.2156 & -0.0363 \\
|
| 11 |
+
TinyStories & 500k & 0.253 & 3.0302 & 3.0297 & +0.0005 \\
|
| 12 |
+
TinyStories & 1M & 0.209 & 2.7195 & 2.7122 & +0.0073 \\
|
| 13 |
+
TinyStories & 2M & 0.137 & 2.4768 & 2.4733 & +0.0035 \\
|
| 14 |
+
TinyStories & 4M & 0.077 & 2.3110 & 2.3167 & -0.0057 \\
|
| 15 |
+
TinyStories & 8M & 0.041 & 2.2086 & 2.2186 & -0.0100 \\
|
| 16 |
+
WikiText-103 & 250k & 0.284 & 5.2075 & 5.1995 & +0.0080 \\
|
| 17 |
+
WikiText-103 & 500k & 0.211 & 4.8170 & 4.8010 & +0.0159 \\
|
| 18 |
+
WikiText-103 & 1M & 0.116 & 4.5390 & 4.5072 & +0.0318 \\
|
| 19 |
+
WikiText-103 & 2M & 0.036 & 4.3108 & 4.2754 & +0.0354 \\
|
| 20 |
+
WikiText-103 & 4M & 0.020 & 4.1125 & 4.1088 & +0.0037 \\
|
| 21 |
+
WikiText-103 & 8M & 0.020 & 3.9572 & 3.9756 & -0.0184 \\
|
| 22 |
+
\bottomrule
|
| 23 |
+
\end{tabular}
|
paper/tables/streaming_validation_table.tex
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
\begin{tabular}{llrrrrr}
|
| 2 |
+
\toprule
|
| 3 |
+
Regime & Protocol & Decay final & Best static final & Gain & Seeds & Paired wins \\
|
| 4 |
+
\midrule
|
| 5 |
+
OpenWebText10K & 4M / 5 seeds & 4.3981 $\pm$ 0.0095 & 4.4455 $\pm$ 0.0120 & 0.0473 & 5 & 5/5 \\
|
| 6 |
+
OpenWebText10K & 8M / 3 seeds & 4.1793 $\pm$ 0.0134 & 4.2156 $\pm$ 0.0142 & 0.0363 & 3 & 3/3 \\
|
| 7 |
+
TinyStories & 4M / 5 seeds & 2.5311 $\pm$ 0.0213 & 2.5444 $\pm$ 0.0211 & 0.0133 & 5 & 5/5 \\
|
| 8 |
+
TinyStories & 8M / 3 seeds & 2.2086 $\pm$ 0.0046 & 2.2186 $\pm$ 0.0032 & 0.0100 & 3 & 3/3 \\
|
| 9 |
+
WikiText-103 & 4M / 5 seeds & 4.0808 $\pm$ 0.0195 & 4.1105 $\pm$ 0.0188 & 0.0297 & 5 & 5/5 \\
|
| 10 |
+
WikiText-103 & 8M / 3 seeds & 3.9572 $\pm$ 0.0085 & 3.9756 $\pm$ 0.0087 & 0.0184 & 3 & 3/3 \\
|
| 11 |
+
\bottomrule
|
| 12 |
+
\end{tabular}
|
runs/smoke_compat/locked_stream/20260605-224834/RESULT_SUMMARY.md
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Locked Streaming Dropout Summary
|
| 2 |
+
|
| 3 |
+
Run directory: `runs/smoke_compat/locked_stream/20260605-224834`
|
| 4 |
+
|
| 5 |
+
Model: `smoke` causal Transformer, 134,144 parameters, 1 layers, 1 heads, 16 embedding dim.
|
| 6 |
+
Training per stage: 2 steps. Sampled tokens are cumulative in each stage row. Seeds present: 123.
|
| 7 |
+
|
| 8 |
+
## Condition Ranking
|
| 9 |
+
|
| 10 |
+
| Condition | Kind | Final dropout | Mean trajectory val loss | Final val loss | Final gap | Dropout path |
|
| 11 |
+
|---|---|---:|---:|---:|---:|---|
|
| 12 |
+
| `static_dropout_0.05` | static | 0.05 | 8.3189 | 8.3194 | 0.0069 | 0.05 -> 0.05 |
|
| 13 |
+
| `smoke_decay` | anchor_decay | 0.05 | 8.3189 | 8.3194 | 0.0014 | 0.10 -> 0.05 |
|
| 14 |
+
|
| 15 |
+
## Stage Trajectory
|
| 16 |
+
|
| 17 |
+
### Stage 0: 512 Prefix Tokens
|
| 18 |
+
|
| 19 |
+
| Condition | Dropout | Mean val loss | Mean train loss | Mean gap | N |
|
| 20 |
+
|---|---:|---:|---:|---:|---:|
|
| 21 |
+
| `static_dropout_0.05` | 0.05 | 8.3184 | 8.3184 | -0.0001 | 1 |
|
| 22 |
+
| `smoke_decay` | 0.10 | 8.3185 | 8.3181 | 0.0003 | 1 |
|
| 23 |
+
|
| 24 |
+
### Stage 1: 768 Prefix Tokens
|
| 25 |
+
|
| 26 |
+
| Condition | Dropout | Mean val loss | Mean train loss | Mean gap | N |
|
| 27 |
+
|---|---:|---:|---:|---:|---:|
|
| 28 |
+
| `smoke_decay` | 0.05 | 8.3194 | 8.3180 | 0.0014 | 1 |
|
| 29 |
+
| `static_dropout_0.05` | 0.05 | 8.3194 | 8.3125 | 0.0069 | 1 |
|
runs/smoke_compat/locked_stream/20260605-224834/config.json
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"args": {
|
| 3 |
+
"mode": "locked_stream",
|
| 4 |
+
"corpus": null,
|
| 5 |
+
"corpus_glob": null,
|
| 6 |
+
"text_column": "text",
|
| 7 |
+
"use_cached_data": true,
|
| 8 |
+
"output_dir": "runs/smoke_compat",
|
| 9 |
+
"resume_from": null,
|
| 10 |
+
"cache_dir": ".cache/dropout_decay",
|
| 11 |
+
"models": [
|
| 12 |
+
"smoke=1x1x16"
|
| 13 |
+
],
|
| 14 |
+
"seeds": [
|
| 15 |
+
123
|
| 16 |
+
],
|
| 17 |
+
"token_limits": [
|
| 18 |
+
5000000
|
| 19 |
+
],
|
| 20 |
+
"stream_token_caps": [
|
| 21 |
+
512,
|
| 22 |
+
768
|
| 23 |
+
],
|
| 24 |
+
"val_tokens": 128,
|
| 25 |
+
"allow_short_corpus": true,
|
| 26 |
+
"force_retokenize": false,
|
| 27 |
+
"vocab_size": 4096,
|
| 28 |
+
"tokenizer_train_chars": 10000000,
|
| 29 |
+
"block_size": 16,
|
| 30 |
+
"batch_size": 2,
|
| 31 |
+
"steps": 2000,
|
| 32 |
+
"stage_steps": 2,
|
| 33 |
+
"dropout_rates": [
|
| 34 |
+
0.05
|
| 35 |
+
],
|
| 36 |
+
"decays": [],
|
| 37 |
+
"anchor_decays": [
|
| 38 |
+
{
|
| 39 |
+
"name": "smoke_decay",
|
| 40 |
+
"kind": "anchor_decay",
|
| 41 |
+
"initial": 0.1,
|
| 42 |
+
"final": 0.05,
|
| 43 |
+
"schedule": "log_prefix_anchor",
|
| 44 |
+
"decay_tokens": null,
|
| 45 |
+
"anchors": [
|
| 46 |
+
[
|
| 47 |
+
512,
|
| 48 |
+
0.1
|
| 49 |
+
],
|
| 50 |
+
[
|
| 51 |
+
768,
|
| 52 |
+
0.05
|
| 53 |
+
]
|
| 54 |
+
]
|
| 55 |
+
}
|
| 56 |
+
],
|
| 57 |
+
"decay_tokens": null,
|
| 58 |
+
"eval_batches": 1,
|
| 59 |
+
"train_eval_batches": 1,
|
| 60 |
+
"trace_eval_batches": 1,
|
| 61 |
+
"eval_every": 0,
|
| 62 |
+
"log_every": 1,
|
| 63 |
+
"lr": 0.0003,
|
| 64 |
+
"weight_decay": 0.1,
|
| 65 |
+
"grad_clip": 1.0,
|
| 66 |
+
"plateau_delta": 0.01,
|
| 67 |
+
"target_min_dropout": 0.1,
|
| 68 |
+
"min_nonzero_margin": 0.01,
|
| 69 |
+
"min_high_dropout_margin": 0.03,
|
| 70 |
+
"screen_early_stop": false,
|
| 71 |
+
"screen_prune_patience": 3,
|
| 72 |
+
"screen_prune_min_delta": 0.01
|
| 73 |
+
},
|
| 74 |
+
"mode": "locked_stream",
|
| 75 |
+
"seeds": [
|
| 76 |
+
123
|
| 77 |
+
],
|
| 78 |
+
"models": [
|
| 79 |
+
{
|
| 80 |
+
"model_name": "smoke",
|
| 81 |
+
"n_layer": 1,
|
| 82 |
+
"n_head": 1,
|
| 83 |
+
"n_embd": 16
|
| 84 |
+
}
|
| 85 |
+
],
|
| 86 |
+
"device": "mps",
|
| 87 |
+
"torch": "2.12.0",
|
| 88 |
+
"python": "3.11.15 (main, Mar 3 2026, 00:52:57) [Clang 21.0.0 (clang-2100.0.123.102)]",
|
| 89 |
+
"mps_available": true,
|
| 90 |
+
"attribution": "Derived from Andrej Karpathy's nanochat project (https://github.com/karpathy/nanochat), MIT License, Copyright (c) 2025 Andrej Karpathy.",
|
| 91 |
+
"tokenizer_path": ".cache/dropout_decay/tokenizer-v4096.json",
|
| 92 |
+
"encoded_path": ".cache/dropout_decay/tokens-v4096-uint16.npy",
|
| 93 |
+
"train_tokens": 5500842,
|
| 94 |
+
"val_tokens": 128,
|
| 95 |
+
"effective_token_limits": [
|
| 96 |
+
5000000
|
| 97 |
+
],
|
| 98 |
+
"effective_stream_token_caps": [
|
| 99 |
+
512,
|
| 100 |
+
768
|
| 101 |
+
],
|
| 102 |
+
"resume_from": null
|
| 103 |
+
}
|
runs/smoke_compat/locked_stream/20260605-224834/metrics.jsonl
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{"condition": "smoke_decay", "condition_kind": "anchor_decay", "dropout_active_final": 0.1, "dropout_final": 0.05, "dropout_initial": 0.1, "dropout_schedule": "log_prefix_anchor", "elapsed_sec": 0.08690309524536133, "eval_loss": 8.318451881408691, "generalization_gap": 0.00031757354736328125, "model_config": {"block_size": 16, "dropout": 0.1, "n_embd": 16, "n_head": 1, "n_layer": 1, "vocab_size": 4096}, "model_name": "smoke", "n_embd": 16, "n_head": 1, "n_layer": 1, "parameters": 134144, "run_mode": "locked_stream", "seed": 123, "stage": 0, "steps": 2, "token_limit": 512, "tokens_seen": 64, "train_eval_loss": 8.318134307861328, "train_loss_last": 8.317201614379883, "val_eval_loss": 8.318451881408691}
|
| 2 |
+
{"condition": "smoke_decay", "condition_kind": "anchor_decay", "dropout_active_final": 0.05, "dropout_final": 0.05, "dropout_initial": 0.1, "dropout_schedule": "log_prefix_anchor", "elapsed_sec": 0.018578290939331055, "eval_loss": 8.319382667541504, "generalization_gap": 0.0013904571533203125, "model_config": {"block_size": 16, "dropout": 0.1, "n_embd": 16, "n_head": 1, "n_layer": 1, "vocab_size": 4096}, "model_name": "smoke", "n_embd": 16, "n_head": 1, "n_layer": 1, "parameters": 134144, "run_mode": "locked_stream", "seed": 123, "stage": 1, "steps": 2, "token_limit": 768, "tokens_seen": 128, "train_eval_loss": 8.317992210388184, "train_loss_last": 8.3181734085083, "val_eval_loss": 8.319382667541504}
|
| 3 |
+
{"condition": "static_dropout_0.05", "condition_kind": "static", "dropout_active_final": 0.05, "dropout_final": 0.05, "dropout_initial": 0.05, "dropout_schedule": "constant", "elapsed_sec": 0.019870996475219727, "eval_loss": 8.318363189697266, "generalization_gap": -8.20159912109375e-05, "model_config": {"block_size": 16, "dropout": 0.05, "n_embd": 16, "n_head": 1, "n_layer": 1, "vocab_size": 4096}, "model_name": "smoke", "n_embd": 16, "n_head": 1, "n_layer": 1, "parameters": 134144, "run_mode": "locked_stream", "seed": 123, "stage": 0, "steps": 2, "token_limit": 512, "tokens_seen": 64, "train_eval_loss": 8.318445205688477, "train_loss_last": 8.317625999450684, "val_eval_loss": 8.318363189697266}
|
| 4 |
+
{"condition": "static_dropout_0.05", "condition_kind": "static", "dropout_active_final": 0.05, "dropout_final": 0.05, "dropout_initial": 0.05, "dropout_schedule": "constant", "elapsed_sec": 0.017475128173828125, "eval_loss": 8.319437026977539, "generalization_gap": 0.0068874359130859375, "model_config": {"block_size": 16, "dropout": 0.05, "n_embd": 16, "n_head": 1, "n_layer": 1, "vocab_size": 4096}, "model_name": "smoke", "n_embd": 16, "n_head": 1, "n_layer": 1, "parameters": 134144, "run_mode": "locked_stream", "seed": 123, "stage": 1, "steps": 2, "token_limit": 768, "tokens_seen": 128, "train_eval_loss": 8.312549591064453, "train_loss_last": 8.316361427307129, "val_eval_loss": 8.319437026977539}
|
runs/smoke_compat/locked_stream/20260605-224834/summary.csv
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
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
|
| 2 |
+
locked_stream,smoke_decay,anchor_decay,0,512,smoke,1,1,16,134144,0.1,0.05,log_prefix_anchor,1,8.318134307861328,0.0,8.318451881408691,0.0,0.00031757354736328125,0.0
|
| 3 |
+
locked_stream,static_dropout_0.05,static,0,512,smoke,1,1,16,134144,0.05,0.05,constant,1,8.318445205688477,0.0,8.318363189697266,0.0,-8.20159912109375e-05,0.0
|
| 4 |
+
locked_stream,smoke_decay,anchor_decay,1,768,smoke,1,1,16,134144,0.1,0.05,log_prefix_anchor,1,8.317992210388184,0.0,8.319382667541504,0.0,0.0013904571533203125,0.0
|
| 5 |
+
locked_stream,static_dropout_0.05,static,1,768,smoke,1,1,16,134144,0.05,0.05,constant,1,8.312549591064453,0.0,8.319437026977539,0.0,0.0068874359130859375,0.0
|
runs/smoke_compat/locked_stream/20260605-224834/summary.json
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
[
|
| 2 |
+
{
|
| 3 |
+
"run_mode": "locked_stream",
|
| 4 |
+
"condition": "smoke_decay",
|
| 5 |
+
"condition_kind": "anchor_decay",
|
| 6 |
+
"stage": 0,
|
| 7 |
+
"token_limit": 512,
|
| 8 |
+
"model_name": "smoke",
|
| 9 |
+
"n_layer": 1,
|
| 10 |
+
"n_head": 1,
|
| 11 |
+
"n_embd": 16,
|
| 12 |
+
"parameters": 134144,
|
| 13 |
+
"dropout_initial": 0.1,
|
| 14 |
+
"dropout_final": 0.05,
|
| 15 |
+
"dropout_schedule": "log_prefix_anchor",
|
| 16 |
+
"n": 1,
|
| 17 |
+
"mean_train_eval_loss": 8.318134307861328,
|
| 18 |
+
"std_train_eval_loss": 0.0,
|
| 19 |
+
"mean_val_eval_loss": 8.318451881408691,
|
| 20 |
+
"std_val_eval_loss": 0.0,
|
| 21 |
+
"mean_generalization_gap": 0.00031757354736328125,
|
| 22 |
+
"std_generalization_gap": 0.0
|
| 23 |
+
},
|
| 24 |
+
{
|
| 25 |
+
"run_mode": "locked_stream",
|
| 26 |
+
"condition": "static_dropout_0.05",
|
| 27 |
+
"condition_kind": "static",
|
| 28 |
+
"stage": 0,
|
| 29 |
+
"token_limit": 512,
|
| 30 |
+
"model_name": "smoke",
|
| 31 |
+
"n_layer": 1,
|
| 32 |
+
"n_head": 1,
|
| 33 |
+
"n_embd": 16,
|
| 34 |
+
"parameters": 134144,
|
| 35 |
+
"dropout_initial": 0.05,
|
| 36 |
+
"dropout_final": 0.05,
|
| 37 |
+
"dropout_schedule": "constant",
|
| 38 |
+
"n": 1,
|
| 39 |
+
"mean_train_eval_loss": 8.318445205688477,
|
| 40 |
+
"std_train_eval_loss": 0.0,
|
| 41 |
+
"mean_val_eval_loss": 8.318363189697266,
|
| 42 |
+
"std_val_eval_loss": 0.0,
|
| 43 |
+
"mean_generalization_gap": -8.20159912109375e-05,
|
| 44 |
+
"std_generalization_gap": 0.0
|
| 45 |
+
},
|
| 46 |
+
{
|
| 47 |
+
"run_mode": "locked_stream",
|
| 48 |
+
"condition": "smoke_decay",
|
| 49 |
+
"condition_kind": "anchor_decay",
|
| 50 |
+
"stage": 1,
|
| 51 |
+
"token_limit": 768,
|
| 52 |
+
"model_name": "smoke",
|
| 53 |
+
"n_layer": 1,
|
| 54 |
+
"n_head": 1,
|
| 55 |
+
"n_embd": 16,
|
| 56 |
+
"parameters": 134144,
|
| 57 |
+
"dropout_initial": 0.1,
|
| 58 |
+
"dropout_final": 0.05,
|
| 59 |
+
"dropout_schedule": "log_prefix_anchor",
|
| 60 |
+
"n": 1,
|
| 61 |
+
"mean_train_eval_loss": 8.317992210388184,
|
| 62 |
+
"std_train_eval_loss": 0.0,
|
| 63 |
+
"mean_val_eval_loss": 8.319382667541504,
|
| 64 |
+
"std_val_eval_loss": 0.0,
|
| 65 |
+
"mean_generalization_gap": 0.0013904571533203125,
|
| 66 |
+
"std_generalization_gap": 0.0
|
| 67 |
+
},
|
| 68 |
+
{
|
| 69 |
+
"run_mode": "locked_stream",
|
| 70 |
+
"condition": "static_dropout_0.05",
|
| 71 |
+
"condition_kind": "static",
|
| 72 |
+
"stage": 1,
|
| 73 |
+
"token_limit": 768,
|
| 74 |
+
"model_name": "smoke",
|
| 75 |
+
"n_layer": 1,
|
| 76 |
+
"n_head": 1,
|
| 77 |
+
"n_embd": 16,
|
| 78 |
+
"parameters": 134144,
|
| 79 |
+
"dropout_initial": 0.05,
|
| 80 |
+
"dropout_final": 0.05,
|
| 81 |
+
"dropout_schedule": "constant",
|
| 82 |
+
"n": 1,
|
| 83 |
+
"mean_train_eval_loss": 8.312549591064453,
|
| 84 |
+
"std_train_eval_loss": 0.0,
|
| 85 |
+
"mean_val_eval_loss": 8.319437026977539,
|
| 86 |
+
"std_val_eval_loss": 0.0,
|
| 87 |
+
"mean_generalization_gap": 0.0068874359130859375,
|
| 88 |
+
"std_generalization_gap": 0.0
|
| 89 |
+
}
|
| 90 |
+
]
|
runs/smoke_compat/locked_stream/20260605-224834/trace.jsonl
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{"condition": "smoke_decay", "dropout": 0.1, "event": "train_step", "model_name": "smoke", "run_mode": "locked_stream", "seed": 123, "stage": 0, "step": 1, "steps": 2, "token_limit": 512, "tokens_seen": 32, "train_batch_loss": 8.31665325164795}
|
| 2 |
+
{"condition": "smoke_decay", "dropout": 0.1, "event": "train_step", "model_name": "smoke", "run_mode": "locked_stream", "seed": 123, "stage": 0, "step": 2, "steps": 2, "token_limit": 512, "tokens_seen": 64, "train_batch_loss": 8.317201614379883}
|
| 3 |
+
{"condition": "smoke_decay", "dropout": 0.05, "event": "train_step", "model_name": "smoke", "run_mode": "locked_stream", "seed": 123, "stage": 1, "step": 1, "steps": 2, "token_limit": 768, "tokens_seen": 96, "train_batch_loss": 8.316765785217285}
|
| 4 |
+
{"condition": "smoke_decay", "dropout": 0.05, "event": "train_step", "model_name": "smoke", "run_mode": "locked_stream", "seed": 123, "stage": 1, "step": 2, "steps": 2, "token_limit": 768, "tokens_seen": 128, "train_batch_loss": 8.3181734085083}
|
| 5 |
+
{"condition": "static_dropout_0.05", "dropout": 0.05, "event": "train_step", "model_name": "smoke", "run_mode": "locked_stream", "seed": 123, "stage": 0, "step": 1, "steps": 2, "token_limit": 512, "tokens_seen": 32, "train_batch_loss": 8.317569732666016}
|
| 6 |
+
{"condition": "static_dropout_0.05", "dropout": 0.05, "event": "train_step", "model_name": "smoke", "run_mode": "locked_stream", "seed": 123, "stage": 0, "step": 2, "steps": 2, "token_limit": 512, "tokens_seen": 64, "train_batch_loss": 8.317625999450684}
|
| 7 |
+
{"condition": "static_dropout_0.05", "dropout": 0.05, "event": "train_step", "model_name": "smoke", "run_mode": "locked_stream", "seed": 123, "stage": 1, "step": 1, "steps": 2, "token_limit": 768, "tokens_seen": 96, "train_batch_loss": 8.318181037902832}
|
| 8 |
+
{"condition": "static_dropout_0.05", "dropout": 0.05, "event": "train_step", "model_name": "smoke", "run_mode": "locked_stream", "seed": 123, "stage": 1, "step": 2, "steps": 2, "token_limit": 768, "tokens_seen": 128, "train_batch_loss": 8.316361427307129}
|
runs/smoke_compat/screen_static/20260605-224821/RESULT_SUMMARY.md
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Static Dropout Screen Summary
|
| 2 |
+
|
| 3 |
+
Run directory: `runs/smoke_compat/screen_static/20260605-224821`
|
| 4 |
+
|
| 5 |
+
## Models
|
| 6 |
+
|
| 7 |
+
| Model | Params | Layers | Heads | Embedding | Block | Vocab | Seeds |
|
| 8 |
+
|---|---:|---:|---:|---:|---:|---:|---|
|
| 9 |
+
| `smoke` | 134,144 | 1 | 1 | 16 | 16 | 4096 | 123 |
|
| 10 |
+
|
| 11 |
+
## Best Dropout By Model And Prefix
|
| 12 |
+
|
| 13 |
+
| Model | Prefix tokens | Effective epochs | Best dropout | Mean val loss | Val std | Mean train loss | Mean gap | Plateau/bracket note |
|
| 14 |
+
|---|---:|---:|---:|---:|---:|---:|---:|---|
|
| 15 |
+
| `smoke` | 512 | 0.12 | 0.10 | 8.3185 | 0.0000 | 8.3181 | 0.0003 | not bracketed; best at top of tested grid |
|
| 16 |
+
|
| 17 |
+
## Model `smoke`
|
| 18 |
+
|
| 19 |
+
### Prefix 512 Tokens (0.12 Effective Epochs)
|
| 20 |
+
|
| 21 |
+
| Dropout | N | Mean val loss | Val std | Mean train loss | Train std | Mean gap | Gap std | Sampled tokens | Params |
|
| 22 |
+
|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:|
|
| 23 |
+
| 0.00 | 1 | 8.3186 | 0.0000 | 8.3134 | 0.0000 | 0.0052 | 0.0000 | 64 | 134,144 |
|
| 24 |
+
| 0.10 | 1 | 8.3185 | 0.0000 | 8.3181 | 0.0000 | 0.0003 | 0.0000 | 64 | 134,144 |
|
runs/smoke_compat/screen_static/20260605-224821/config.json
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"args": {
|
| 3 |
+
"mode": "screen_static",
|
| 4 |
+
"corpus": null,
|
| 5 |
+
"corpus_glob": null,
|
| 6 |
+
"text_column": "text",
|
| 7 |
+
"use_cached_data": true,
|
| 8 |
+
"output_dir": "runs/smoke_compat",
|
| 9 |
+
"resume_from": null,
|
| 10 |
+
"cache_dir": ".cache/dropout_decay",
|
| 11 |
+
"models": [
|
| 12 |
+
"smoke=1x1x16"
|
| 13 |
+
],
|
| 14 |
+
"seeds": [
|
| 15 |
+
123
|
| 16 |
+
],
|
| 17 |
+
"token_limits": [
|
| 18 |
+
512
|
| 19 |
+
],
|
| 20 |
+
"stream_token_caps": [
|
| 21 |
+
5000000,
|
| 22 |
+
10000000,
|
| 23 |
+
20000000,
|
| 24 |
+
40000000
|
| 25 |
+
],
|
| 26 |
+
"val_tokens": 128,
|
| 27 |
+
"allow_short_corpus": true,
|
| 28 |
+
"force_retokenize": false,
|
| 29 |
+
"vocab_size": 4096,
|
| 30 |
+
"tokenizer_train_chars": 10000000,
|
| 31 |
+
"block_size": 16,
|
| 32 |
+
"batch_size": 2,
|
| 33 |
+
"steps": 2,
|
| 34 |
+
"stage_steps": 1000,
|
| 35 |
+
"dropout_rates": [
|
| 36 |
+
0.0,
|
| 37 |
+
0.1
|
| 38 |
+
],
|
| 39 |
+
"decays": [],
|
| 40 |
+
"anchor_decays": [],
|
| 41 |
+
"decay_tokens": null,
|
| 42 |
+
"eval_batches": 1,
|
| 43 |
+
"train_eval_batches": 1,
|
| 44 |
+
"trace_eval_batches": 1,
|
| 45 |
+
"eval_every": 0,
|
| 46 |
+
"log_every": 1,
|
| 47 |
+
"lr": 0.0003,
|
| 48 |
+
"weight_decay": 0.1,
|
| 49 |
+
"grad_clip": 1.0,
|
| 50 |
+
"plateau_delta": 0.01,
|
| 51 |
+
"target_min_dropout": 0.1,
|
| 52 |
+
"min_nonzero_margin": 0.01,
|
| 53 |
+
"min_high_dropout_margin": 0.03,
|
| 54 |
+
"screen_early_stop": false,
|
| 55 |
+
"screen_prune_patience": 3,
|
| 56 |
+
"screen_prune_min_delta": 0.01
|
| 57 |
+
},
|
| 58 |
+
"mode": "screen_static",
|
| 59 |
+
"seeds": [
|
| 60 |
+
123
|
| 61 |
+
],
|
| 62 |
+
"models": [
|
| 63 |
+
{
|
| 64 |
+
"model_name": "smoke",
|
| 65 |
+
"n_layer": 1,
|
| 66 |
+
"n_head": 1,
|
| 67 |
+
"n_embd": 16
|
| 68 |
+
}
|
| 69 |
+
],
|
| 70 |
+
"device": "mps",
|
| 71 |
+
"torch": "2.12.0",
|
| 72 |
+
"python": "3.11.15 (main, Mar 3 2026, 00:52:57) [Clang 21.0.0 (clang-2100.0.123.102)]",
|
| 73 |
+
"mps_available": true,
|
| 74 |
+
"attribution": "Derived from Andrej Karpathy's nanochat project (https://github.com/karpathy/nanochat), MIT License, Copyright (c) 2025 Andrej Karpathy.",
|
| 75 |
+
"tokenizer_path": ".cache/dropout_decay/tokenizer-v4096.json",
|
| 76 |
+
"encoded_path": ".cache/dropout_decay/tokens-v4096-uint16.npy",
|
| 77 |
+
"train_tokens": 5500842,
|
| 78 |
+
"val_tokens": 128,
|
| 79 |
+
"effective_token_limits": [
|
| 80 |
+
512
|
| 81 |
+
],
|
| 82 |
+
"effective_stream_token_caps": [
|
| 83 |
+
5000000,
|
| 84 |
+
5500842,
|
| 85 |
+
5500842,
|
| 86 |
+
5500842
|
| 87 |
+
],
|
| 88 |
+
"resume_from": null
|
| 89 |
+
}
|
runs/smoke_compat/screen_static/20260605-224821/dropout_curves.svg
ADDED
|
|
runs/smoke_compat/screen_static/20260605-224821/metrics.jsonl
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{"condition": "static_dropout_0", "condition_kind": "static", "dropout_active_final": 0.0, "dropout_final": 0.0, "dropout_initial": 0.0, "dropout_schedule": "constant", "elapsed_sec": 0.5898959636688232, "eval_loss": 8.318593978881836, "generalization_gap": 0.005218505859375, "model_config": {"block_size": 16, "dropout": 0.0, "n_embd": 16, "n_head": 1, "n_layer": 1, "vocab_size": 4096}, "model_name": "smoke", "n_embd": 16, "n_head": 1, "n_layer": 1, "parameters": 134144, "run_mode": "screen_static", "seed": 123, "stage": null, "steps": 2, "token_limit": 512, "tokens_seen": 64, "train_eval_loss": 8.313375473022461, "train_loss_last": 8.315808296203613, "val_eval_loss": 8.318593978881836}
|
| 2 |
+
{"condition": "static_dropout_0.1", "condition_kind": "static", "dropout_active_final": 0.1, "dropout_final": 0.1, "dropout_initial": 0.1, "dropout_schedule": "constant", "elapsed_sec": 0.10111308097839355, "eval_loss": 8.318451881408691, "generalization_gap": 0.00031757354736328125, "model_config": {"block_size": 16, "dropout": 0.1, "n_embd": 16, "n_head": 1, "n_layer": 1, "vocab_size": 4096}, "model_name": "smoke", "n_embd": 16, "n_head": 1, "n_layer": 1, "parameters": 134144, "run_mode": "screen_static", "seed": 123, "stage": null, "steps": 2, "token_limit": 512, "tokens_seen": 64, "train_eval_loss": 8.318134307861328, "train_loss_last": 8.317201614379883, "val_eval_loss": 8.318451881408691}
|
runs/smoke_compat/screen_static/20260605-224821/model_selection.csv
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
|
|
|
|
|
|
|
| 1 |
+
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
|
| 2 |
+
screen_static,512,smoke,1,1,16,134144,1,0.1,8.318451881408691,0.0,0.0,0.1,0.01,8.318593978881836,0.00014209747314453125,0.1,8.318451881408691,0.00014209747314453125,0.1,8.318451881408691,0.0,False,True,"[{""dropout"": 0.0, ""mean_generalization_gap"": 0.005218505859375, ""mean_train_loss"": 8.313375473022461, ""mean_val_loss"": 8.318593978881836, ""n"": 1, ""std_val_loss"": 0.0}, {""dropout"": 0.1, ""mean_generalization_gap"": 0.00031757354736328125, ""mean_train_loss"": 8.318134307861328, ""mean_val_loss"": 8.318451881408691, ""n"": 1, ""std_val_loss"": 0.0}]"
|
runs/smoke_compat/screen_static/20260605-224821/model_selection.json
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
[
|
| 2 |
+
{
|
| 3 |
+
"run_mode": "screen_static",
|
| 4 |
+
"token_limit": 512,
|
| 5 |
+
"model_name": "smoke",
|
| 6 |
+
"n_layer": 1,
|
| 7 |
+
"n_head": 1,
|
| 8 |
+
"n_embd": 16,
|
| 9 |
+
"parameters": 134144,
|
| 10 |
+
"n": 1,
|
| 11 |
+
"best_dropout": 0.1,
|
| 12 |
+
"best_val_loss": 8.318451881408691,
|
| 13 |
+
"best_val_std": 0.0,
|
| 14 |
+
"plateau_start_dropout": 0.0,
|
| 15 |
+
"plateau_end_dropout": 0.1,
|
| 16 |
+
"plateau_delta": 0.01,
|
| 17 |
+
"zero_dropout_val_loss": 8.318593978881836,
|
| 18 |
+
"zero_minus_best": 0.00014209747314453125,
|
| 19 |
+
"best_nonzero_dropout": 0.1,
|
| 20 |
+
"best_nonzero_val_loss": 8.318451881408691,
|
| 21 |
+
"zero_minus_best_nonzero": 0.00014209747314453125,
|
| 22 |
+
"max_dropout": 0.1,
|
| 23 |
+
"max_dropout_val_loss": 8.318451881408691,
|
| 24 |
+
"max_dropout_minus_best": 0.0,
|
| 25 |
+
"has_nonzero_optimum": false,
|
| 26 |
+
"meets_target_dropout": true,
|
| 27 |
+
"curve_json": "[{\"dropout\": 0.0, \"mean_generalization_gap\": 0.005218505859375, \"mean_train_loss\": 8.313375473022461, \"mean_val_loss\": 8.318593978881836, \"n\": 1, \"std_val_loss\": 0.0}, {\"dropout\": 0.1, \"mean_generalization_gap\": 0.00031757354736328125, \"mean_train_loss\": 8.318134307861328, \"mean_val_loss\": 8.318451881408691, \"n\": 1, \"std_val_loss\": 0.0}]"
|
| 28 |
+
}
|
| 29 |
+
]
|
runs/smoke_compat/screen_static/20260605-224821/summary.csv
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
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
|
| 2 |
+
screen_static,static_dropout_0,static,,512,smoke,1,1,16,134144,0.0,0.0,constant,1,8.313375473022461,0.0,8.318593978881836,0.0,0.005218505859375,0.0
|
| 3 |
+
screen_static,static_dropout_0.1,static,,512,smoke,1,1,16,134144,0.1,0.1,constant,1,8.318134307861328,0.0,8.318451881408691,0.0,0.00031757354736328125,0.0
|
runs/smoke_compat/screen_static/20260605-224821/summary.json
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
[
|
| 2 |
+
{
|
| 3 |
+
"run_mode": "screen_static",
|
| 4 |
+
"condition": "static_dropout_0",
|
| 5 |
+
"condition_kind": "static",
|
| 6 |
+
"stage": null,
|
| 7 |
+
"token_limit": 512,
|
| 8 |
+
"model_name": "smoke",
|
| 9 |
+
"n_layer": 1,
|
| 10 |
+
"n_head": 1,
|
| 11 |
+
"n_embd": 16,
|
| 12 |
+
"parameters": 134144,
|
| 13 |
+
"dropout_initial": 0.0,
|
| 14 |
+
"dropout_final": 0.0,
|
| 15 |
+
"dropout_schedule": "constant",
|
| 16 |
+
"n": 1,
|
| 17 |
+
"mean_train_eval_loss": 8.313375473022461,
|
| 18 |
+
"std_train_eval_loss": 0.0,
|
| 19 |
+
"mean_val_eval_loss": 8.318593978881836,
|
| 20 |
+
"std_val_eval_loss": 0.0,
|
| 21 |
+
"mean_generalization_gap": 0.005218505859375,
|
| 22 |
+
"std_generalization_gap": 0.0
|
| 23 |
+
},
|
| 24 |
+
{
|
| 25 |
+
"run_mode": "screen_static",
|
| 26 |
+
"condition": "static_dropout_0.1",
|
| 27 |
+
"condition_kind": "static",
|
| 28 |
+
"stage": null,
|
| 29 |
+
"token_limit": 512,
|
| 30 |
+
"model_name": "smoke",
|
| 31 |
+
"n_layer": 1,
|
| 32 |
+
"n_head": 1,
|
| 33 |
+
"n_embd": 16,
|
| 34 |
+
"parameters": 134144,
|
| 35 |
+
"dropout_initial": 0.1,
|
| 36 |
+
"dropout_final": 0.1,
|
| 37 |
+
"dropout_schedule": "constant",
|
| 38 |
+
"n": 1,
|
| 39 |
+
"mean_train_eval_loss": 8.318134307861328,
|
| 40 |
+
"std_train_eval_loss": 0.0,
|
| 41 |
+
"mean_val_eval_loss": 8.318451881408691,
|
| 42 |
+
"std_val_eval_loss": 0.0,
|
| 43 |
+
"mean_generalization_gap": 0.00031757354736328125,
|
| 44 |
+
"std_generalization_gap": 0.0
|
| 45 |
+
}
|
| 46 |
+
]
|
runs/smoke_compat/screen_static/20260605-224821/trace.jsonl
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{"condition": "static_dropout_0", "dropout": 0.0, "event": "train_step", "model_name": "smoke", "run_mode": "screen_static", "seed": 123, "stage": null, "step": 1, "steps": 2, "token_limit": 512, "tokens_seen": 32, "train_batch_loss": 8.318573951721191}
|
| 2 |
+
{"condition": "static_dropout_0", "dropout": 0.0, "event": "train_step", "model_name": "smoke", "run_mode": "screen_static", "seed": 123, "stage": null, "step": 2, "steps": 2, "token_limit": 512, "tokens_seen": 64, "train_batch_loss": 8.315808296203613}
|
| 3 |
+
{"condition": "static_dropout_0.1", "dropout": 0.1, "event": "train_step", "model_name": "smoke", "run_mode": "screen_static", "seed": 123, "stage": null, "step": 1, "steps": 2, "token_limit": 512, "tokens_seen": 32, "train_batch_loss": 8.31665325164795}
|
| 4 |
+
{"condition": "static_dropout_0.1", "dropout": 0.1, "event": "train_step", "model_name": "smoke", "run_mode": "screen_static", "seed": 123, "stage": null, "step": 2, "steps": 2, "token_limit": 512, "tokens_seen": 64, "train_batch_loss": 8.317201614379883}
|
scripts/generate_paper_assets.py
ADDED
|
@@ -0,0 +1,579 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""
|
| 3 |
+
Derived from Andrej Karpathy's nanochat project.
|
| 4 |
+
|
| 5 |
+
MIT License
|
| 6 |
+
|
| 7 |
+
Copyright (c) 2025 Andrej Karpathy
|
| 8 |
+
|
| 9 |
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
| 10 |
+
of this software and associated documentation files (the "Software"), to deal
|
| 11 |
+
in the Software without restriction, including without limitation the rights
|
| 12 |
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
| 13 |
+
copies of the Software, and to permit persons to whom the Software is
|
| 14 |
+
furnished to do so, subject to the following conditions:
|
| 15 |
+
|
| 16 |
+
The above copyright notice and this permission notice shall be included in all
|
| 17 |
+
copies or substantial portions of the Software.
|
| 18 |
+
"""
|
| 19 |
+
|
| 20 |
+
from __future__ import annotations
|
| 21 |
+
|
| 22 |
+
import csv
|
| 23 |
+
import json
|
| 24 |
+
import math
|
| 25 |
+
from pathlib import Path
|
| 26 |
+
import statistics
|
| 27 |
+
|
| 28 |
+
|
| 29 |
+
ROOT = Path(__file__).resolve().parents[1]
|
| 30 |
+
PAPER_DIR = ROOT / "paper"
|
| 31 |
+
FIG_DIR = PAPER_DIR / "figures"
|
| 32 |
+
TABLE_DIR = PAPER_DIR / "tables"
|
| 33 |
+
|
| 34 |
+
|
| 35 |
+
REGIMES = {
|
| 36 |
+
"openweb": {
|
| 37 |
+
"label": "OpenWebText10K",
|
| 38 |
+
"short": "OWT10K",
|
| 39 |
+
"coeff": ROOT
|
| 40 |
+
/ "runs/coefficient_calibration/cross_regime_backtest/openwebtext10k_main_interaction/coefficients.json",
|
| 41 |
+
"cells": ROOT
|
| 42 |
+
/ "runs/coefficient_calibration/cross_regime_backtest/openwebtext10k_main_interaction/calibration_cells.csv",
|
| 43 |
+
"curve_run": ROOT / "runs/screen_static/20260525-133008",
|
| 44 |
+
"curve_model": "L12_H8_D320",
|
| 45 |
+
"curve_prefix": 1_000_000,
|
| 46 |
+
"curve_title": "OpenWebText10K, L12, 1M",
|
| 47 |
+
"small_stream": ROOT
|
| 48 |
+
/ "runs/openwebtext10k_l16_updated_formula_clean_5seed/locked_stream/20260530-174525",
|
| 49 |
+
"small_condition": "openwebtext10k_interaction",
|
| 50 |
+
"small_model": "L16_H8_D384",
|
| 51 |
+
"small_protocol": "4M / 5 seeds",
|
| 52 |
+
"l20_stream": ROOT
|
| 53 |
+
/ "runs/openwebtext10k_l20_8m_extrapolation_3seed/locked_stream/20260601-191150",
|
| 54 |
+
"l20_condition": "openwebtext10k_l20_interaction_8m",
|
| 55 |
+
},
|
| 56 |
+
"tinystories": {
|
| 57 |
+
"label": "TinyStories",
|
| 58 |
+
"short": "TinyStories",
|
| 59 |
+
"coeff": ROOT
|
| 60 |
+
/ "runs/coefficient_calibration/tinystories_combined_plus_all_holdouts_interaction/coefficients.json",
|
| 61 |
+
"cells": ROOT
|
| 62 |
+
/ "runs/coefficient_calibration/tinystories_combined_plus_all_holdouts_interaction/calibration_cells.csv",
|
| 63 |
+
"curve_run": ROOT
|
| 64 |
+
/ "runs/regime_calibration_tinystories_coarse/screen_static/20260529-161726",
|
| 65 |
+
"curve_model": "L12_H8_D320",
|
| 66 |
+
"curve_prefix": 1_000_000,
|
| 67 |
+
"curve_title": "TinyStories, L12, 1M",
|
| 68 |
+
"small_stream": [
|
| 69 |
+
ROOT
|
| 70 |
+
/ "runs/streaming_tinystories_interaction_schedule_l12/locked_stream/20260530-053831",
|
| 71 |
+
ROOT
|
| 72 |
+
/ "runs/streaming_tinystories_multiseed_validation_l12/locked_stream/20260530-111523",
|
| 73 |
+
ROOT
|
| 74 |
+
/ "runs/streaming_tinystories_multiseed_validation_l12/locked_stream/20260530-141335",
|
| 75 |
+
],
|
| 76 |
+
"small_condition": "interaction",
|
| 77 |
+
"small_model": "L12_H8_D320",
|
| 78 |
+
"small_protocol": "4M / 5 seeds",
|
| 79 |
+
"l20_stream": ROOT
|
| 80 |
+
/ "runs/tinystories_l20_8m_extrapolation_3seed/locked_stream/20260602-112249",
|
| 81 |
+
"l20_condition": "tinystories_l20_interaction_8m",
|
| 82 |
+
},
|
| 83 |
+
"wikitext": {
|
| 84 |
+
"label": "WikiText-103",
|
| 85 |
+
"short": "WikiText",
|
| 86 |
+
"coeff": ROOT / "runs/coefficient_calibration/wikitext103_interaction/coefficients.json",
|
| 87 |
+
"cells": ROOT / "runs/coefficient_calibration/wikitext103_interaction/calibration_cells.csv",
|
| 88 |
+
"curve_run": ROOT
|
| 89 |
+
/ "runs/wikitext103_interaction_static_screen/screen_static/20260603-153314",
|
| 90 |
+
"curve_model": "L12_H8_D320",
|
| 91 |
+
"curve_prefix": 250_000,
|
| 92 |
+
"curve_title": "WikiText-103, L12, 250k",
|
| 93 |
+
"small_stream": ROOT
|
| 94 |
+
/ "runs/wikitext103_l12_streaming_validation_5seed/locked_stream/20260531-093525",
|
| 95 |
+
"small_condition": "wikitext103_formula_l12",
|
| 96 |
+
"small_model": "L12_H8_D320",
|
| 97 |
+
"small_protocol": "4M / 5 seeds",
|
| 98 |
+
"l20_stream": ROOT
|
| 99 |
+
/ "runs/wikitext103_l20_8m_extrapolation_3seed/locked_stream/20260603-231952",
|
| 100 |
+
"l20_condition": "wikitext103_l20_interaction_8m",
|
| 101 |
+
},
|
| 102 |
+
}
|
| 103 |
+
|
| 104 |
+
|
| 105 |
+
def read_json(path: Path) -> dict | list:
|
| 106 |
+
return json.loads(path.read_text(encoding="utf-8"))
|
| 107 |
+
|
| 108 |
+
|
| 109 |
+
def read_jsonl(path: Path) -> list[dict]:
|
| 110 |
+
rows = []
|
| 111 |
+
for line in path.read_text(encoding="utf-8").splitlines():
|
| 112 |
+
if line.strip():
|
| 113 |
+
rows.append(json.loads(line))
|
| 114 |
+
return rows
|
| 115 |
+
|
| 116 |
+
|
| 117 |
+
def read_csv(path: Path) -> list[dict]:
|
| 118 |
+
with path.open(newline="", encoding="utf-8") as handle:
|
| 119 |
+
return list(csv.DictReader(handle))
|
| 120 |
+
|
| 121 |
+
|
| 122 |
+
def mean_std(values: list[float]) -> tuple[float, float]:
|
| 123 |
+
if not values:
|
| 124 |
+
return float("nan"), float("nan")
|
| 125 |
+
mean = statistics.fmean(values)
|
| 126 |
+
std = statistics.stdev(values) if len(values) > 1 else 0.0
|
| 127 |
+
return mean, std
|
| 128 |
+
|
| 129 |
+
|
| 130 |
+
def fmt(x: float, digits: int = 4) -> str:
|
| 131 |
+
return f"{x:.{digits}f}"
|
| 132 |
+
|
| 133 |
+
|
| 134 |
+
def tex_escape(value: object) -> str:
|
| 135 |
+
text = str(value)
|
| 136 |
+
return (
|
| 137 |
+
text.replace("\\", "\\textbackslash{}")
|
| 138 |
+
.replace("_", "\\_")
|
| 139 |
+
.replace("&", "\\&")
|
| 140 |
+
.replace("%", "\\%")
|
| 141 |
+
.replace("#", "\\#")
|
| 142 |
+
)
|
| 143 |
+
|
| 144 |
+
|
| 145 |
+
def token_label(value: int) -> str:
|
| 146 |
+
if value >= 1_000_000:
|
| 147 |
+
whole = value / 1_000_000
|
| 148 |
+
return f"{whole:g}M"
|
| 149 |
+
return f"{value // 1000}k"
|
| 150 |
+
|
| 151 |
+
|
| 152 |
+
def metric_stage_summary(run_dir: Path | list[Path], condition_name: str) -> dict:
|
| 153 |
+
run_dirs = run_dir if isinstance(run_dir, list) else [run_dir]
|
| 154 |
+
rows = []
|
| 155 |
+
for path in run_dirs:
|
| 156 |
+
rows.extend(read_jsonl(path / "metrics.jsonl"))
|
| 157 |
+
stages = sorted({int(row["stage"]) for row in rows if row.get("stage") is not None})
|
| 158 |
+
final_stage = max(stages)
|
| 159 |
+
by_condition_stage: dict[tuple[str, int], list[dict]] = {}
|
| 160 |
+
for row in rows:
|
| 161 |
+
if row.get("stage") is None:
|
| 162 |
+
continue
|
| 163 |
+
key = (row["condition"], int(row["stage"]))
|
| 164 |
+
by_condition_stage.setdefault(key, []).append(row)
|
| 165 |
+
|
| 166 |
+
condition_rows = by_condition_stage[(condition_name, final_stage)]
|
| 167 |
+
final_vals = [float(row["val_eval_loss"]) for row in condition_rows]
|
| 168 |
+
condition_final_mean, condition_final_std = mean_std(final_vals)
|
| 169 |
+
|
| 170 |
+
final_static_by_condition: dict[str, list[dict]] = {}
|
| 171 |
+
for row in rows:
|
| 172 |
+
if int(row.get("stage", -1)) != final_stage:
|
| 173 |
+
continue
|
| 174 |
+
if row["condition_kind"] == "static":
|
| 175 |
+
final_static_by_condition.setdefault(row["condition"], []).append(row)
|
| 176 |
+
static_means = {
|
| 177 |
+
condition: statistics.fmean(float(row["val_eval_loss"]) for row in group)
|
| 178 |
+
for condition, group in final_static_by_condition.items()
|
| 179 |
+
}
|
| 180 |
+
best_static_condition = min(static_means, key=static_means.get)
|
| 181 |
+
best_static_rows = final_static_by_condition[best_static_condition]
|
| 182 |
+
best_static_vals = [float(row["val_eval_loss"]) for row in best_static_rows]
|
| 183 |
+
best_static_mean, best_static_std = mean_std(best_static_vals)
|
| 184 |
+
|
| 185 |
+
final_static_by_seed: dict[int, tuple[str, float]] = {}
|
| 186 |
+
for row in rows:
|
| 187 |
+
if int(row.get("stage", -1)) != final_stage or row["condition_kind"] != "static":
|
| 188 |
+
continue
|
| 189 |
+
seed = int(row["seed"])
|
| 190 |
+
value = float(row["val_eval_loss"])
|
| 191 |
+
if seed not in final_static_by_seed or value < final_static_by_seed[seed][1]:
|
| 192 |
+
final_static_by_seed[seed] = (row["condition"], value)
|
| 193 |
+
|
| 194 |
+
paired_deltas = []
|
| 195 |
+
for row in condition_rows:
|
| 196 |
+
seed = int(row["seed"])
|
| 197 |
+
paired_deltas.append(float(row["val_eval_loss"]) - final_static_by_seed[seed][1])
|
| 198 |
+
paired_mean, paired_std = mean_std(paired_deltas)
|
| 199 |
+
|
| 200 |
+
stage_deltas = []
|
| 201 |
+
for stage in stages:
|
| 202 |
+
condition_group = by_condition_stage[(condition_name, stage)]
|
| 203 |
+
condition_mean = statistics.fmean(
|
| 204 |
+
float(row["val_eval_loss"]) for row in condition_group
|
| 205 |
+
)
|
| 206 |
+
static_candidates = []
|
| 207 |
+
for (condition, candidate_stage), group in by_condition_stage.items():
|
| 208 |
+
if candidate_stage != stage:
|
| 209 |
+
continue
|
| 210 |
+
if group[0]["condition_kind"] != "static":
|
| 211 |
+
continue
|
| 212 |
+
static_candidates.append(
|
| 213 |
+
(
|
| 214 |
+
condition,
|
| 215 |
+
statistics.fmean(float(row["val_eval_loss"]) for row in group),
|
| 216 |
+
statistics.stdev(float(row["val_eval_loss"]) for row in group)
|
| 217 |
+
if len(group) > 1
|
| 218 |
+
else 0.0,
|
| 219 |
+
)
|
| 220 |
+
)
|
| 221 |
+
best_static_stage = min(static_candidates, key=lambda item: item[1])
|
| 222 |
+
first = condition_group[0]
|
| 223 |
+
stage_deltas.append(
|
| 224 |
+
{
|
| 225 |
+
"stage": stage,
|
| 226 |
+
"prefix": int(first["token_limit"]),
|
| 227 |
+
"condition_dropout": statistics.fmean(
|
| 228 |
+
float(row["dropout_active_final"]) for row in condition_group
|
| 229 |
+
),
|
| 230 |
+
"condition_mean": condition_mean,
|
| 231 |
+
"condition_std": statistics.stdev(
|
| 232 |
+
float(row["val_eval_loss"]) for row in condition_group
|
| 233 |
+
)
|
| 234 |
+
if len(condition_group) > 1
|
| 235 |
+
else 0.0,
|
| 236 |
+
"best_static_condition": best_static_stage[0],
|
| 237 |
+
"best_static_mean": best_static_stage[1],
|
| 238 |
+
"best_static_std": best_static_stage[2],
|
| 239 |
+
"delta": condition_mean - best_static_stage[1],
|
| 240 |
+
}
|
| 241 |
+
)
|
| 242 |
+
|
| 243 |
+
return {
|
| 244 |
+
"n": len(condition_rows),
|
| 245 |
+
"final_stage": final_stage,
|
| 246 |
+
"final_prefix": int(condition_rows[0]["token_limit"]),
|
| 247 |
+
"condition_final_mean": condition_final_mean,
|
| 248 |
+
"condition_final_std": condition_final_std,
|
| 249 |
+
"best_static_condition": best_static_condition,
|
| 250 |
+
"best_static_mean": best_static_mean,
|
| 251 |
+
"best_static_std": best_static_std,
|
| 252 |
+
"improvement": best_static_mean - condition_final_mean,
|
| 253 |
+
"paired_deltas": paired_deltas,
|
| 254 |
+
"paired_wins": sum(1 for delta in paired_deltas if delta < 0),
|
| 255 |
+
"paired_mean_delta": paired_mean,
|
| 256 |
+
"paired_std_delta": paired_std,
|
| 257 |
+
"stage_deltas": stage_deltas,
|
| 258 |
+
}
|
| 259 |
+
|
| 260 |
+
|
| 261 |
+
def static_curve_points(run_dir: Path, model: str, prefix: int) -> list[tuple[float, float]]:
|
| 262 |
+
rows = read_json(run_dir / "summary.json")
|
| 263 |
+
curve = []
|
| 264 |
+
for row in rows:
|
| 265 |
+
if row["run_mode"] != "screen_static":
|
| 266 |
+
continue
|
| 267 |
+
if row["condition_kind"] != "static":
|
| 268 |
+
continue
|
| 269 |
+
if row["model_name"] != model:
|
| 270 |
+
continue
|
| 271 |
+
if int(row["token_limit"]) != prefix:
|
| 272 |
+
continue
|
| 273 |
+
curve.append((float(row["dropout_initial"]), float(row["mean_val_eval_loss"])))
|
| 274 |
+
return sorted(curve)
|
| 275 |
+
|
| 276 |
+
|
| 277 |
+
def calibration_points(cells_path: Path) -> dict[str, list[tuple[int, float]]]:
|
| 278 |
+
rows = read_csv(cells_path)
|
| 279 |
+
grouped: dict[str, dict[int, float]] = {}
|
| 280 |
+
for row in rows:
|
| 281 |
+
sampled = int(float(row["sampled_tokens"]))
|
| 282 |
+
unique = int(float(row["unique_tokens"]))
|
| 283 |
+
source = row["source"]
|
| 284 |
+
if "sample_pressure_low" in source:
|
| 285 |
+
continue
|
| 286 |
+
if "heldout_model" in source:
|
| 287 |
+
continue
|
| 288 |
+
model = row["model_name"]
|
| 289 |
+
target = max(0.0, float(row["target_dropout"]))
|
| 290 |
+
grouped.setdefault(model, {})[unique] = target
|
| 291 |
+
return {
|
| 292 |
+
model: sorted(points.items())
|
| 293 |
+
for model, points in sorted(grouped.items())
|
| 294 |
+
if len(points) >= 2
|
| 295 |
+
}
|
| 296 |
+
|
| 297 |
+
|
| 298 |
+
def coeff_summary(regime_key: str, paths: dict) -> dict:
|
| 299 |
+
coeff = read_json(paths["coeff"])
|
| 300 |
+
return {
|
| 301 |
+
"regime": paths["label"],
|
| 302 |
+
"cells": int(coeff["metrics"]["n"]),
|
| 303 |
+
"A": float(coeff["coefficients"]["A"]),
|
| 304 |
+
"B": float(coeff["coefficients"]["B"]),
|
| 305 |
+
"D": float(coeff["coefficients"]["D"]),
|
| 306 |
+
"C0": float(coeff["coefficients"]["C0"]),
|
| 307 |
+
"mae": float(coeff["metrics"]["mae"]),
|
| 308 |
+
"rmse": float(coeff["metrics"]["rmse"]),
|
| 309 |
+
"leave_model_mae": float(coeff["cv"]["leave_model"]["mae"]),
|
| 310 |
+
"leave_prefix_mae": float(coeff["cv"]["leave_prefix"]["mae"]),
|
| 311 |
+
"formula": coeff["formula"],
|
| 312 |
+
}
|
| 313 |
+
|
| 314 |
+
|
| 315 |
+
def tikz_coordinates(points: list[tuple[float, float]], digits: int = 4) -> str:
|
| 316 |
+
return " ".join(f"({x:.4g},{y:.{digits}f})" for x, y in points)
|
| 317 |
+
|
| 318 |
+
|
| 319 |
+
def write_calibration_curve_figure() -> None:
|
| 320 |
+
axes = []
|
| 321 |
+
for key, paths in REGIMES.items():
|
| 322 |
+
raw_curve = static_curve_points(
|
| 323 |
+
paths["curve_run"], paths["curve_model"], paths["curve_prefix"]
|
| 324 |
+
)
|
| 325 |
+
minimum = min(loss for _, loss in raw_curve)
|
| 326 |
+
points = [(dropout, loss - minimum) for dropout, loss in raw_curve]
|
| 327 |
+
best = min(raw_curve, key=lambda item: item[1])
|
| 328 |
+
axes.append(
|
| 329 |
+
rf"""
|
| 330 |
+
\nextgroupplot[
|
| 331 |
+
title={{{paths["curve_title"]}}},
|
| 332 |
+
xlabel={{Dropout}},
|
| 333 |
+
ylabel={{Validation loss above best}},
|
| 334 |
+
ymin=0,
|
| 335 |
+
grid=both,
|
| 336 |
+
]
|
| 337 |
+
\addplot+[thick, mark=*] coordinates {{{tikz_coordinates(points, digits=5)}}};
|
| 338 |
+
\addlegendentry{{static sweep}}
|
| 339 |
+
\addplot+[only marks, mark=star, mark size=3pt] coordinates {{({best[0]:.4g},0)}};
|
| 340 |
+
\addlegendentry{{target $p^\star={best[0]:.2f}$}}
|
| 341 |
+
"""
|
| 342 |
+
)
|
| 343 |
+
content = (
|
| 344 |
+
"\\begin{tikzpicture}\n"
|
| 345 |
+
"\\begin{groupplot}[\n"
|
| 346 |
+
" group style={group size=3 by 1, horizontal sep=1.0cm},\n"
|
| 347 |
+
" width=0.32\\textwidth,\n"
|
| 348 |
+
" height=0.26\\textwidth,\n"
|
| 349 |
+
" legend style={font=\\scriptsize},\n"
|
| 350 |
+
" tick label style={font=\\scriptsize},\n"
|
| 351 |
+
" label style={font=\\scriptsize},\n"
|
| 352 |
+
" title style={font=\\scriptsize},\n"
|
| 353 |
+
"]\n"
|
| 354 |
+
+ "\n".join(axes)
|
| 355 |
+
+ "\n\\end{groupplot}\n\\end{tikzpicture}\n"
|
| 356 |
+
)
|
| 357 |
+
(FIG_DIR / "calibration_cell_curves.tex").write_text(content, encoding="utf-8")
|
| 358 |
+
|
| 359 |
+
|
| 360 |
+
def write_static_optima_figure() -> None:
|
| 361 |
+
axes = []
|
| 362 |
+
for key, paths in REGIMES.items():
|
| 363 |
+
grouped = calibration_points(paths["cells"])
|
| 364 |
+
labels = sorted({prefix for points in grouped.values() for prefix, _ in points})
|
| 365 |
+
label_to_x = {prefix: idx for idx, prefix in enumerate(labels)}
|
| 366 |
+
plots = []
|
| 367 |
+
for model, points in grouped.items():
|
| 368 |
+
coords = [
|
| 369 |
+
(label_to_x[prefix], target)
|
| 370 |
+
for prefix, target in points
|
| 371 |
+
if prefix in label_to_x
|
| 372 |
+
]
|
| 373 |
+
plots.append(
|
| 374 |
+
"\\addplot+[thick, mark=*] coordinates {"
|
| 375 |
+
+ tikz_coordinates(coords)
|
| 376 |
+
+ "};\n"
|
| 377 |
+
+ f"\\addlegendentry{{{tex_escape(model)}}}"
|
| 378 |
+
)
|
| 379 |
+
xticks = ",".join(str(i) for i in range(len(labels)))
|
| 380 |
+
xticklabels = ",".join(token_label(prefix) for prefix in labels)
|
| 381 |
+
axes.append(
|
| 382 |
+
rf"""
|
| 383 |
+
\nextgroupplot[
|
| 384 |
+
title={{{paths["label"]}}},
|
| 385 |
+
xlabel={{Unique prefix $U$}},
|
| 386 |
+
ylabel={{Fitted target dropout}},
|
| 387 |
+
xtick={{{xticks}}},
|
| 388 |
+
xticklabels={{{xticklabels}}},
|
| 389 |
+
ymin=0,
|
| 390 |
+
ymax=0.65,
|
| 391 |
+
grid=both,
|
| 392 |
+
]
|
| 393 |
+
{chr(10).join(plots)}
|
| 394 |
+
"""
|
| 395 |
+
)
|
| 396 |
+
content = (
|
| 397 |
+
"\\begin{tikzpicture}\n"
|
| 398 |
+
"\\begin{groupplot}[\n"
|
| 399 |
+
" group style={group size=3 by 1, horizontal sep=1.0cm},\n"
|
| 400 |
+
" width=0.32\\textwidth,\n"
|
| 401 |
+
" height=0.28\\textwidth,\n"
|
| 402 |
+
" legend style={font=\\tiny},\n"
|
| 403 |
+
" tick label style={font=\\scriptsize},\n"
|
| 404 |
+
" label style={font=\\scriptsize},\n"
|
| 405 |
+
" title style={font=\\scriptsize},\n"
|
| 406 |
+
"]\n"
|
| 407 |
+
+ "\n".join(axes)
|
| 408 |
+
+ "\n\\end{groupplot}\n\\end{tikzpicture}\n"
|
| 409 |
+
)
|
| 410 |
+
(FIG_DIR / "static_optima_by_regime.tex").write_text(content, encoding="utf-8")
|
| 411 |
+
|
| 412 |
+
|
| 413 |
+
def write_l20_stage_delta_figure(summaries: dict[str, dict]) -> None:
|
| 414 |
+
axes = []
|
| 415 |
+
for key, paths in REGIMES.items():
|
| 416 |
+
stage_rows = summaries[key]["l20"]["stage_deltas"]
|
| 417 |
+
labels = [token_label(row["prefix"]) for row in stage_rows]
|
| 418 |
+
coords = [(idx, row["delta"]) for idx, row in enumerate(stage_rows)]
|
| 419 |
+
zero_coords = [(0, 0.0), (len(stage_rows) - 1, 0.0)]
|
| 420 |
+
xticks = ",".join(str(i) for i in range(len(labels)))
|
| 421 |
+
xticklabels = ",".join(labels)
|
| 422 |
+
axes.append(
|
| 423 |
+
rf"""
|
| 424 |
+
\nextgroupplot[
|
| 425 |
+
title={{{paths["label"]}}},
|
| 426 |
+
xlabel={{Prefix}},
|
| 427 |
+
ylabel={{Decay $-$ best static}},
|
| 428 |
+
xtick={{{xticks}}},
|
| 429 |
+
xticklabels={{{xticklabels}}},
|
| 430 |
+
grid=both,
|
| 431 |
+
]
|
| 432 |
+
\addplot+[thick, mark=*] coordinates {{{tikz_coordinates(coords, digits=5)}}};
|
| 433 |
+
\addplot+[black, dashed, mark=none] coordinates {{{tikz_coordinates(zero_coords, digits=5)}}};
|
| 434 |
+
"""
|
| 435 |
+
)
|
| 436 |
+
content = (
|
| 437 |
+
"\\begin{tikzpicture}\n"
|
| 438 |
+
"\\begin{groupplot}[\n"
|
| 439 |
+
" group style={group size=3 by 1, horizontal sep=1.0cm},\n"
|
| 440 |
+
" width=0.32\\textwidth,\n"
|
| 441 |
+
" height=0.27\\textwidth,\n"
|
| 442 |
+
" tick label style={font=\\scriptsize},\n"
|
| 443 |
+
" label style={font=\\scriptsize},\n"
|
| 444 |
+
" title style={font=\\scriptsize},\n"
|
| 445 |
+
"]\n"
|
| 446 |
+
+ "\n".join(axes)
|
| 447 |
+
+ "\n\\end{groupplot}\n\\end{tikzpicture}\n"
|
| 448 |
+
)
|
| 449 |
+
(FIG_DIR / "l20_stage_deltas.tex").write_text(content, encoding="utf-8")
|
| 450 |
+
|
| 451 |
+
|
| 452 |
+
def write_final_improvement_figure(summaries: dict[str, dict]) -> None:
|
| 453 |
+
labels = []
|
| 454 |
+
coords_small = []
|
| 455 |
+
coords_l20 = []
|
| 456 |
+
for idx, (key, paths) in enumerate(REGIMES.items()):
|
| 457 |
+
labels.append(paths["short"])
|
| 458 |
+
coords_small.append((idx, summaries[key]["small"]["improvement"]))
|
| 459 |
+
coords_l20.append((idx, summaries[key]["l20"]["improvement"]))
|
| 460 |
+
xticks = ",".join(str(i) for i in range(len(labels)))
|
| 461 |
+
xticklabels = ",".join(labels)
|
| 462 |
+
content = rf"""
|
| 463 |
+
\begin{{tikzpicture}}
|
| 464 |
+
\begin{{axis}}[
|
| 465 |
+
ybar,
|
| 466 |
+
bar width=8pt,
|
| 467 |
+
width=0.72\textwidth,
|
| 468 |
+
height=0.34\textwidth,
|
| 469 |
+
ymin=0,
|
| 470 |
+
ylabel={{Best static final loss $-$ decay final loss}},
|
| 471 |
+
symbolic x coords={{{xticklabels}}},
|
| 472 |
+
xtick=data,
|
| 473 |
+
xtick={{{xticklabels}}},
|
| 474 |
+
grid=both,
|
| 475 |
+
legend style={{font=\scriptsize, at={{(0.5,1.04)}}, anchor=south, legend columns=2}},
|
| 476 |
+
tick label style={{font=\scriptsize}},
|
| 477 |
+
label style={{font=\scriptsize}},
|
| 478 |
+
]
|
| 479 |
+
\addplot coordinates {{{" ".join(f"({label},{y:.5f})" for label, (_, y) in zip(labels, coords_small))}}};
|
| 480 |
+
\addlegendentry{{4M validation}}
|
| 481 |
+
\addplot coordinates {{{" ".join(f"({label},{y:.5f})" for label, (_, y) in zip(labels, coords_l20))}}};
|
| 482 |
+
\addlegendentry{{L20 8M extrapolation}}
|
| 483 |
+
\end{{axis}}
|
| 484 |
+
\end{{tikzpicture}}
|
| 485 |
+
"""
|
| 486 |
+
(FIG_DIR / "final_improvements.tex").write_text(content, encoding="utf-8")
|
| 487 |
+
|
| 488 |
+
|
| 489 |
+
def write_tables(summaries: dict[str, dict], coeffs: dict[str, dict]) -> None:
|
| 490 |
+
coeff_lines = [
|
| 491 |
+
"\\begin{tabular}{lrrrrrrrr}",
|
| 492 |
+
"\\toprule",
|
| 493 |
+
"Regime & Cells & A & B & D & C0 & MAE & L-model MAE & L-prefix MAE \\\\",
|
| 494 |
+
"\\midrule",
|
| 495 |
+
]
|
| 496 |
+
for key in REGIMES:
|
| 497 |
+
row = coeffs[key]
|
| 498 |
+
coeff_lines.append(
|
| 499 |
+
f"{tex_escape(row['regime'])} & {row['cells']} & "
|
| 500 |
+
f"{row['A']:.4f} & {row['B']:.4f} & {row['D']:.4f} & {row['C0']:.4f} & "
|
| 501 |
+
f"{row['mae']:.4f} & {row['leave_model_mae']:.4f} & {row['leave_prefix_mae']:.4f} \\\\"
|
| 502 |
+
)
|
| 503 |
+
coeff_lines.extend(["\\bottomrule", "\\end{tabular}"])
|
| 504 |
+
(TABLE_DIR / "coefficient_table.tex").write_text(
|
| 505 |
+
"\n".join(coeff_lines) + "\n", encoding="utf-8"
|
| 506 |
+
)
|
| 507 |
+
|
| 508 |
+
val_lines = [
|
| 509 |
+
"\\begin{tabular}{llrrrrr}",
|
| 510 |
+
"\\toprule",
|
| 511 |
+
"Regime & Protocol & Decay final & Best static final & Gain & Seeds & Paired wins \\\\",
|
| 512 |
+
"\\midrule",
|
| 513 |
+
]
|
| 514 |
+
for key, paths in REGIMES.items():
|
| 515 |
+
for protocol_key, protocol_label in [("small", paths["small_protocol"]), ("l20", "8M / 3 seeds")]:
|
| 516 |
+
row = summaries[key][protocol_key]
|
| 517 |
+
val_lines.append(
|
| 518 |
+
f"{tex_escape(paths['label'])} & {tex_escape(protocol_label)} & "
|
| 519 |
+
f"{row['condition_final_mean']:.4f} $\\pm$ {row['condition_final_std']:.4f} & "
|
| 520 |
+
f"{row['best_static_mean']:.4f} $\\pm$ {row['best_static_std']:.4f} & "
|
| 521 |
+
f"{row['improvement']:.4f} & {row['n']} & {row['paired_wins']}/{row['n']} \\\\"
|
| 522 |
+
)
|
| 523 |
+
val_lines.extend(["\\bottomrule", "\\end{tabular}"])
|
| 524 |
+
(TABLE_DIR / "streaming_validation_table.tex").write_text(
|
| 525 |
+
"\n".join(val_lines) + "\n", encoding="utf-8"
|
| 526 |
+
)
|
| 527 |
+
|
| 528 |
+
l20_lines = [
|
| 529 |
+
"\\begin{tabular}{lrrrrr}",
|
| 530 |
+
"\\toprule",
|
| 531 |
+
"Regime & Prefix & Decay dropout & Decay val & Best static val & Delta \\\\",
|
| 532 |
+
"\\midrule",
|
| 533 |
+
]
|
| 534 |
+
for key, paths in REGIMES.items():
|
| 535 |
+
for row in summaries[key]["l20"]["stage_deltas"]:
|
| 536 |
+
l20_lines.append(
|
| 537 |
+
f"{tex_escape(paths['label'])} & {token_label(row['prefix'])} & "
|
| 538 |
+
f"{row['condition_dropout']:.3f} & {row['condition_mean']:.4f} & "
|
| 539 |
+
f"{row['best_static_mean']:.4f} & {row['delta']:+.4f} \\\\"
|
| 540 |
+
)
|
| 541 |
+
l20_lines.extend(["\\bottomrule", "\\end{tabular}"])
|
| 542 |
+
(TABLE_DIR / "l20_stage_table.tex").write_text(
|
| 543 |
+
"\n".join(l20_lines) + "\n", encoding="utf-8"
|
| 544 |
+
)
|
| 545 |
+
|
| 546 |
+
summary = {
|
| 547 |
+
key: {
|
| 548 |
+
"coefficients": coeffs[key],
|
| 549 |
+
"small_stream": summaries[key]["small"],
|
| 550 |
+
"l20_stream": summaries[key]["l20"],
|
| 551 |
+
}
|
| 552 |
+
for key in REGIMES
|
| 553 |
+
}
|
| 554 |
+
(PAPER_DIR / "paper_results_summary.json").write_text(
|
| 555 |
+
json.dumps(summary, indent=2), encoding="utf-8"
|
| 556 |
+
)
|
| 557 |
+
|
| 558 |
+
|
| 559 |
+
def main() -> None:
|
| 560 |
+
FIG_DIR.mkdir(parents=True, exist_ok=True)
|
| 561 |
+
TABLE_DIR.mkdir(parents=True, exist_ok=True)
|
| 562 |
+
summaries = {}
|
| 563 |
+
coeffs = {}
|
| 564 |
+
for key, paths in REGIMES.items():
|
| 565 |
+
coeffs[key] = coeff_summary(key, paths)
|
| 566 |
+
summaries[key] = {
|
| 567 |
+
"small": metric_stage_summary(paths["small_stream"], paths["small_condition"]),
|
| 568 |
+
"l20": metric_stage_summary(paths["l20_stream"], paths["l20_condition"]),
|
| 569 |
+
}
|
| 570 |
+
write_calibration_curve_figure()
|
| 571 |
+
write_static_optima_figure()
|
| 572 |
+
write_l20_stage_delta_figure(summaries)
|
| 573 |
+
write_final_improvement_figure(summaries)
|
| 574 |
+
write_tables(summaries, coeffs)
|
| 575 |
+
print(json.dumps({"figures": str(FIG_DIR), "tables": str(TABLE_DIR)}, indent=2))
|
| 576 |
+
|
| 577 |
+
|
| 578 |
+
if __name__ == "__main__":
|
| 579 |
+
main()
|
src/dropout_decay/data/__init__.py
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Derived from Andrej Karpathy's nanochat project.
|
| 3 |
+
|
| 4 |
+
MIT License
|
| 5 |
+
|
| 6 |
+
Copyright (c) 2025 Andrej Karpathy
|
| 7 |
+
|
| 8 |
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
| 9 |
+
of this software and associated documentation files (the "Software"), to deal
|
| 10 |
+
in the Software without restriction, including without limitation the rights
|
| 11 |
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
| 12 |
+
copies of the Software, and to permit persons to whom the Software is
|
| 13 |
+
furnished to do so, subject to the following conditions:
|
| 14 |
+
|
| 15 |
+
The above copyright notice and this permission notice shall be included in all
|
| 16 |
+
copies or substantial portions of the Software.
|
| 17 |
+
"""
|
| 18 |
+
|
| 19 |
+
from dropout_decay.datasets import (
|
| 20 |
+
CachedTokenizer,
|
| 21 |
+
TokenSplits,
|
| 22 |
+
encode_corpus,
|
| 23 |
+
iter_documents,
|
| 24 |
+
load_cached_splits,
|
| 25 |
+
preview_documents,
|
| 26 |
+
required_token_count,
|
| 27 |
+
resolve_paths,
|
| 28 |
+
train_or_load_tokenizer,
|
| 29 |
+
)
|
| 30 |
+
from dropout_decay.specs import DropoutCondition, ModelSpec, anchor_dropout
|
| 31 |
+
|
| 32 |
+
__all__ = [
|
| 33 |
+
"CachedTokenizer",
|
| 34 |
+
"DropoutCondition",
|
| 35 |
+
"ModelSpec",
|
| 36 |
+
"TokenSplits",
|
| 37 |
+
"anchor_dropout",
|
| 38 |
+
"encode_corpus",
|
| 39 |
+
"iter_documents",
|
| 40 |
+
"load_cached_splits",
|
| 41 |
+
"preview_documents",
|
| 42 |
+
"required_token_count",
|
| 43 |
+
"resolve_paths",
|
| 44 |
+
"train_or_load_tokenizer",
|
| 45 |
+
]
|
src/dropout_decay/datasets/__init__.py
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Derived from Andrej Karpathy's nanochat project.
|
| 3 |
+
|
| 4 |
+
MIT License
|
| 5 |
+
|
| 6 |
+
Copyright (c) 2025 Andrej Karpathy
|
| 7 |
+
|
| 8 |
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
| 9 |
+
of this software and associated documentation files (the "Software"), to deal
|
| 10 |
+
in the Software without restriction, including without limitation the rights
|
| 11 |
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
| 12 |
+
copies of the Software, and to permit persons to whom the Software is
|
| 13 |
+
furnished to do so, subject to the following conditions:
|
| 14 |
+
|
| 15 |
+
The above copyright notice and this permission notice shall be included in all
|
| 16 |
+
copies or substantial portions of the Software.
|
| 17 |
+
"""
|
| 18 |
+
|
| 19 |
+
from dropout_decay.datasets.corpus import (
|
| 20 |
+
CachedTokenizer,
|
| 21 |
+
TokenSplits,
|
| 22 |
+
encode_corpus,
|
| 23 |
+
iter_documents,
|
| 24 |
+
load_cached_splits,
|
| 25 |
+
preview_documents,
|
| 26 |
+
required_token_count,
|
| 27 |
+
resolve_paths,
|
| 28 |
+
train_or_load_tokenizer,
|
| 29 |
+
)
|
| 30 |
+
|
| 31 |
+
__all__ = [
|
| 32 |
+
"CachedTokenizer",
|
| 33 |
+
"TokenSplits",
|
| 34 |
+
"encode_corpus",
|
| 35 |
+
"iter_documents",
|
| 36 |
+
"load_cached_splits",
|
| 37 |
+
"preview_documents",
|
| 38 |
+
"required_token_count",
|
| 39 |
+
"resolve_paths",
|
| 40 |
+
"train_or_load_tokenizer",
|
| 41 |
+
]
|
src/dropout_decay/{data.py → datasets/corpus.py}
RENAMED
|
@@ -143,7 +143,7 @@ def train_or_load_tokenizer(
|
|
| 143 |
text_column: str,
|
| 144 |
force_retrain: bool = False,
|
| 145 |
) -> object:
|
| 146 |
-
from dropout_decay.
|
| 147 |
|
| 148 |
tokenizer_path = output_dir / f"tokenizer-v{vocab_size}.json"
|
| 149 |
if tokenizer_path.exists() and not force_retrain:
|
|
|
|
| 143 |
text_column: str,
|
| 144 |
force_retrain: bool = False,
|
| 145 |
) -> object:
|
| 146 |
+
from dropout_decay.tokenization import BpeTokenizer
|
| 147 |
|
| 148 |
tokenizer_path = output_dir / f"tokenizer-v{vocab_size}.json"
|
| 149 |
if tokenizer_path.exists() and not force_retrain:
|
src/dropout_decay/experiment.py
CHANGED
|
@@ -16,1694 +16,13 @@ The above copyright notice and this permission notice shall be included in all
|
|
| 16 |
copies or substantial portions of the Software.
|
| 17 |
"""
|
| 18 |
|
| 19 |
-
from
|
| 20 |
|
| 21 |
-
|
| 22 |
-
|
| 23 |
-
|
| 24 |
-
|
| 25 |
-
from datetime import datetime
|
| 26 |
-
import json
|
| 27 |
-
import math
|
| 28 |
-
import os
|
| 29 |
-
from pathlib import Path
|
| 30 |
-
import random
|
| 31 |
-
import statistics
|
| 32 |
-
import sys
|
| 33 |
-
import time
|
| 34 |
-
from typing import Callable
|
| 35 |
-
|
| 36 |
-
import numpy as np
|
| 37 |
-
import torch
|
| 38 |
-
|
| 39 |
-
from dropout_decay.data import (
|
| 40 |
-
encode_corpus,
|
| 41 |
-
load_cached_splits,
|
| 42 |
-
resolve_paths,
|
| 43 |
-
train_or_load_tokenizer,
|
| 44 |
-
)
|
| 45 |
-
from dropout_decay.license import NANOCHAT_ATTRIBUTION
|
| 46 |
-
from dropout_decay.model import DropoutGPT, GPTConfig
|
| 47 |
-
from dropout_decay.scheduler import DropoutDecayConfig, DropoutDecayScheduler
|
| 48 |
-
|
| 49 |
-
|
| 50 |
-
DEFAULT_DROPOUT_RATES = [0.0, 0.02, 0.05, 0.08, 0.10, 0.14, 0.20, 0.30, 0.50]
|
| 51 |
-
SUMMARY_FIELDS = [
|
| 52 |
-
"run_mode",
|
| 53 |
-
"condition",
|
| 54 |
-
"condition_kind",
|
| 55 |
-
"stage",
|
| 56 |
-
"token_limit",
|
| 57 |
-
"model_name",
|
| 58 |
-
"n_layer",
|
| 59 |
-
"n_head",
|
| 60 |
-
"n_embd",
|
| 61 |
-
"parameters",
|
| 62 |
-
"dropout_initial",
|
| 63 |
-
"dropout_final",
|
| 64 |
-
"dropout_schedule",
|
| 65 |
-
"n",
|
| 66 |
-
"mean_train_eval_loss",
|
| 67 |
-
"std_train_eval_loss",
|
| 68 |
-
"mean_val_eval_loss",
|
| 69 |
-
"std_val_eval_loss",
|
| 70 |
-
"mean_generalization_gap",
|
| 71 |
-
"std_generalization_gap",
|
| 72 |
]
|
| 73 |
-
SELECTION_FIELDS = [
|
| 74 |
-
"run_mode",
|
| 75 |
-
"token_limit",
|
| 76 |
-
"model_name",
|
| 77 |
-
"n_layer",
|
| 78 |
-
"n_head",
|
| 79 |
-
"n_embd",
|
| 80 |
-
"parameters",
|
| 81 |
-
"n",
|
| 82 |
-
"best_dropout",
|
| 83 |
-
"best_val_loss",
|
| 84 |
-
"best_val_std",
|
| 85 |
-
"plateau_start_dropout",
|
| 86 |
-
"plateau_end_dropout",
|
| 87 |
-
"plateau_delta",
|
| 88 |
-
"zero_dropout_val_loss",
|
| 89 |
-
"zero_minus_best",
|
| 90 |
-
"best_nonzero_dropout",
|
| 91 |
-
"best_nonzero_val_loss",
|
| 92 |
-
"zero_minus_best_nonzero",
|
| 93 |
-
"max_dropout",
|
| 94 |
-
"max_dropout_val_loss",
|
| 95 |
-
"max_dropout_minus_best",
|
| 96 |
-
"has_nonzero_optimum",
|
| 97 |
-
"meets_target_dropout",
|
| 98 |
-
"curve_json",
|
| 99 |
-
]
|
| 100 |
-
|
| 101 |
-
|
| 102 |
-
@dataclass(frozen=True)
|
| 103 |
-
class ModelSpec:
|
| 104 |
-
name: str
|
| 105 |
-
n_layer: int
|
| 106 |
-
n_head: int
|
| 107 |
-
n_embd: int
|
| 108 |
-
|
| 109 |
-
def config(self, vocab_size: int, block_size: int, dropout: float) -> GPTConfig:
|
| 110 |
-
return GPTConfig(
|
| 111 |
-
block_size=block_size,
|
| 112 |
-
vocab_size=vocab_size,
|
| 113 |
-
n_layer=self.n_layer,
|
| 114 |
-
n_head=self.n_head,
|
| 115 |
-
n_embd=self.n_embd,
|
| 116 |
-
dropout=dropout,
|
| 117 |
-
)
|
| 118 |
-
|
| 119 |
-
def to_dict(self) -> dict[str, int | str]:
|
| 120 |
-
return {
|
| 121 |
-
"model_name": self.name,
|
| 122 |
-
"n_layer": self.n_layer,
|
| 123 |
-
"n_head": self.n_head,
|
| 124 |
-
"n_embd": self.n_embd,
|
| 125 |
-
}
|
| 126 |
-
|
| 127 |
-
|
| 128 |
-
@dataclass(frozen=True)
|
| 129 |
-
class DropoutCondition:
|
| 130 |
-
name: str
|
| 131 |
-
kind: str
|
| 132 |
-
initial: float
|
| 133 |
-
final: float
|
| 134 |
-
schedule: str = "constant"
|
| 135 |
-
decay_tokens: int | None = None
|
| 136 |
-
anchors: tuple[tuple[int, float], ...] = ()
|
| 137 |
-
|
| 138 |
-
def to_dict(self) -> dict:
|
| 139 |
-
return {
|
| 140 |
-
"name": self.name,
|
| 141 |
-
"kind": self.kind,
|
| 142 |
-
"initial": self.initial,
|
| 143 |
-
"final": self.final,
|
| 144 |
-
"schedule": self.schedule,
|
| 145 |
-
"decay_tokens": self.decay_tokens,
|
| 146 |
-
"anchors": [list(anchor) for anchor in self.anchors],
|
| 147 |
-
}
|
| 148 |
-
|
| 149 |
-
def make_fn(
|
| 150 |
-
self,
|
| 151 |
-
fallback_decay_tokens: int,
|
| 152 |
-
unique_tokens: int | None = None,
|
| 153 |
-
) -> Callable[[int], float]:
|
| 154 |
-
if self.kind == "static":
|
| 155 |
-
return lambda _tokens_seen, p=self.initial: p
|
| 156 |
-
if self.kind == "anchor_decay":
|
| 157 |
-
if unique_tokens is None:
|
| 158 |
-
raise ValueError("anchor_decay conditions require unique_tokens")
|
| 159 |
-
p = anchor_dropout(unique_tokens, self.anchors)
|
| 160 |
-
return lambda _tokens_seen, p=p: p
|
| 161 |
-
scheduler = DropoutDecayScheduler(
|
| 162 |
-
DropoutDecayConfig(
|
| 163 |
-
initial_dropout=self.initial,
|
| 164 |
-
final_dropout=self.final,
|
| 165 |
-
decay_tokens=self.decay_tokens or fallback_decay_tokens,
|
| 166 |
-
schedule=self.schedule,
|
| 167 |
-
)
|
| 168 |
-
)
|
| 169 |
-
return scheduler.value
|
| 170 |
-
|
| 171 |
-
|
| 172 |
-
def anchor_dropout(unique_tokens: int, anchors: tuple[tuple[int, float], ...]) -> float:
|
| 173 |
-
if not anchors:
|
| 174 |
-
raise ValueError("anchor dropout schedule requires at least one anchor")
|
| 175 |
-
ordered = sorted(anchors)
|
| 176 |
-
if unique_tokens <= ordered[0][0]:
|
| 177 |
-
return ordered[0][1]
|
| 178 |
-
if unique_tokens >= ordered[-1][0]:
|
| 179 |
-
return ordered[-1][1]
|
| 180 |
-
log_unique = math.log(unique_tokens)
|
| 181 |
-
for (left_tokens, left_dropout), (right_tokens, right_dropout) in zip(
|
| 182 |
-
ordered, ordered[1:]
|
| 183 |
-
):
|
| 184 |
-
if left_tokens <= unique_tokens <= right_tokens:
|
| 185 |
-
left_log = math.log(left_tokens)
|
| 186 |
-
right_log = math.log(right_tokens)
|
| 187 |
-
mix = (log_unique - left_log) / (right_log - left_log)
|
| 188 |
-
return left_dropout + mix * (right_dropout - left_dropout)
|
| 189 |
-
return ordered[-1][1]
|
| 190 |
-
|
| 191 |
-
|
| 192 |
-
def assert_mps_only() -> torch.device:
|
| 193 |
-
if os.environ.get("PYTORCH_ENABLE_MPS_FALLBACK") == "1":
|
| 194 |
-
raise SystemExit(
|
| 195 |
-
"Refusing to run: PYTORCH_ENABLE_MPS_FALLBACK=1 could execute CPU fallbacks."
|
| 196 |
-
)
|
| 197 |
-
if not torch.backends.mps.is_built():
|
| 198 |
-
raise SystemExit("PyTorch was not built with MPS. Stopping.")
|
| 199 |
-
if not torch.backends.mps.is_available():
|
| 200 |
-
raise SystemExit("MPS is not available. Stopping before any Torch experiment work.")
|
| 201 |
-
device = torch.device("mps")
|
| 202 |
-
torch.set_default_device(device)
|
| 203 |
-
return device
|
| 204 |
-
|
| 205 |
-
|
| 206 |
-
def clean_name(value: str) -> str:
|
| 207 |
-
cleaned = "".join(
|
| 208 |
-
c if c.isalnum() or c in {"-", "_"} else "_" for c in value.strip()
|
| 209 |
-
)
|
| 210 |
-
return cleaned.strip("_") or "model"
|
| 211 |
-
|
| 212 |
-
|
| 213 |
-
def rate_label(rate: float) -> str:
|
| 214 |
-
label = f"{rate:.3f}".rstrip("0").rstrip(".")
|
| 215 |
-
return label if label else "0"
|
| 216 |
-
|
| 217 |
-
|
| 218 |
-
def parse_model_spec(raw: str) -> ModelSpec:
|
| 219 |
-
if "=" in raw:
|
| 220 |
-
name, dims = raw.split("=", 1)
|
| 221 |
-
name = clean_name(name)
|
| 222 |
-
else:
|
| 223 |
-
dims = raw
|
| 224 |
-
name = ""
|
| 225 |
-
parts = dims.lower().replace(",", "x").split("x")
|
| 226 |
-
if len(parts) != 3:
|
| 227 |
-
raise argparse.ArgumentTypeError(
|
| 228 |
-
"model specs must look like 8x8x256 or name=8x8x256"
|
| 229 |
-
)
|
| 230 |
-
try:
|
| 231 |
-
n_layer, n_head, n_embd = [int(part) for part in parts]
|
| 232 |
-
except ValueError as exc:
|
| 233 |
-
raise argparse.ArgumentTypeError("model dimensions must be integers") from exc
|
| 234 |
-
if n_layer <= 0 or n_head <= 0 or n_embd <= 0:
|
| 235 |
-
raise argparse.ArgumentTypeError("model dimensions must be positive")
|
| 236 |
-
if n_embd % n_head != 0:
|
| 237 |
-
raise argparse.ArgumentTypeError("n_embd must be divisible by n_head")
|
| 238 |
-
if (n_embd // n_head) % 2 != 0:
|
| 239 |
-
raise argparse.ArgumentTypeError("n_embd / n_head must be even for rotary attention")
|
| 240 |
-
return ModelSpec(
|
| 241 |
-
name or f"L{n_layer}_H{n_head}_D{n_embd}",
|
| 242 |
-
n_layer,
|
| 243 |
-
n_head,
|
| 244 |
-
n_embd,
|
| 245 |
-
)
|
| 246 |
-
|
| 247 |
-
|
| 248 |
-
def parse_decay_spec(raw: str) -> DropoutCondition:
|
| 249 |
-
parts = raw.split(":")
|
| 250 |
-
if len(parts) not in {3, 4, 5}:
|
| 251 |
-
raise argparse.ArgumentTypeError(
|
| 252 |
-
"decay specs must look like "
|
| 253 |
-
"name:initial:final[:cosine|smoothstep|linear[:decay_tokens]]"
|
| 254 |
-
)
|
| 255 |
-
name = clean_name(parts[0])
|
| 256 |
-
try:
|
| 257 |
-
initial = float(parts[1])
|
| 258 |
-
final = float(parts[2])
|
| 259 |
-
decay_tokens = int(parts[4]) if len(parts) == 5 else None
|
| 260 |
-
except ValueError as exc:
|
| 261 |
-
raise argparse.ArgumentTypeError(
|
| 262 |
-
"decay dropout values and decay_tokens must be numeric"
|
| 263 |
-
) from exc
|
| 264 |
-
schedule = parts[3] if len(parts) >= 4 else "cosine"
|
| 265 |
-
if schedule not in {"cosine", "smoothstep", "linear"}:
|
| 266 |
-
raise argparse.ArgumentTypeError(
|
| 267 |
-
"decay schedule must be cosine, smoothstep, or linear"
|
| 268 |
-
)
|
| 269 |
-
return DropoutCondition(
|
| 270 |
-
name=name,
|
| 271 |
-
kind="decay",
|
| 272 |
-
initial=initial,
|
| 273 |
-
final=final,
|
| 274 |
-
schedule=schedule,
|
| 275 |
-
decay_tokens=decay_tokens,
|
| 276 |
-
)
|
| 277 |
-
|
| 278 |
-
|
| 279 |
-
def parse_anchor_decay_spec(raw: str) -> DropoutCondition:
|
| 280 |
-
if ":" not in raw:
|
| 281 |
-
raise argparse.ArgumentTypeError(
|
| 282 |
-
"anchor decay specs must look like name:250000=0.60,500000=0.40"
|
| 283 |
-
)
|
| 284 |
-
name, raw_anchors = raw.split(":", 1)
|
| 285 |
-
anchors: list[tuple[int, float]] = []
|
| 286 |
-
for piece in raw_anchors.split(","):
|
| 287 |
-
if "=" not in piece:
|
| 288 |
-
raise argparse.ArgumentTypeError(
|
| 289 |
-
"anchor decay anchors must look like token_count=dropout"
|
| 290 |
-
)
|
| 291 |
-
raw_tokens, raw_dropout = piece.split("=", 1)
|
| 292 |
-
try:
|
| 293 |
-
token_count = int(raw_tokens)
|
| 294 |
-
dropout = float(raw_dropout)
|
| 295 |
-
except ValueError as exc:
|
| 296 |
-
raise argparse.ArgumentTypeError(
|
| 297 |
-
"anchor token counts must be integers and dropout values numeric"
|
| 298 |
-
) from exc
|
| 299 |
-
if token_count <= 0:
|
| 300 |
-
raise argparse.ArgumentTypeError("anchor token counts must be positive")
|
| 301 |
-
if not 0.0 <= dropout < 1.0:
|
| 302 |
-
raise argparse.ArgumentTypeError("anchor dropout must satisfy 0 <= p < 1")
|
| 303 |
-
anchors.append((token_count, dropout))
|
| 304 |
-
anchors = sorted(set(anchors))
|
| 305 |
-
if len(anchors) < 2:
|
| 306 |
-
raise argparse.ArgumentTypeError("provide at least two dropout anchors")
|
| 307 |
-
for left, right in zip(anchors, anchors[1:]):
|
| 308 |
-
if right[1] > left[1]:
|
| 309 |
-
raise argparse.ArgumentTypeError(
|
| 310 |
-
"anchor dropout values must be non-increasing as token counts grow"
|
| 311 |
-
)
|
| 312 |
-
return DropoutCondition(
|
| 313 |
-
name=clean_name(name),
|
| 314 |
-
kind="anchor_decay",
|
| 315 |
-
initial=anchors[0][1],
|
| 316 |
-
final=anchors[-1][1],
|
| 317 |
-
schedule="log_prefix_anchor",
|
| 318 |
-
anchors=tuple(anchors),
|
| 319 |
-
)
|
| 320 |
-
|
| 321 |
-
|
| 322 |
-
def default_seeds(mode: str, seeds: list[int] | None) -> list[int]:
|
| 323 |
-
if seeds:
|
| 324 |
-
return seeds
|
| 325 |
-
return [1] if mode == "screen_static" else [1, 2, 3]
|
| 326 |
-
|
| 327 |
-
|
| 328 |
-
def set_seed(seed: int) -> None:
|
| 329 |
-
random.seed(seed)
|
| 330 |
-
np.random.seed(seed)
|
| 331 |
-
torch.manual_seed(seed)
|
| 332 |
-
|
| 333 |
-
|
| 334 |
-
def batch_seed(seed: int, model: ModelSpec, dropout_code: int, stage: int | None) -> int:
|
| 335 |
-
return (
|
| 336 |
-
seed * 1_000_003
|
| 337 |
-
+ model.n_layer * 100_003
|
| 338 |
-
+ model.n_head * 10_007
|
| 339 |
-
+ model.n_embd * 101
|
| 340 |
-
+ dropout_code * 37
|
| 341 |
-
+ (stage or 0) * 997
|
| 342 |
-
)
|
| 343 |
-
|
| 344 |
-
|
| 345 |
-
def make_batch(
|
| 346 |
-
tokens: np.ndarray,
|
| 347 |
-
token_limit: int,
|
| 348 |
-
batch_size: int,
|
| 349 |
-
block_size: int,
|
| 350 |
-
rng: np.random.Generator,
|
| 351 |
-
device: torch.device,
|
| 352 |
-
) -> tuple[torch.Tensor, torch.Tensor]:
|
| 353 |
-
limit = min(token_limit, len(tokens))
|
| 354 |
-
max_start = limit - block_size - 1
|
| 355 |
-
if max_start <= 0:
|
| 356 |
-
raise ValueError("token_limit is too small for the requested block_size")
|
| 357 |
-
starts = rng.integers(0, max_start, size=batch_size)
|
| 358 |
-
x_np = np.stack([tokens[start : start + block_size] for start in starts]).astype(
|
| 359 |
-
np.int64
|
| 360 |
-
)
|
| 361 |
-
y_np = np.stack(
|
| 362 |
-
[tokens[start + 1 : start + 1 + block_size] for start in starts]
|
| 363 |
-
).astype(np.int64)
|
| 364 |
-
return (
|
| 365 |
-
torch.tensor(x_np, dtype=torch.long, device=device),
|
| 366 |
-
torch.tensor(y_np, dtype=torch.long, device=device),
|
| 367 |
-
)
|
| 368 |
-
|
| 369 |
-
|
| 370 |
-
@torch.no_grad()
|
| 371 |
-
def estimate_loss(
|
| 372 |
-
model: DropoutGPT,
|
| 373 |
-
tokens: np.ndarray,
|
| 374 |
-
token_limit: int,
|
| 375 |
-
batches: int,
|
| 376 |
-
args: argparse.Namespace,
|
| 377 |
-
device: torch.device,
|
| 378 |
-
rng_seed: int,
|
| 379 |
-
) -> float:
|
| 380 |
-
if batches <= 0:
|
| 381 |
-
return float("nan")
|
| 382 |
-
model.eval()
|
| 383 |
-
rng = np.random.default_rng(rng_seed)
|
| 384 |
-
losses: list[float] = []
|
| 385 |
-
for _ in range(batches):
|
| 386 |
-
x, y = make_batch(tokens, token_limit, args.batch_size, args.block_size, rng, device)
|
| 387 |
-
_, loss = model(x, y)
|
| 388 |
-
losses.append(float(loss.item()))
|
| 389 |
-
model.train()
|
| 390 |
-
return float(statistics.fmean(losses))
|
| 391 |
-
|
| 392 |
-
|
| 393 |
-
def write_jsonl_row(handle, row: dict) -> None:
|
| 394 |
-
handle.write(json.dumps(row, sort_keys=True) + "\n")
|
| 395 |
-
handle.flush()
|
| 396 |
-
|
| 397 |
-
|
| 398 |
-
def metric_key(row: dict) -> tuple:
|
| 399 |
-
return (
|
| 400 |
-
row["run_mode"],
|
| 401 |
-
row["condition"],
|
| 402 |
-
row["condition_kind"],
|
| 403 |
-
row.get("stage"),
|
| 404 |
-
int(row["token_limit"]),
|
| 405 |
-
row["model_name"],
|
| 406 |
-
int(row["n_layer"]),
|
| 407 |
-
int(row["n_head"]),
|
| 408 |
-
int(row["n_embd"]),
|
| 409 |
-
int(row["seed"]),
|
| 410 |
-
float(row["dropout_initial"]),
|
| 411 |
-
float(row["dropout_final"]),
|
| 412 |
-
row["dropout_schedule"],
|
| 413 |
-
)
|
| 414 |
-
|
| 415 |
-
|
| 416 |
-
def planned_metric_key(
|
| 417 |
-
*,
|
| 418 |
-
mode: str,
|
| 419 |
-
condition: DropoutCondition,
|
| 420 |
-
model_spec: ModelSpec,
|
| 421 |
-
seed: int,
|
| 422 |
-
token_limit: int,
|
| 423 |
-
stage: int | None = None,
|
| 424 |
-
) -> tuple:
|
| 425 |
-
return (
|
| 426 |
-
mode,
|
| 427 |
-
condition.name,
|
| 428 |
-
condition.kind,
|
| 429 |
-
stage,
|
| 430 |
-
int(token_limit),
|
| 431 |
-
model_spec.name,
|
| 432 |
-
int(model_spec.n_layer),
|
| 433 |
-
int(model_spec.n_head),
|
| 434 |
-
int(model_spec.n_embd),
|
| 435 |
-
int(seed),
|
| 436 |
-
float(condition.initial),
|
| 437 |
-
float(condition.final),
|
| 438 |
-
condition.schedule,
|
| 439 |
-
)
|
| 440 |
-
|
| 441 |
-
|
| 442 |
-
def load_metrics(path: Path) -> list[dict]:
|
| 443 |
-
if not path.exists():
|
| 444 |
-
return []
|
| 445 |
-
rows = []
|
| 446 |
-
for line in path.read_text(encoding="utf-8").splitlines():
|
| 447 |
-
if line.strip():
|
| 448 |
-
rows.append(json.loads(line))
|
| 449 |
-
return rows
|
| 450 |
-
|
| 451 |
-
|
| 452 |
-
def train_segment(
|
| 453 |
-
*,
|
| 454 |
-
run_mode: str,
|
| 455 |
-
condition: DropoutCondition,
|
| 456 |
-
model_spec: ModelSpec,
|
| 457 |
-
config: GPTConfig,
|
| 458 |
-
train_tokens: np.ndarray,
|
| 459 |
-
val_tokens: np.ndarray,
|
| 460 |
-
token_limit: int,
|
| 461 |
-
steps: int,
|
| 462 |
-
seed: int,
|
| 463 |
-
args: argparse.Namespace,
|
| 464 |
-
device: torch.device,
|
| 465 |
-
dropout_fn: Callable[[int], float],
|
| 466 |
-
metrics_file,
|
| 467 |
-
trace_file,
|
| 468 |
-
stage: int | None = None,
|
| 469 |
-
model: DropoutGPT | None = None,
|
| 470 |
-
optimizer: torch.optim.Optimizer | None = None,
|
| 471 |
-
tokens_seen_start: int = 0,
|
| 472 |
-
) -> tuple[DropoutGPT, torch.optim.Optimizer, int, dict]:
|
| 473 |
-
if model is None:
|
| 474 |
-
set_seed(seed)
|
| 475 |
-
model = DropoutGPT(config).to(device)
|
| 476 |
-
optimizer = torch.optim.AdamW(
|
| 477 |
-
model.parameters(),
|
| 478 |
-
lr=args.lr,
|
| 479 |
-
betas=(0.9, 0.95),
|
| 480 |
-
weight_decay=args.weight_decay,
|
| 481 |
-
)
|
| 482 |
-
else:
|
| 483 |
-
torch.manual_seed(seed + 10_000 + (stage or 0))
|
| 484 |
-
if optimizer is None:
|
| 485 |
-
raise ValueError("optimizer is required when reusing a model")
|
| 486 |
-
|
| 487 |
-
model.train()
|
| 488 |
-
dropout_code = int(round(condition.initial * 10_000))
|
| 489 |
-
rng = np.random.default_rng(batch_seed(seed, model_spec, dropout_code, stage))
|
| 490 |
-
tokens_seen = tokens_seen_start
|
| 491 |
-
last_loss = float("nan")
|
| 492 |
-
active_dropout = condition.initial
|
| 493 |
-
t0 = time.time()
|
| 494 |
-
|
| 495 |
-
for step in range(1, steps + 1):
|
| 496 |
-
active_dropout = dropout_fn(tokens_seen)
|
| 497 |
-
model.set_dropout(active_dropout)
|
| 498 |
-
x, y = make_batch(train_tokens, token_limit, args.batch_size, args.block_size, rng, device)
|
| 499 |
-
_, loss = model(x, y)
|
| 500 |
-
optimizer.zero_grad(set_to_none=True)
|
| 501 |
-
loss.backward()
|
| 502 |
-
if args.grad_clip > 0:
|
| 503 |
-
torch.nn.utils.clip_grad_norm_(model.parameters(), args.grad_clip)
|
| 504 |
-
optimizer.step()
|
| 505 |
-
|
| 506 |
-
tokens_seen += args.batch_size * args.block_size
|
| 507 |
-
last_loss = float(loss.item())
|
| 508 |
-
if args.log_every > 0 and step % args.log_every == 0:
|
| 509 |
-
write_jsonl_row(
|
| 510 |
-
trace_file,
|
| 511 |
-
{
|
| 512 |
-
"event": "train_step",
|
| 513 |
-
"run_mode": run_mode,
|
| 514 |
-
"condition": condition.name,
|
| 515 |
-
"model_name": model_spec.name,
|
| 516 |
-
"seed": seed,
|
| 517 |
-
"stage": stage,
|
| 518 |
-
"step": step,
|
| 519 |
-
"steps": steps,
|
| 520 |
-
"token_limit": int(token_limit),
|
| 521 |
-
"tokens_seen": int(tokens_seen),
|
| 522 |
-
"dropout": float(active_dropout),
|
| 523 |
-
"train_batch_loss": last_loss,
|
| 524 |
-
},
|
| 525 |
-
)
|
| 526 |
-
if args.eval_every > 0 and step % args.eval_every == 0:
|
| 527 |
-
train_eval = estimate_loss(
|
| 528 |
-
model,
|
| 529 |
-
train_tokens,
|
| 530 |
-
token_limit,
|
| 531 |
-
args.trace_eval_batches,
|
| 532 |
-
args,
|
| 533 |
-
device,
|
| 534 |
-
rng_seed=seed + 20_000 + step,
|
| 535 |
-
)
|
| 536 |
-
val_eval = estimate_loss(
|
| 537 |
-
model,
|
| 538 |
-
val_tokens,
|
| 539 |
-
len(val_tokens),
|
| 540 |
-
args.trace_eval_batches,
|
| 541 |
-
args,
|
| 542 |
-
device,
|
| 543 |
-
rng_seed=seed + 30_000 + step,
|
| 544 |
-
)
|
| 545 |
-
write_jsonl_row(
|
| 546 |
-
trace_file,
|
| 547 |
-
{
|
| 548 |
-
"event": "eval_step",
|
| 549 |
-
"run_mode": run_mode,
|
| 550 |
-
"condition": condition.name,
|
| 551 |
-
"model_name": model_spec.name,
|
| 552 |
-
"seed": seed,
|
| 553 |
-
"stage": stage,
|
| 554 |
-
"step": step,
|
| 555 |
-
"steps": steps,
|
| 556 |
-
"token_limit": int(token_limit),
|
| 557 |
-
"tokens_seen": int(tokens_seen),
|
| 558 |
-
"dropout": float(active_dropout),
|
| 559 |
-
"train_eval_loss": train_eval,
|
| 560 |
-
"val_eval_loss": val_eval,
|
| 561 |
-
"generalization_gap": val_eval - train_eval,
|
| 562 |
-
},
|
| 563 |
-
)
|
| 564 |
-
|
| 565 |
-
train_eval = estimate_loss(
|
| 566 |
-
model,
|
| 567 |
-
train_tokens,
|
| 568 |
-
token_limit,
|
| 569 |
-
args.train_eval_batches,
|
| 570 |
-
args,
|
| 571 |
-
device,
|
| 572 |
-
rng_seed=seed + 40_000 + (stage or 0),
|
| 573 |
-
)
|
| 574 |
-
val_eval = estimate_loss(
|
| 575 |
-
model,
|
| 576 |
-
val_tokens,
|
| 577 |
-
len(val_tokens),
|
| 578 |
-
args.eval_batches,
|
| 579 |
-
args,
|
| 580 |
-
device,
|
| 581 |
-
rng_seed=seed + 50_000 + (stage or 0),
|
| 582 |
-
)
|
| 583 |
-
row = {
|
| 584 |
-
"run_mode": run_mode,
|
| 585 |
-
"condition": condition.name,
|
| 586 |
-
"condition_kind": condition.kind,
|
| 587 |
-
"seed": seed,
|
| 588 |
-
"stage": stage,
|
| 589 |
-
"token_limit": int(token_limit),
|
| 590 |
-
"steps": int(steps),
|
| 591 |
-
"tokens_seen": int(tokens_seen),
|
| 592 |
-
"dropout_initial": float(condition.initial),
|
| 593 |
-
"dropout_final": float(condition.final),
|
| 594 |
-
"dropout_schedule": condition.schedule,
|
| 595 |
-
"dropout_active_final": float(active_dropout),
|
| 596 |
-
"train_loss_last": last_loss,
|
| 597 |
-
"train_eval_loss": train_eval,
|
| 598 |
-
"val_eval_loss": val_eval,
|
| 599 |
-
"eval_loss": val_eval,
|
| 600 |
-
"generalization_gap": val_eval - train_eval,
|
| 601 |
-
"elapsed_sec": time.time() - t0,
|
| 602 |
-
"parameters": model.num_parameters(),
|
| 603 |
-
"model_config": config.to_dict(),
|
| 604 |
-
**model_spec.to_dict(),
|
| 605 |
-
}
|
| 606 |
-
write_jsonl_row(metrics_file, row)
|
| 607 |
-
return model, optimizer, tokens_seen, row
|
| 608 |
-
|
| 609 |
-
|
| 610 |
-
def summarize(rows: list[dict]) -> list[dict]:
|
| 611 |
-
groups: dict[tuple, list[dict]] = defaultdict(list)
|
| 612 |
-
for row in rows:
|
| 613 |
-
key = (
|
| 614 |
-
row["run_mode"],
|
| 615 |
-
row["condition"],
|
| 616 |
-
row["condition_kind"],
|
| 617 |
-
row["stage"],
|
| 618 |
-
row["token_limit"],
|
| 619 |
-
row["model_name"],
|
| 620 |
-
row["n_layer"],
|
| 621 |
-
row["n_head"],
|
| 622 |
-
row["n_embd"],
|
| 623 |
-
row["parameters"],
|
| 624 |
-
row["dropout_initial"],
|
| 625 |
-
row["dropout_final"],
|
| 626 |
-
row["dropout_schedule"],
|
| 627 |
-
)
|
| 628 |
-
groups[key].append(row)
|
| 629 |
-
|
| 630 |
-
summary: list[dict] = []
|
| 631 |
-
for group_rows in groups.values():
|
| 632 |
-
first = group_rows[0]
|
| 633 |
-
item = {field: first[field] for field in SUMMARY_FIELDS if field in first}
|
| 634 |
-
item["n"] = len(group_rows)
|
| 635 |
-
for source, mean_key, std_key in [
|
| 636 |
-
("train_eval_loss", "mean_train_eval_loss", "std_train_eval_loss"),
|
| 637 |
-
("val_eval_loss", "mean_val_eval_loss", "std_val_eval_loss"),
|
| 638 |
-
("generalization_gap", "mean_generalization_gap", "std_generalization_gap"),
|
| 639 |
-
]:
|
| 640 |
-
values = [float(row[source]) for row in group_rows]
|
| 641 |
-
item[mean_key] = statistics.fmean(values)
|
| 642 |
-
item[std_key] = statistics.stdev(values) if len(values) > 1 else 0.0
|
| 643 |
-
summary.append(item)
|
| 644 |
-
return sorted(
|
| 645 |
-
summary,
|
| 646 |
-
key=lambda row: (
|
| 647 |
-
row["run_mode"],
|
| 648 |
-
row["model_name"],
|
| 649 |
-
row["token_limit"],
|
| 650 |
-
row["condition"],
|
| 651 |
-
row["stage"] or -1,
|
| 652 |
-
),
|
| 653 |
-
)
|
| 654 |
-
|
| 655 |
-
|
| 656 |
-
def write_csv(path: Path, rows: list[dict], fieldnames: list[str]) -> None:
|
| 657 |
-
with path.open("w", newline="", encoding="utf-8") as handle:
|
| 658 |
-
writer = csv.DictWriter(handle, fieldnames=fieldnames, extrasaction="ignore")
|
| 659 |
-
writer.writeheader()
|
| 660 |
-
writer.writerows(rows)
|
| 661 |
-
|
| 662 |
-
|
| 663 |
-
def format_duration(seconds: float) -> str:
|
| 664 |
-
seconds = max(0, int(seconds))
|
| 665 |
-
hours, remainder = divmod(seconds, 3600)
|
| 666 |
-
minutes, secs = divmod(remainder, 60)
|
| 667 |
-
if hours:
|
| 668 |
-
return f"{hours}h{minutes:02d}m"
|
| 669 |
-
if minutes:
|
| 670 |
-
return f"{minutes}m{secs:02d}s"
|
| 671 |
-
return f"{secs}s"
|
| 672 |
-
|
| 673 |
-
|
| 674 |
-
class ProgressMeter:
|
| 675 |
-
def __init__(self, total: int):
|
| 676 |
-
self.total = max(0, total)
|
| 677 |
-
self.done = 0
|
| 678 |
-
self.started_at = time.time()
|
| 679 |
-
|
| 680 |
-
def mark_done(self, row: dict) -> None:
|
| 681 |
-
self.done += 1
|
| 682 |
-
elapsed = time.time() - self.started_at
|
| 683 |
-
mean = elapsed / self.done if self.done else 0.0
|
| 684 |
-
remaining = max(0, self.total - self.done)
|
| 685 |
-
eta = remaining * mean
|
| 686 |
-
stage = row.get("stage")
|
| 687 |
-
stage_text = f"stage={stage} " if stage is not None else ""
|
| 688 |
-
print(
|
| 689 |
-
"progress "
|
| 690 |
-
f"{self.done}/{self.total} "
|
| 691 |
-
f"eta={format_duration(eta)} "
|
| 692 |
-
f"mode={row['run_mode']} "
|
| 693 |
-
f"model={row['model_name']} "
|
| 694 |
-
f"params={int(row['parameters']):,} "
|
| 695 |
-
f"prefix={int(row['token_limit']):,} "
|
| 696 |
-
f"{stage_text}"
|
| 697 |
-
f"seed={row['seed']} "
|
| 698 |
-
f"condition={row['condition']} "
|
| 699 |
-
f"dropout={float(row['dropout_active_final']):.3f} "
|
| 700 |
-
f"val={row['val_eval_loss']:.4f} "
|
| 701 |
-
f"train={row['train_eval_loss']:.4f} "
|
| 702 |
-
f"gap={row['generalization_gap']:.4f} "
|
| 703 |
-
f"elapsed={format_duration(float(row['elapsed_sec']))}",
|
| 704 |
-
flush=True,
|
| 705 |
-
)
|
| 706 |
-
|
| 707 |
-
|
| 708 |
-
def build_model_selection(summary: list[dict], args: argparse.Namespace) -> list[dict]:
|
| 709 |
-
groups: dict[tuple, list[dict]] = defaultdict(list)
|
| 710 |
-
for row in summary:
|
| 711 |
-
if row["condition_kind"] == "static" and row["stage"] is None:
|
| 712 |
-
groups[(row["run_mode"], row["token_limit"], row["model_name"])].append(row)
|
| 713 |
-
|
| 714 |
-
selection: list[dict] = []
|
| 715 |
-
for (run_mode, token_limit, model_name), rows in groups.items():
|
| 716 |
-
curve = sorted(rows, key=lambda row: row["dropout_initial"])
|
| 717 |
-
best = min(curve, key=lambda row: row["mean_val_eval_loss"])
|
| 718 |
-
plateau = [
|
| 719 |
-
row for row in curve
|
| 720 |
-
if row["mean_val_eval_loss"] <= best["mean_val_eval_loss"] + args.plateau_delta
|
| 721 |
-
]
|
| 722 |
-
zero = next((row for row in curve if row["dropout_initial"] == 0.0), None)
|
| 723 |
-
nonzero = [row for row in curve if row["dropout_initial"] > 0.0]
|
| 724 |
-
best_nonzero = min(nonzero, key=lambda row: row["mean_val_eval_loss"]) if nonzero else None
|
| 725 |
-
max_dropout = max(curve, key=lambda row: row["dropout_initial"])
|
| 726 |
-
|
| 727 |
-
zero_loss = zero["mean_val_eval_loss"] if zero else None
|
| 728 |
-
zero_minus_best = zero_loss - best["mean_val_eval_loss"] if zero_loss is not None else None
|
| 729 |
-
zero_minus_best_nonzero = (
|
| 730 |
-
zero_loss - best_nonzero["mean_val_eval_loss"]
|
| 731 |
-
if zero_loss is not None and best_nonzero is not None
|
| 732 |
-
else None
|
| 733 |
-
)
|
| 734 |
-
max_minus_best = max_dropout["mean_val_eval_loss"] - best["mean_val_eval_loss"]
|
| 735 |
-
has_nonzero_optimum = (
|
| 736 |
-
best["dropout_initial"] > 0.0
|
| 737 |
-
and zero_minus_best is not None
|
| 738 |
-
and zero_minus_best >= args.min_nonzero_margin
|
| 739 |
-
and max_minus_best >= args.min_high_dropout_margin
|
| 740 |
-
)
|
| 741 |
-
curve_json = json.dumps(
|
| 742 |
-
[
|
| 743 |
-
{
|
| 744 |
-
"dropout": row["dropout_initial"],
|
| 745 |
-
"mean_val_loss": row["mean_val_eval_loss"],
|
| 746 |
-
"std_val_loss": row["std_val_eval_loss"],
|
| 747 |
-
"mean_train_loss": row["mean_train_eval_loss"],
|
| 748 |
-
"mean_generalization_gap": row["mean_generalization_gap"],
|
| 749 |
-
"n": row["n"],
|
| 750 |
-
}
|
| 751 |
-
for row in curve
|
| 752 |
-
],
|
| 753 |
-
sort_keys=True,
|
| 754 |
-
)
|
| 755 |
-
selection.append(
|
| 756 |
-
{
|
| 757 |
-
"run_mode": run_mode,
|
| 758 |
-
"token_limit": token_limit,
|
| 759 |
-
"model_name": model_name,
|
| 760 |
-
"n_layer": best["n_layer"],
|
| 761 |
-
"n_head": best["n_head"],
|
| 762 |
-
"n_embd": best["n_embd"],
|
| 763 |
-
"parameters": best["parameters"],
|
| 764 |
-
"n": best["n"],
|
| 765 |
-
"best_dropout": best["dropout_initial"],
|
| 766 |
-
"best_val_loss": best["mean_val_eval_loss"],
|
| 767 |
-
"best_val_std": best["std_val_eval_loss"],
|
| 768 |
-
"plateau_start_dropout": min(row["dropout_initial"] for row in plateau),
|
| 769 |
-
"plateau_end_dropout": max(row["dropout_initial"] for row in plateau),
|
| 770 |
-
"plateau_delta": args.plateau_delta,
|
| 771 |
-
"zero_dropout_val_loss": zero_loss,
|
| 772 |
-
"zero_minus_best": zero_minus_best,
|
| 773 |
-
"best_nonzero_dropout": (
|
| 774 |
-
best_nonzero["dropout_initial"] if best_nonzero else None
|
| 775 |
-
),
|
| 776 |
-
"best_nonzero_val_loss": (
|
| 777 |
-
best_nonzero["mean_val_eval_loss"] if best_nonzero else None
|
| 778 |
-
),
|
| 779 |
-
"zero_minus_best_nonzero": zero_minus_best_nonzero,
|
| 780 |
-
"max_dropout": max_dropout["dropout_initial"],
|
| 781 |
-
"max_dropout_val_loss": max_dropout["mean_val_eval_loss"],
|
| 782 |
-
"max_dropout_minus_best": max_minus_best,
|
| 783 |
-
"has_nonzero_optimum": has_nonzero_optimum,
|
| 784 |
-
"meets_target_dropout": best["dropout_initial"] >= args.target_min_dropout,
|
| 785 |
-
"curve_json": curve_json,
|
| 786 |
-
}
|
| 787 |
-
)
|
| 788 |
-
return sorted(
|
| 789 |
-
selection,
|
| 790 |
-
key=lambda row: (
|
| 791 |
-
not row["has_nonzero_optimum"],
|
| 792 |
-
not row["meets_target_dropout"],
|
| 793 |
-
row["best_val_loss"],
|
| 794 |
-
),
|
| 795 |
-
)
|
| 796 |
-
|
| 797 |
-
|
| 798 |
-
def write_screen_markdown_summary(output_dir: Path, rows: list[dict]) -> None:
|
| 799 |
-
if not rows:
|
| 800 |
-
return
|
| 801 |
-
static_rows = [
|
| 802 |
-
row
|
| 803 |
-
for row in rows
|
| 804 |
-
if row["run_mode"] == "screen_static" and row["condition_kind"] == "static"
|
| 805 |
-
]
|
| 806 |
-
if not static_rows:
|
| 807 |
-
return
|
| 808 |
-
|
| 809 |
-
by_model_prefix_rate: dict[tuple[str, int, float], list[dict]] = defaultdict(list)
|
| 810 |
-
for row in rows:
|
| 811 |
-
if row["run_mode"] == "screen_static" and row["condition_kind"] == "static":
|
| 812 |
-
by_model_prefix_rate[
|
| 813 |
-
(
|
| 814 |
-
row["model_name"],
|
| 815 |
-
int(row["token_limit"]),
|
| 816 |
-
float(row["dropout_initial"]),
|
| 817 |
-
)
|
| 818 |
-
].append(row)
|
| 819 |
-
|
| 820 |
-
aggregates: list[dict] = []
|
| 821 |
-
for (model_name, prefix, dropout), group_rows in by_model_prefix_rate.items():
|
| 822 |
-
first = group_rows[0]
|
| 823 |
-
val_losses = [float(row["val_eval_loss"]) for row in group_rows]
|
| 824 |
-
train_losses = [float(row["train_eval_loss"]) for row in group_rows]
|
| 825 |
-
gaps = [float(row["generalization_gap"]) for row in group_rows]
|
| 826 |
-
aggregates.append(
|
| 827 |
-
{
|
| 828 |
-
"model_name": model_name,
|
| 829 |
-
"token_limit": prefix,
|
| 830 |
-
"dropout_initial": dropout,
|
| 831 |
-
"n": len(group_rows),
|
| 832 |
-
"mean_val_eval_loss": statistics.fmean(val_losses),
|
| 833 |
-
"std_val_eval_loss": statistics.stdev(val_losses)
|
| 834 |
-
if len(val_losses) > 1
|
| 835 |
-
else 0.0,
|
| 836 |
-
"mean_train_eval_loss": statistics.fmean(train_losses),
|
| 837 |
-
"std_train_eval_loss": statistics.stdev(train_losses)
|
| 838 |
-
if len(train_losses) > 1
|
| 839 |
-
else 0.0,
|
| 840 |
-
"mean_generalization_gap": statistics.fmean(gaps),
|
| 841 |
-
"std_generalization_gap": statistics.stdev(gaps)
|
| 842 |
-
if len(gaps) > 1
|
| 843 |
-
else 0.0,
|
| 844 |
-
"parameters": int(first["parameters"]),
|
| 845 |
-
"n_layer": int(first["n_layer"]),
|
| 846 |
-
"n_head": int(first["n_head"]),
|
| 847 |
-
"n_embd": int(first["n_embd"]),
|
| 848 |
-
"block_size": int(first["model_config"]["block_size"]),
|
| 849 |
-
"vocab_size": int(first["model_config"]["vocab_size"]),
|
| 850 |
-
"tokens_seen": int(first["tokens_seen"]),
|
| 851 |
-
"seeds": sorted({int(row["seed"]) for row in group_rows}),
|
| 852 |
-
}
|
| 853 |
-
)
|
| 854 |
-
|
| 855 |
-
by_model: dict[str, list[dict]] = defaultdict(list)
|
| 856 |
-
for row in aggregates:
|
| 857 |
-
by_model[row["model_name"]].append(row)
|
| 858 |
-
|
| 859 |
-
model_rows = []
|
| 860 |
-
for model_name, model_group in by_model.items():
|
| 861 |
-
first = model_group[0]
|
| 862 |
-
seeds = sorted({seed for row in model_group for seed in row["seeds"]})
|
| 863 |
-
model_rows.append(
|
| 864 |
-
{
|
| 865 |
-
"model_name": model_name,
|
| 866 |
-
"parameters": first["parameters"],
|
| 867 |
-
"n_layer": first["n_layer"],
|
| 868 |
-
"n_head": first["n_head"],
|
| 869 |
-
"n_embd": first["n_embd"],
|
| 870 |
-
"block_size": first["block_size"],
|
| 871 |
-
"vocab_size": first["vocab_size"],
|
| 872 |
-
"seeds": seeds,
|
| 873 |
-
}
|
| 874 |
-
)
|
| 875 |
-
|
| 876 |
-
lines = [
|
| 877 |
-
"# Static Dropout Screen Summary",
|
| 878 |
-
"",
|
| 879 |
-
f"Run directory: `{output_dir}`",
|
| 880 |
-
"",
|
| 881 |
-
"## Models",
|
| 882 |
-
"",
|
| 883 |
-
"| Model | Params | Layers | Heads | Embedding | Block | Vocab | Seeds |",
|
| 884 |
-
"|---|---:|---:|---:|---:|---:|---:|---|",
|
| 885 |
-
]
|
| 886 |
-
for model in sorted(model_rows, key=lambda item: item["parameters"]):
|
| 887 |
-
lines.append(
|
| 888 |
-
"| "
|
| 889 |
-
f"`{model['model_name']}` | {model['parameters']:,} | "
|
| 890 |
-
f"{model['n_layer']} | {model['n_head']} | {model['n_embd']} | "
|
| 891 |
-
f"{model['block_size']} | {model['vocab_size']} | "
|
| 892 |
-
f"{', '.join(str(seed) for seed in model['seeds'])} |"
|
| 893 |
-
)
|
| 894 |
-
lines.extend(
|
| 895 |
-
[
|
| 896 |
-
"",
|
| 897 |
-
"## Best Dropout By Model And Prefix",
|
| 898 |
-
"",
|
| 899 |
-
"| Model | Prefix tokens | Effective epochs | Best dropout | Mean val loss | Val std | Mean train loss | Mean gap | Plateau/bracket note |",
|
| 900 |
-
"|---|---:|---:|---:|---:|---:|---:|---:|---|",
|
| 901 |
-
]
|
| 902 |
-
)
|
| 903 |
-
for model_name, model_group in sorted(by_model.items()):
|
| 904 |
-
by_prefix: dict[int, list[dict]] = defaultdict(list)
|
| 905 |
-
for row in model_group:
|
| 906 |
-
by_prefix[int(row["token_limit"])].append(row)
|
| 907 |
-
for prefix, prefix_rows in sorted(by_prefix.items()):
|
| 908 |
-
best = min(prefix_rows, key=lambda row: row["mean_val_eval_loss"])
|
| 909 |
-
rates = [float(row["dropout_initial"]) for row in prefix_rows]
|
| 910 |
-
eff_epochs = float(best["tokens_seen"]) / prefix
|
| 911 |
-
if best["dropout_initial"] == max(rates):
|
| 912 |
-
note = "not bracketed; best at top of tested grid"
|
| 913 |
-
elif best["dropout_initial"] == min(rates):
|
| 914 |
-
note = "not bracketed; best at bottom of tested grid"
|
| 915 |
-
else:
|
| 916 |
-
note = "bracketed by tested grid"
|
| 917 |
-
lines.append(
|
| 918 |
-
"| "
|
| 919 |
-
f"`{model_name}` | {prefix:,} | {eff_epochs:.2f} | "
|
| 920 |
-
f"{best['dropout_initial']:.2f} | "
|
| 921 |
-
f"{best['mean_val_eval_loss']:.4f} | "
|
| 922 |
-
f"{best['std_val_eval_loss']:.4f} | "
|
| 923 |
-
f"{best['mean_train_eval_loss']:.4f} | "
|
| 924 |
-
f"{best['mean_generalization_gap']:.4f} | {note} |"
|
| 925 |
-
)
|
| 926 |
-
|
| 927 |
-
for model_name, model_group in sorted(by_model.items()):
|
| 928 |
-
by_prefix = defaultdict(list)
|
| 929 |
-
for row in model_group:
|
| 930 |
-
by_prefix[int(row["token_limit"])].append(row)
|
| 931 |
-
lines.extend(
|
| 932 |
-
[
|
| 933 |
-
"",
|
| 934 |
-
f"## Model `{model_name}`",
|
| 935 |
-
]
|
| 936 |
-
)
|
| 937 |
-
for prefix, prefix_rows in sorted(by_prefix.items()):
|
| 938 |
-
eff_epochs = float(prefix_rows[0]["tokens_seen"]) / prefix
|
| 939 |
-
lines.extend(
|
| 940 |
-
[
|
| 941 |
-
"",
|
| 942 |
-
f"### Prefix {prefix:,} Tokens ({eff_epochs:.2f} Effective Epochs)",
|
| 943 |
-
"",
|
| 944 |
-
"| Dropout | N | Mean val loss | Val std | Mean train loss | Train std | Mean gap | Gap std | Sampled tokens | Params |",
|
| 945 |
-
"|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:|",
|
| 946 |
-
]
|
| 947 |
-
)
|
| 948 |
-
for row in sorted(prefix_rows, key=lambda item: item["dropout_initial"]):
|
| 949 |
-
lines.append(
|
| 950 |
-
"| "
|
| 951 |
-
f"{row['dropout_initial']:.2f} | {row['n']} | "
|
| 952 |
-
f"{row['mean_val_eval_loss']:.4f} | "
|
| 953 |
-
f"{row['std_val_eval_loss']:.4f} | "
|
| 954 |
-
f"{row['mean_train_eval_loss']:.4f} | "
|
| 955 |
-
f"{row['std_train_eval_loss']:.4f} | "
|
| 956 |
-
f"{row['mean_generalization_gap']:.4f} | "
|
| 957 |
-
f"{row['std_generalization_gap']:.4f} | "
|
| 958 |
-
f"{int(row['tokens_seen']):,} | {int(row['parameters']):,} |"
|
| 959 |
-
)
|
| 960 |
-
|
| 961 |
-
output = "\n".join(lines) + "\n"
|
| 962 |
-
(output_dir / "RESULT_SUMMARY.md").write_text(output, encoding="utf-8")
|
| 963 |
-
|
| 964 |
-
|
| 965 |
-
def svg_escape(value: object) -> str:
|
| 966 |
-
return (
|
| 967 |
-
str(value)
|
| 968 |
-
.replace("&", "&")
|
| 969 |
-
.replace("<", "<")
|
| 970 |
-
.replace(">", ">")
|
| 971 |
-
.replace('"', """)
|
| 972 |
-
)
|
| 973 |
-
|
| 974 |
-
|
| 975 |
-
def write_dropout_curve_svg(output_dir: Path, summary: list[dict]) -> None:
|
| 976 |
-
rows = [
|
| 977 |
-
row
|
| 978 |
-
for row in summary
|
| 979 |
-
if row["run_mode"] == "screen_static" and row["condition_kind"] == "static"
|
| 980 |
-
]
|
| 981 |
-
if not rows:
|
| 982 |
-
return
|
| 983 |
-
|
| 984 |
-
grouped: dict[tuple[str, int], list[dict]] = defaultdict(list)
|
| 985 |
-
model_params: dict[str, int] = {}
|
| 986 |
-
for row in rows:
|
| 987 |
-
model_name = row["model_name"]
|
| 988 |
-
grouped[(model_name, int(row["token_limit"]))].append(row)
|
| 989 |
-
model_params[model_name] = int(row["parameters"])
|
| 990 |
-
|
| 991 |
-
models = sorted(model_params, key=lambda name: model_params[name])
|
| 992 |
-
prefixes = sorted({int(row["token_limit"]) for row in rows})
|
| 993 |
-
panel_w, panel_h = 230, 170
|
| 994 |
-
margin_l, margin_t = 58, 34
|
| 995 |
-
plot_w, plot_h = 142, 94
|
| 996 |
-
gap_x, gap_y = 18, 38
|
| 997 |
-
width = margin_l + len(prefixes) * panel_w + gap_x
|
| 998 |
-
height = 70 + len(models) * (panel_h + gap_y)
|
| 999 |
-
colors = ["#1f77b4", "#d62728", "#2ca02c", "#9467bd", "#ff7f0e"]
|
| 1000 |
-
|
| 1001 |
-
parts = [
|
| 1002 |
-
f'<svg xmlns="http://www.w3.org/2000/svg" width="{width}" height="{height}" viewBox="0 0 {width} {height}">',
|
| 1003 |
-
"<style>",
|
| 1004 |
-
"text{font-family:Arial,Helvetica,sans-serif;fill:#111827}",
|
| 1005 |
-
".small{font-size:10px}.label{font-size:11px}.title{font-size:15px;font-weight:700}",
|
| 1006 |
-
".axis{stroke:#374151;stroke-width:1}.grid{stroke:#e5e7eb;stroke-width:1}.line{fill:none;stroke-width:2}",
|
| 1007 |
-
"</style>",
|
| 1008 |
-
'<rect width="100%" height="100%" fill="#ffffff"/>',
|
| 1009 |
-
'<text x="24" y="28" class="title">Static dropout law: validation loss vs dropout</text>',
|
| 1010 |
-
'<text x="24" y="48" class="label">Each panel uses its own y-scale. Points are one-seed means unless N > 1.</text>',
|
| 1011 |
-
]
|
| 1012 |
-
|
| 1013 |
-
for col, prefix in enumerate(prefixes):
|
| 1014 |
-
x = margin_l + col * panel_w + plot_w / 2
|
| 1015 |
-
parts.append(
|
| 1016 |
-
f'<text x="{x:.1f}" y="70" text-anchor="middle" class="label">{prefix:,} prefix tokens</text>'
|
| 1017 |
-
)
|
| 1018 |
-
|
| 1019 |
-
for row_idx, model_name in enumerate(models):
|
| 1020 |
-
row_y = 92 + row_idx * (panel_h + gap_y)
|
| 1021 |
-
parts.append(
|
| 1022 |
-
f'<text x="24" y="{row_y + 48}" class="label" transform="rotate(-90 24 {row_y + 48})">'
|
| 1023 |
-
f'{svg_escape(model_name)} ({model_params[model_name] / 1_000_000:.1f}M)</text>'
|
| 1024 |
-
)
|
| 1025 |
-
for col, prefix in enumerate(prefixes):
|
| 1026 |
-
panel_x = margin_l + col * panel_w
|
| 1027 |
-
panel_y = row_y
|
| 1028 |
-
curve = sorted(
|
| 1029 |
-
grouped.get((model_name, prefix), []),
|
| 1030 |
-
key=lambda item: float(item["dropout_initial"]),
|
| 1031 |
-
)
|
| 1032 |
-
if not curve:
|
| 1033 |
-
continue
|
| 1034 |
-
losses = [float(item["mean_val_eval_loss"]) for item in curve]
|
| 1035 |
-
min_loss, max_loss = min(losses), max(losses)
|
| 1036 |
-
pad = max(0.02, (max_loss - min_loss) * 0.08)
|
| 1037 |
-
y_min, y_max = min_loss - pad, max_loss + pad
|
| 1038 |
-
best = min(curve, key=lambda item: float(item["mean_val_eval_loss"]))
|
| 1039 |
-
|
| 1040 |
-
def px(dropout: float) -> float:
|
| 1041 |
-
return panel_x + (dropout / 0.9) * plot_w
|
| 1042 |
-
|
| 1043 |
-
def py(loss: float) -> float:
|
| 1044 |
-
scale = (loss - y_min) / (y_max - y_min)
|
| 1045 |
-
return panel_y + plot_h - scale * plot_h
|
| 1046 |
-
|
| 1047 |
-
parts.extend(
|
| 1048 |
-
[
|
| 1049 |
-
f'<line x1="{panel_x:.1f}" y1="{panel_y:.1f}" x2="{panel_x:.1f}" y2="{panel_y + plot_h:.1f}" class="axis"/>',
|
| 1050 |
-
f'<line x1="{panel_x:.1f}" y1="{panel_y + plot_h:.1f}" x2="{panel_x + plot_w:.1f}" y2="{panel_y + plot_h:.1f}" class="axis"/>',
|
| 1051 |
-
f'<line x1="{panel_x:.1f}" y1="{panel_y:.1f}" x2="{panel_x + plot_w:.1f}" y2="{panel_y:.1f}" class="grid"/>',
|
| 1052 |
-
f'<text x="{panel_x:.1f}" y="{panel_y - 6:.1f}" class="small">{y_max:.2f}</text>',
|
| 1053 |
-
f'<text x="{panel_x:.1f}" y="{panel_y + plot_h + 13:.1f}" class="small">{y_min:.2f}</text>',
|
| 1054 |
-
f'<text x="{panel_x:.1f}" y="{panel_y + plot_h + 28:.1f}" class="small">0</text>',
|
| 1055 |
-
f'<text x="{panel_x + plot_w:.1f}" y="{panel_y + plot_h + 28:.1f}" text-anchor="end" class="small">0.9</text>',
|
| 1056 |
-
]
|
| 1057 |
-
)
|
| 1058 |
-
points = " ".join(
|
| 1059 |
-
f"{px(float(item['dropout_initial'])):.1f},{py(float(item['mean_val_eval_loss'])):.1f}"
|
| 1060 |
-
for item in curve
|
| 1061 |
-
)
|
| 1062 |
-
color = colors[row_idx % len(colors)]
|
| 1063 |
-
parts.append(f'<polyline points="{points}" class="line" stroke="{color}"/>')
|
| 1064 |
-
for item in curve:
|
| 1065 |
-
dropout = float(item["dropout_initial"])
|
| 1066 |
-
loss = float(item["mean_val_eval_loss"])
|
| 1067 |
-
radius = 4 if item is best else 2.7
|
| 1068 |
-
fill = "#111827" if item is best else "#ffffff"
|
| 1069 |
-
parts.append(
|
| 1070 |
-
f'<circle cx="{px(dropout):.1f}" cy="{py(loss):.1f}" r="{radius}" fill="{fill}" stroke="{color}" stroke-width="1.5"/>'
|
| 1071 |
-
)
|
| 1072 |
-
parts.append(
|
| 1073 |
-
f'<text x="{panel_x + plot_w + 8:.1f}" y="{panel_y + 14:.1f}" class="small">'
|
| 1074 |
-
f'best p={float(best["dropout_initial"]):.2f}</text>'
|
| 1075 |
-
)
|
| 1076 |
-
parts.append(
|
| 1077 |
-
f'<text x="{panel_x + plot_w + 8:.1f}" y="{panel_y + 28:.1f}" class="small">'
|
| 1078 |
-
f'loss={float(best["mean_val_eval_loss"]):.3f}</text>'
|
| 1079 |
-
)
|
| 1080 |
-
|
| 1081 |
-
parts.append("</svg>")
|
| 1082 |
-
(output_dir / "dropout_curves.svg").write_text("\n".join(parts), encoding="utf-8")
|
| 1083 |
-
|
| 1084 |
-
|
| 1085 |
-
def write_stream_markdown_summary(output_dir: Path, rows: list[dict]) -> None:
|
| 1086 |
-
stream_rows = [row for row in rows if row["run_mode"] == "locked_stream"]
|
| 1087 |
-
if not stream_rows:
|
| 1088 |
-
return
|
| 1089 |
-
|
| 1090 |
-
by_condition_stage: dict[tuple[str, int], list[dict]] = defaultdict(list)
|
| 1091 |
-
by_condition: dict[str, list[dict]] = defaultdict(list)
|
| 1092 |
-
for row in stream_rows:
|
| 1093 |
-
condition = row["condition"]
|
| 1094 |
-
by_condition_stage[(condition, int(row["stage"]))].append(row)
|
| 1095 |
-
by_condition[condition].append(row)
|
| 1096 |
-
|
| 1097 |
-
first = stream_rows[0]
|
| 1098 |
-
seeds = sorted({int(row["seed"]) for row in stream_rows})
|
| 1099 |
-
conditions = sorted(
|
| 1100 |
-
by_condition,
|
| 1101 |
-
key=lambda name: (
|
| 1102 |
-
by_condition[name][0]["condition_kind"] != "anchor_decay",
|
| 1103 |
-
by_condition[name][0]["dropout_initial"],
|
| 1104 |
-
name,
|
| 1105 |
-
),
|
| 1106 |
-
)
|
| 1107 |
-
stages = sorted({int(row["stage"]) for row in stream_rows})
|
| 1108 |
-
|
| 1109 |
-
lines = [
|
| 1110 |
-
"# Locked Streaming Dropout Summary",
|
| 1111 |
-
"",
|
| 1112 |
-
f"Run directory: `{output_dir}`",
|
| 1113 |
-
"",
|
| 1114 |
-
(
|
| 1115 |
-
f"Model: `{first['model_name']}` causal Transformer, "
|
| 1116 |
-
f"{int(first['parameters']):,} parameters, {first['n_layer']} layers, "
|
| 1117 |
-
f"{first['n_head']} heads, {first['n_embd']} embedding dim."
|
| 1118 |
-
),
|
| 1119 |
-
(
|
| 1120 |
-
f"Training per stage: {first['steps']:,} steps. "
|
| 1121 |
-
"Sampled tokens are cumulative in each stage row. "
|
| 1122 |
-
f"Seeds present: {', '.join(str(seed) for seed in seeds)}."
|
| 1123 |
-
),
|
| 1124 |
-
"",
|
| 1125 |
-
"## Condition Ranking",
|
| 1126 |
-
"",
|
| 1127 |
-
"| Condition | Kind | Final dropout | Mean trajectory val loss | Final val loss | Final gap | Dropout path |",
|
| 1128 |
-
"|---|---|---:|---:|---:|---:|---|",
|
| 1129 |
-
]
|
| 1130 |
-
|
| 1131 |
-
ranking = []
|
| 1132 |
-
for condition in conditions:
|
| 1133 |
-
stage_items = []
|
| 1134 |
-
for stage in stages:
|
| 1135 |
-
group = by_condition_stage.get((condition, stage), [])
|
| 1136 |
-
if not group:
|
| 1137 |
-
continue
|
| 1138 |
-
stage_items.append(
|
| 1139 |
-
{
|
| 1140 |
-
"stage": stage,
|
| 1141 |
-
"token_limit": int(group[0]["token_limit"]),
|
| 1142 |
-
"mean_val": statistics.fmean(
|
| 1143 |
-
float(row["val_eval_loss"]) for row in group
|
| 1144 |
-
),
|
| 1145 |
-
"mean_gap": statistics.fmean(
|
| 1146 |
-
float(row["generalization_gap"]) for row in group
|
| 1147 |
-
),
|
| 1148 |
-
"mean_dropout": statistics.fmean(
|
| 1149 |
-
float(row["dropout_active_final"]) for row in group
|
| 1150 |
-
),
|
| 1151 |
-
"kind": group[0]["condition_kind"],
|
| 1152 |
-
}
|
| 1153 |
-
)
|
| 1154 |
-
if not stage_items:
|
| 1155 |
-
continue
|
| 1156 |
-
final = max(stage_items, key=lambda item: item["stage"])
|
| 1157 |
-
ranking.append(
|
| 1158 |
-
{
|
| 1159 |
-
"condition": condition,
|
| 1160 |
-
"kind": stage_items[0]["kind"],
|
| 1161 |
-
"trajectory_val": statistics.fmean(item["mean_val"] for item in stage_items),
|
| 1162 |
-
"final_val": final["mean_val"],
|
| 1163 |
-
"final_gap": final["mean_gap"],
|
| 1164 |
-
"final_dropout": final["mean_dropout"],
|
| 1165 |
-
"dropout_path": " -> ".join(
|
| 1166 |
-
f"{item['mean_dropout']:.2f}" for item in stage_items
|
| 1167 |
-
),
|
| 1168 |
-
}
|
| 1169 |
-
)
|
| 1170 |
-
|
| 1171 |
-
for item in sorted(ranking, key=lambda row: row["trajectory_val"]):
|
| 1172 |
-
lines.append(
|
| 1173 |
-
"| "
|
| 1174 |
-
f"`{item['condition']}` | {item['kind']} | "
|
| 1175 |
-
f"{item['final_dropout']:.2f} | {item['trajectory_val']:.4f} | "
|
| 1176 |
-
f"{item['final_val']:.4f} | {item['final_gap']:.4f} | "
|
| 1177 |
-
f"{item['dropout_path']} |"
|
| 1178 |
-
)
|
| 1179 |
-
|
| 1180 |
-
lines.extend(["", "## Stage Trajectory", ""])
|
| 1181 |
-
for stage in stages:
|
| 1182 |
-
stage_groups = {
|
| 1183 |
-
condition: by_condition_stage[(condition, stage)]
|
| 1184 |
-
for condition in conditions
|
| 1185 |
-
if (condition, stage) in by_condition_stage
|
| 1186 |
-
}
|
| 1187 |
-
if not stage_groups:
|
| 1188 |
-
continue
|
| 1189 |
-
prefix = int(next(iter(stage_groups.values()))[0]["token_limit"])
|
| 1190 |
-
lines.extend(
|
| 1191 |
-
[
|
| 1192 |
-
f"### Stage {stage}: {prefix:,} Prefix Tokens",
|
| 1193 |
-
"",
|
| 1194 |
-
"| Condition | Dropout | Mean val loss | Mean train loss | Mean gap | N |",
|
| 1195 |
-
"|---|---:|---:|---:|---:|---:|",
|
| 1196 |
-
]
|
| 1197 |
-
)
|
| 1198 |
-
for condition, group in sorted(
|
| 1199 |
-
stage_groups.items(),
|
| 1200 |
-
key=lambda item: statistics.fmean(
|
| 1201 |
-
float(row["val_eval_loss"]) for row in item[1]
|
| 1202 |
-
),
|
| 1203 |
-
):
|
| 1204 |
-
val = statistics.fmean(float(row["val_eval_loss"]) for row in group)
|
| 1205 |
-
train = statistics.fmean(float(row["train_eval_loss"]) for row in group)
|
| 1206 |
-
gap = statistics.fmean(float(row["generalization_gap"]) for row in group)
|
| 1207 |
-
dropout = statistics.fmean(
|
| 1208 |
-
float(row["dropout_active_final"]) for row in group
|
| 1209 |
-
)
|
| 1210 |
-
lines.append(
|
| 1211 |
-
"| "
|
| 1212 |
-
f"`{condition}` | {dropout:.2f} | {val:.4f} | "
|
| 1213 |
-
f"{train:.4f} | {gap:.4f} | {len(group)} |"
|
| 1214 |
-
)
|
| 1215 |
-
lines.append("")
|
| 1216 |
-
|
| 1217 |
-
(output_dir / "RESULT_SUMMARY.md").write_text(
|
| 1218 |
-
"\n".join(lines).rstrip() + "\n",
|
| 1219 |
-
encoding="utf-8",
|
| 1220 |
-
)
|
| 1221 |
-
|
| 1222 |
-
|
| 1223 |
-
def static_conditions(dropout_rates: list[float]) -> list[DropoutCondition]:
|
| 1224 |
-
return [
|
| 1225 |
-
DropoutCondition(
|
| 1226 |
-
name=f"static_dropout_{rate_label(rate)}",
|
| 1227 |
-
kind="static",
|
| 1228 |
-
initial=rate,
|
| 1229 |
-
final=rate,
|
| 1230 |
-
)
|
| 1231 |
-
for rate in dropout_rates
|
| 1232 |
-
]
|
| 1233 |
-
|
| 1234 |
-
|
| 1235 |
-
def run_fixed_static_sweep(
|
| 1236 |
-
*,
|
| 1237 |
-
args: argparse.Namespace,
|
| 1238 |
-
model_specs: list[ModelSpec],
|
| 1239 |
-
seeds: list[int],
|
| 1240 |
-
train_tokens: np.ndarray,
|
| 1241 |
-
val_tokens: np.ndarray,
|
| 1242 |
-
tokenizer_vocab_size: int,
|
| 1243 |
-
token_limits: list[int],
|
| 1244 |
-
device: torch.device,
|
| 1245 |
-
metrics_file,
|
| 1246 |
-
trace_file,
|
| 1247 |
-
completed_keys: set[tuple] | None = None,
|
| 1248 |
-
) -> list[dict]:
|
| 1249 |
-
rows: list[dict] = []
|
| 1250 |
-
completed_keys = completed_keys or set()
|
| 1251 |
-
conditions = static_conditions(sorted(set(args.dropout_rates)))
|
| 1252 |
-
planned = 0
|
| 1253 |
-
for token_limit in token_limits:
|
| 1254 |
-
for model_spec in model_specs:
|
| 1255 |
-
for condition in conditions:
|
| 1256 |
-
for seed in seeds:
|
| 1257 |
-
key = planned_metric_key(
|
| 1258 |
-
mode=args.mode,
|
| 1259 |
-
condition=condition,
|
| 1260 |
-
model_spec=model_spec,
|
| 1261 |
-
seed=seed,
|
| 1262 |
-
token_limit=token_limit,
|
| 1263 |
-
)
|
| 1264 |
-
if key not in completed_keys:
|
| 1265 |
-
planned += 1
|
| 1266 |
-
progress = ProgressMeter(planned)
|
| 1267 |
-
for token_limit in token_limits:
|
| 1268 |
-
for model_spec in model_specs:
|
| 1269 |
-
best_val_loss = float("inf")
|
| 1270 |
-
worse_streak = 0
|
| 1271 |
-
for condition in conditions:
|
| 1272 |
-
condition_rows: list[dict] = []
|
| 1273 |
-
for seed in seeds:
|
| 1274 |
-
key = planned_metric_key(
|
| 1275 |
-
mode=args.mode,
|
| 1276 |
-
condition=condition,
|
| 1277 |
-
model_spec=model_spec,
|
| 1278 |
-
seed=seed,
|
| 1279 |
-
token_limit=token_limit,
|
| 1280 |
-
)
|
| 1281 |
-
if key in completed_keys:
|
| 1282 |
-
write_jsonl_row(
|
| 1283 |
-
trace_file,
|
| 1284 |
-
{
|
| 1285 |
-
"event": "skipped_completed_condition",
|
| 1286 |
-
"run_mode": args.mode,
|
| 1287 |
-
"condition": condition.name,
|
| 1288 |
-
"model_name": model_spec.name,
|
| 1289 |
-
"seed": seed,
|
| 1290 |
-
"stage": None,
|
| 1291 |
-
"token_limit": int(token_limit),
|
| 1292 |
-
},
|
| 1293 |
-
)
|
| 1294 |
-
continue
|
| 1295 |
-
config = model_spec.config(
|
| 1296 |
-
tokenizer_vocab_size,
|
| 1297 |
-
args.block_size,
|
| 1298 |
-
condition.initial,
|
| 1299 |
-
)
|
| 1300 |
-
model, optimizer, _, row = train_segment(
|
| 1301 |
-
run_mode=args.mode,
|
| 1302 |
-
condition=condition,
|
| 1303 |
-
model_spec=model_spec,
|
| 1304 |
-
config=config,
|
| 1305 |
-
train_tokens=train_tokens,
|
| 1306 |
-
val_tokens=val_tokens,
|
| 1307 |
-
token_limit=token_limit,
|
| 1308 |
-
steps=args.steps,
|
| 1309 |
-
seed=seed,
|
| 1310 |
-
args=args,
|
| 1311 |
-
device=device,
|
| 1312 |
-
dropout_fn=condition.make_fn(
|
| 1313 |
-
args.steps * args.batch_size * args.block_size
|
| 1314 |
-
),
|
| 1315 |
-
metrics_file=metrics_file,
|
| 1316 |
-
trace_file=trace_file,
|
| 1317 |
-
)
|
| 1318 |
-
rows.append(row)
|
| 1319 |
-
condition_rows.append(row)
|
| 1320 |
-
completed_keys.add(metric_key(row))
|
| 1321 |
-
progress.mark_done(row)
|
| 1322 |
-
del model, optimizer
|
| 1323 |
-
torch.mps.empty_cache()
|
| 1324 |
-
|
| 1325 |
-
if not condition_rows:
|
| 1326 |
-
continue
|
| 1327 |
-
mean_val_loss = statistics.fmean(
|
| 1328 |
-
float(row["val_eval_loss"]) for row in condition_rows
|
| 1329 |
-
)
|
| 1330 |
-
if mean_val_loss < best_val_loss - args.screen_prune_min_delta:
|
| 1331 |
-
best_val_loss = mean_val_loss
|
| 1332 |
-
worse_streak = 0
|
| 1333 |
-
elif mean_val_loss > best_val_loss + args.screen_prune_min_delta:
|
| 1334 |
-
worse_streak += 1
|
| 1335 |
-
|
| 1336 |
-
if (
|
| 1337 |
-
args.mode == "screen_static"
|
| 1338 |
-
and args.screen_early_stop
|
| 1339 |
-
and worse_streak >= args.screen_prune_patience
|
| 1340 |
-
and condition.initial >= args.target_min_dropout
|
| 1341 |
-
):
|
| 1342 |
-
write_jsonl_row(
|
| 1343 |
-
trace_file,
|
| 1344 |
-
{
|
| 1345 |
-
"event": "screen_pruned_model",
|
| 1346 |
-
"run_mode": args.mode,
|
| 1347 |
-
"model_name": model_spec.name,
|
| 1348 |
-
"token_limit": int(token_limit),
|
| 1349 |
-
"best_val_loss": best_val_loss,
|
| 1350 |
-
"pruned_after_dropout": condition.initial,
|
| 1351 |
-
"worse_streak": worse_streak,
|
| 1352 |
-
"remaining_dropouts": [
|
| 1353 |
-
rate
|
| 1354 |
-
for rate in args.dropout_rates
|
| 1355 |
-
if rate > condition.initial
|
| 1356 |
-
],
|
| 1357 |
-
},
|
| 1358 |
-
)
|
| 1359 |
-
break
|
| 1360 |
-
return rows
|
| 1361 |
-
|
| 1362 |
-
|
| 1363 |
-
def run_locked_stream(
|
| 1364 |
-
*,
|
| 1365 |
-
args: argparse.Namespace,
|
| 1366 |
-
model_specs: list[ModelSpec],
|
| 1367 |
-
seeds: list[int],
|
| 1368 |
-
train_tokens: np.ndarray,
|
| 1369 |
-
val_tokens: np.ndarray,
|
| 1370 |
-
tokenizer_vocab_size: int,
|
| 1371 |
-
stream_caps: list[int],
|
| 1372 |
-
device: torch.device,
|
| 1373 |
-
metrics_file,
|
| 1374 |
-
trace_file,
|
| 1375 |
-
completed_keys: set[tuple] | None = None,
|
| 1376 |
-
) -> list[dict]:
|
| 1377 |
-
rows: list[dict] = []
|
| 1378 |
-
completed_keys = completed_keys or set()
|
| 1379 |
-
conditions = args.anchor_decays + static_conditions(args.dropout_rates) + args.decays
|
| 1380 |
-
fallback_decay_tokens = (
|
| 1381 |
-
args.decay_tokens
|
| 1382 |
-
or args.stage_steps * args.batch_size * args.block_size * len(stream_caps)
|
| 1383 |
-
)
|
| 1384 |
-
planned = 0
|
| 1385 |
-
for model_spec in model_specs:
|
| 1386 |
-
for condition in conditions:
|
| 1387 |
-
for seed in seeds:
|
| 1388 |
-
for stage, token_limit in enumerate(stream_caps):
|
| 1389 |
-
key = planned_metric_key(
|
| 1390 |
-
mode=args.mode,
|
| 1391 |
-
condition=condition,
|
| 1392 |
-
model_spec=model_spec,
|
| 1393 |
-
seed=seed,
|
| 1394 |
-
token_limit=token_limit,
|
| 1395 |
-
stage=stage,
|
| 1396 |
-
)
|
| 1397 |
-
if key not in completed_keys:
|
| 1398 |
-
planned += 1
|
| 1399 |
-
progress = ProgressMeter(planned)
|
| 1400 |
-
for model_spec in model_specs:
|
| 1401 |
-
for condition in conditions:
|
| 1402 |
-
for seed in seeds:
|
| 1403 |
-
model = None
|
| 1404 |
-
optimizer = None
|
| 1405 |
-
tokens_seen = 0
|
| 1406 |
-
for stage, token_limit in enumerate(stream_caps):
|
| 1407 |
-
key = planned_metric_key(
|
| 1408 |
-
mode=args.mode,
|
| 1409 |
-
condition=condition,
|
| 1410 |
-
model_spec=model_spec,
|
| 1411 |
-
seed=seed,
|
| 1412 |
-
token_limit=token_limit,
|
| 1413 |
-
stage=stage,
|
| 1414 |
-
)
|
| 1415 |
-
if key in completed_keys:
|
| 1416 |
-
write_jsonl_row(
|
| 1417 |
-
trace_file,
|
| 1418 |
-
{
|
| 1419 |
-
"event": "skipped_completed_condition",
|
| 1420 |
-
"run_mode": args.mode,
|
| 1421 |
-
"condition": condition.name,
|
| 1422 |
-
"model_name": model_spec.name,
|
| 1423 |
-
"seed": seed,
|
| 1424 |
-
"stage": stage,
|
| 1425 |
-
"token_limit": int(token_limit),
|
| 1426 |
-
},
|
| 1427 |
-
)
|
| 1428 |
-
continue
|
| 1429 |
-
config = model_spec.config(
|
| 1430 |
-
tokenizer_vocab_size,
|
| 1431 |
-
args.block_size,
|
| 1432 |
-
condition.initial,
|
| 1433 |
-
)
|
| 1434 |
-
model, optimizer, tokens_seen, row = train_segment(
|
| 1435 |
-
run_mode=args.mode,
|
| 1436 |
-
condition=condition,
|
| 1437 |
-
model_spec=model_spec,
|
| 1438 |
-
config=config,
|
| 1439 |
-
train_tokens=train_tokens,
|
| 1440 |
-
val_tokens=val_tokens,
|
| 1441 |
-
token_limit=token_limit,
|
| 1442 |
-
steps=args.stage_steps,
|
| 1443 |
-
seed=seed,
|
| 1444 |
-
args=args,
|
| 1445 |
-
device=device,
|
| 1446 |
-
dropout_fn=condition.make_fn(
|
| 1447 |
-
fallback_decay_tokens,
|
| 1448 |
-
unique_tokens=token_limit,
|
| 1449 |
-
),
|
| 1450 |
-
metrics_file=metrics_file,
|
| 1451 |
-
trace_file=trace_file,
|
| 1452 |
-
stage=stage,
|
| 1453 |
-
model=model,
|
| 1454 |
-
optimizer=optimizer,
|
| 1455 |
-
tokens_seen_start=tokens_seen,
|
| 1456 |
-
)
|
| 1457 |
-
rows.append(row)
|
| 1458 |
-
completed_keys.add(metric_key(row))
|
| 1459 |
-
progress.mark_done(row)
|
| 1460 |
-
del model, optimizer
|
| 1461 |
-
torch.mps.empty_cache()
|
| 1462 |
-
return rows
|
| 1463 |
-
|
| 1464 |
-
|
| 1465 |
-
def prepare_data(args: argparse.Namespace, output_dir: Path, required_train_tokens: int):
|
| 1466 |
-
cache_dir = Path(args.cache_dir) if args.cache_dir else output_dir / "cache"
|
| 1467 |
-
cache_dir.mkdir(parents=True, exist_ok=True)
|
| 1468 |
-
if args.use_cached_data:
|
| 1469 |
-
if args.force_retokenize:
|
| 1470 |
-
raise ValueError("--use-cached-data cannot be combined with --force-retokenize")
|
| 1471 |
-
return load_cached_splits(
|
| 1472 |
-
cache_dir=cache_dir,
|
| 1473 |
-
vocab_size=args.vocab_size,
|
| 1474 |
-
max_required_train_tokens=required_train_tokens,
|
| 1475 |
-
val_tokens=args.val_tokens,
|
| 1476 |
-
allow_short_corpus=args.allow_short_corpus,
|
| 1477 |
-
)
|
| 1478 |
-
|
| 1479 |
-
paths = resolve_paths(args.corpus, args.corpus_glob)
|
| 1480 |
-
tokenizer = train_or_load_tokenizer(
|
| 1481 |
-
paths=paths,
|
| 1482 |
-
output_dir=cache_dir,
|
| 1483 |
-
vocab_size=args.vocab_size,
|
| 1484 |
-
tokenizer_train_chars=args.tokenizer_train_chars,
|
| 1485 |
-
text_column=args.text_column,
|
| 1486 |
-
force_retrain=args.force_retokenize,
|
| 1487 |
-
)
|
| 1488 |
-
splits = encode_corpus(
|
| 1489 |
-
paths=paths,
|
| 1490 |
-
tokenizer=tokenizer,
|
| 1491 |
-
output_dir=cache_dir,
|
| 1492 |
-
max_required_train_tokens=required_train_tokens,
|
| 1493 |
-
val_tokens=args.val_tokens,
|
| 1494 |
-
text_column=args.text_column,
|
| 1495 |
-
allow_short_corpus=args.allow_short_corpus,
|
| 1496 |
-
force_reencode=args.force_retokenize,
|
| 1497 |
-
)
|
| 1498 |
-
return tokenizer, splits
|
| 1499 |
-
|
| 1500 |
-
|
| 1501 |
-
def run(args: argparse.Namespace) -> Path:
|
| 1502 |
-
device = assert_mps_only()
|
| 1503 |
-
seeds = default_seeds(args.mode, args.seeds)
|
| 1504 |
-
model_specs = [parse_model_spec(spec) for spec in args.models]
|
| 1505 |
-
if args.mode != "locked_stream" and (args.decays or args.anchor_decays):
|
| 1506 |
-
raise ValueError("--decays and --anchor-decays are only used with --mode locked_stream")
|
| 1507 |
-
if args.resume_from and args.mode == "locked_stream":
|
| 1508 |
-
raise ValueError("--resume-from currently supports fixed static sweeps only")
|
| 1509 |
-
|
| 1510 |
-
if args.resume_from:
|
| 1511 |
-
output_dir = Path(args.resume_from)
|
| 1512 |
-
if not output_dir.exists():
|
| 1513 |
-
raise FileNotFoundError(f"resume directory does not exist: {output_dir}")
|
| 1514 |
-
else:
|
| 1515 |
-
run_id = datetime.now().strftime("%Y%m%d-%H%M%S")
|
| 1516 |
-
output_dir = Path(args.output_dir) / args.mode / run_id
|
| 1517 |
-
output_dir.mkdir(parents=True, exist_ok=True)
|
| 1518 |
-
required_train_tokens = max(
|
| 1519 |
-
args.stream_token_caps if args.mode == "locked_stream" else args.token_limits
|
| 1520 |
-
)
|
| 1521 |
-
tokenizer, splits = prepare_data(args, output_dir, required_train_tokens)
|
| 1522 |
-
token_limits = [min(limit, len(splits.train)) for limit in args.token_limits]
|
| 1523 |
-
stream_caps = [min(limit, len(splits.train)) for limit in args.stream_token_caps]
|
| 1524 |
-
|
| 1525 |
-
args_payload = vars(args).copy()
|
| 1526 |
-
args_payload["decays"] = [condition.to_dict() for condition in args.decays]
|
| 1527 |
-
args_payload["anchor_decays"] = [
|
| 1528 |
-
condition.to_dict() for condition in args.anchor_decays
|
| 1529 |
-
]
|
| 1530 |
-
config_payload = {
|
| 1531 |
-
"args": args_payload,
|
| 1532 |
-
"mode": args.mode,
|
| 1533 |
-
"seeds": seeds,
|
| 1534 |
-
"models": [model.to_dict() for model in model_specs],
|
| 1535 |
-
"device": str(device),
|
| 1536 |
-
"torch": torch.__version__,
|
| 1537 |
-
"python": sys.version,
|
| 1538 |
-
"mps_available": torch.backends.mps.is_available(),
|
| 1539 |
-
"attribution": NANOCHAT_ATTRIBUTION,
|
| 1540 |
-
"tokenizer_path": str(splits.tokenizer_path),
|
| 1541 |
-
"encoded_path": str(splits.encoded_path),
|
| 1542 |
-
"train_tokens": int(len(splits.train)),
|
| 1543 |
-
"val_tokens": int(len(splits.val)),
|
| 1544 |
-
"effective_token_limits": [int(limit) for limit in token_limits],
|
| 1545 |
-
"effective_stream_token_caps": [int(limit) for limit in stream_caps],
|
| 1546 |
-
"resume_from": str(args.resume_from) if args.resume_from else None,
|
| 1547 |
-
}
|
| 1548 |
-
config_name = "config.resume.json" if args.resume_from else "config.json"
|
| 1549 |
-
(output_dir / config_name).write_text(
|
| 1550 |
-
json.dumps(config_payload, indent=2),
|
| 1551 |
-
encoding="utf-8",
|
| 1552 |
-
)
|
| 1553 |
-
|
| 1554 |
-
metrics_path = output_dir / "metrics.jsonl"
|
| 1555 |
-
trace_path = output_dir / "trace.jsonl"
|
| 1556 |
-
existing_rows = load_metrics(metrics_path) if args.resume_from else []
|
| 1557 |
-
completed_keys = {metric_key(row) for row in existing_rows}
|
| 1558 |
-
with (
|
| 1559 |
-
metrics_path.open("a" if args.resume_from else "w", encoding="utf-8") as metrics_file,
|
| 1560 |
-
trace_path.open("a" if args.resume_from else "w", encoding="utf-8") as trace_file,
|
| 1561 |
-
):
|
| 1562 |
-
if args.mode in {"screen_static", "confirm_static"}:
|
| 1563 |
-
new_rows = run_fixed_static_sweep(
|
| 1564 |
-
args=args,
|
| 1565 |
-
model_specs=model_specs,
|
| 1566 |
-
seeds=seeds,
|
| 1567 |
-
train_tokens=splits.train,
|
| 1568 |
-
val_tokens=splits.val,
|
| 1569 |
-
tokenizer_vocab_size=tokenizer.vocab_size,
|
| 1570 |
-
token_limits=token_limits,
|
| 1571 |
-
device=device,
|
| 1572 |
-
metrics_file=metrics_file,
|
| 1573 |
-
trace_file=trace_file,
|
| 1574 |
-
completed_keys=completed_keys,
|
| 1575 |
-
)
|
| 1576 |
-
else:
|
| 1577 |
-
new_rows = run_locked_stream(
|
| 1578 |
-
args=args,
|
| 1579 |
-
model_specs=model_specs,
|
| 1580 |
-
seeds=seeds,
|
| 1581 |
-
train_tokens=splits.train,
|
| 1582 |
-
val_tokens=splits.val,
|
| 1583 |
-
tokenizer_vocab_size=tokenizer.vocab_size,
|
| 1584 |
-
stream_caps=stream_caps,
|
| 1585 |
-
device=device,
|
| 1586 |
-
metrics_file=metrics_file,
|
| 1587 |
-
trace_file=trace_file,
|
| 1588 |
-
)
|
| 1589 |
-
rows = existing_rows + new_rows
|
| 1590 |
-
|
| 1591 |
-
summary = summarize(rows)
|
| 1592 |
-
(output_dir / "summary.json").write_text(
|
| 1593 |
-
json.dumps(summary, indent=2),
|
| 1594 |
-
encoding="utf-8",
|
| 1595 |
-
)
|
| 1596 |
-
write_csv(output_dir / "summary.csv", summary, SUMMARY_FIELDS)
|
| 1597 |
-
if args.mode in {"screen_static", "confirm_static"}:
|
| 1598 |
-
selection = build_model_selection(summary, args)
|
| 1599 |
-
(output_dir / "model_selection.json").write_text(
|
| 1600 |
-
json.dumps(selection, indent=2),
|
| 1601 |
-
encoding="utf-8",
|
| 1602 |
-
)
|
| 1603 |
-
write_csv(output_dir / "model_selection.csv", selection, SELECTION_FIELDS)
|
| 1604 |
-
write_screen_markdown_summary(output_dir, rows)
|
| 1605 |
-
write_dropout_curve_svg(output_dir, summary)
|
| 1606 |
-
elif args.mode == "locked_stream":
|
| 1607 |
-
write_stream_markdown_summary(output_dir, rows)
|
| 1608 |
-
|
| 1609 |
-
print(
|
| 1610 |
-
json.dumps(
|
| 1611 |
-
{
|
| 1612 |
-
"output_dir": str(output_dir),
|
| 1613 |
-
"new_rows": len(new_rows),
|
| 1614 |
-
"total_metric_rows": len(rows),
|
| 1615 |
-
"summary_rows": len(summary),
|
| 1616 |
-
},
|
| 1617 |
-
indent=2,
|
| 1618 |
-
)
|
| 1619 |
-
)
|
| 1620 |
-
return output_dir
|
| 1621 |
-
|
| 1622 |
-
|
| 1623 |
-
def build_parser() -> argparse.ArgumentParser:
|
| 1624 |
-
parser = argparse.ArgumentParser(
|
| 1625 |
-
description="MPS-only dropout/model selection experiments"
|
| 1626 |
-
)
|
| 1627 |
-
parser.add_argument(
|
| 1628 |
-
"--mode",
|
| 1629 |
-
choices=["screen_static", "confirm_static", "locked_stream"],
|
| 1630 |
-
default="screen_static",
|
| 1631 |
-
)
|
| 1632 |
-
parser.add_argument("--corpus", default=None, help="Text or parquet corpus path")
|
| 1633 |
-
parser.add_argument("--corpus-glob", default=None, help="Glob of text/parquet corpus paths")
|
| 1634 |
-
parser.add_argument("--text-column", default="text", help="Parquet text column")
|
| 1635 |
-
parser.add_argument(
|
| 1636 |
-
"--use-cached-data",
|
| 1637 |
-
action="store_true",
|
| 1638 |
-
help=(
|
| 1639 |
-
"Load tokenizer-v{vocab}.json and tokens-v{vocab}-*.npy from --cache-dir "
|
| 1640 |
-
"instead of requiring the original text/parquet corpus."
|
| 1641 |
-
),
|
| 1642 |
-
)
|
| 1643 |
-
parser.add_argument("--output-dir", default="runs")
|
| 1644 |
-
parser.add_argument(
|
| 1645 |
-
"--resume-from",
|
| 1646 |
-
default=None,
|
| 1647 |
-
help="Existing fixed-static run directory; completed metric rows are skipped",
|
| 1648 |
-
)
|
| 1649 |
-
parser.add_argument("--cache-dir", default=".cache/dropout_decay")
|
| 1650 |
-
parser.add_argument(
|
| 1651 |
-
"--models",
|
| 1652 |
-
nargs="+",
|
| 1653 |
-
default=["8x8x256"],
|
| 1654 |
-
help="Model specs like 8x8x256 or name=8x8x256",
|
| 1655 |
-
)
|
| 1656 |
-
parser.add_argument("--seeds", nargs="+", type=int, default=None)
|
| 1657 |
-
parser.add_argument("--token-limits", nargs="+", type=int, default=[5_000_000])
|
| 1658 |
-
parser.add_argument(
|
| 1659 |
-
"--stream-token-caps",
|
| 1660 |
-
nargs="+",
|
| 1661 |
-
type=int,
|
| 1662 |
-
default=[5_000_000, 10_000_000, 20_000_000, 40_000_000],
|
| 1663 |
-
)
|
| 1664 |
-
parser.add_argument("--val-tokens", type=int, default=500_000)
|
| 1665 |
-
parser.add_argument("--allow-short-corpus", action="store_true")
|
| 1666 |
-
parser.add_argument("--force-retokenize", action="store_true")
|
| 1667 |
-
parser.add_argument("--vocab-size", type=int, default=4096)
|
| 1668 |
-
parser.add_argument("--tokenizer-train-chars", type=int, default=10_000_000)
|
| 1669 |
-
parser.add_argument("--block-size", type=int, default=128)
|
| 1670 |
-
parser.add_argument("--batch-size", type=int, default=16)
|
| 1671 |
-
parser.add_argument("--steps", type=int, default=2000)
|
| 1672 |
-
parser.add_argument("--stage-steps", type=int, default=1000)
|
| 1673 |
-
parser.add_argument("--dropout-rates", nargs="*", type=float, default=DEFAULT_DROPOUT_RATES)
|
| 1674 |
-
parser.add_argument("--decays", nargs="*", type=parse_decay_spec, default=[])
|
| 1675 |
-
parser.add_argument(
|
| 1676 |
-
"--anchor-decays",
|
| 1677 |
-
nargs="*",
|
| 1678 |
-
type=parse_anchor_decay_spec,
|
| 1679 |
-
default=[],
|
| 1680 |
-
help=(
|
| 1681 |
-
"Prefix-token anchor schedules like "
|
| 1682 |
-
"fit:250000=0.60,500000=0.40,1000000=0.30"
|
| 1683 |
-
),
|
| 1684 |
-
)
|
| 1685 |
-
parser.add_argument("--decay-tokens", type=int, default=None)
|
| 1686 |
-
parser.add_argument("--eval-batches", type=int, default=64)
|
| 1687 |
-
parser.add_argument("--train-eval-batches", type=int, default=32)
|
| 1688 |
-
parser.add_argument("--trace-eval-batches", type=int, default=8)
|
| 1689 |
-
parser.add_argument("--eval-every", type=int, default=0)
|
| 1690 |
-
parser.add_argument("--log-every", type=int, default=100)
|
| 1691 |
-
parser.add_argument("--lr", type=float, default=3e-4)
|
| 1692 |
-
parser.add_argument("--weight-decay", type=float, default=0.1)
|
| 1693 |
-
parser.add_argument("--grad-clip", type=float, default=1.0)
|
| 1694 |
-
parser.add_argument("--plateau-delta", type=float, default=0.01)
|
| 1695 |
-
parser.add_argument("--target-min-dropout", type=float, default=0.10)
|
| 1696 |
-
parser.add_argument("--min-nonzero-margin", type=float, default=0.01)
|
| 1697 |
-
parser.add_argument("--min-high-dropout-margin", type=float, default=0.03)
|
| 1698 |
-
parser.add_argument("--screen-early-stop", action="store_true")
|
| 1699 |
-
parser.add_argument("--screen-prune-patience", type=int, default=3)
|
| 1700 |
-
parser.add_argument("--screen-prune-min-delta", type=float, default=0.01)
|
| 1701 |
-
return parser
|
| 1702 |
-
|
| 1703 |
-
|
| 1704 |
-
def main() -> None:
|
| 1705 |
-
args = build_parser().parse_args()
|
| 1706 |
-
run(args)
|
| 1707 |
|
| 1708 |
|
| 1709 |
if __name__ == "__main__":
|
|
|
|
| 16 |
copies or substantial portions of the Software.
|
| 17 |
"""
|
| 18 |
|
| 19 |
+
from dropout_decay.experiments.runner import build_parser, main, run
|
| 20 |
|
| 21 |
+
__all__ = [
|
| 22 |
+
"build_parser",
|
| 23 |
+
"main",
|
| 24 |
+
"run",
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 25 |
]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 26 |
|
| 27 |
|
| 28 |
if __name__ == "__main__":
|
src/dropout_decay/experiments/__init__.py
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Derived from Andrej Karpathy's nanochat project.
|
| 3 |
+
|
| 4 |
+
MIT License
|
| 5 |
+
|
| 6 |
+
Copyright (c) 2025 Andrej Karpathy
|
| 7 |
+
|
| 8 |
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
| 9 |
+
of this software and associated documentation files (the "Software"), to deal
|
| 10 |
+
in the Software without restriction, including without limitation the rights
|
| 11 |
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
| 12 |
+
copies of the Software, and to permit persons to whom the Software is
|
| 13 |
+
furnished to do so, subject to the following conditions:
|
| 14 |
+
|
| 15 |
+
The above copyright notice and this permission notice shall be included in all
|
| 16 |
+
copies or substantial portions of the Software.
|
| 17 |
+
"""
|
| 18 |
+
|
| 19 |
+
from dropout_decay.experiments.runner import build_parser, main, run
|
| 20 |
+
|
| 21 |
+
__all__ = [
|
| 22 |
+
"build_parser",
|
| 23 |
+
"main",
|
| 24 |
+
"run",
|
| 25 |
+
]
|
src/dropout_decay/experiments/artifacts.py
ADDED
|
@@ -0,0 +1,281 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Derived from Andrej Karpathy's nanochat project.
|
| 3 |
+
|
| 4 |
+
MIT License
|
| 5 |
+
|
| 6 |
+
Copyright (c) 2025 Andrej Karpathy
|
| 7 |
+
|
| 8 |
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
| 9 |
+
of this software and associated documentation files (the "Software"), to deal
|
| 10 |
+
in the Software without restriction, including without limitation the rights
|
| 11 |
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
| 12 |
+
copies of the Software, and to permit persons to whom the Software is
|
| 13 |
+
furnished to do so, subject to the following conditions:
|
| 14 |
+
|
| 15 |
+
The above copyright notice and this permission notice shall be included in all
|
| 16 |
+
copies or substantial portions of the Software.
|
| 17 |
+
"""
|
| 18 |
+
|
| 19 |
+
from __future__ import annotations
|
| 20 |
+
|
| 21 |
+
import argparse
|
| 22 |
+
import csv
|
| 23 |
+
from collections import defaultdict
|
| 24 |
+
import json
|
| 25 |
+
from pathlib import Path
|
| 26 |
+
import statistics
|
| 27 |
+
|
| 28 |
+
from dropout_decay.specs import DropoutCondition, ModelSpec
|
| 29 |
+
|
| 30 |
+
|
| 31 |
+
SUMMARY_FIELDS = [
|
| 32 |
+
"run_mode",
|
| 33 |
+
"condition",
|
| 34 |
+
"condition_kind",
|
| 35 |
+
"stage",
|
| 36 |
+
"token_limit",
|
| 37 |
+
"model_name",
|
| 38 |
+
"n_layer",
|
| 39 |
+
"n_head",
|
| 40 |
+
"n_embd",
|
| 41 |
+
"parameters",
|
| 42 |
+
"dropout_initial",
|
| 43 |
+
"dropout_final",
|
| 44 |
+
"dropout_schedule",
|
| 45 |
+
"n",
|
| 46 |
+
"mean_train_eval_loss",
|
| 47 |
+
"std_train_eval_loss",
|
| 48 |
+
"mean_val_eval_loss",
|
| 49 |
+
"std_val_eval_loss",
|
| 50 |
+
"mean_generalization_gap",
|
| 51 |
+
"std_generalization_gap",
|
| 52 |
+
]
|
| 53 |
+
SELECTION_FIELDS = [
|
| 54 |
+
"run_mode",
|
| 55 |
+
"token_limit",
|
| 56 |
+
"model_name",
|
| 57 |
+
"n_layer",
|
| 58 |
+
"n_head",
|
| 59 |
+
"n_embd",
|
| 60 |
+
"parameters",
|
| 61 |
+
"n",
|
| 62 |
+
"best_dropout",
|
| 63 |
+
"best_val_loss",
|
| 64 |
+
"best_val_std",
|
| 65 |
+
"plateau_start_dropout",
|
| 66 |
+
"plateau_end_dropout",
|
| 67 |
+
"plateau_delta",
|
| 68 |
+
"zero_dropout_val_loss",
|
| 69 |
+
"zero_minus_best",
|
| 70 |
+
"best_nonzero_dropout",
|
| 71 |
+
"best_nonzero_val_loss",
|
| 72 |
+
"zero_minus_best_nonzero",
|
| 73 |
+
"max_dropout",
|
| 74 |
+
"max_dropout_val_loss",
|
| 75 |
+
"max_dropout_minus_best",
|
| 76 |
+
"has_nonzero_optimum",
|
| 77 |
+
"meets_target_dropout",
|
| 78 |
+
"curve_json",
|
| 79 |
+
]
|
| 80 |
+
|
| 81 |
+
|
| 82 |
+
def write_jsonl_row(handle, row: dict) -> None:
|
| 83 |
+
handle.write(json.dumps(row, sort_keys=True) + "\n")
|
| 84 |
+
handle.flush()
|
| 85 |
+
|
| 86 |
+
|
| 87 |
+
def metric_key(row: dict) -> tuple:
|
| 88 |
+
return (
|
| 89 |
+
row["run_mode"],
|
| 90 |
+
row["condition"],
|
| 91 |
+
row["condition_kind"],
|
| 92 |
+
row.get("stage"),
|
| 93 |
+
int(row["token_limit"]),
|
| 94 |
+
row["model_name"],
|
| 95 |
+
int(row["n_layer"]),
|
| 96 |
+
int(row["n_head"]),
|
| 97 |
+
int(row["n_embd"]),
|
| 98 |
+
int(row["seed"]),
|
| 99 |
+
float(row["dropout_initial"]),
|
| 100 |
+
float(row["dropout_final"]),
|
| 101 |
+
row["dropout_schedule"],
|
| 102 |
+
)
|
| 103 |
+
|
| 104 |
+
|
| 105 |
+
def planned_metric_key(
|
| 106 |
+
*,
|
| 107 |
+
mode: str,
|
| 108 |
+
condition: DropoutCondition,
|
| 109 |
+
model_spec: ModelSpec,
|
| 110 |
+
seed: int,
|
| 111 |
+
token_limit: int,
|
| 112 |
+
stage: int | None = None,
|
| 113 |
+
) -> tuple:
|
| 114 |
+
return (
|
| 115 |
+
mode,
|
| 116 |
+
condition.name,
|
| 117 |
+
condition.kind,
|
| 118 |
+
stage,
|
| 119 |
+
int(token_limit),
|
| 120 |
+
model_spec.name,
|
| 121 |
+
int(model_spec.n_layer),
|
| 122 |
+
int(model_spec.n_head),
|
| 123 |
+
int(model_spec.n_embd),
|
| 124 |
+
int(seed),
|
| 125 |
+
float(condition.initial),
|
| 126 |
+
float(condition.final),
|
| 127 |
+
condition.schedule,
|
| 128 |
+
)
|
| 129 |
+
|
| 130 |
+
|
| 131 |
+
def load_metrics(path: Path) -> list[dict]:
|
| 132 |
+
if not path.exists():
|
| 133 |
+
return []
|
| 134 |
+
rows = []
|
| 135 |
+
for line in path.read_text(encoding="utf-8").splitlines():
|
| 136 |
+
if line.strip():
|
| 137 |
+
rows.append(json.loads(line))
|
| 138 |
+
return rows
|
| 139 |
+
|
| 140 |
+
|
| 141 |
+
def summarize(rows: list[dict]) -> list[dict]:
|
| 142 |
+
groups: dict[tuple, list[dict]] = defaultdict(list)
|
| 143 |
+
for row in rows:
|
| 144 |
+
key = (
|
| 145 |
+
row["run_mode"],
|
| 146 |
+
row["condition"],
|
| 147 |
+
row["condition_kind"],
|
| 148 |
+
row["stage"],
|
| 149 |
+
row["token_limit"],
|
| 150 |
+
row["model_name"],
|
| 151 |
+
row["n_layer"],
|
| 152 |
+
row["n_head"],
|
| 153 |
+
row["n_embd"],
|
| 154 |
+
row["parameters"],
|
| 155 |
+
row["dropout_initial"],
|
| 156 |
+
row["dropout_final"],
|
| 157 |
+
row["dropout_schedule"],
|
| 158 |
+
)
|
| 159 |
+
groups[key].append(row)
|
| 160 |
+
|
| 161 |
+
summary: list[dict] = []
|
| 162 |
+
for group_rows in groups.values():
|
| 163 |
+
first = group_rows[0]
|
| 164 |
+
item = {field: first[field] for field in SUMMARY_FIELDS if field in first}
|
| 165 |
+
item["n"] = len(group_rows)
|
| 166 |
+
for source, mean_key, std_key in [
|
| 167 |
+
("train_eval_loss", "mean_train_eval_loss", "std_train_eval_loss"),
|
| 168 |
+
("val_eval_loss", "mean_val_eval_loss", "std_val_eval_loss"),
|
| 169 |
+
("generalization_gap", "mean_generalization_gap", "std_generalization_gap"),
|
| 170 |
+
]:
|
| 171 |
+
values = [float(row[source]) for row in group_rows]
|
| 172 |
+
item[mean_key] = statistics.fmean(values)
|
| 173 |
+
item[std_key] = statistics.stdev(values) if len(values) > 1 else 0.0
|
| 174 |
+
summary.append(item)
|
| 175 |
+
return sorted(
|
| 176 |
+
summary,
|
| 177 |
+
key=lambda row: (
|
| 178 |
+
row["run_mode"],
|
| 179 |
+
row["model_name"],
|
| 180 |
+
row["token_limit"],
|
| 181 |
+
row["condition"],
|
| 182 |
+
row["stage"] or -1,
|
| 183 |
+
),
|
| 184 |
+
)
|
| 185 |
+
|
| 186 |
+
|
| 187 |
+
def write_csv(path: Path, rows: list[dict], fieldnames: list[str]) -> None:
|
| 188 |
+
with path.open("w", newline="", encoding="utf-8") as handle:
|
| 189 |
+
writer = csv.DictWriter(handle, fieldnames=fieldnames, extrasaction="ignore")
|
| 190 |
+
writer.writeheader()
|
| 191 |
+
writer.writerows(rows)
|
| 192 |
+
|
| 193 |
+
|
| 194 |
+
def build_model_selection(summary: list[dict], args: argparse.Namespace) -> list[dict]:
|
| 195 |
+
groups: dict[tuple, list[dict]] = defaultdict(list)
|
| 196 |
+
for row in summary:
|
| 197 |
+
if row["condition_kind"] == "static" and row["stage"] is None:
|
| 198 |
+
groups[(row["run_mode"], row["token_limit"], row["model_name"])].append(row)
|
| 199 |
+
|
| 200 |
+
selection: list[dict] = []
|
| 201 |
+
for (run_mode, token_limit, model_name), rows in groups.items():
|
| 202 |
+
curve = sorted(rows, key=lambda row: row["dropout_initial"])
|
| 203 |
+
best = min(curve, key=lambda row: row["mean_val_eval_loss"])
|
| 204 |
+
plateau = [
|
| 205 |
+
row for row in curve
|
| 206 |
+
if row["mean_val_eval_loss"] <= best["mean_val_eval_loss"] + args.plateau_delta
|
| 207 |
+
]
|
| 208 |
+
zero = next((row for row in curve if row["dropout_initial"] == 0.0), None)
|
| 209 |
+
nonzero = [row for row in curve if row["dropout_initial"] > 0.0]
|
| 210 |
+
best_nonzero = min(nonzero, key=lambda row: row["mean_val_eval_loss"]) if nonzero else None
|
| 211 |
+
max_dropout = max(curve, key=lambda row: row["dropout_initial"])
|
| 212 |
+
|
| 213 |
+
zero_loss = zero["mean_val_eval_loss"] if zero else None
|
| 214 |
+
zero_minus_best = zero_loss - best["mean_val_eval_loss"] if zero_loss is not None else None
|
| 215 |
+
zero_minus_best_nonzero = (
|
| 216 |
+
zero_loss - best_nonzero["mean_val_eval_loss"]
|
| 217 |
+
if zero_loss is not None and best_nonzero is not None
|
| 218 |
+
else None
|
| 219 |
+
)
|
| 220 |
+
max_minus_best = max_dropout["mean_val_eval_loss"] - best["mean_val_eval_loss"]
|
| 221 |
+
has_nonzero_optimum = (
|
| 222 |
+
best["dropout_initial"] > 0.0
|
| 223 |
+
and zero_minus_best is not None
|
| 224 |
+
and zero_minus_best >= args.min_nonzero_margin
|
| 225 |
+
and max_minus_best >= args.min_high_dropout_margin
|
| 226 |
+
)
|
| 227 |
+
curve_json = json.dumps(
|
| 228 |
+
[
|
| 229 |
+
{
|
| 230 |
+
"dropout": row["dropout_initial"],
|
| 231 |
+
"mean_val_loss": row["mean_val_eval_loss"],
|
| 232 |
+
"std_val_loss": row["std_val_eval_loss"],
|
| 233 |
+
"mean_train_loss": row["mean_train_eval_loss"],
|
| 234 |
+
"mean_generalization_gap": row["mean_generalization_gap"],
|
| 235 |
+
"n": row["n"],
|
| 236 |
+
}
|
| 237 |
+
for row in curve
|
| 238 |
+
],
|
| 239 |
+
sort_keys=True,
|
| 240 |
+
)
|
| 241 |
+
selection.append(
|
| 242 |
+
{
|
| 243 |
+
"run_mode": run_mode,
|
| 244 |
+
"token_limit": token_limit,
|
| 245 |
+
"model_name": model_name,
|
| 246 |
+
"n_layer": best["n_layer"],
|
| 247 |
+
"n_head": best["n_head"],
|
| 248 |
+
"n_embd": best["n_embd"],
|
| 249 |
+
"parameters": best["parameters"],
|
| 250 |
+
"n": best["n"],
|
| 251 |
+
"best_dropout": best["dropout_initial"],
|
| 252 |
+
"best_val_loss": best["mean_val_eval_loss"],
|
| 253 |
+
"best_val_std": best["std_val_eval_loss"],
|
| 254 |
+
"plateau_start_dropout": min(row["dropout_initial"] for row in plateau),
|
| 255 |
+
"plateau_end_dropout": max(row["dropout_initial"] for row in plateau),
|
| 256 |
+
"plateau_delta": args.plateau_delta,
|
| 257 |
+
"zero_dropout_val_loss": zero_loss,
|
| 258 |
+
"zero_minus_best": zero_minus_best,
|
| 259 |
+
"best_nonzero_dropout": (
|
| 260 |
+
best_nonzero["dropout_initial"] if best_nonzero else None
|
| 261 |
+
),
|
| 262 |
+
"best_nonzero_val_loss": (
|
| 263 |
+
best_nonzero["mean_val_eval_loss"] if best_nonzero else None
|
| 264 |
+
),
|
| 265 |
+
"zero_minus_best_nonzero": zero_minus_best_nonzero,
|
| 266 |
+
"max_dropout": max_dropout["dropout_initial"],
|
| 267 |
+
"max_dropout_val_loss": max_dropout["mean_val_eval_loss"],
|
| 268 |
+
"max_dropout_minus_best": max_minus_best,
|
| 269 |
+
"has_nonzero_optimum": has_nonzero_optimum,
|
| 270 |
+
"meets_target_dropout": best["dropout_initial"] >= args.target_min_dropout,
|
| 271 |
+
"curve_json": curve_json,
|
| 272 |
+
}
|
| 273 |
+
)
|
| 274 |
+
return sorted(
|
| 275 |
+
selection,
|
| 276 |
+
key=lambda row: (
|
| 277 |
+
not row["has_nonzero_optimum"],
|
| 278 |
+
not row["meets_target_dropout"],
|
| 279 |
+
row["best_val_loss"],
|
| 280 |
+
),
|
| 281 |
+
)
|
src/dropout_decay/experiments/device.py
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Derived from Andrej Karpathy's nanochat project.
|
| 3 |
+
|
| 4 |
+
MIT License
|
| 5 |
+
|
| 6 |
+
Copyright (c) 2025 Andrej Karpathy
|
| 7 |
+
|
| 8 |
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
| 9 |
+
of this software and associated documentation files (the "Software"), to deal
|
| 10 |
+
in the Software without restriction, including without limitation the rights
|
| 11 |
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
| 12 |
+
copies of the Software, and to permit persons to whom the Software is
|
| 13 |
+
furnished to do so, subject to the following conditions:
|
| 14 |
+
|
| 15 |
+
The above copyright notice and this permission notice shall be included in all
|
| 16 |
+
copies or substantial portions of the Software.
|
| 17 |
+
"""
|
| 18 |
+
|
| 19 |
+
from __future__ import annotations
|
| 20 |
+
|
| 21 |
+
import os
|
| 22 |
+
|
| 23 |
+
import torch
|
| 24 |
+
|
| 25 |
+
|
| 26 |
+
def assert_mps_only() -> torch.device:
|
| 27 |
+
if os.environ.get("PYTORCH_ENABLE_MPS_FALLBACK") == "1":
|
| 28 |
+
raise SystemExit(
|
| 29 |
+
"Refusing to run: PYTORCH_ENABLE_MPS_FALLBACK=1 could execute CPU fallbacks."
|
| 30 |
+
)
|
| 31 |
+
if not torch.backends.mps.is_built():
|
| 32 |
+
raise SystemExit("PyTorch was not built with MPS. Stopping.")
|
| 33 |
+
if not torch.backends.mps.is_available():
|
| 34 |
+
raise SystemExit("MPS is not available. Stopping before any Torch experiment work.")
|
| 35 |
+
device = torch.device("mps")
|
| 36 |
+
torch.set_default_device(device)
|
| 37 |
+
return device
|
src/dropout_decay/experiments/parsing.py
ADDED
|
@@ -0,0 +1,148 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Derived from Andrej Karpathy's nanochat project.
|
| 3 |
+
|
| 4 |
+
MIT License
|
| 5 |
+
|
| 6 |
+
Copyright (c) 2025 Andrej Karpathy
|
| 7 |
+
|
| 8 |
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
| 9 |
+
of this software and associated documentation files (the "Software"), to deal
|
| 10 |
+
in the Software without restriction, including without limitation the rights
|
| 11 |
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
| 12 |
+
copies of the Software, and to permit persons to whom the Software is
|
| 13 |
+
furnished to do so, subject to the following conditions:
|
| 14 |
+
|
| 15 |
+
The above copyright notice and this permission notice shall be included in all
|
| 16 |
+
copies or substantial portions of the Software.
|
| 17 |
+
"""
|
| 18 |
+
|
| 19 |
+
from __future__ import annotations
|
| 20 |
+
|
| 21 |
+
import argparse
|
| 22 |
+
|
| 23 |
+
from dropout_decay.specs import DropoutCondition, ModelSpec
|
| 24 |
+
|
| 25 |
+
|
| 26 |
+
DEFAULT_DROPOUT_RATES = [0.0, 0.02, 0.05, 0.08, 0.10, 0.14, 0.20, 0.30, 0.50]
|
| 27 |
+
|
| 28 |
+
|
| 29 |
+
def clean_name(value: str) -> str:
|
| 30 |
+
cleaned = "".join(
|
| 31 |
+
c if c.isalnum() or c in {"-", "_"} else "_" for c in value.strip()
|
| 32 |
+
)
|
| 33 |
+
return cleaned.strip("_") or "model"
|
| 34 |
+
|
| 35 |
+
|
| 36 |
+
def rate_label(rate: float) -> str:
|
| 37 |
+
label = f"{rate:.3f}".rstrip("0").rstrip(".")
|
| 38 |
+
return label if label else "0"
|
| 39 |
+
|
| 40 |
+
|
| 41 |
+
def parse_model_spec(raw: str) -> ModelSpec:
|
| 42 |
+
if "=" in raw:
|
| 43 |
+
name, dims = raw.split("=", 1)
|
| 44 |
+
name = clean_name(name)
|
| 45 |
+
else:
|
| 46 |
+
dims = raw
|
| 47 |
+
name = ""
|
| 48 |
+
parts = dims.lower().replace(",", "x").split("x")
|
| 49 |
+
if len(parts) != 3:
|
| 50 |
+
raise argparse.ArgumentTypeError(
|
| 51 |
+
"model specs must look like 8x8x256 or name=8x8x256"
|
| 52 |
+
)
|
| 53 |
+
try:
|
| 54 |
+
n_layer, n_head, n_embd = [int(part) for part in parts]
|
| 55 |
+
except ValueError as exc:
|
| 56 |
+
raise argparse.ArgumentTypeError("model dimensions must be integers") from exc
|
| 57 |
+
if n_layer <= 0 or n_head <= 0 or n_embd <= 0:
|
| 58 |
+
raise argparse.ArgumentTypeError("model dimensions must be positive")
|
| 59 |
+
if n_embd % n_head != 0:
|
| 60 |
+
raise argparse.ArgumentTypeError("n_embd must be divisible by n_head")
|
| 61 |
+
if (n_embd // n_head) % 2 != 0:
|
| 62 |
+
raise argparse.ArgumentTypeError("n_embd / n_head must be even for rotary attention")
|
| 63 |
+
return ModelSpec(
|
| 64 |
+
name or f"L{n_layer}_H{n_head}_D{n_embd}",
|
| 65 |
+
n_layer,
|
| 66 |
+
n_head,
|
| 67 |
+
n_embd,
|
| 68 |
+
)
|
| 69 |
+
|
| 70 |
+
|
| 71 |
+
def parse_decay_spec(raw: str) -> DropoutCondition:
|
| 72 |
+
parts = raw.split(":")
|
| 73 |
+
if len(parts) not in {3, 4, 5}:
|
| 74 |
+
raise argparse.ArgumentTypeError(
|
| 75 |
+
"decay specs must look like "
|
| 76 |
+
"name:initial:final[:cosine|smoothstep|linear[:decay_tokens]]"
|
| 77 |
+
)
|
| 78 |
+
name = clean_name(parts[0])
|
| 79 |
+
try:
|
| 80 |
+
initial = float(parts[1])
|
| 81 |
+
final = float(parts[2])
|
| 82 |
+
decay_tokens = int(parts[4]) if len(parts) == 5 else None
|
| 83 |
+
except ValueError as exc:
|
| 84 |
+
raise argparse.ArgumentTypeError(
|
| 85 |
+
"decay dropout values and decay_tokens must be numeric"
|
| 86 |
+
) from exc
|
| 87 |
+
schedule = parts[3] if len(parts) >= 4 else "cosine"
|
| 88 |
+
if schedule not in {"cosine", "smoothstep", "linear"}:
|
| 89 |
+
raise argparse.ArgumentTypeError(
|
| 90 |
+
"decay schedule must be cosine, smoothstep, or linear"
|
| 91 |
+
)
|
| 92 |
+
return DropoutCondition(
|
| 93 |
+
name=name,
|
| 94 |
+
kind="decay",
|
| 95 |
+
initial=initial,
|
| 96 |
+
final=final,
|
| 97 |
+
schedule=schedule,
|
| 98 |
+
decay_tokens=decay_tokens,
|
| 99 |
+
)
|
| 100 |
+
|
| 101 |
+
|
| 102 |
+
def parse_anchor_decay_spec(raw: str) -> DropoutCondition:
|
| 103 |
+
if ":" not in raw:
|
| 104 |
+
raise argparse.ArgumentTypeError(
|
| 105 |
+
"anchor decay specs must look like name:250000=0.60,500000=0.40"
|
| 106 |
+
)
|
| 107 |
+
name, raw_anchors = raw.split(":", 1)
|
| 108 |
+
anchors: list[tuple[int, float]] = []
|
| 109 |
+
for piece in raw_anchors.split(","):
|
| 110 |
+
if "=" not in piece:
|
| 111 |
+
raise argparse.ArgumentTypeError(
|
| 112 |
+
"anchor decay anchors must look like token_count=dropout"
|
| 113 |
+
)
|
| 114 |
+
raw_tokens, raw_dropout = piece.split("=", 1)
|
| 115 |
+
try:
|
| 116 |
+
token_count = int(raw_tokens)
|
| 117 |
+
dropout = float(raw_dropout)
|
| 118 |
+
except ValueError as exc:
|
| 119 |
+
raise argparse.ArgumentTypeError(
|
| 120 |
+
"anchor token counts must be integers and dropout values numeric"
|
| 121 |
+
) from exc
|
| 122 |
+
if token_count <= 0:
|
| 123 |
+
raise argparse.ArgumentTypeError("anchor token counts must be positive")
|
| 124 |
+
if not 0.0 <= dropout < 1.0:
|
| 125 |
+
raise argparse.ArgumentTypeError("anchor dropout must satisfy 0 <= p < 1")
|
| 126 |
+
anchors.append((token_count, dropout))
|
| 127 |
+
anchors = sorted(set(anchors))
|
| 128 |
+
if len(anchors) < 2:
|
| 129 |
+
raise argparse.ArgumentTypeError("provide at least two dropout anchors")
|
| 130 |
+
for left, right in zip(anchors, anchors[1:]):
|
| 131 |
+
if right[1] > left[1]:
|
| 132 |
+
raise argparse.ArgumentTypeError(
|
| 133 |
+
"anchor dropout values must be non-increasing as token counts grow"
|
| 134 |
+
)
|
| 135 |
+
return DropoutCondition(
|
| 136 |
+
name=clean_name(name),
|
| 137 |
+
kind="anchor_decay",
|
| 138 |
+
initial=anchors[0][1],
|
| 139 |
+
final=anchors[-1][1],
|
| 140 |
+
schedule="log_prefix_anchor",
|
| 141 |
+
anchors=tuple(anchors),
|
| 142 |
+
)
|
| 143 |
+
|
| 144 |
+
|
| 145 |
+
def default_seeds(mode: str, seeds: list[int] | None) -> list[int]:
|
| 146 |
+
if seeds:
|
| 147 |
+
return seeds
|
| 148 |
+
return [1] if mode == "screen_static" else [1, 2, 3]
|
src/dropout_decay/experiments/progress.py
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Derived from Andrej Karpathy's nanochat project.
|
| 3 |
+
|
| 4 |
+
MIT License
|
| 5 |
+
|
| 6 |
+
Copyright (c) 2025 Andrej Karpathy
|
| 7 |
+
|
| 8 |
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
| 9 |
+
of this software and associated documentation files (the "Software"), to deal
|
| 10 |
+
in the Software without restriction, including without limitation the rights
|
| 11 |
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
| 12 |
+
copies of the Software, and to permit persons to whom the Software is
|
| 13 |
+
furnished to do so, subject to the following conditions:
|
| 14 |
+
|
| 15 |
+
The above copyright notice and this permission notice shall be included in all
|
| 16 |
+
copies or substantial portions of the Software.
|
| 17 |
+
"""
|
| 18 |
+
|
| 19 |
+
from __future__ import annotations
|
| 20 |
+
|
| 21 |
+
import time
|
| 22 |
+
|
| 23 |
+
|
| 24 |
+
def format_duration(seconds: float) -> str:
|
| 25 |
+
seconds = max(0, int(seconds))
|
| 26 |
+
hours, remainder = divmod(seconds, 3600)
|
| 27 |
+
minutes, secs = divmod(remainder, 60)
|
| 28 |
+
if hours:
|
| 29 |
+
return f"{hours}h{minutes:02d}m"
|
| 30 |
+
if minutes:
|
| 31 |
+
return f"{minutes}m{secs:02d}s"
|
| 32 |
+
return f"{secs}s"
|
| 33 |
+
|
| 34 |
+
|
| 35 |
+
class ProgressMeter:
|
| 36 |
+
def __init__(self, total: int):
|
| 37 |
+
self.total = max(0, total)
|
| 38 |
+
self.done = 0
|
| 39 |
+
self.started_at = time.time()
|
| 40 |
+
|
| 41 |
+
def mark_done(self, row: dict) -> None:
|
| 42 |
+
self.done += 1
|
| 43 |
+
elapsed = time.time() - self.started_at
|
| 44 |
+
mean = elapsed / self.done if self.done else 0.0
|
| 45 |
+
remaining = max(0, self.total - self.done)
|
| 46 |
+
eta = remaining * mean
|
| 47 |
+
stage = row.get("stage")
|
| 48 |
+
stage_text = f"stage={stage} " if stage is not None else ""
|
| 49 |
+
print(
|
| 50 |
+
"progress "
|
| 51 |
+
f"{self.done}/{self.total} "
|
| 52 |
+
f"eta={format_duration(eta)} "
|
| 53 |
+
f"mode={row['run_mode']} "
|
| 54 |
+
f"model={row['model_name']} "
|
| 55 |
+
f"params={int(row['parameters']):,} "
|
| 56 |
+
f"prefix={int(row['token_limit']):,} "
|
| 57 |
+
f"{stage_text}"
|
| 58 |
+
f"seed={row['seed']} "
|
| 59 |
+
f"condition={row['condition']} "
|
| 60 |
+
f"dropout={float(row['dropout_active_final']):.3f} "
|
| 61 |
+
f"val={row['val_eval_loss']:.4f} "
|
| 62 |
+
f"train={row['train_eval_loss']:.4f} "
|
| 63 |
+
f"gap={row['generalization_gap']:.4f} "
|
| 64 |
+
f"elapsed={format_duration(float(row['elapsed_sec']))}",
|
| 65 |
+
flush=True,
|
| 66 |
+
)
|
src/dropout_decay/experiments/reports.py
ADDED
|
@@ -0,0 +1,448 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Derived from Andrej Karpathy's nanochat project.
|
| 3 |
+
|
| 4 |
+
MIT License
|
| 5 |
+
|
| 6 |
+
Copyright (c) 2025 Andrej Karpathy
|
| 7 |
+
|
| 8 |
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
| 9 |
+
of this software and associated documentation files (the "Software"), to deal
|
| 10 |
+
in the Software without restriction, including without limitation the rights
|
| 11 |
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
| 12 |
+
copies of the Software, and to permit persons to whom the Software is
|
| 13 |
+
furnished to do so, subject to the following conditions:
|
| 14 |
+
|
| 15 |
+
The above copyright notice and this permission notice shall be included in all
|
| 16 |
+
copies or substantial portions of the Software.
|
| 17 |
+
"""
|
| 18 |
+
|
| 19 |
+
from __future__ import annotations
|
| 20 |
+
|
| 21 |
+
from collections import defaultdict
|
| 22 |
+
from pathlib import Path
|
| 23 |
+
import statistics
|
| 24 |
+
|
| 25 |
+
|
| 26 |
+
def write_screen_markdown_summary(output_dir: Path, rows: list[dict]) -> None:
|
| 27 |
+
if not rows:
|
| 28 |
+
return
|
| 29 |
+
static_rows = [
|
| 30 |
+
row
|
| 31 |
+
for row in rows
|
| 32 |
+
if row["run_mode"] == "screen_static" and row["condition_kind"] == "static"
|
| 33 |
+
]
|
| 34 |
+
if not static_rows:
|
| 35 |
+
return
|
| 36 |
+
|
| 37 |
+
by_model_prefix_rate: dict[tuple[str, int, float], list[dict]] = defaultdict(list)
|
| 38 |
+
for row in rows:
|
| 39 |
+
if row["run_mode"] == "screen_static" and row["condition_kind"] == "static":
|
| 40 |
+
by_model_prefix_rate[
|
| 41 |
+
(
|
| 42 |
+
row["model_name"],
|
| 43 |
+
int(row["token_limit"]),
|
| 44 |
+
float(row["dropout_initial"]),
|
| 45 |
+
)
|
| 46 |
+
].append(row)
|
| 47 |
+
|
| 48 |
+
aggregates: list[dict] = []
|
| 49 |
+
for (model_name, prefix, dropout), group_rows in by_model_prefix_rate.items():
|
| 50 |
+
first = group_rows[0]
|
| 51 |
+
val_losses = [float(row["val_eval_loss"]) for row in group_rows]
|
| 52 |
+
train_losses = [float(row["train_eval_loss"]) for row in group_rows]
|
| 53 |
+
gaps = [float(row["generalization_gap"]) for row in group_rows]
|
| 54 |
+
aggregates.append(
|
| 55 |
+
{
|
| 56 |
+
"model_name": model_name,
|
| 57 |
+
"token_limit": prefix,
|
| 58 |
+
"dropout_initial": dropout,
|
| 59 |
+
"n": len(group_rows),
|
| 60 |
+
"mean_val_eval_loss": statistics.fmean(val_losses),
|
| 61 |
+
"std_val_eval_loss": statistics.stdev(val_losses)
|
| 62 |
+
if len(val_losses) > 1
|
| 63 |
+
else 0.0,
|
| 64 |
+
"mean_train_eval_loss": statistics.fmean(train_losses),
|
| 65 |
+
"std_train_eval_loss": statistics.stdev(train_losses)
|
| 66 |
+
if len(train_losses) > 1
|
| 67 |
+
else 0.0,
|
| 68 |
+
"mean_generalization_gap": statistics.fmean(gaps),
|
| 69 |
+
"std_generalization_gap": statistics.stdev(gaps)
|
| 70 |
+
if len(gaps) > 1
|
| 71 |
+
else 0.0,
|
| 72 |
+
"parameters": int(first["parameters"]),
|
| 73 |
+
"n_layer": int(first["n_layer"]),
|
| 74 |
+
"n_head": int(first["n_head"]),
|
| 75 |
+
"n_embd": int(first["n_embd"]),
|
| 76 |
+
"block_size": int(first["model_config"]["block_size"]),
|
| 77 |
+
"vocab_size": int(first["model_config"]["vocab_size"]),
|
| 78 |
+
"tokens_seen": int(first["tokens_seen"]),
|
| 79 |
+
"seeds": sorted({int(row["seed"]) for row in group_rows}),
|
| 80 |
+
}
|
| 81 |
+
)
|
| 82 |
+
|
| 83 |
+
by_model: dict[str, list[dict]] = defaultdict(list)
|
| 84 |
+
for row in aggregates:
|
| 85 |
+
by_model[row["model_name"]].append(row)
|
| 86 |
+
|
| 87 |
+
model_rows = []
|
| 88 |
+
for model_name, model_group in by_model.items():
|
| 89 |
+
first = model_group[0]
|
| 90 |
+
seeds = sorted({seed for row in model_group for seed in row["seeds"]})
|
| 91 |
+
model_rows.append(
|
| 92 |
+
{
|
| 93 |
+
"model_name": model_name,
|
| 94 |
+
"parameters": first["parameters"],
|
| 95 |
+
"n_layer": first["n_layer"],
|
| 96 |
+
"n_head": first["n_head"],
|
| 97 |
+
"n_embd": first["n_embd"],
|
| 98 |
+
"block_size": first["block_size"],
|
| 99 |
+
"vocab_size": first["vocab_size"],
|
| 100 |
+
"seeds": seeds,
|
| 101 |
+
}
|
| 102 |
+
)
|
| 103 |
+
|
| 104 |
+
lines = [
|
| 105 |
+
"# Static Dropout Screen Summary",
|
| 106 |
+
"",
|
| 107 |
+
f"Run directory: `{output_dir}`",
|
| 108 |
+
"",
|
| 109 |
+
"## Models",
|
| 110 |
+
"",
|
| 111 |
+
"| Model | Params | Layers | Heads | Embedding | Block | Vocab | Seeds |",
|
| 112 |
+
"|---|---:|---:|---:|---:|---:|---:|---|",
|
| 113 |
+
]
|
| 114 |
+
for model in sorted(model_rows, key=lambda item: item["parameters"]):
|
| 115 |
+
lines.append(
|
| 116 |
+
"| "
|
| 117 |
+
f"`{model['model_name']}` | {model['parameters']:,} | "
|
| 118 |
+
f"{model['n_layer']} | {model['n_head']} | {model['n_embd']} | "
|
| 119 |
+
f"{model['block_size']} | {model['vocab_size']} | "
|
| 120 |
+
f"{', '.join(str(seed) for seed in model['seeds'])} |"
|
| 121 |
+
)
|
| 122 |
+
lines.extend(
|
| 123 |
+
[
|
| 124 |
+
"",
|
| 125 |
+
"## Best Dropout By Model And Prefix",
|
| 126 |
+
"",
|
| 127 |
+
"| Model | Prefix tokens | Effective epochs | Best dropout | Mean val loss | Val std | Mean train loss | Mean gap | Plateau/bracket note |",
|
| 128 |
+
"|---|---:|---:|---:|---:|---:|---:|---:|---|",
|
| 129 |
+
]
|
| 130 |
+
)
|
| 131 |
+
for model_name, model_group in sorted(by_model.items()):
|
| 132 |
+
by_prefix: dict[int, list[dict]] = defaultdict(list)
|
| 133 |
+
for row in model_group:
|
| 134 |
+
by_prefix[int(row["token_limit"])].append(row)
|
| 135 |
+
for prefix, prefix_rows in sorted(by_prefix.items()):
|
| 136 |
+
best = min(prefix_rows, key=lambda row: row["mean_val_eval_loss"])
|
| 137 |
+
rates = [float(row["dropout_initial"]) for row in prefix_rows]
|
| 138 |
+
eff_epochs = float(best["tokens_seen"]) / prefix
|
| 139 |
+
if best["dropout_initial"] == max(rates):
|
| 140 |
+
note = "not bracketed; best at top of tested grid"
|
| 141 |
+
elif best["dropout_initial"] == min(rates):
|
| 142 |
+
note = "not bracketed; best at bottom of tested grid"
|
| 143 |
+
else:
|
| 144 |
+
note = "bracketed by tested grid"
|
| 145 |
+
lines.append(
|
| 146 |
+
"| "
|
| 147 |
+
f"`{model_name}` | {prefix:,} | {eff_epochs:.2f} | "
|
| 148 |
+
f"{best['dropout_initial']:.2f} | "
|
| 149 |
+
f"{best['mean_val_eval_loss']:.4f} | "
|
| 150 |
+
f"{best['std_val_eval_loss']:.4f} | "
|
| 151 |
+
f"{best['mean_train_eval_loss']:.4f} | "
|
| 152 |
+
f"{best['mean_generalization_gap']:.4f} | {note} |"
|
| 153 |
+
)
|
| 154 |
+
|
| 155 |
+
for model_name, model_group in sorted(by_model.items()):
|
| 156 |
+
by_prefix = defaultdict(list)
|
| 157 |
+
for row in model_group:
|
| 158 |
+
by_prefix[int(row["token_limit"])].append(row)
|
| 159 |
+
lines.extend(
|
| 160 |
+
[
|
| 161 |
+
"",
|
| 162 |
+
f"## Model `{model_name}`",
|
| 163 |
+
]
|
| 164 |
+
)
|
| 165 |
+
for prefix, prefix_rows in sorted(by_prefix.items()):
|
| 166 |
+
eff_epochs = float(prefix_rows[0]["tokens_seen"]) / prefix
|
| 167 |
+
lines.extend(
|
| 168 |
+
[
|
| 169 |
+
"",
|
| 170 |
+
f"### Prefix {prefix:,} Tokens ({eff_epochs:.2f} Effective Epochs)",
|
| 171 |
+
"",
|
| 172 |
+
"| Dropout | N | Mean val loss | Val std | Mean train loss | Train std | Mean gap | Gap std | Sampled tokens | Params |",
|
| 173 |
+
"|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:|",
|
| 174 |
+
]
|
| 175 |
+
)
|
| 176 |
+
for row in sorted(prefix_rows, key=lambda item: item["dropout_initial"]):
|
| 177 |
+
lines.append(
|
| 178 |
+
"| "
|
| 179 |
+
f"{row['dropout_initial']:.2f} | {row['n']} | "
|
| 180 |
+
f"{row['mean_val_eval_loss']:.4f} | "
|
| 181 |
+
f"{row['std_val_eval_loss']:.4f} | "
|
| 182 |
+
f"{row['mean_train_eval_loss']:.4f} | "
|
| 183 |
+
f"{row['std_train_eval_loss']:.4f} | "
|
| 184 |
+
f"{row['mean_generalization_gap']:.4f} | "
|
| 185 |
+
f"{row['std_generalization_gap']:.4f} | "
|
| 186 |
+
f"{int(row['tokens_seen']):,} | {int(row['parameters']):,} |"
|
| 187 |
+
)
|
| 188 |
+
|
| 189 |
+
output = "\n".join(lines) + "\n"
|
| 190 |
+
(output_dir / "RESULT_SUMMARY.md").write_text(output, encoding="utf-8")
|
| 191 |
+
|
| 192 |
+
|
| 193 |
+
def svg_escape(value: object) -> str:
|
| 194 |
+
return (
|
| 195 |
+
str(value)
|
| 196 |
+
.replace("&", "&")
|
| 197 |
+
.replace("<", "<")
|
| 198 |
+
.replace(">", ">")
|
| 199 |
+
.replace('"', """)
|
| 200 |
+
)
|
| 201 |
+
|
| 202 |
+
|
| 203 |
+
def write_dropout_curve_svg(output_dir: Path, summary: list[dict]) -> None:
|
| 204 |
+
rows = [
|
| 205 |
+
row
|
| 206 |
+
for row in summary
|
| 207 |
+
if row["run_mode"] == "screen_static" and row["condition_kind"] == "static"
|
| 208 |
+
]
|
| 209 |
+
if not rows:
|
| 210 |
+
return
|
| 211 |
+
|
| 212 |
+
grouped: dict[tuple[str, int], list[dict]] = defaultdict(list)
|
| 213 |
+
model_params: dict[str, int] = {}
|
| 214 |
+
for row in rows:
|
| 215 |
+
model_name = row["model_name"]
|
| 216 |
+
grouped[(model_name, int(row["token_limit"]))].append(row)
|
| 217 |
+
model_params[model_name] = int(row["parameters"])
|
| 218 |
+
|
| 219 |
+
models = sorted(model_params, key=lambda name: model_params[name])
|
| 220 |
+
prefixes = sorted({int(row["token_limit"]) for row in rows})
|
| 221 |
+
panel_w, panel_h = 230, 170
|
| 222 |
+
margin_l, margin_t = 58, 34
|
| 223 |
+
plot_w, plot_h = 142, 94
|
| 224 |
+
gap_x, gap_y = 18, 38
|
| 225 |
+
width = margin_l + len(prefixes) * panel_w + gap_x
|
| 226 |
+
height = 70 + len(models) * (panel_h + gap_y)
|
| 227 |
+
colors = ["#1f77b4", "#d62728", "#2ca02c", "#9467bd", "#ff7f0e"]
|
| 228 |
+
|
| 229 |
+
parts = [
|
| 230 |
+
f'<svg xmlns="http://www.w3.org/2000/svg" width="{width}" height="{height}" viewBox="0 0 {width} {height}">',
|
| 231 |
+
"<style>",
|
| 232 |
+
"text{font-family:Arial,Helvetica,sans-serif;fill:#111827}",
|
| 233 |
+
".small{font-size:10px}.label{font-size:11px}.title{font-size:15px;font-weight:700}",
|
| 234 |
+
".axis{stroke:#374151;stroke-width:1}.grid{stroke:#e5e7eb;stroke-width:1}.line{fill:none;stroke-width:2}",
|
| 235 |
+
"</style>",
|
| 236 |
+
'<rect width="100%" height="100%" fill="#ffffff"/>',
|
| 237 |
+
'<text x="24" y="28" class="title">Static dropout law: validation loss vs dropout</text>',
|
| 238 |
+
'<text x="24" y="48" class="label">Each panel uses its own y-scale. Points are one-seed means unless N > 1.</text>',
|
| 239 |
+
]
|
| 240 |
+
|
| 241 |
+
for col, prefix in enumerate(prefixes):
|
| 242 |
+
x = margin_l + col * panel_w + plot_w / 2
|
| 243 |
+
parts.append(
|
| 244 |
+
f'<text x="{x:.1f}" y="70" text-anchor="middle" class="label">{prefix:,} prefix tokens</text>'
|
| 245 |
+
)
|
| 246 |
+
|
| 247 |
+
for row_idx, model_name in enumerate(models):
|
| 248 |
+
row_y = 92 + row_idx * (panel_h + gap_y)
|
| 249 |
+
parts.append(
|
| 250 |
+
f'<text x="24" y="{row_y + 48}" class="label" transform="rotate(-90 24 {row_y + 48})">'
|
| 251 |
+
f'{svg_escape(model_name)} ({model_params[model_name] / 1_000_000:.1f}M)</text>'
|
| 252 |
+
)
|
| 253 |
+
for col, prefix in enumerate(prefixes):
|
| 254 |
+
panel_x = margin_l + col * panel_w
|
| 255 |
+
panel_y = row_y
|
| 256 |
+
curve = sorted(
|
| 257 |
+
grouped.get((model_name, prefix), []),
|
| 258 |
+
key=lambda item: float(item["dropout_initial"]),
|
| 259 |
+
)
|
| 260 |
+
if not curve:
|
| 261 |
+
continue
|
| 262 |
+
losses = [float(item["mean_val_eval_loss"]) for item in curve]
|
| 263 |
+
min_loss, max_loss = min(losses), max(losses)
|
| 264 |
+
pad = max(0.02, (max_loss - min_loss) * 0.08)
|
| 265 |
+
y_min, y_max = min_loss - pad, max_loss + pad
|
| 266 |
+
best = min(curve, key=lambda item: float(item["mean_val_eval_loss"]))
|
| 267 |
+
|
| 268 |
+
def px(dropout: float) -> float:
|
| 269 |
+
return panel_x + (dropout / 0.9) * plot_w
|
| 270 |
+
|
| 271 |
+
def py(loss: float) -> float:
|
| 272 |
+
scale = (loss - y_min) / (y_max - y_min)
|
| 273 |
+
return panel_y + plot_h - scale * plot_h
|
| 274 |
+
|
| 275 |
+
parts.extend(
|
| 276 |
+
[
|
| 277 |
+
f'<line x1="{panel_x:.1f}" y1="{panel_y:.1f}" x2="{panel_x:.1f}" y2="{panel_y + plot_h:.1f}" class="axis"/>',
|
| 278 |
+
f'<line x1="{panel_x:.1f}" y1="{panel_y + plot_h:.1f}" x2="{panel_x + plot_w:.1f}" y2="{panel_y + plot_h:.1f}" class="axis"/>',
|
| 279 |
+
f'<line x1="{panel_x:.1f}" y1="{panel_y:.1f}" x2="{panel_x + plot_w:.1f}" y2="{panel_y:.1f}" class="grid"/>',
|
| 280 |
+
f'<text x="{panel_x:.1f}" y="{panel_y - 6:.1f}" class="small">{y_max:.2f}</text>',
|
| 281 |
+
f'<text x="{panel_x:.1f}" y="{panel_y + plot_h + 13:.1f}" class="small">{y_min:.2f}</text>',
|
| 282 |
+
f'<text x="{panel_x:.1f}" y="{panel_y + plot_h + 28:.1f}" class="small">0</text>',
|
| 283 |
+
f'<text x="{panel_x + plot_w:.1f}" y="{panel_y + plot_h + 28:.1f}" text-anchor="end" class="small">0.9</text>',
|
| 284 |
+
]
|
| 285 |
+
)
|
| 286 |
+
points = " ".join(
|
| 287 |
+
f"{px(float(item['dropout_initial'])):.1f},{py(float(item['mean_val_eval_loss'])):.1f}"
|
| 288 |
+
for item in curve
|
| 289 |
+
)
|
| 290 |
+
color = colors[row_idx % len(colors)]
|
| 291 |
+
parts.append(f'<polyline points="{points}" class="line" stroke="{color}"/>')
|
| 292 |
+
for item in curve:
|
| 293 |
+
dropout = float(item["dropout_initial"])
|
| 294 |
+
loss = float(item["mean_val_eval_loss"])
|
| 295 |
+
radius = 4 if item is best else 2.7
|
| 296 |
+
fill = "#111827" if item is best else "#ffffff"
|
| 297 |
+
parts.append(
|
| 298 |
+
f'<circle cx="{px(dropout):.1f}" cy="{py(loss):.1f}" r="{radius}" fill="{fill}" stroke="{color}" stroke-width="1.5"/>'
|
| 299 |
+
)
|
| 300 |
+
parts.append(
|
| 301 |
+
f'<text x="{panel_x + plot_w + 8:.1f}" y="{panel_y + 14:.1f}" class="small">'
|
| 302 |
+
f'best p={float(best["dropout_initial"]):.2f}</text>'
|
| 303 |
+
)
|
| 304 |
+
parts.append(
|
| 305 |
+
f'<text x="{panel_x + plot_w + 8:.1f}" y="{panel_y + 28:.1f}" class="small">'
|
| 306 |
+
f'loss={float(best["mean_val_eval_loss"]):.3f}</text>'
|
| 307 |
+
)
|
| 308 |
+
|
| 309 |
+
parts.append("</svg>")
|
| 310 |
+
(output_dir / "dropout_curves.svg").write_text("\n".join(parts), encoding="utf-8")
|
| 311 |
+
|
| 312 |
+
|
| 313 |
+
def write_stream_markdown_summary(output_dir: Path, rows: list[dict]) -> None:
|
| 314 |
+
stream_rows = [row for row in rows if row["run_mode"] == "locked_stream"]
|
| 315 |
+
if not stream_rows:
|
| 316 |
+
return
|
| 317 |
+
|
| 318 |
+
by_condition_stage: dict[tuple[str, int], list[dict]] = defaultdict(list)
|
| 319 |
+
by_condition: dict[str, list[dict]] = defaultdict(list)
|
| 320 |
+
for row in stream_rows:
|
| 321 |
+
condition = row["condition"]
|
| 322 |
+
by_condition_stage[(condition, int(row["stage"]))].append(row)
|
| 323 |
+
by_condition[condition].append(row)
|
| 324 |
+
|
| 325 |
+
first = stream_rows[0]
|
| 326 |
+
seeds = sorted({int(row["seed"]) for row in stream_rows})
|
| 327 |
+
conditions = sorted(
|
| 328 |
+
by_condition,
|
| 329 |
+
key=lambda name: (
|
| 330 |
+
by_condition[name][0]["condition_kind"] != "anchor_decay",
|
| 331 |
+
by_condition[name][0]["dropout_initial"],
|
| 332 |
+
name,
|
| 333 |
+
),
|
| 334 |
+
)
|
| 335 |
+
stages = sorted({int(row["stage"]) for row in stream_rows})
|
| 336 |
+
|
| 337 |
+
lines = [
|
| 338 |
+
"# Locked Streaming Dropout Summary",
|
| 339 |
+
"",
|
| 340 |
+
f"Run directory: `{output_dir}`",
|
| 341 |
+
"",
|
| 342 |
+
(
|
| 343 |
+
f"Model: `{first['model_name']}` causal Transformer, "
|
| 344 |
+
f"{int(first['parameters']):,} parameters, {first['n_layer']} layers, "
|
| 345 |
+
f"{first['n_head']} heads, {first['n_embd']} embedding dim."
|
| 346 |
+
),
|
| 347 |
+
(
|
| 348 |
+
f"Training per stage: {first['steps']:,} steps. "
|
| 349 |
+
"Sampled tokens are cumulative in each stage row. "
|
| 350 |
+
f"Seeds present: {', '.join(str(seed) for seed in seeds)}."
|
| 351 |
+
),
|
| 352 |
+
"",
|
| 353 |
+
"## Condition Ranking",
|
| 354 |
+
"",
|
| 355 |
+
"| Condition | Kind | Final dropout | Mean trajectory val loss | Final val loss | Final gap | Dropout path |",
|
| 356 |
+
"|---|---|---:|---:|---:|---:|---|",
|
| 357 |
+
]
|
| 358 |
+
|
| 359 |
+
ranking = []
|
| 360 |
+
for condition in conditions:
|
| 361 |
+
stage_items = []
|
| 362 |
+
for stage in stages:
|
| 363 |
+
group = by_condition_stage.get((condition, stage), [])
|
| 364 |
+
if not group:
|
| 365 |
+
continue
|
| 366 |
+
stage_items.append(
|
| 367 |
+
{
|
| 368 |
+
"stage": stage,
|
| 369 |
+
"token_limit": int(group[0]["token_limit"]),
|
| 370 |
+
"mean_val": statistics.fmean(
|
| 371 |
+
float(row["val_eval_loss"]) for row in group
|
| 372 |
+
),
|
| 373 |
+
"mean_gap": statistics.fmean(
|
| 374 |
+
float(row["generalization_gap"]) for row in group
|
| 375 |
+
),
|
| 376 |
+
"mean_dropout": statistics.fmean(
|
| 377 |
+
float(row["dropout_active_final"]) for row in group
|
| 378 |
+
),
|
| 379 |
+
"kind": group[0]["condition_kind"],
|
| 380 |
+
}
|
| 381 |
+
)
|
| 382 |
+
if not stage_items:
|
| 383 |
+
continue
|
| 384 |
+
final = max(stage_items, key=lambda item: item["stage"])
|
| 385 |
+
ranking.append(
|
| 386 |
+
{
|
| 387 |
+
"condition": condition,
|
| 388 |
+
"kind": stage_items[0]["kind"],
|
| 389 |
+
"trajectory_val": statistics.fmean(item["mean_val"] for item in stage_items),
|
| 390 |
+
"final_val": final["mean_val"],
|
| 391 |
+
"final_gap": final["mean_gap"],
|
| 392 |
+
"final_dropout": final["mean_dropout"],
|
| 393 |
+
"dropout_path": " -> ".join(
|
| 394 |
+
f"{item['mean_dropout']:.2f}" for item in stage_items
|
| 395 |
+
),
|
| 396 |
+
}
|
| 397 |
+
)
|
| 398 |
+
|
| 399 |
+
for item in sorted(ranking, key=lambda row: row["trajectory_val"]):
|
| 400 |
+
lines.append(
|
| 401 |
+
"| "
|
| 402 |
+
f"`{item['condition']}` | {item['kind']} | "
|
| 403 |
+
f"{item['final_dropout']:.2f} | {item['trajectory_val']:.4f} | "
|
| 404 |
+
f"{item['final_val']:.4f} | {item['final_gap']:.4f} | "
|
| 405 |
+
f"{item['dropout_path']} |"
|
| 406 |
+
)
|
| 407 |
+
|
| 408 |
+
lines.extend(["", "## Stage Trajectory", ""])
|
| 409 |
+
for stage in stages:
|
| 410 |
+
stage_groups = {
|
| 411 |
+
condition: by_condition_stage[(condition, stage)]
|
| 412 |
+
for condition in conditions
|
| 413 |
+
if (condition, stage) in by_condition_stage
|
| 414 |
+
}
|
| 415 |
+
if not stage_groups:
|
| 416 |
+
continue
|
| 417 |
+
prefix = int(next(iter(stage_groups.values()))[0]["token_limit"])
|
| 418 |
+
lines.extend(
|
| 419 |
+
[
|
| 420 |
+
f"### Stage {stage}: {prefix:,} Prefix Tokens",
|
| 421 |
+
"",
|
| 422 |
+
"| Condition | Dropout | Mean val loss | Mean train loss | Mean gap | N |",
|
| 423 |
+
"|---|---:|---:|---:|---:|---:|",
|
| 424 |
+
]
|
| 425 |
+
)
|
| 426 |
+
for condition, group in sorted(
|
| 427 |
+
stage_groups.items(),
|
| 428 |
+
key=lambda item: statistics.fmean(
|
| 429 |
+
float(row["val_eval_loss"]) for row in item[1]
|
| 430 |
+
),
|
| 431 |
+
):
|
| 432 |
+
val = statistics.fmean(float(row["val_eval_loss"]) for row in group)
|
| 433 |
+
train = statistics.fmean(float(row["train_eval_loss"]) for row in group)
|
| 434 |
+
gap = statistics.fmean(float(row["generalization_gap"]) for row in group)
|
| 435 |
+
dropout = statistics.fmean(
|
| 436 |
+
float(row["dropout_active_final"]) for row in group
|
| 437 |
+
)
|
| 438 |
+
lines.append(
|
| 439 |
+
"| "
|
| 440 |
+
f"`{condition}` | {dropout:.2f} | {val:.4f} | "
|
| 441 |
+
f"{train:.4f} | {gap:.4f} | {len(group)} |"
|
| 442 |
+
)
|
| 443 |
+
lines.append("")
|
| 444 |
+
|
| 445 |
+
(output_dir / "RESULT_SUMMARY.md").write_text(
|
| 446 |
+
"\n".join(lines).rstrip() + "\n",
|
| 447 |
+
encoding="utf-8",
|
| 448 |
+
)
|
src/dropout_decay/experiments/runner.py
ADDED
|
@@ -0,0 +1,555 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Derived from Andrej Karpathy's nanochat project.
|
| 3 |
+
|
| 4 |
+
MIT License
|
| 5 |
+
|
| 6 |
+
Copyright (c) 2025 Andrej Karpathy
|
| 7 |
+
|
| 8 |
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
| 9 |
+
of this software and associated documentation files (the "Software"), to deal
|
| 10 |
+
in the Software without restriction, including without limitation the rights
|
| 11 |
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
| 12 |
+
copies of the Software, and to permit persons to whom the Software is
|
| 13 |
+
furnished to do so, subject to the following conditions:
|
| 14 |
+
|
| 15 |
+
The above copyright notice and this permission notice shall be included in all
|
| 16 |
+
copies or substantial portions of the Software.
|
| 17 |
+
"""
|
| 18 |
+
|
| 19 |
+
from __future__ import annotations
|
| 20 |
+
|
| 21 |
+
import argparse
|
| 22 |
+
from datetime import datetime
|
| 23 |
+
import json
|
| 24 |
+
from pathlib import Path
|
| 25 |
+
import statistics
|
| 26 |
+
import sys
|
| 27 |
+
|
| 28 |
+
import numpy as np
|
| 29 |
+
import torch
|
| 30 |
+
|
| 31 |
+
from dropout_decay.datasets import (
|
| 32 |
+
encode_corpus,
|
| 33 |
+
load_cached_splits,
|
| 34 |
+
resolve_paths,
|
| 35 |
+
train_or_load_tokenizer,
|
| 36 |
+
)
|
| 37 |
+
from dropout_decay.experiments.artifacts import (
|
| 38 |
+
SELECTION_FIELDS,
|
| 39 |
+
SUMMARY_FIELDS,
|
| 40 |
+
build_model_selection,
|
| 41 |
+
load_metrics,
|
| 42 |
+
metric_key,
|
| 43 |
+
planned_metric_key,
|
| 44 |
+
summarize,
|
| 45 |
+
write_csv,
|
| 46 |
+
write_jsonl_row,
|
| 47 |
+
)
|
| 48 |
+
from dropout_decay.experiments.device import assert_mps_only
|
| 49 |
+
from dropout_decay.experiments.parsing import (
|
| 50 |
+
DEFAULT_DROPOUT_RATES,
|
| 51 |
+
default_seeds,
|
| 52 |
+
parse_anchor_decay_spec,
|
| 53 |
+
parse_decay_spec,
|
| 54 |
+
parse_model_spec,
|
| 55 |
+
rate_label,
|
| 56 |
+
)
|
| 57 |
+
from dropout_decay.experiments.progress import ProgressMeter
|
| 58 |
+
from dropout_decay.experiments.reports import (
|
| 59 |
+
write_dropout_curve_svg,
|
| 60 |
+
write_screen_markdown_summary,
|
| 61 |
+
write_stream_markdown_summary,
|
| 62 |
+
)
|
| 63 |
+
from dropout_decay.experiments.training import train_segment
|
| 64 |
+
from dropout_decay.license import NANOCHAT_ATTRIBUTION
|
| 65 |
+
from dropout_decay.specs import DropoutCondition, ModelSpec
|
| 66 |
+
|
| 67 |
+
|
| 68 |
+
def static_conditions(dropout_rates: list[float]) -> list[DropoutCondition]:
|
| 69 |
+
return [
|
| 70 |
+
DropoutCondition(
|
| 71 |
+
name=f"static_dropout_{rate_label(rate)}",
|
| 72 |
+
kind="static",
|
| 73 |
+
initial=rate,
|
| 74 |
+
final=rate,
|
| 75 |
+
)
|
| 76 |
+
for rate in dropout_rates
|
| 77 |
+
]
|
| 78 |
+
|
| 79 |
+
|
| 80 |
+
def run_fixed_static_sweep(
|
| 81 |
+
*,
|
| 82 |
+
args: argparse.Namespace,
|
| 83 |
+
model_specs: list[ModelSpec],
|
| 84 |
+
seeds: list[int],
|
| 85 |
+
train_tokens: np.ndarray,
|
| 86 |
+
val_tokens: np.ndarray,
|
| 87 |
+
tokenizer_vocab_size: int,
|
| 88 |
+
token_limits: list[int],
|
| 89 |
+
device: torch.device,
|
| 90 |
+
metrics_file,
|
| 91 |
+
trace_file,
|
| 92 |
+
completed_keys: set[tuple] | None = None,
|
| 93 |
+
) -> list[dict]:
|
| 94 |
+
rows: list[dict] = []
|
| 95 |
+
completed_keys = completed_keys or set()
|
| 96 |
+
conditions = static_conditions(sorted(set(args.dropout_rates)))
|
| 97 |
+
planned = 0
|
| 98 |
+
for token_limit in token_limits:
|
| 99 |
+
for model_spec in model_specs:
|
| 100 |
+
for condition in conditions:
|
| 101 |
+
for seed in seeds:
|
| 102 |
+
key = planned_metric_key(
|
| 103 |
+
mode=args.mode,
|
| 104 |
+
condition=condition,
|
| 105 |
+
model_spec=model_spec,
|
| 106 |
+
seed=seed,
|
| 107 |
+
token_limit=token_limit,
|
| 108 |
+
)
|
| 109 |
+
if key not in completed_keys:
|
| 110 |
+
planned += 1
|
| 111 |
+
progress = ProgressMeter(planned)
|
| 112 |
+
for token_limit in token_limits:
|
| 113 |
+
for model_spec in model_specs:
|
| 114 |
+
best_val_loss = float("inf")
|
| 115 |
+
worse_streak = 0
|
| 116 |
+
for condition in conditions:
|
| 117 |
+
condition_rows: list[dict] = []
|
| 118 |
+
for seed in seeds:
|
| 119 |
+
key = planned_metric_key(
|
| 120 |
+
mode=args.mode,
|
| 121 |
+
condition=condition,
|
| 122 |
+
model_spec=model_spec,
|
| 123 |
+
seed=seed,
|
| 124 |
+
token_limit=token_limit,
|
| 125 |
+
)
|
| 126 |
+
if key in completed_keys:
|
| 127 |
+
write_jsonl_row(
|
| 128 |
+
trace_file,
|
| 129 |
+
{
|
| 130 |
+
"event": "skipped_completed_condition",
|
| 131 |
+
"run_mode": args.mode,
|
| 132 |
+
"condition": condition.name,
|
| 133 |
+
"model_name": model_spec.name,
|
| 134 |
+
"seed": seed,
|
| 135 |
+
"stage": None,
|
| 136 |
+
"token_limit": int(token_limit),
|
| 137 |
+
},
|
| 138 |
+
)
|
| 139 |
+
continue
|
| 140 |
+
config = model_spec.config(
|
| 141 |
+
tokenizer_vocab_size,
|
| 142 |
+
args.block_size,
|
| 143 |
+
condition.initial,
|
| 144 |
+
)
|
| 145 |
+
model, optimizer, _, row = train_segment(
|
| 146 |
+
run_mode=args.mode,
|
| 147 |
+
condition=condition,
|
| 148 |
+
model_spec=model_spec,
|
| 149 |
+
config=config,
|
| 150 |
+
train_tokens=train_tokens,
|
| 151 |
+
val_tokens=val_tokens,
|
| 152 |
+
token_limit=token_limit,
|
| 153 |
+
steps=args.steps,
|
| 154 |
+
seed=seed,
|
| 155 |
+
args=args,
|
| 156 |
+
device=device,
|
| 157 |
+
dropout_fn=condition.make_fn(
|
| 158 |
+
args.steps * args.batch_size * args.block_size
|
| 159 |
+
),
|
| 160 |
+
metrics_file=metrics_file,
|
| 161 |
+
trace_file=trace_file,
|
| 162 |
+
)
|
| 163 |
+
rows.append(row)
|
| 164 |
+
condition_rows.append(row)
|
| 165 |
+
completed_keys.add(metric_key(row))
|
| 166 |
+
progress.mark_done(row)
|
| 167 |
+
del model, optimizer
|
| 168 |
+
torch.mps.empty_cache()
|
| 169 |
+
|
| 170 |
+
if not condition_rows:
|
| 171 |
+
continue
|
| 172 |
+
mean_val_loss = statistics.fmean(
|
| 173 |
+
float(row["val_eval_loss"]) for row in condition_rows
|
| 174 |
+
)
|
| 175 |
+
if mean_val_loss < best_val_loss - args.screen_prune_min_delta:
|
| 176 |
+
best_val_loss = mean_val_loss
|
| 177 |
+
worse_streak = 0
|
| 178 |
+
elif mean_val_loss > best_val_loss + args.screen_prune_min_delta:
|
| 179 |
+
worse_streak += 1
|
| 180 |
+
|
| 181 |
+
if (
|
| 182 |
+
args.mode == "screen_static"
|
| 183 |
+
and args.screen_early_stop
|
| 184 |
+
and worse_streak >= args.screen_prune_patience
|
| 185 |
+
and condition.initial >= args.target_min_dropout
|
| 186 |
+
):
|
| 187 |
+
write_jsonl_row(
|
| 188 |
+
trace_file,
|
| 189 |
+
{
|
| 190 |
+
"event": "screen_pruned_model",
|
| 191 |
+
"run_mode": args.mode,
|
| 192 |
+
"model_name": model_spec.name,
|
| 193 |
+
"token_limit": int(token_limit),
|
| 194 |
+
"best_val_loss": best_val_loss,
|
| 195 |
+
"pruned_after_dropout": condition.initial,
|
| 196 |
+
"worse_streak": worse_streak,
|
| 197 |
+
"remaining_dropouts": [
|
| 198 |
+
rate
|
| 199 |
+
for rate in args.dropout_rates
|
| 200 |
+
if rate > condition.initial
|
| 201 |
+
],
|
| 202 |
+
},
|
| 203 |
+
)
|
| 204 |
+
break
|
| 205 |
+
return rows
|
| 206 |
+
|
| 207 |
+
|
| 208 |
+
def run_locked_stream(
|
| 209 |
+
*,
|
| 210 |
+
args: argparse.Namespace,
|
| 211 |
+
model_specs: list[ModelSpec],
|
| 212 |
+
seeds: list[int],
|
| 213 |
+
train_tokens: np.ndarray,
|
| 214 |
+
val_tokens: np.ndarray,
|
| 215 |
+
tokenizer_vocab_size: int,
|
| 216 |
+
stream_caps: list[int],
|
| 217 |
+
device: torch.device,
|
| 218 |
+
metrics_file,
|
| 219 |
+
trace_file,
|
| 220 |
+
completed_keys: set[tuple] | None = None,
|
| 221 |
+
) -> list[dict]:
|
| 222 |
+
rows: list[dict] = []
|
| 223 |
+
completed_keys = completed_keys or set()
|
| 224 |
+
conditions = args.anchor_decays + static_conditions(args.dropout_rates) + args.decays
|
| 225 |
+
fallback_decay_tokens = (
|
| 226 |
+
args.decay_tokens
|
| 227 |
+
or args.stage_steps * args.batch_size * args.block_size * len(stream_caps)
|
| 228 |
+
)
|
| 229 |
+
planned = 0
|
| 230 |
+
for model_spec in model_specs:
|
| 231 |
+
for condition in conditions:
|
| 232 |
+
for seed in seeds:
|
| 233 |
+
for stage, token_limit in enumerate(stream_caps):
|
| 234 |
+
key = planned_metric_key(
|
| 235 |
+
mode=args.mode,
|
| 236 |
+
condition=condition,
|
| 237 |
+
model_spec=model_spec,
|
| 238 |
+
seed=seed,
|
| 239 |
+
token_limit=token_limit,
|
| 240 |
+
stage=stage,
|
| 241 |
+
)
|
| 242 |
+
if key not in completed_keys:
|
| 243 |
+
planned += 1
|
| 244 |
+
progress = ProgressMeter(planned)
|
| 245 |
+
for model_spec in model_specs:
|
| 246 |
+
for condition in conditions:
|
| 247 |
+
for seed in seeds:
|
| 248 |
+
model = None
|
| 249 |
+
optimizer = None
|
| 250 |
+
tokens_seen = 0
|
| 251 |
+
for stage, token_limit in enumerate(stream_caps):
|
| 252 |
+
key = planned_metric_key(
|
| 253 |
+
mode=args.mode,
|
| 254 |
+
condition=condition,
|
| 255 |
+
model_spec=model_spec,
|
| 256 |
+
seed=seed,
|
| 257 |
+
token_limit=token_limit,
|
| 258 |
+
stage=stage,
|
| 259 |
+
)
|
| 260 |
+
if key in completed_keys:
|
| 261 |
+
write_jsonl_row(
|
| 262 |
+
trace_file,
|
| 263 |
+
{
|
| 264 |
+
"event": "skipped_completed_condition",
|
| 265 |
+
"run_mode": args.mode,
|
| 266 |
+
"condition": condition.name,
|
| 267 |
+
"model_name": model_spec.name,
|
| 268 |
+
"seed": seed,
|
| 269 |
+
"stage": stage,
|
| 270 |
+
"token_limit": int(token_limit),
|
| 271 |
+
},
|
| 272 |
+
)
|
| 273 |
+
continue
|
| 274 |
+
config = model_spec.config(
|
| 275 |
+
tokenizer_vocab_size,
|
| 276 |
+
args.block_size,
|
| 277 |
+
condition.initial,
|
| 278 |
+
)
|
| 279 |
+
model, optimizer, tokens_seen, row = train_segment(
|
| 280 |
+
run_mode=args.mode,
|
| 281 |
+
condition=condition,
|
| 282 |
+
model_spec=model_spec,
|
| 283 |
+
config=config,
|
| 284 |
+
train_tokens=train_tokens,
|
| 285 |
+
val_tokens=val_tokens,
|
| 286 |
+
token_limit=token_limit,
|
| 287 |
+
steps=args.stage_steps,
|
| 288 |
+
seed=seed,
|
| 289 |
+
args=args,
|
| 290 |
+
device=device,
|
| 291 |
+
dropout_fn=condition.make_fn(
|
| 292 |
+
fallback_decay_tokens,
|
| 293 |
+
unique_tokens=token_limit,
|
| 294 |
+
),
|
| 295 |
+
metrics_file=metrics_file,
|
| 296 |
+
trace_file=trace_file,
|
| 297 |
+
stage=stage,
|
| 298 |
+
model=model,
|
| 299 |
+
optimizer=optimizer,
|
| 300 |
+
tokens_seen_start=tokens_seen,
|
| 301 |
+
)
|
| 302 |
+
rows.append(row)
|
| 303 |
+
completed_keys.add(metric_key(row))
|
| 304 |
+
progress.mark_done(row)
|
| 305 |
+
del model, optimizer
|
| 306 |
+
torch.mps.empty_cache()
|
| 307 |
+
return rows
|
| 308 |
+
|
| 309 |
+
|
| 310 |
+
def prepare_data(args: argparse.Namespace, output_dir: Path, required_train_tokens: int):
|
| 311 |
+
cache_dir = Path(args.cache_dir) if args.cache_dir else output_dir / "cache"
|
| 312 |
+
cache_dir.mkdir(parents=True, exist_ok=True)
|
| 313 |
+
if args.use_cached_data:
|
| 314 |
+
if args.force_retokenize:
|
| 315 |
+
raise ValueError("--use-cached-data cannot be combined with --force-retokenize")
|
| 316 |
+
return load_cached_splits(
|
| 317 |
+
cache_dir=cache_dir,
|
| 318 |
+
vocab_size=args.vocab_size,
|
| 319 |
+
max_required_train_tokens=required_train_tokens,
|
| 320 |
+
val_tokens=args.val_tokens,
|
| 321 |
+
allow_short_corpus=args.allow_short_corpus,
|
| 322 |
+
)
|
| 323 |
+
|
| 324 |
+
paths = resolve_paths(args.corpus, args.corpus_glob)
|
| 325 |
+
tokenizer = train_or_load_tokenizer(
|
| 326 |
+
paths=paths,
|
| 327 |
+
output_dir=cache_dir,
|
| 328 |
+
vocab_size=args.vocab_size,
|
| 329 |
+
tokenizer_train_chars=args.tokenizer_train_chars,
|
| 330 |
+
text_column=args.text_column,
|
| 331 |
+
force_retrain=args.force_retokenize,
|
| 332 |
+
)
|
| 333 |
+
splits = encode_corpus(
|
| 334 |
+
paths=paths,
|
| 335 |
+
tokenizer=tokenizer,
|
| 336 |
+
output_dir=cache_dir,
|
| 337 |
+
max_required_train_tokens=required_train_tokens,
|
| 338 |
+
val_tokens=args.val_tokens,
|
| 339 |
+
text_column=args.text_column,
|
| 340 |
+
allow_short_corpus=args.allow_short_corpus,
|
| 341 |
+
force_reencode=args.force_retokenize,
|
| 342 |
+
)
|
| 343 |
+
return tokenizer, splits
|
| 344 |
+
|
| 345 |
+
|
| 346 |
+
def run(args: argparse.Namespace) -> Path:
|
| 347 |
+
device = assert_mps_only()
|
| 348 |
+
seeds = default_seeds(args.mode, args.seeds)
|
| 349 |
+
model_specs = [parse_model_spec(spec) for spec in args.models]
|
| 350 |
+
if args.mode != "locked_stream" and (args.decays or args.anchor_decays):
|
| 351 |
+
raise ValueError("--decays and --anchor-decays are only used with --mode locked_stream")
|
| 352 |
+
if args.resume_from and args.mode == "locked_stream":
|
| 353 |
+
raise ValueError("--resume-from currently supports fixed static sweeps only")
|
| 354 |
+
|
| 355 |
+
if args.resume_from:
|
| 356 |
+
output_dir = Path(args.resume_from)
|
| 357 |
+
if not output_dir.exists():
|
| 358 |
+
raise FileNotFoundError(f"resume directory does not exist: {output_dir}")
|
| 359 |
+
else:
|
| 360 |
+
run_id = datetime.now().strftime("%Y%m%d-%H%M%S")
|
| 361 |
+
output_dir = Path(args.output_dir) / args.mode / run_id
|
| 362 |
+
output_dir.mkdir(parents=True, exist_ok=True)
|
| 363 |
+
required_train_tokens = max(
|
| 364 |
+
args.stream_token_caps if args.mode == "locked_stream" else args.token_limits
|
| 365 |
+
)
|
| 366 |
+
tokenizer, splits = prepare_data(args, output_dir, required_train_tokens)
|
| 367 |
+
token_limits = [min(limit, len(splits.train)) for limit in args.token_limits]
|
| 368 |
+
stream_caps = [min(limit, len(splits.train)) for limit in args.stream_token_caps]
|
| 369 |
+
|
| 370 |
+
args_payload = vars(args).copy()
|
| 371 |
+
args_payload["decays"] = [condition.to_dict() for condition in args.decays]
|
| 372 |
+
args_payload["anchor_decays"] = [
|
| 373 |
+
condition.to_dict() for condition in args.anchor_decays
|
| 374 |
+
]
|
| 375 |
+
config_payload = {
|
| 376 |
+
"args": args_payload,
|
| 377 |
+
"mode": args.mode,
|
| 378 |
+
"seeds": seeds,
|
| 379 |
+
"models": [model.to_dict() for model in model_specs],
|
| 380 |
+
"device": str(device),
|
| 381 |
+
"torch": torch.__version__,
|
| 382 |
+
"python": sys.version,
|
| 383 |
+
"mps_available": torch.backends.mps.is_available(),
|
| 384 |
+
"attribution": NANOCHAT_ATTRIBUTION,
|
| 385 |
+
"tokenizer_path": str(splits.tokenizer_path),
|
| 386 |
+
"encoded_path": str(splits.encoded_path),
|
| 387 |
+
"train_tokens": int(len(splits.train)),
|
| 388 |
+
"val_tokens": int(len(splits.val)),
|
| 389 |
+
"effective_token_limits": [int(limit) for limit in token_limits],
|
| 390 |
+
"effective_stream_token_caps": [int(limit) for limit in stream_caps],
|
| 391 |
+
"resume_from": str(args.resume_from) if args.resume_from else None,
|
| 392 |
+
}
|
| 393 |
+
config_name = "config.resume.json" if args.resume_from else "config.json"
|
| 394 |
+
(output_dir / config_name).write_text(
|
| 395 |
+
json.dumps(config_payload, indent=2),
|
| 396 |
+
encoding="utf-8",
|
| 397 |
+
)
|
| 398 |
+
|
| 399 |
+
metrics_path = output_dir / "metrics.jsonl"
|
| 400 |
+
trace_path = output_dir / "trace.jsonl"
|
| 401 |
+
existing_rows = load_metrics(metrics_path) if args.resume_from else []
|
| 402 |
+
completed_keys = {metric_key(row) for row in existing_rows}
|
| 403 |
+
with (
|
| 404 |
+
metrics_path.open("a" if args.resume_from else "w", encoding="utf-8") as metrics_file,
|
| 405 |
+
trace_path.open("a" if args.resume_from else "w", encoding="utf-8") as trace_file,
|
| 406 |
+
):
|
| 407 |
+
if args.mode in {"screen_static", "confirm_static"}:
|
| 408 |
+
new_rows = run_fixed_static_sweep(
|
| 409 |
+
args=args,
|
| 410 |
+
model_specs=model_specs,
|
| 411 |
+
seeds=seeds,
|
| 412 |
+
train_tokens=splits.train,
|
| 413 |
+
val_tokens=splits.val,
|
| 414 |
+
tokenizer_vocab_size=tokenizer.vocab_size,
|
| 415 |
+
token_limits=token_limits,
|
| 416 |
+
device=device,
|
| 417 |
+
metrics_file=metrics_file,
|
| 418 |
+
trace_file=trace_file,
|
| 419 |
+
completed_keys=completed_keys,
|
| 420 |
+
)
|
| 421 |
+
else:
|
| 422 |
+
new_rows = run_locked_stream(
|
| 423 |
+
args=args,
|
| 424 |
+
model_specs=model_specs,
|
| 425 |
+
seeds=seeds,
|
| 426 |
+
train_tokens=splits.train,
|
| 427 |
+
val_tokens=splits.val,
|
| 428 |
+
tokenizer_vocab_size=tokenizer.vocab_size,
|
| 429 |
+
stream_caps=stream_caps,
|
| 430 |
+
device=device,
|
| 431 |
+
metrics_file=metrics_file,
|
| 432 |
+
trace_file=trace_file,
|
| 433 |
+
)
|
| 434 |
+
rows = existing_rows + new_rows
|
| 435 |
+
|
| 436 |
+
summary = summarize(rows)
|
| 437 |
+
(output_dir / "summary.json").write_text(
|
| 438 |
+
json.dumps(summary, indent=2),
|
| 439 |
+
encoding="utf-8",
|
| 440 |
+
)
|
| 441 |
+
write_csv(output_dir / "summary.csv", summary, SUMMARY_FIELDS)
|
| 442 |
+
if args.mode in {"screen_static", "confirm_static"}:
|
| 443 |
+
selection = build_model_selection(summary, args)
|
| 444 |
+
(output_dir / "model_selection.json").write_text(
|
| 445 |
+
json.dumps(selection, indent=2),
|
| 446 |
+
encoding="utf-8",
|
| 447 |
+
)
|
| 448 |
+
write_csv(output_dir / "model_selection.csv", selection, SELECTION_FIELDS)
|
| 449 |
+
write_screen_markdown_summary(output_dir, rows)
|
| 450 |
+
write_dropout_curve_svg(output_dir, summary)
|
| 451 |
+
elif args.mode == "locked_stream":
|
| 452 |
+
write_stream_markdown_summary(output_dir, rows)
|
| 453 |
+
|
| 454 |
+
print(
|
| 455 |
+
json.dumps(
|
| 456 |
+
{
|
| 457 |
+
"output_dir": str(output_dir),
|
| 458 |
+
"new_rows": len(new_rows),
|
| 459 |
+
"total_metric_rows": len(rows),
|
| 460 |
+
"summary_rows": len(summary),
|
| 461 |
+
},
|
| 462 |
+
indent=2,
|
| 463 |
+
)
|
| 464 |
+
)
|
| 465 |
+
return output_dir
|
| 466 |
+
|
| 467 |
+
|
| 468 |
+
def build_parser() -> argparse.ArgumentParser:
|
| 469 |
+
parser = argparse.ArgumentParser(
|
| 470 |
+
description="MPS-only dropout/model selection experiments"
|
| 471 |
+
)
|
| 472 |
+
parser.add_argument(
|
| 473 |
+
"--mode",
|
| 474 |
+
choices=["screen_static", "confirm_static", "locked_stream"],
|
| 475 |
+
default="screen_static",
|
| 476 |
+
)
|
| 477 |
+
parser.add_argument("--corpus", default=None, help="Text or parquet corpus path")
|
| 478 |
+
parser.add_argument("--corpus-glob", default=None, help="Glob of text/parquet corpus paths")
|
| 479 |
+
parser.add_argument("--text-column", default="text", help="Parquet text column")
|
| 480 |
+
parser.add_argument(
|
| 481 |
+
"--use-cached-data",
|
| 482 |
+
action="store_true",
|
| 483 |
+
help=(
|
| 484 |
+
"Load tokenizer-v{vocab}.json and tokens-v{vocab}-*.npy from --cache-dir "
|
| 485 |
+
"instead of requiring the original text/parquet corpus."
|
| 486 |
+
),
|
| 487 |
+
)
|
| 488 |
+
parser.add_argument("--output-dir", default="runs")
|
| 489 |
+
parser.add_argument(
|
| 490 |
+
"--resume-from",
|
| 491 |
+
default=None,
|
| 492 |
+
help="Existing fixed-static run directory; completed metric rows are skipped",
|
| 493 |
+
)
|
| 494 |
+
parser.add_argument("--cache-dir", default=".cache/dropout_decay")
|
| 495 |
+
parser.add_argument(
|
| 496 |
+
"--models",
|
| 497 |
+
nargs="+",
|
| 498 |
+
default=["8x8x256"],
|
| 499 |
+
help="Model specs like 8x8x256 or name=8x8x256",
|
| 500 |
+
)
|
| 501 |
+
parser.add_argument("--seeds", nargs="+", type=int, default=None)
|
| 502 |
+
parser.add_argument("--token-limits", nargs="+", type=int, default=[5_000_000])
|
| 503 |
+
parser.add_argument(
|
| 504 |
+
"--stream-token-caps",
|
| 505 |
+
nargs="+",
|
| 506 |
+
type=int,
|
| 507 |
+
default=[5_000_000, 10_000_000, 20_000_000, 40_000_000],
|
| 508 |
+
)
|
| 509 |
+
parser.add_argument("--val-tokens", type=int, default=500_000)
|
| 510 |
+
parser.add_argument("--allow-short-corpus", action="store_true")
|
| 511 |
+
parser.add_argument("--force-retokenize", action="store_true")
|
| 512 |
+
parser.add_argument("--vocab-size", type=int, default=4096)
|
| 513 |
+
parser.add_argument("--tokenizer-train-chars", type=int, default=10_000_000)
|
| 514 |
+
parser.add_argument("--block-size", type=int, default=128)
|
| 515 |
+
parser.add_argument("--batch-size", type=int, default=16)
|
| 516 |
+
parser.add_argument("--steps", type=int, default=2000)
|
| 517 |
+
parser.add_argument("--stage-steps", type=int, default=1000)
|
| 518 |
+
parser.add_argument("--dropout-rates", nargs="*", type=float, default=DEFAULT_DROPOUT_RATES)
|
| 519 |
+
parser.add_argument("--decays", nargs="*", type=parse_decay_spec, default=[])
|
| 520 |
+
parser.add_argument(
|
| 521 |
+
"--anchor-decays",
|
| 522 |
+
nargs="*",
|
| 523 |
+
type=parse_anchor_decay_spec,
|
| 524 |
+
default=[],
|
| 525 |
+
help=(
|
| 526 |
+
"Prefix-token anchor schedules like "
|
| 527 |
+
"fit:250000=0.60,500000=0.40,1000000=0.30"
|
| 528 |
+
),
|
| 529 |
+
)
|
| 530 |
+
parser.add_argument("--decay-tokens", type=int, default=None)
|
| 531 |
+
parser.add_argument("--eval-batches", type=int, default=64)
|
| 532 |
+
parser.add_argument("--train-eval-batches", type=int, default=32)
|
| 533 |
+
parser.add_argument("--trace-eval-batches", type=int, default=8)
|
| 534 |
+
parser.add_argument("--eval-every", type=int, default=0)
|
| 535 |
+
parser.add_argument("--log-every", type=int, default=100)
|
| 536 |
+
parser.add_argument("--lr", type=float, default=3e-4)
|
| 537 |
+
parser.add_argument("--weight-decay", type=float, default=0.1)
|
| 538 |
+
parser.add_argument("--grad-clip", type=float, default=1.0)
|
| 539 |
+
parser.add_argument("--plateau-delta", type=float, default=0.01)
|
| 540 |
+
parser.add_argument("--target-min-dropout", type=float, default=0.10)
|
| 541 |
+
parser.add_argument("--min-nonzero-margin", type=float, default=0.01)
|
| 542 |
+
parser.add_argument("--min-high-dropout-margin", type=float, default=0.03)
|
| 543 |
+
parser.add_argument("--screen-early-stop", action="store_true")
|
| 544 |
+
parser.add_argument("--screen-prune-patience", type=int, default=3)
|
| 545 |
+
parser.add_argument("--screen-prune-min-delta", type=float, default=0.01)
|
| 546 |
+
return parser
|
| 547 |
+
|
| 548 |
+
|
| 549 |
+
def main() -> None:
|
| 550 |
+
args = build_parser().parse_args()
|
| 551 |
+
run(args)
|
| 552 |
+
|
| 553 |
+
|
| 554 |
+
if __name__ == "__main__":
|
| 555 |
+
main()
|
src/dropout_decay/experiments/training.py
ADDED
|
@@ -0,0 +1,261 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Derived from Andrej Karpathy's nanochat project.
|
| 3 |
+
|
| 4 |
+
MIT License
|
| 5 |
+
|
| 6 |
+
Copyright (c) 2025 Andrej Karpathy
|
| 7 |
+
|
| 8 |
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
| 9 |
+
of this software and associated documentation files (the "Software"), to deal
|
| 10 |
+
in the Software without restriction, including without limitation the rights
|
| 11 |
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
| 12 |
+
copies of the Software, and to permit persons to whom the Software is
|
| 13 |
+
furnished to do so, subject to the following conditions:
|
| 14 |
+
|
| 15 |
+
The above copyright notice and this permission notice shall be included in all
|
| 16 |
+
copies or substantial portions of the Software.
|
| 17 |
+
"""
|
| 18 |
+
|
| 19 |
+
from __future__ import annotations
|
| 20 |
+
|
| 21 |
+
import argparse
|
| 22 |
+
import random
|
| 23 |
+
import statistics
|
| 24 |
+
import time
|
| 25 |
+
from typing import Callable
|
| 26 |
+
|
| 27 |
+
import numpy as np
|
| 28 |
+
import torch
|
| 29 |
+
|
| 30 |
+
from dropout_decay.experiments.artifacts import write_jsonl_row
|
| 31 |
+
from dropout_decay.models import DropoutGPT, GPTConfig
|
| 32 |
+
from dropout_decay.specs import DropoutCondition, ModelSpec
|
| 33 |
+
|
| 34 |
+
|
| 35 |
+
def set_seed(seed: int) -> None:
|
| 36 |
+
random.seed(seed)
|
| 37 |
+
np.random.seed(seed)
|
| 38 |
+
torch.manual_seed(seed)
|
| 39 |
+
|
| 40 |
+
|
| 41 |
+
def batch_seed(
|
| 42 |
+
seed: int, model: ModelSpec, dropout_code: int, stage: int | None
|
| 43 |
+
) -> int:
|
| 44 |
+
return (
|
| 45 |
+
seed * 1_000_003
|
| 46 |
+
+ model.n_layer * 100_003
|
| 47 |
+
+ model.n_head * 10_007
|
| 48 |
+
+ model.n_embd * 101
|
| 49 |
+
+ dropout_code * 37
|
| 50 |
+
+ (stage or 0) * 997
|
| 51 |
+
)
|
| 52 |
+
|
| 53 |
+
|
| 54 |
+
def make_batch(
|
| 55 |
+
tokens: np.ndarray,
|
| 56 |
+
token_limit: int,
|
| 57 |
+
batch_size: int,
|
| 58 |
+
block_size: int,
|
| 59 |
+
rng: np.random.Generator,
|
| 60 |
+
device: torch.device,
|
| 61 |
+
) -> tuple[torch.Tensor, torch.Tensor]:
|
| 62 |
+
limit = min(token_limit, len(tokens))
|
| 63 |
+
max_start = limit - block_size - 1
|
| 64 |
+
if max_start <= 0:
|
| 65 |
+
raise ValueError("token_limit is too small for the requested block_size")
|
| 66 |
+
starts = rng.integers(0, max_start, size=batch_size)
|
| 67 |
+
x_np = np.stack([tokens[start : start + block_size] for start in starts]).astype(
|
| 68 |
+
np.int64
|
| 69 |
+
)
|
| 70 |
+
y_np = np.stack(
|
| 71 |
+
[tokens[start + 1 : start + 1 + block_size] for start in starts]
|
| 72 |
+
).astype(np.int64)
|
| 73 |
+
return (
|
| 74 |
+
torch.tensor(x_np, dtype=torch.long, device=device),
|
| 75 |
+
torch.tensor(y_np, dtype=torch.long, device=device),
|
| 76 |
+
)
|
| 77 |
+
|
| 78 |
+
|
| 79 |
+
@torch.no_grad()
|
| 80 |
+
def estimate_loss(
|
| 81 |
+
model: DropoutGPT,
|
| 82 |
+
tokens: np.ndarray,
|
| 83 |
+
token_limit: int,
|
| 84 |
+
batches: int,
|
| 85 |
+
args: argparse.Namespace,
|
| 86 |
+
device: torch.device,
|
| 87 |
+
rng_seed: int,
|
| 88 |
+
) -> float:
|
| 89 |
+
if batches <= 0:
|
| 90 |
+
return float("nan")
|
| 91 |
+
model.eval()
|
| 92 |
+
rng = np.random.default_rng(rng_seed)
|
| 93 |
+
losses: list[float] = []
|
| 94 |
+
for _ in range(batches):
|
| 95 |
+
x, y = make_batch(
|
| 96 |
+
tokens, token_limit, args.batch_size, args.block_size, rng, device
|
| 97 |
+
)
|
| 98 |
+
_, loss = model(x, y)
|
| 99 |
+
losses.append(float(loss.item()))
|
| 100 |
+
model.train()
|
| 101 |
+
return float(statistics.fmean(losses))
|
| 102 |
+
|
| 103 |
+
|
| 104 |
+
def train_segment(
|
| 105 |
+
*,
|
| 106 |
+
run_mode: str,
|
| 107 |
+
condition: DropoutCondition,
|
| 108 |
+
model_spec: ModelSpec,
|
| 109 |
+
config: GPTConfig,
|
| 110 |
+
train_tokens: np.ndarray,
|
| 111 |
+
val_tokens: np.ndarray,
|
| 112 |
+
token_limit: int,
|
| 113 |
+
steps: int,
|
| 114 |
+
seed: int,
|
| 115 |
+
args: argparse.Namespace,
|
| 116 |
+
device: torch.device,
|
| 117 |
+
dropout_fn: Callable[[int], float],
|
| 118 |
+
metrics_file,
|
| 119 |
+
trace_file,
|
| 120 |
+
stage: int | None = None,
|
| 121 |
+
model: DropoutGPT | None = None,
|
| 122 |
+
optimizer: torch.optim.Optimizer | None = None,
|
| 123 |
+
tokens_seen_start: int = 0,
|
| 124 |
+
) -> tuple[DropoutGPT, torch.optim.Optimizer, int, dict]:
|
| 125 |
+
if model is None:
|
| 126 |
+
set_seed(seed)
|
| 127 |
+
model = DropoutGPT(config).to(device)
|
| 128 |
+
optimizer = torch.optim.AdamW(
|
| 129 |
+
model.parameters(),
|
| 130 |
+
lr=args.lr,
|
| 131 |
+
betas=(0.9, 0.95),
|
| 132 |
+
weight_decay=args.weight_decay,
|
| 133 |
+
)
|
| 134 |
+
else:
|
| 135 |
+
torch.manual_seed(seed + 10_000 + (stage or 0))
|
| 136 |
+
if optimizer is None:
|
| 137 |
+
raise ValueError("optimizer is required when reusing a model")
|
| 138 |
+
|
| 139 |
+
model.train()
|
| 140 |
+
dropout_code = int(round(condition.initial * 10_000))
|
| 141 |
+
rng = np.random.default_rng(batch_seed(seed, model_spec, dropout_code, stage))
|
| 142 |
+
tokens_seen = tokens_seen_start
|
| 143 |
+
last_loss = float("nan")
|
| 144 |
+
active_dropout = condition.initial
|
| 145 |
+
t0 = time.time()
|
| 146 |
+
|
| 147 |
+
for step in range(1, steps + 1):
|
| 148 |
+
active_dropout = dropout_fn(tokens_seen)
|
| 149 |
+
model.set_dropout(active_dropout)
|
| 150 |
+
x, y = make_batch(
|
| 151 |
+
train_tokens, token_limit, args.batch_size, args.block_size, rng, device
|
| 152 |
+
)
|
| 153 |
+
_, loss = model(x, y)
|
| 154 |
+
optimizer.zero_grad(set_to_none=True)
|
| 155 |
+
loss.backward()
|
| 156 |
+
if args.grad_clip > 0:
|
| 157 |
+
torch.nn.utils.clip_grad_norm_(model.parameters(), args.grad_clip)
|
| 158 |
+
optimizer.step()
|
| 159 |
+
|
| 160 |
+
tokens_seen += args.batch_size * args.block_size
|
| 161 |
+
last_loss = float(loss.item())
|
| 162 |
+
if args.log_every > 0 and step % args.log_every == 0:
|
| 163 |
+
write_jsonl_row(
|
| 164 |
+
trace_file,
|
| 165 |
+
{
|
| 166 |
+
"event": "train_step",
|
| 167 |
+
"run_mode": run_mode,
|
| 168 |
+
"condition": condition.name,
|
| 169 |
+
"model_name": model_spec.name,
|
| 170 |
+
"seed": seed,
|
| 171 |
+
"stage": stage,
|
| 172 |
+
"step": step,
|
| 173 |
+
"steps": steps,
|
| 174 |
+
"token_limit": int(token_limit),
|
| 175 |
+
"tokens_seen": int(tokens_seen),
|
| 176 |
+
"dropout": float(active_dropout),
|
| 177 |
+
"train_batch_loss": last_loss,
|
| 178 |
+
},
|
| 179 |
+
)
|
| 180 |
+
if args.eval_every > 0 and step % args.eval_every == 0:
|
| 181 |
+
train_eval = estimate_loss(
|
| 182 |
+
model,
|
| 183 |
+
train_tokens,
|
| 184 |
+
token_limit,
|
| 185 |
+
args.trace_eval_batches,
|
| 186 |
+
args,
|
| 187 |
+
device,
|
| 188 |
+
rng_seed=seed + 20_000 + step,
|
| 189 |
+
)
|
| 190 |
+
val_eval = estimate_loss(
|
| 191 |
+
model,
|
| 192 |
+
val_tokens,
|
| 193 |
+
len(val_tokens),
|
| 194 |
+
args.trace_eval_batches,
|
| 195 |
+
args,
|
| 196 |
+
device,
|
| 197 |
+
rng_seed=seed + 30_000 + step,
|
| 198 |
+
)
|
| 199 |
+
write_jsonl_row(
|
| 200 |
+
trace_file,
|
| 201 |
+
{
|
| 202 |
+
"event": "eval_step",
|
| 203 |
+
"run_mode": run_mode,
|
| 204 |
+
"condition": condition.name,
|
| 205 |
+
"model_name": model_spec.name,
|
| 206 |
+
"seed": seed,
|
| 207 |
+
"stage": stage,
|
| 208 |
+
"step": step,
|
| 209 |
+
"steps": steps,
|
| 210 |
+
"token_limit": int(token_limit),
|
| 211 |
+
"tokens_seen": int(tokens_seen),
|
| 212 |
+
"dropout": float(active_dropout),
|
| 213 |
+
"train_eval_loss": train_eval,
|
| 214 |
+
"val_eval_loss": val_eval,
|
| 215 |
+
"generalization_gap": val_eval - train_eval,
|
| 216 |
+
},
|
| 217 |
+
)
|
| 218 |
+
|
| 219 |
+
train_eval = estimate_loss(
|
| 220 |
+
model,
|
| 221 |
+
train_tokens,
|
| 222 |
+
token_limit,
|
| 223 |
+
args.train_eval_batches,
|
| 224 |
+
args,
|
| 225 |
+
device,
|
| 226 |
+
rng_seed=seed + 40_000 + (stage or 0),
|
| 227 |
+
)
|
| 228 |
+
val_eval = estimate_loss(
|
| 229 |
+
model,
|
| 230 |
+
val_tokens,
|
| 231 |
+
len(val_tokens),
|
| 232 |
+
args.eval_batches,
|
| 233 |
+
args,
|
| 234 |
+
device,
|
| 235 |
+
rng_seed=seed + 50_000 + (stage or 0),
|
| 236 |
+
)
|
| 237 |
+
row = {
|
| 238 |
+
"run_mode": run_mode,
|
| 239 |
+
"condition": condition.name,
|
| 240 |
+
"condition_kind": condition.kind,
|
| 241 |
+
"seed": seed,
|
| 242 |
+
"stage": stage,
|
| 243 |
+
"token_limit": int(token_limit),
|
| 244 |
+
"steps": int(steps),
|
| 245 |
+
"tokens_seen": int(tokens_seen),
|
| 246 |
+
"dropout_initial": float(condition.initial),
|
| 247 |
+
"dropout_final": float(condition.final),
|
| 248 |
+
"dropout_schedule": condition.schedule,
|
| 249 |
+
"dropout_active_final": float(active_dropout),
|
| 250 |
+
"train_loss_last": last_loss,
|
| 251 |
+
"train_eval_loss": train_eval,
|
| 252 |
+
"val_eval_loss": val_eval,
|
| 253 |
+
"eval_loss": val_eval,
|
| 254 |
+
"generalization_gap": val_eval - train_eval,
|
| 255 |
+
"elapsed_sec": time.time() - t0,
|
| 256 |
+
"parameters": model.num_parameters(),
|
| 257 |
+
"model_config": config.to_dict(),
|
| 258 |
+
**model_spec.to_dict(),
|
| 259 |
+
}
|
| 260 |
+
write_jsonl_row(metrics_file, row)
|
| 261 |
+
return model, optimizer, tokens_seen, row
|
src/dropout_decay/model.py
CHANGED
|
@@ -16,212 +16,24 @@ The above copyright notice and this permission notice shall be included in all
|
|
| 16 |
copies or substantial portions of the Software.
|
| 17 |
"""
|
| 18 |
|
| 19 |
-
from
|
| 20 |
-
|
| 21 |
-
|
| 22 |
-
|
| 23 |
-
|
| 24 |
-
|
| 25 |
-
|
| 26 |
-
|
| 27 |
-
|
| 28 |
-
|
| 29 |
-
|
| 30 |
-
|
| 31 |
-
|
| 32 |
-
|
| 33 |
-
|
| 34 |
-
|
| 35 |
-
|
| 36 |
-
|
| 37 |
-
|
| 38 |
-
|
| 39 |
-
|
| 40 |
-
|
| 41 |
-
|
| 42 |
-
def rms_norm(x: torch.Tensor) -> torch.Tensor:
|
| 43 |
-
return F.rms_norm(x, (x.size(-1),))
|
| 44 |
-
|
| 45 |
-
|
| 46 |
-
class Linear(nn.Linear):
|
| 47 |
-
"""Bias-free linear layer matching the nanochat architectural style."""
|
| 48 |
-
|
| 49 |
-
def __init__(self, in_features: int, out_features: int):
|
| 50 |
-
super().__init__(in_features, out_features, bias=False)
|
| 51 |
-
|
| 52 |
-
|
| 53 |
-
def apply_rotary_emb(
|
| 54 |
-
x: torch.Tensor, cos: torch.Tensor, sin: torch.Tensor
|
| 55 |
-
) -> torch.Tensor:
|
| 56 |
-
half = x.shape[-1] // 2
|
| 57 |
-
x1, x2 = x[..., :half], x[..., half:]
|
| 58 |
-
return torch.cat((x1 * cos + x2 * sin, x1 * (-sin) + x2 * cos), dim=-1)
|
| 59 |
-
|
| 60 |
-
|
| 61 |
-
class CausalSelfAttention(nn.Module):
|
| 62 |
-
def __init__(self, config: GPTConfig):
|
| 63 |
-
super().__init__()
|
| 64 |
-
if config.n_embd % config.n_head != 0:
|
| 65 |
-
raise ValueError("n_embd must be divisible by n_head")
|
| 66 |
-
self.n_head = config.n_head
|
| 67 |
-
self.head_dim = config.n_embd // config.n_head
|
| 68 |
-
if self.head_dim % 2 != 0:
|
| 69 |
-
raise ValueError("rotary attention requires an even head dimension")
|
| 70 |
-
self.dropout_p = config.dropout
|
| 71 |
-
self.c_q = Linear(config.n_embd, config.n_embd)
|
| 72 |
-
self.c_k = Linear(config.n_embd, config.n_embd)
|
| 73 |
-
self.c_v = Linear(config.n_embd, config.n_embd)
|
| 74 |
-
self.c_proj = Linear(config.n_embd, config.n_embd)
|
| 75 |
-
self.resid_dropout = nn.Dropout(config.dropout)
|
| 76 |
-
mask = torch.ones(config.block_size, config.block_size, dtype=torch.bool).tril()
|
| 77 |
-
self.register_buffer(
|
| 78 |
-
"causal_mask",
|
| 79 |
-
mask.view(1, 1, config.block_size, config.block_size),
|
| 80 |
-
persistent=False,
|
| 81 |
-
)
|
| 82 |
-
|
| 83 |
-
def set_dropout(self, p: float) -> None:
|
| 84 |
-
self.dropout_p = p
|
| 85 |
-
self.resid_dropout.p = p
|
| 86 |
-
|
| 87 |
-
def forward(
|
| 88 |
-
self, x: torch.Tensor, cos: torch.Tensor, sin: torch.Tensor
|
| 89 |
-
) -> torch.Tensor:
|
| 90 |
-
batch, seq_len, channels = x.shape
|
| 91 |
-
q = self.c_q(x).view(batch, seq_len, self.n_head, self.head_dim).transpose(1, 2)
|
| 92 |
-
k = self.c_k(x).view(batch, seq_len, self.n_head, self.head_dim).transpose(1, 2)
|
| 93 |
-
v = self.c_v(x).view(batch, seq_len, self.n_head, self.head_dim).transpose(1, 2)
|
| 94 |
-
|
| 95 |
-
q = rms_norm(apply_rotary_emb(q, cos, sin)) * 1.2
|
| 96 |
-
k = rms_norm(apply_rotary_emb(k, cos, sin)) * 1.2
|
| 97 |
-
|
| 98 |
-
att = (q @ k.transpose(-2, -1)) / math.sqrt(self.head_dim)
|
| 99 |
-
mask = self.causal_mask[:, :, :seq_len, :seq_len]
|
| 100 |
-
att = att.masked_fill(~mask, float("-inf"))
|
| 101 |
-
att = F.softmax(att.float(), dim=-1).to(dtype=x.dtype)
|
| 102 |
-
att = F.dropout(att, p=self.dropout_p, training=self.training)
|
| 103 |
-
y = att @ v
|
| 104 |
-
y = y.transpose(1, 2).contiguous().view(batch, seq_len, channels)
|
| 105 |
-
return self.resid_dropout(self.c_proj(y))
|
| 106 |
-
|
| 107 |
-
|
| 108 |
-
class MLP(nn.Module):
|
| 109 |
-
def __init__(self, config: GPTConfig):
|
| 110 |
-
super().__init__()
|
| 111 |
-
self.c_fc = Linear(config.n_embd, 4 * config.n_embd)
|
| 112 |
-
self.c_proj = Linear(4 * config.n_embd, config.n_embd)
|
| 113 |
-
self.dropout = nn.Dropout(config.dropout)
|
| 114 |
-
|
| 115 |
-
def set_dropout(self, p: float) -> None:
|
| 116 |
-
self.dropout.p = p
|
| 117 |
-
|
| 118 |
-
def forward(self, x: torch.Tensor) -> torch.Tensor:
|
| 119 |
-
x = self.c_fc(x)
|
| 120 |
-
x = F.relu(x).square()
|
| 121 |
-
x = self.c_proj(x)
|
| 122 |
-
return self.dropout(x)
|
| 123 |
-
|
| 124 |
-
|
| 125 |
-
class Block(nn.Module):
|
| 126 |
-
def __init__(self, config: GPTConfig):
|
| 127 |
-
super().__init__()
|
| 128 |
-
self.attn = CausalSelfAttention(config)
|
| 129 |
-
self.mlp = MLP(config)
|
| 130 |
-
|
| 131 |
-
def set_dropout(self, p: float) -> None:
|
| 132 |
-
self.attn.set_dropout(p)
|
| 133 |
-
self.mlp.set_dropout(p)
|
| 134 |
-
|
| 135 |
-
def forward(
|
| 136 |
-
self, x: torch.Tensor, cos: torch.Tensor, sin: torch.Tensor
|
| 137 |
-
) -> torch.Tensor:
|
| 138 |
-
x = x + self.attn(rms_norm(x), cos, sin)
|
| 139 |
-
x = x + self.mlp(rms_norm(x))
|
| 140 |
-
return x
|
| 141 |
-
|
| 142 |
-
|
| 143 |
-
class DropoutGPT(nn.Module):
|
| 144 |
-
"""Minimal nanochat-style causal Transformer with dynamic dropout control."""
|
| 145 |
-
|
| 146 |
-
def __init__(self, config: GPTConfig):
|
| 147 |
-
super().__init__()
|
| 148 |
-
self.config = config
|
| 149 |
-
self.token_embedding = nn.Embedding(config.vocab_size, config.n_embd)
|
| 150 |
-
self.embedding_dropout = nn.Dropout(config.dropout)
|
| 151 |
-
self.blocks = nn.ModuleList(Block(config) for _ in range(config.n_layer))
|
| 152 |
-
self.lm_head = Linear(config.n_embd, config.vocab_size)
|
| 153 |
-
self.register_buffer(
|
| 154 |
-
"cos",
|
| 155 |
-
torch.empty(1, 1, config.block_size, config.n_embd // config.n_head // 2),
|
| 156 |
-
persistent=False,
|
| 157 |
-
)
|
| 158 |
-
self.register_buffer(
|
| 159 |
-
"sin",
|
| 160 |
-
torch.empty(1, 1, config.block_size, config.n_embd // config.n_head // 2),
|
| 161 |
-
persistent=False,
|
| 162 |
-
)
|
| 163 |
-
self._init_weights()
|
| 164 |
-
self._init_rotary()
|
| 165 |
-
|
| 166 |
-
@torch.no_grad()
|
| 167 |
-
def _init_weights(self) -> None:
|
| 168 |
-
nn.init.normal_(self.token_embedding.weight, mean=0.0, std=0.8)
|
| 169 |
-
nn.init.normal_(self.lm_head.weight, mean=0.0, std=0.001)
|
| 170 |
-
scale = (3.0 ** 0.5) * (self.config.n_embd ** -0.5)
|
| 171 |
-
for block in self.blocks:
|
| 172 |
-
nn.init.uniform_(block.attn.c_q.weight, -scale, scale)
|
| 173 |
-
nn.init.uniform_(block.attn.c_k.weight, -scale, scale)
|
| 174 |
-
nn.init.uniform_(block.attn.c_v.weight, -scale, scale)
|
| 175 |
-
nn.init.zeros_(block.attn.c_proj.weight)
|
| 176 |
-
nn.init.uniform_(block.mlp.c_fc.weight, -0.4 * scale, 0.4 * scale)
|
| 177 |
-
nn.init.zeros_(block.mlp.c_proj.weight)
|
| 178 |
-
|
| 179 |
-
@torch.no_grad()
|
| 180 |
-
def _init_rotary(self) -> None:
|
| 181 |
-
head_dim = self.config.n_embd // self.config.n_head
|
| 182 |
-
channel_range = torch.arange(
|
| 183 |
-
0,
|
| 184 |
-
head_dim,
|
| 185 |
-
2,
|
| 186 |
-
dtype=torch.float32,
|
| 187 |
-
device=self.cos.device,
|
| 188 |
-
)
|
| 189 |
-
inv_freq = 1.0 / (100000 ** (channel_range / head_dim))
|
| 190 |
-
positions = torch.arange(
|
| 191 |
-
self.config.block_size,
|
| 192 |
-
dtype=torch.float32,
|
| 193 |
-
device=self.cos.device,
|
| 194 |
-
)
|
| 195 |
-
freqs = torch.outer(positions, inv_freq)
|
| 196 |
-
self.cos.copy_(freqs.cos().view(1, 1, self.config.block_size, head_dim // 2))
|
| 197 |
-
self.sin.copy_(freqs.sin().view(1, 1, self.config.block_size, head_dim // 2))
|
| 198 |
-
|
| 199 |
-
def set_dropout(self, p: float) -> None:
|
| 200 |
-
if not 0.0 <= p < 1.0:
|
| 201 |
-
raise ValueError("dropout must satisfy 0 <= p < 1")
|
| 202 |
-
self.embedding_dropout.p = p
|
| 203 |
-
for block in self.blocks:
|
| 204 |
-
block.set_dropout(p)
|
| 205 |
-
|
| 206 |
-
def num_parameters(self) -> int:
|
| 207 |
-
return sum(p.numel() for p in self.parameters())
|
| 208 |
-
|
| 209 |
-
def forward(
|
| 210 |
-
self, idx: torch.Tensor, targets: torch.Tensor | None = None
|
| 211 |
-
) -> torch.Tensor | tuple[torch.Tensor, torch.Tensor]:
|
| 212 |
-
_, seq_len = idx.shape
|
| 213 |
-
if seq_len > self.config.block_size:
|
| 214 |
-
raise ValueError("sequence length exceeds block_size")
|
| 215 |
-
cos = self.cos[:, :, :seq_len, :]
|
| 216 |
-
sin = self.sin[:, :, :seq_len, :]
|
| 217 |
-
x = self.embedding_dropout(rms_norm(self.token_embedding(idx)))
|
| 218 |
-
for block in self.blocks:
|
| 219 |
-
x = block(x, cos, sin)
|
| 220 |
-
logits = self.lm_head(rms_norm(x)).float()
|
| 221 |
-
if targets is None:
|
| 222 |
-
return logits
|
| 223 |
-
loss = F.cross_entropy(
|
| 224 |
-
logits.reshape(-1, logits.size(-1)),
|
| 225 |
-
targets.reshape(-1),
|
| 226 |
-
)
|
| 227 |
-
return logits, loss
|
|
|
|
| 16 |
copies or substantial portions of the Software.
|
| 17 |
"""
|
| 18 |
|
| 19 |
+
from dropout_decay.models.gpt import (
|
| 20 |
+
Block,
|
| 21 |
+
CausalSelfAttention,
|
| 22 |
+
DropoutGPT,
|
| 23 |
+
GPTConfig,
|
| 24 |
+
Linear,
|
| 25 |
+
MLP,
|
| 26 |
+
apply_rotary_emb,
|
| 27 |
+
rms_norm,
|
| 28 |
+
)
|
| 29 |
+
|
| 30 |
+
__all__ = [
|
| 31 |
+
"Block",
|
| 32 |
+
"CausalSelfAttention",
|
| 33 |
+
"DropoutGPT",
|
| 34 |
+
"GPTConfig",
|
| 35 |
+
"Linear",
|
| 36 |
+
"MLP",
|
| 37 |
+
"apply_rotary_emb",
|
| 38 |
+
"rms_norm",
|
| 39 |
+
]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
src/dropout_decay/models/__init__.py
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Derived from Andrej Karpathy's nanochat project.
|
| 3 |
+
|
| 4 |
+
MIT License
|
| 5 |
+
|
| 6 |
+
Copyright (c) 2025 Andrej Karpathy
|
| 7 |
+
|
| 8 |
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
| 9 |
+
of this software and associated documentation files (the "Software"), to deal
|
| 10 |
+
in the Software without restriction, including without limitation the rights
|
| 11 |
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
| 12 |
+
copies of the Software, and to permit persons to whom the Software is
|
| 13 |
+
furnished to do so, subject to the following conditions:
|
| 14 |
+
|
| 15 |
+
The above copyright notice and this permission notice shall be included in all
|
| 16 |
+
copies or substantial portions of the Software.
|
| 17 |
+
"""
|
| 18 |
+
|
| 19 |
+
from dropout_decay.models.gpt import (
|
| 20 |
+
Block,
|
| 21 |
+
CausalSelfAttention,
|
| 22 |
+
DropoutGPT,
|
| 23 |
+
GPTConfig,
|
| 24 |
+
Linear,
|
| 25 |
+
MLP,
|
| 26 |
+
apply_rotary_emb,
|
| 27 |
+
rms_norm,
|
| 28 |
+
)
|
| 29 |
+
|
| 30 |
+
__all__ = [
|
| 31 |
+
"Block",
|
| 32 |
+
"CausalSelfAttention",
|
| 33 |
+
"DropoutGPT",
|
| 34 |
+
"GPTConfig",
|
| 35 |
+
"Linear",
|
| 36 |
+
"MLP",
|
| 37 |
+
"apply_rotary_emb",
|
| 38 |
+
"rms_norm",
|
| 39 |
+
]
|
src/dropout_decay/models/gpt.py
ADDED
|
@@ -0,0 +1,227 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Derived from Andrej Karpathy's nanochat project.
|
| 3 |
+
|
| 4 |
+
MIT License
|
| 5 |
+
|
| 6 |
+
Copyright (c) 2025 Andrej Karpathy
|
| 7 |
+
|
| 8 |
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
| 9 |
+
of this software and associated documentation files (the "Software"), to deal
|
| 10 |
+
in the Software without restriction, including without limitation the rights
|
| 11 |
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
| 12 |
+
copies of the Software, and to permit persons to whom the Software is
|
| 13 |
+
furnished to do so, subject to the following conditions:
|
| 14 |
+
|
| 15 |
+
The above copyright notice and this permission notice shall be included in all
|
| 16 |
+
copies or substantial portions of the Software.
|
| 17 |
+
"""
|
| 18 |
+
|
| 19 |
+
from __future__ import annotations
|
| 20 |
+
|
| 21 |
+
from dataclasses import asdict, dataclass
|
| 22 |
+
import math
|
| 23 |
+
|
| 24 |
+
import torch
|
| 25 |
+
import torch.nn as nn
|
| 26 |
+
import torch.nn.functional as F
|
| 27 |
+
|
| 28 |
+
|
| 29 |
+
@dataclass(frozen=True)
|
| 30 |
+
class GPTConfig:
|
| 31 |
+
block_size: int
|
| 32 |
+
vocab_size: int
|
| 33 |
+
n_layer: int
|
| 34 |
+
n_head: int
|
| 35 |
+
n_embd: int
|
| 36 |
+
dropout: float
|
| 37 |
+
|
| 38 |
+
def to_dict(self) -> dict[str, int | float]:
|
| 39 |
+
return asdict(self)
|
| 40 |
+
|
| 41 |
+
|
| 42 |
+
def rms_norm(x: torch.Tensor) -> torch.Tensor:
|
| 43 |
+
return F.rms_norm(x, (x.size(-1),))
|
| 44 |
+
|
| 45 |
+
|
| 46 |
+
class Linear(nn.Linear):
|
| 47 |
+
"""Bias-free linear layer matching the nanochat architectural style."""
|
| 48 |
+
|
| 49 |
+
def __init__(self, in_features: int, out_features: int):
|
| 50 |
+
super().__init__(in_features, out_features, bias=False)
|
| 51 |
+
|
| 52 |
+
|
| 53 |
+
def apply_rotary_emb(
|
| 54 |
+
x: torch.Tensor, cos: torch.Tensor, sin: torch.Tensor
|
| 55 |
+
) -> torch.Tensor:
|
| 56 |
+
half = x.shape[-1] // 2
|
| 57 |
+
x1, x2 = x[..., :half], x[..., half:]
|
| 58 |
+
return torch.cat((x1 * cos + x2 * sin, x1 * (-sin) + x2 * cos), dim=-1)
|
| 59 |
+
|
| 60 |
+
|
| 61 |
+
class CausalSelfAttention(nn.Module):
|
| 62 |
+
def __init__(self, config: GPTConfig):
|
| 63 |
+
super().__init__()
|
| 64 |
+
if config.n_embd % config.n_head != 0:
|
| 65 |
+
raise ValueError("n_embd must be divisible by n_head")
|
| 66 |
+
self.n_head = config.n_head
|
| 67 |
+
self.head_dim = config.n_embd // config.n_head
|
| 68 |
+
if self.head_dim % 2 != 0:
|
| 69 |
+
raise ValueError("rotary attention requires an even head dimension")
|
| 70 |
+
self.dropout_p = config.dropout
|
| 71 |
+
self.c_q = Linear(config.n_embd, config.n_embd)
|
| 72 |
+
self.c_k = Linear(config.n_embd, config.n_embd)
|
| 73 |
+
self.c_v = Linear(config.n_embd, config.n_embd)
|
| 74 |
+
self.c_proj = Linear(config.n_embd, config.n_embd)
|
| 75 |
+
self.resid_dropout = nn.Dropout(config.dropout)
|
| 76 |
+
mask = torch.ones(config.block_size, config.block_size, dtype=torch.bool).tril()
|
| 77 |
+
self.register_buffer(
|
| 78 |
+
"causal_mask",
|
| 79 |
+
mask.view(1, 1, config.block_size, config.block_size),
|
| 80 |
+
persistent=False,
|
| 81 |
+
)
|
| 82 |
+
|
| 83 |
+
def set_dropout(self, p: float) -> None:
|
| 84 |
+
self.dropout_p = p
|
| 85 |
+
self.resid_dropout.p = p
|
| 86 |
+
|
| 87 |
+
def forward(
|
| 88 |
+
self, x: torch.Tensor, cos: torch.Tensor, sin: torch.Tensor
|
| 89 |
+
) -> torch.Tensor:
|
| 90 |
+
batch, seq_len, channels = x.shape
|
| 91 |
+
q = self.c_q(x).view(batch, seq_len, self.n_head, self.head_dim).transpose(1, 2)
|
| 92 |
+
k = self.c_k(x).view(batch, seq_len, self.n_head, self.head_dim).transpose(1, 2)
|
| 93 |
+
v = self.c_v(x).view(batch, seq_len, self.n_head, self.head_dim).transpose(1, 2)
|
| 94 |
+
|
| 95 |
+
q = rms_norm(apply_rotary_emb(q, cos, sin)) * 1.2
|
| 96 |
+
k = rms_norm(apply_rotary_emb(k, cos, sin)) * 1.2
|
| 97 |
+
|
| 98 |
+
att = (q @ k.transpose(-2, -1)) / math.sqrt(self.head_dim)
|
| 99 |
+
mask = self.causal_mask[:, :, :seq_len, :seq_len]
|
| 100 |
+
att = att.masked_fill(~mask, float("-inf"))
|
| 101 |
+
att = F.softmax(att.float(), dim=-1).to(dtype=x.dtype)
|
| 102 |
+
att = F.dropout(att, p=self.dropout_p, training=self.training)
|
| 103 |
+
y = att @ v
|
| 104 |
+
y = y.transpose(1, 2).contiguous().view(batch, seq_len, channels)
|
| 105 |
+
return self.resid_dropout(self.c_proj(y))
|
| 106 |
+
|
| 107 |
+
|
| 108 |
+
class MLP(nn.Module):
|
| 109 |
+
def __init__(self, config: GPTConfig):
|
| 110 |
+
super().__init__()
|
| 111 |
+
self.c_fc = Linear(config.n_embd, 4 * config.n_embd)
|
| 112 |
+
self.c_proj = Linear(4 * config.n_embd, config.n_embd)
|
| 113 |
+
self.dropout = nn.Dropout(config.dropout)
|
| 114 |
+
|
| 115 |
+
def set_dropout(self, p: float) -> None:
|
| 116 |
+
self.dropout.p = p
|
| 117 |
+
|
| 118 |
+
def forward(self, x: torch.Tensor) -> torch.Tensor:
|
| 119 |
+
x = self.c_fc(x)
|
| 120 |
+
x = F.relu(x).square()
|
| 121 |
+
x = self.c_proj(x)
|
| 122 |
+
return self.dropout(x)
|
| 123 |
+
|
| 124 |
+
|
| 125 |
+
class Block(nn.Module):
|
| 126 |
+
def __init__(self, config: GPTConfig):
|
| 127 |
+
super().__init__()
|
| 128 |
+
self.attn = CausalSelfAttention(config)
|
| 129 |
+
self.mlp = MLP(config)
|
| 130 |
+
|
| 131 |
+
def set_dropout(self, p: float) -> None:
|
| 132 |
+
self.attn.set_dropout(p)
|
| 133 |
+
self.mlp.set_dropout(p)
|
| 134 |
+
|
| 135 |
+
def forward(
|
| 136 |
+
self, x: torch.Tensor, cos: torch.Tensor, sin: torch.Tensor
|
| 137 |
+
) -> torch.Tensor:
|
| 138 |
+
x = x + self.attn(rms_norm(x), cos, sin)
|
| 139 |
+
x = x + self.mlp(rms_norm(x))
|
| 140 |
+
return x
|
| 141 |
+
|
| 142 |
+
|
| 143 |
+
class DropoutGPT(nn.Module):
|
| 144 |
+
"""Minimal nanochat-style causal Transformer with dynamic dropout control."""
|
| 145 |
+
|
| 146 |
+
def __init__(self, config: GPTConfig):
|
| 147 |
+
super().__init__()
|
| 148 |
+
self.config = config
|
| 149 |
+
self.token_embedding = nn.Embedding(config.vocab_size, config.n_embd)
|
| 150 |
+
self.embedding_dropout = nn.Dropout(config.dropout)
|
| 151 |
+
self.blocks = nn.ModuleList(Block(config) for _ in range(config.n_layer))
|
| 152 |
+
self.lm_head = Linear(config.n_embd, config.vocab_size)
|
| 153 |
+
self.register_buffer(
|
| 154 |
+
"cos",
|
| 155 |
+
torch.empty(1, 1, config.block_size, config.n_embd // config.n_head // 2),
|
| 156 |
+
persistent=False,
|
| 157 |
+
)
|
| 158 |
+
self.register_buffer(
|
| 159 |
+
"sin",
|
| 160 |
+
torch.empty(1, 1, config.block_size, config.n_embd // config.n_head // 2),
|
| 161 |
+
persistent=False,
|
| 162 |
+
)
|
| 163 |
+
self._init_weights()
|
| 164 |
+
self._init_rotary()
|
| 165 |
+
|
| 166 |
+
@torch.no_grad()
|
| 167 |
+
def _init_weights(self) -> None:
|
| 168 |
+
nn.init.normal_(self.token_embedding.weight, mean=0.0, std=0.8)
|
| 169 |
+
nn.init.normal_(self.lm_head.weight, mean=0.0, std=0.001)
|
| 170 |
+
scale = (3.0 ** 0.5) * (self.config.n_embd ** -0.5)
|
| 171 |
+
for block in self.blocks:
|
| 172 |
+
nn.init.uniform_(block.attn.c_q.weight, -scale, scale)
|
| 173 |
+
nn.init.uniform_(block.attn.c_k.weight, -scale, scale)
|
| 174 |
+
nn.init.uniform_(block.attn.c_v.weight, -scale, scale)
|
| 175 |
+
nn.init.zeros_(block.attn.c_proj.weight)
|
| 176 |
+
nn.init.uniform_(block.mlp.c_fc.weight, -0.4 * scale, 0.4 * scale)
|
| 177 |
+
nn.init.zeros_(block.mlp.c_proj.weight)
|
| 178 |
+
|
| 179 |
+
@torch.no_grad()
|
| 180 |
+
def _init_rotary(self) -> None:
|
| 181 |
+
head_dim = self.config.n_embd // self.config.n_head
|
| 182 |
+
channel_range = torch.arange(
|
| 183 |
+
0,
|
| 184 |
+
head_dim,
|
| 185 |
+
2,
|
| 186 |
+
dtype=torch.float32,
|
| 187 |
+
device=self.cos.device,
|
| 188 |
+
)
|
| 189 |
+
inv_freq = 1.0 / (100000 ** (channel_range / head_dim))
|
| 190 |
+
positions = torch.arange(
|
| 191 |
+
self.config.block_size,
|
| 192 |
+
dtype=torch.float32,
|
| 193 |
+
device=self.cos.device,
|
| 194 |
+
)
|
| 195 |
+
freqs = torch.outer(positions, inv_freq)
|
| 196 |
+
self.cos.copy_(freqs.cos().view(1, 1, self.config.block_size, head_dim // 2))
|
| 197 |
+
self.sin.copy_(freqs.sin().view(1, 1, self.config.block_size, head_dim // 2))
|
| 198 |
+
|
| 199 |
+
def set_dropout(self, p: float) -> None:
|
| 200 |
+
if not 0.0 <= p < 1.0:
|
| 201 |
+
raise ValueError("dropout must satisfy 0 <= p < 1")
|
| 202 |
+
self.embedding_dropout.p = p
|
| 203 |
+
for block in self.blocks:
|
| 204 |
+
block.set_dropout(p)
|
| 205 |
+
|
| 206 |
+
def num_parameters(self) -> int:
|
| 207 |
+
return sum(p.numel() for p in self.parameters())
|
| 208 |
+
|
| 209 |
+
def forward(
|
| 210 |
+
self, idx: torch.Tensor, targets: torch.Tensor | None = None
|
| 211 |
+
) -> torch.Tensor | tuple[torch.Tensor, torch.Tensor]:
|
| 212 |
+
_, seq_len = idx.shape
|
| 213 |
+
if seq_len > self.config.block_size:
|
| 214 |
+
raise ValueError("sequence length exceeds block_size")
|
| 215 |
+
cos = self.cos[:, :, :seq_len, :]
|
| 216 |
+
sin = self.sin[:, :, :seq_len, :]
|
| 217 |
+
x = self.embedding_dropout(rms_norm(self.token_embedding(idx)))
|
| 218 |
+
for block in self.blocks:
|
| 219 |
+
x = block(x, cos, sin)
|
| 220 |
+
logits = self.lm_head(rms_norm(x)).float()
|
| 221 |
+
if targets is None:
|
| 222 |
+
return logits
|
| 223 |
+
loss = F.cross_entropy(
|
| 224 |
+
logits.reshape(-1, logits.size(-1)),
|
| 225 |
+
targets.reshape(-1),
|
| 226 |
+
)
|
| 227 |
+
return logits, loss
|
src/dropout_decay/scheduler.py
CHANGED
|
@@ -16,41 +16,9 @@ The above copyright notice and this permission notice shall be included in all
|
|
| 16 |
copies or substantial portions of the Software.
|
| 17 |
"""
|
| 18 |
|
| 19 |
-
from
|
| 20 |
|
| 21 |
-
|
| 22 |
-
|
| 23 |
-
|
| 24 |
-
|
| 25 |
-
@dataclass(frozen=True)
|
| 26 |
-
class DropoutDecayConfig:
|
| 27 |
-
initial_dropout: float = 0.80
|
| 28 |
-
final_dropout: float = 0.10
|
| 29 |
-
decay_tokens: int = 40_000_000
|
| 30 |
-
schedule: str = "cosine"
|
| 31 |
-
|
| 32 |
-
|
| 33 |
-
class DropoutDecayScheduler:
|
| 34 |
-
"""Smoothly maps total processed/observed tokens to an active dropout rate."""
|
| 35 |
-
|
| 36 |
-
def __init__(self, config: DropoutDecayConfig):
|
| 37 |
-
if not 0.0 <= config.final_dropout <= config.initial_dropout < 1.0:
|
| 38 |
-
raise ValueError("dropout values must satisfy 0 <= final <= initial < 1")
|
| 39 |
-
if config.decay_tokens <= 0:
|
| 40 |
-
raise ValueError("decay_tokens must be positive")
|
| 41 |
-
if config.schedule not in {"cosine", "smoothstep", "linear"}:
|
| 42 |
-
raise ValueError("schedule must be cosine, smoothstep, or linear")
|
| 43 |
-
self.config = config
|
| 44 |
-
|
| 45 |
-
def value(self, tokens_seen: int) -> float:
|
| 46 |
-
progress = min(max(tokens_seen / self.config.decay_tokens, 0.0), 1.0)
|
| 47 |
-
if self.config.schedule == "cosine":
|
| 48 |
-
mix = 0.5 * (1.0 + math.cos(math.pi * progress))
|
| 49 |
-
elif self.config.schedule == "smoothstep":
|
| 50 |
-
smooth = progress * progress * (3.0 - 2.0 * progress)
|
| 51 |
-
mix = 1.0 - smooth
|
| 52 |
-
else:
|
| 53 |
-
mix = 1.0 - progress
|
| 54 |
-
return self.config.final_dropout + (
|
| 55 |
-
self.config.initial_dropout - self.config.final_dropout
|
| 56 |
-
) * mix
|
|
|
|
| 16 |
copies or substantial portions of the Software.
|
| 17 |
"""
|
| 18 |
|
| 19 |
+
from dropout_decay.schedules.dropout import DropoutDecayConfig, DropoutDecayScheduler
|
| 20 |
|
| 21 |
+
__all__ = [
|
| 22 |
+
"DropoutDecayConfig",
|
| 23 |
+
"DropoutDecayScheduler",
|
| 24 |
+
]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
src/dropout_decay/schedules/__init__.py
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Derived from Andrej Karpathy's nanochat project.
|
| 3 |
+
|
| 4 |
+
MIT License
|
| 5 |
+
|
| 6 |
+
Copyright (c) 2025 Andrej Karpathy
|
| 7 |
+
|
| 8 |
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
| 9 |
+
of this software and associated documentation files (the "Software"), to deal
|
| 10 |
+
in the Software without restriction, including without limitation the rights
|
| 11 |
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
| 12 |
+
copies of the Software, and to permit persons to whom the Software is
|
| 13 |
+
furnished to do so, subject to the following conditions:
|
| 14 |
+
|
| 15 |
+
The above copyright notice and this permission notice shall be included in all
|
| 16 |
+
copies or substantial portions of the Software.
|
| 17 |
+
"""
|
| 18 |
+
|
| 19 |
+
from dropout_decay.schedules.dropout import DropoutDecayConfig, DropoutDecayScheduler
|
| 20 |
+
|
| 21 |
+
__all__ = [
|
| 22 |
+
"DropoutDecayConfig",
|
| 23 |
+
"DropoutDecayScheduler",
|
| 24 |
+
]
|
src/dropout_decay/schedules/dropout.py
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Derived from Andrej Karpathy's nanochat project.
|
| 3 |
+
|
| 4 |
+
MIT License
|
| 5 |
+
|
| 6 |
+
Copyright (c) 2025 Andrej Karpathy
|
| 7 |
+
|
| 8 |
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
| 9 |
+
of this software and associated documentation files (the "Software"), to deal
|
| 10 |
+
in the Software without restriction, including without limitation the rights
|
| 11 |
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
| 12 |
+
copies of the Software, and to permit persons to whom the Software is
|
| 13 |
+
furnished to do so, subject to the following conditions:
|
| 14 |
+
|
| 15 |
+
The above copyright notice and this permission notice shall be included in all
|
| 16 |
+
copies or substantial portions of the Software.
|
| 17 |
+
"""
|
| 18 |
+
|
| 19 |
+
from __future__ import annotations
|
| 20 |
+
|
| 21 |
+
from dataclasses import dataclass
|
| 22 |
+
import math
|
| 23 |
+
|
| 24 |
+
|
| 25 |
+
@dataclass(frozen=True)
|
| 26 |
+
class DropoutDecayConfig:
|
| 27 |
+
initial_dropout: float = 0.80
|
| 28 |
+
final_dropout: float = 0.10
|
| 29 |
+
decay_tokens: int = 40_000_000
|
| 30 |
+
schedule: str = "cosine"
|
| 31 |
+
|
| 32 |
+
|
| 33 |
+
class DropoutDecayScheduler:
|
| 34 |
+
"""Smoothly maps total processed/observed tokens to an active dropout rate."""
|
| 35 |
+
|
| 36 |
+
def __init__(self, config: DropoutDecayConfig):
|
| 37 |
+
if not 0.0 <= config.final_dropout <= config.initial_dropout < 1.0:
|
| 38 |
+
raise ValueError("dropout values must satisfy 0 <= final <= initial < 1")
|
| 39 |
+
if config.decay_tokens <= 0:
|
| 40 |
+
raise ValueError("decay_tokens must be positive")
|
| 41 |
+
if config.schedule not in {"cosine", "smoothstep", "linear"}:
|
| 42 |
+
raise ValueError("schedule must be cosine, smoothstep, or linear")
|
| 43 |
+
self.config = config
|
| 44 |
+
|
| 45 |
+
def value(self, tokens_seen: int) -> float:
|
| 46 |
+
progress = min(max(tokens_seen / self.config.decay_tokens, 0.0), 1.0)
|
| 47 |
+
if self.config.schedule == "cosine":
|
| 48 |
+
mix = 0.5 * (1.0 + math.cos(math.pi * progress))
|
| 49 |
+
elif self.config.schedule == "smoothstep":
|
| 50 |
+
smooth = progress * progress * (3.0 - 2.0 * progress)
|
| 51 |
+
mix = 1.0 - smooth
|
| 52 |
+
else:
|
| 53 |
+
mix = 1.0 - progress
|
| 54 |
+
return self.config.final_dropout + (
|
| 55 |
+
self.config.initial_dropout - self.config.final_dropout
|
| 56 |
+
) * mix
|
src/dropout_decay/specs/__init__.py
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Derived from Andrej Karpathy's nanochat project.
|
| 3 |
+
|
| 4 |
+
MIT License
|
| 5 |
+
|
| 6 |
+
Copyright (c) 2025 Andrej Karpathy
|
| 7 |
+
|
| 8 |
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
| 9 |
+
of this software and associated documentation files (the "Software"), to deal
|
| 10 |
+
in the Software without restriction, including without limitation the rights
|
| 11 |
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
| 12 |
+
copies of the Software, and to permit persons to whom the Software is
|
| 13 |
+
furnished to do so, subject to the following conditions:
|
| 14 |
+
|
| 15 |
+
The above copyright notice and this permission notice shall be included in all
|
| 16 |
+
copies or substantial portions of the Software.
|
| 17 |
+
"""
|
| 18 |
+
|
| 19 |
+
from dropout_decay.specs.dropout_condition import DropoutCondition, anchor_dropout
|
| 20 |
+
from dropout_decay.specs.model_spec import ModelSpec
|
| 21 |
+
|
| 22 |
+
__all__ = [
|
| 23 |
+
"DropoutCondition",
|
| 24 |
+
"ModelSpec",
|
| 25 |
+
"anchor_dropout",
|
| 26 |
+
]
|
src/dropout_decay/specs/dropout_condition.py
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Derived from Andrej Karpathy's nanochat project.
|
| 3 |
+
|
| 4 |
+
MIT License
|
| 5 |
+
|
| 6 |
+
Copyright (c) 2025 Andrej Karpathy
|
| 7 |
+
|
| 8 |
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
| 9 |
+
of this software and associated documentation files (the "Software"), to deal
|
| 10 |
+
in the Software without restriction, including without limitation the rights
|
| 11 |
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
| 12 |
+
copies of the Software, and to permit persons to whom the Software is
|
| 13 |
+
furnished to do so, subject to the following conditions:
|
| 14 |
+
|
| 15 |
+
The above copyright notice and this permission notice shall be included in all
|
| 16 |
+
copies or substantial portions of the Software.
|
| 17 |
+
"""
|
| 18 |
+
|
| 19 |
+
from __future__ import annotations
|
| 20 |
+
|
| 21 |
+
from dataclasses import dataclass
|
| 22 |
+
import math
|
| 23 |
+
from typing import Callable
|
| 24 |
+
|
| 25 |
+
from dropout_decay.schedules import DropoutDecayConfig, DropoutDecayScheduler
|
| 26 |
+
|
| 27 |
+
|
| 28 |
+
@dataclass(frozen=True)
|
| 29 |
+
class DropoutCondition:
|
| 30 |
+
name: str
|
| 31 |
+
kind: str
|
| 32 |
+
initial: float
|
| 33 |
+
final: float
|
| 34 |
+
schedule: str = "constant"
|
| 35 |
+
decay_tokens: int | None = None
|
| 36 |
+
anchors: tuple[tuple[int, float], ...] = ()
|
| 37 |
+
|
| 38 |
+
def to_dict(self) -> dict:
|
| 39 |
+
return {
|
| 40 |
+
"name": self.name,
|
| 41 |
+
"kind": self.kind,
|
| 42 |
+
"initial": self.initial,
|
| 43 |
+
"final": self.final,
|
| 44 |
+
"schedule": self.schedule,
|
| 45 |
+
"decay_tokens": self.decay_tokens,
|
| 46 |
+
"anchors": [list(anchor) for anchor in self.anchors],
|
| 47 |
+
}
|
| 48 |
+
|
| 49 |
+
def make_fn(
|
| 50 |
+
self,
|
| 51 |
+
fallback_decay_tokens: int,
|
| 52 |
+
unique_tokens: int | None = None,
|
| 53 |
+
) -> Callable[[int], float]:
|
| 54 |
+
if self.kind == "static":
|
| 55 |
+
return lambda _tokens_seen, p=self.initial: p
|
| 56 |
+
if self.kind == "anchor_decay":
|
| 57 |
+
if unique_tokens is None:
|
| 58 |
+
raise ValueError("anchor_decay conditions require unique_tokens")
|
| 59 |
+
p = anchor_dropout(unique_tokens, self.anchors)
|
| 60 |
+
return lambda _tokens_seen, p=p: p
|
| 61 |
+
scheduler = DropoutDecayScheduler(
|
| 62 |
+
DropoutDecayConfig(
|
| 63 |
+
initial_dropout=self.initial,
|
| 64 |
+
final_dropout=self.final,
|
| 65 |
+
decay_tokens=self.decay_tokens or fallback_decay_tokens,
|
| 66 |
+
schedule=self.schedule,
|
| 67 |
+
)
|
| 68 |
+
)
|
| 69 |
+
return scheduler.value
|
| 70 |
+
|
| 71 |
+
|
| 72 |
+
def anchor_dropout(unique_tokens: int, anchors: tuple[tuple[int, float], ...]) -> float:
|
| 73 |
+
if not anchors:
|
| 74 |
+
raise ValueError("anchor dropout schedule requires at least one anchor")
|
| 75 |
+
ordered = sorted(anchors)
|
| 76 |
+
if unique_tokens <= ordered[0][0]:
|
| 77 |
+
return ordered[0][1]
|
| 78 |
+
if unique_tokens >= ordered[-1][0]:
|
| 79 |
+
return ordered[-1][1]
|
| 80 |
+
log_unique = math.log(unique_tokens)
|
| 81 |
+
for (left_tokens, left_dropout), (right_tokens, right_dropout) in zip(
|
| 82 |
+
ordered, ordered[1:]
|
| 83 |
+
):
|
| 84 |
+
if left_tokens <= unique_tokens <= right_tokens:
|
| 85 |
+
left_log = math.log(left_tokens)
|
| 86 |
+
right_log = math.log(right_tokens)
|
| 87 |
+
mix = (log_unique - left_log) / (right_log - left_log)
|
| 88 |
+
return left_dropout + mix * (right_dropout - left_dropout)
|
| 89 |
+
return ordered[-1][1]
|