Mandeep Sidhu commited on
Commit
58904bb
·
1 Parent(s): e7a7275

Prepare publication-ready research artifact

Browse files
Files changed (38) hide show
  1. .cache/dropout_decay_tinystories_8m/tokenizer-v4096.json +0 -0
  2. .cache/dropout_decay_tinystories_8m/tokens-v4096-uint16.npy +3 -0
  3. .cache/dropout_decay_wikitext103_8m/tokenizer-v4096.json +0 -0
  4. .cache/dropout_decay_wikitext103_8m/tokens-v4096-uint16.npy +3 -0
  5. .gitignore +9 -0
  6. README.md +26 -16
  7. REPRODUCING.md +75 -10
  8. paper/dropout_decay_pressure_law.tex +0 -501
  9. paper/dropout_decay_regime_specific_pressure_law.bbl +0 -73
  10. paper/dropout_decay_regime_specific_pressure_law.pdf +2 -2
  11. paper/dropout_decay_regime_specific_pressure_law.tex +303 -60
  12. paper/figures/calibration_cell_curves.tex +0 -2
  13. paper/figures/calibration_to_streaming_protocol.tex +27 -0
  14. paper/figures/static_optima_by_regime.tex +9 -9
  15. paper/paper_results_summary.json +355 -0
  16. paper/tables/calibration_weight_table.tex +10 -0
  17. paper/tables/coefficient_table.tex +1 -1
  18. paper/tables/evidence_ladder_table.tex +9 -0
  19. paper/tables/experiment_protocol_table.tex +11 -0
  20. paper/tables/l20_schedule_path_table.tex +9 -0
  21. paper/tables/l20_stage_table.tex +1 -1
  22. paper/tables/paired_gain_ci_table.tex +12 -0
  23. paper/tables/protocol_budget_table.tex +12 -0
  24. paper/tables/regime_setup_table.tex +9 -0
  25. paper/tables/reproducibility_table.tex +13 -0
  26. paper/tables/streaming_validation_table.tex +4 -4
  27. paper/tables/symbol_table.tex +15 -0
  28. pyproject.toml +12 -0
  29. scripts/generate_paper_assets.py +353 -15
  30. scripts/summarize_cross_regime_backtest.py +1 -1
  31. src/dropout_decay/data/__init__.py +2 -0
  32. src/dropout_decay/experiment.py +2 -0
  33. src/dropout_decay/model.py +2 -0
  34. src/dropout_decay/scheduler.py +2 -0
  35. src/dropout_decay/tokenizer.py +2 -0
  36. tests/test_data_and_device.py +55 -0
  37. tests/test_schedules.py +74 -0
  38. tests/test_specs_and_parsing.py +50 -0
.cache/dropout_decay_tinystories_8m/tokenizer-v4096.json ADDED
The diff for this file is too large to render. See raw diff
 
.cache/dropout_decay_tinystories_8m/tokens-v4096-uint16.npy ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:35221770a252f4f3b4e86210495bbd02fc7830fba3ea0dc92deab4e98af60103
3
+ size 17000354
.cache/dropout_decay_wikitext103_8m/tokenizer-v4096.json ADDED
The diff for this file is too large to render. See raw diff
 
.cache/dropout_decay_wikitext103_8m/tokens-v4096-uint16.npy ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:2198e999a2292ddfb549781af602d790356c3afd8cc1008c07cb96b58aa60a20
3
+ size 17000462
.gitignore CHANGED
@@ -11,6 +11,11 @@ __pycache__/
11
  *.npy
12
  .venv/
13
  *.egg-info/
 
 
 
 
 
14
 
15
  # Keep generated scratch artifacts out by default, but allow the curated
16
  # reproducibility caches and corpora that make this repo standalone.
@@ -23,6 +28,10 @@ __pycache__/
23
  !.cache/dropout_decay_wikitext103/**
24
  !.cache/dropout_decay_openwebtext10k_8m/
25
  !.cache/dropout_decay_openwebtext10k_8m/**
 
 
 
 
26
 
27
  data/*
28
  !data/openwebtext10k/
 
11
  *.npy
12
  .venv/
13
  *.egg-info/
14
+ .pytest_cache/
15
+ .mypy_cache/
16
+ .ruff_cache/
17
+ build/
18
+ dist/
19
 
20
  # Keep generated scratch artifacts out by default, but allow the curated
21
  # reproducibility caches and corpora that make this repo standalone.
 
28
  !.cache/dropout_decay_wikitext103/**
29
  !.cache/dropout_decay_openwebtext10k_8m/
30
  !.cache/dropout_decay_openwebtext10k_8m/**
31
+ !.cache/dropout_decay_tinystories_8m/
32
+ !.cache/dropout_decay_tinystories_8m/**
33
+ !.cache/dropout_decay_wikitext103_8m/
34
+ !.cache/dropout_decay_wikitext103_8m/**
35
 
36
  data/*
37
  !data/openwebtext10k/
README.md CHANGED
@@ -54,11 +54,24 @@ The existing repo-local caches are:
54
  - `.cache/dropout_decay_tinystories/tokens-v4096-uint16.npy`
55
  - `.cache/dropout_decay_wikitext103/tokenizer-v4096.json`
56
  - `.cache/dropout_decay_wikitext103/tokens-v4096-uint16.npy`
 
 
 
 
 
 
57
 
58
  Use a project-local Python environment with MPS-capable PyTorch, for example
59
  `.venv/bin/python`. Attribution to nanochat remains in the source and docs, but
60
  experiment commands should not point into a separate nanochat repository.
61
 
 
 
 
 
 
 
 
62
  ## Workflow
63
 
64
  1. Screen candidate model sizes with cheap static dropout sweeps.
@@ -76,22 +89,19 @@ Every run writes:
76
  - `model_selection.csv` / `model_selection.json`: static-sweep optimum and
77
  plateau diagnostics for screen and confirm runs.
78
 
79
- Old exploratory outputs are archived under `archive/`.
80
-
81
- For exact headline reproduction, see `REPRODUCING.md`. For the current
82
- first-reader hypothesis document, see
83
- `docs/current_hypothesis_for_ml_engineers.md`. For the denser run-by-run
84
- research summary, see `docs/dropout_decay_research_report_v2.md`. For the corpus
85
- difficulty follow-up that motivated the next formula refinement, see
86
- `docs/corpus_difficulty_probe_20260529.md`. For the first single-seed
87
- probe-calibrated streaming test, see
88
- `docs/probe_calibrated_stream_20260529.md`. For the coefficient refit using only
89
- existing saved results, see
90
- `docs/coefficient_refit_existing_data_20260529.md`. For a first-reader report on
91
- regime definition and coefficient interpretation, see
92
- `docs/regime_coefficient_report_20260529.md`. For the live coefficient
93
- calibration plan, see `docs/coefficient_calibration_plan.md`. For the current
94
- arXiv-style paper draft, see `paper/dropout_decay_pressure_law.tex`.
95
 
96
  ## Step 1: Cheap Static Screen
97
 
 
54
  - `.cache/dropout_decay_tinystories/tokens-v4096-uint16.npy`
55
  - `.cache/dropout_decay_wikitext103/tokenizer-v4096.json`
56
  - `.cache/dropout_decay_wikitext103/tokens-v4096-uint16.npy`
57
+ - `.cache/dropout_decay_openwebtext10k_8m/tokenizer-v4096.json`
58
+ - `.cache/dropout_decay_openwebtext10k_8m/tokens-v4096-uint16.npy`
59
+ - `.cache/dropout_decay_tinystories_8m/tokenizer-v4096.json`
60
+ - `.cache/dropout_decay_tinystories_8m/tokens-v4096-uint16.npy`
61
+ - `.cache/dropout_decay_wikitext103_8m/tokenizer-v4096.json`
62
+ - `.cache/dropout_decay_wikitext103_8m/tokens-v4096-uint16.npy`
63
 
64
  Use a project-local Python environment with MPS-capable PyTorch, for example
65
  `.venv/bin/python`. Attribution to nanochat remains in the source and docs, but
66
  experiment commands should not point into a separate nanochat repository.
67
 
68
+ Fast non-training checks can be run without launching an MPS experiment:
69
+
70
+ ```bash
71
+ PYTHONPATH=src .venv/bin/python -m unittest discover -s tests -v
72
+ PYTHONPATH=src .venv/bin/python scripts/generate_paper_assets.py
73
+ ```
74
+
75
  ## Workflow
76
 
77
  1. Screen candidate model sizes with cheap static dropout sweeps.
 
89
  - `model_selection.csv` / `model_selection.json`: static-sweep optimum and
90
  plateau diagnostics for screen and confirm runs.
91
 
92
+ Old exploratory outputs are archived under `archive/`; they are not part of the
93
+ primary evidence path for the paper.
94
+
95
+ For exact headline reproduction, see `REPRODUCING.md`. Current documentation is:
96
+
97
+ - `docs/plan.md`: regime-validation and coefficient-fitting plan.
98
+ - `docs/formula_coefficient_methodology.md`: coefficient target extraction,
99
+ weighting, and fitting details.
100
+ - `docs/openwebtext10k_streaming_report.md`: OpenWebText10K regime report.
101
+ - `docs/tinystories_streaming_report.md`: TinyStories regime report.
102
+ - `docs/wikitext103_streaming_report.md`: WikiText-103 regime report.
103
+ - `paper/dropout_decay_regime_specific_pressure_law.tex`: paper source.
104
+ - `paper/dropout_decay_regime_specific_pressure_law.pdf`: generated paper PDF.
 
 
 
105
 
106
  ## Step 1: Cheap Static Screen
107
 
REPRODUCING.md CHANGED
@@ -21,6 +21,12 @@ python3.11 -m venv .venv
21
  .venv/bin/python -m pip install -e .
22
  ```
23
 
 
 
 
 
 
 
24
  Verify MPS:
25
 
26
  ```bash
@@ -58,6 +64,12 @@ The experiments in the current report used these repo-local caches:
58
  .cache/dropout_decay_tinystories/tokens-v4096-uint16.npy
59
  .cache/dropout_decay_wikitext103/tokenizer-v4096.json
60
  .cache/dropout_decay_wikitext103/tokens-v4096-uint16.npy
 
 
 
 
 
 
61
  ```
62
 
63
  These binary artifacts are intended to be published with the repository through
@@ -124,18 +136,36 @@ Expected:
124
  5000970 500000
125
  ```
126
 
127
- ## Headline Formula
 
 
 
 
 
 
128
 
129
- The tested formula is:
130
 
131
  ```text
132
- p = clamp(0.02, 0.65,
133
- 0.154 * log10(params / unique_tokens)
134
- + 0.249 * log10(cumulative_sampled_tokens / unique_tokens)
135
- - 0.210)
 
136
  ```
137
 
138
- For the standard protocol:
 
 
 
 
 
 
 
 
 
 
 
139
 
140
  - stream prefixes: `250000 500000 1000000 2000000 4000000`
141
  - stage steps: `1000`
@@ -143,6 +173,40 @@ For the standard protocol:
143
  - block size: `128`
144
  - cumulative sampled tokens after stage `i`: `i * 1000 * 16 * 128`
145
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
146
  ## Reproduce Model-Size Validation
147
 
148
  Example L12 command:
@@ -386,6 +450,7 @@ paired low-decay wins: 3/3
386
  - Do not claim the formula is universal.
387
  - The supported claim is final-validation improvement under this
388
  expanding-prefix protocol.
389
- - PDFs are generated artifacts and are ignored by Git.
390
- - The exact cached token file should be published through an appropriate binary
391
- artifact mechanism once dataset provenance is finalized.
 
 
21
  .venv/bin/python -m pip install -e .
22
  ```
23
 
24
+ For development and publication checks, install the optional test dependency:
25
+
26
+ ```bash
27
+ .venv/bin/python -m pip install -e ".[dev]"
28
+ ```
29
+
30
  Verify MPS:
31
 
32
  ```bash
 
64
  .cache/dropout_decay_tinystories/tokens-v4096-uint16.npy
65
  .cache/dropout_decay_wikitext103/tokenizer-v4096.json
66
  .cache/dropout_decay_wikitext103/tokens-v4096-uint16.npy
67
+ .cache/dropout_decay_openwebtext10k_8m/tokenizer-v4096.json
68
+ .cache/dropout_decay_openwebtext10k_8m/tokens-v4096-uint16.npy
69
+ .cache/dropout_decay_tinystories_8m/tokenizer-v4096.json
70
+ .cache/dropout_decay_tinystories_8m/tokens-v4096-uint16.npy
71
+ .cache/dropout_decay_wikitext103_8m/tokenizer-v4096.json
72
+ .cache/dropout_decay_wikitext103_8m/tokens-v4096-uint16.npy
73
  ```
74
 
75
  These binary artifacts are intended to be published with the repository through
 
136
  5000970 500000
137
  ```
138
 
139
+ Run the fast non-training test suite:
140
+
141
+ ```bash
142
+ PYTHONPATH=src .venv/bin/python -m unittest discover -s tests -v
143
+ ```
144
+
145
+ ## Current Pressure Law
146
 
147
+ The paper tests a per-regime interaction pressure law:
148
 
149
  ```text
150
+ p_t = clamp(p_min, p_max,
151
+ A * log10(P / U_t)
152
+ + B * log10(C_t / U_t)
153
+ + D * log10(P / U_t) * log10(C_t / U_t)
154
+ + C0)
155
  ```
156
 
157
+ where `P` is trainable parameter count, `U_t` is the currently revealed unique
158
+ token prefix, and `C_t` is the cumulative sampled training-token count consumed
159
+ by the optimizer. The fitted coefficients are regime-specific; the current
160
+ paper tables read them from:
161
+
162
+ ```text
163
+ runs/coefficient_calibration/cross_regime_backtest/openwebtext10k_main_interaction/coefficients.json
164
+ runs/coefficient_calibration/tinystories_combined_plus_all_holdouts_interaction/coefficients.json
165
+ runs/coefficient_calibration/wikitext103_interaction/coefficients.json
166
+ ```
167
+
168
+ For the standard 4M validation protocol:
169
 
170
  - stream prefixes: `250000 500000 1000000 2000000 4000000`
171
  - stage steps: `1000`
 
173
  - block size: `128`
174
  - cumulative sampled tokens after stage `i`: `i * 1000 * 16 * 128`
175
 
176
+ For the L20 8M extrapolation tier, the saved configs in `runs/` use the
177
+ regime-specific 8M caches listed above and the frozen coefficient-derived
178
+ dropout paths recorded in the run configs.
179
+
180
+ ## Rebuild Paper Tables and Figures
181
+
182
+ The manuscript source is:
183
+
184
+ ```text
185
+ paper/dropout_decay_regime_specific_pressure_law.tex
186
+ ```
187
+
188
+ Regenerate the paper tables, figures, and result summary from saved metrics:
189
+
190
+ ```bash
191
+ PYTHONPATH=src .venv/bin/python scripts/generate_paper_assets.py
192
+ ```
193
+
194
+ Build the PDF, keeping LaTeX intermediates outside `paper/`:
195
+
196
+ ```bash
197
+ mkdir -p /tmp/dropout-paper-build
198
+ cd paper
199
+ latexmk -pdf -outdir=/tmp/dropout-paper-build \
200
+ -interaction=nonstopmode -halt-on-error \
201
+ dropout_decay_regime_specific_pressure_law.tex
202
+ cp /tmp/dropout-paper-build/dropout_decay_regime_specific_pressure_law.pdf \
203
+ dropout_decay_regime_specific_pressure_law.pdf
204
+ ```
205
+
206
+ The sections below provide additional reproduction commands for saved
207
+ validation runs. They are intentionally left in the original run format so old
208
+ result artifacts remain compatible.
209
+
210
  ## Reproduce Model-Size Validation
211
 
212
  Example L12 command:
 
450
  - Do not claim the formula is universal.
451
  - The supported claim is final-validation improvement under this
452
  expanding-prefix protocol.
453
+ - The current PDF is tracked as a generated paper artifact; transient LaTeX
454
+ build files are ignored.
455
+ - Cached token arrays and parquet source files should be published through the
456
+ configured Git LFS rules.
paper/dropout_decay_pressure_law.tex DELETED
@@ -1,501 +0,0 @@
1
- \documentclass[11pt]{article}
2
-
3
- \usepackage[margin=1in]{geometry}
4
- \usepackage{amsmath}
5
- \usepackage{amssymb}
6
- \usepackage{booktabs}
7
- \usepackage{graphicx}
8
- \usepackage{hyperref}
9
- \usepackage[numbers,sort&compress]{natbib}
10
-
11
- \title{Pressure-Aware Dropout Scheduling for Expanding-Stream Language Model Training}
12
- \author{Anonymous Authors}
13
- \date{May 29, 2026}
14
-
15
- \newcommand{\clamp}{\operatorname{clamp}}
16
- \newcommand{\params}{P}
17
- \newcommand{\unique}{U_t}
18
- \newcommand{\sampled}{C_t}
19
-
20
- \begin{document}
21
- \maketitle
22
-
23
- \begin{abstract}
24
- Dropout is usually selected as a fixed hyperparameter, but fixed regularization
25
- is mismatched to streaming regimes where the amount of unique training data
26
- changes over time. We study expanding-prefix language model training, where a
27
- causal Transformer first trains on a small token prefix and later continues on
28
- larger prefixes. Static dropout sweeps show that the best dropout rate is not
29
- constant: it rises with model/data pressure and repeated sampling, and falls as
30
- more unique data becomes available. We propose a pressure-law family
31
- \[
32
- p_t = \clamp(p_{\min}, p_{\max},
33
- A \log_{10}(\params / \unique)
34
- + B \log_{10}(\sampled / \unique)
35
- + C),
36
- \]
37
- where $\params$ is parameter count, $\unique$ is the number of unique available
38
- tokens, and $\sampled$ is the cumulative number of sampled training tokens
39
- consumed by the optimizer. On the local nanochat-style regime, ordinary least
40
- squares over static dropout optima gives $A=0.225$, $B=0.169$, and $C=-0.210$.
41
- For dynamic streaming, a validated schedule with $A=0.154$, $B=0.249$, and
42
- $C=-0.210$ wins all 15 paired final-loss comparisons across 8.39M to 31.46M
43
- parameter models and transfers to two architecture-shape holdouts. Public-corpus
44
- tests show a boundary condition: corpus identity shifts the optimum, so the
45
- coefficients are regime-calibrated constants rather than universal constants.
46
- \end{abstract}
47
-
48
- \section{Introduction}
49
-
50
- Language models trained on expanding streams face a changing regularization
51
- problem. At the beginning of the stream, a model may have many parameters
52
- relative to the number of unique tokens currently available. The optimizer may
53
- also sample the same prefix repeatedly. Later, the same model sees more unique
54
- data, reducing the need for strong regularization. A single fixed dropout rate
55
- cannot simultaneously match both conditions.
56
-
57
- The naive hypothesis is that one should use extremely high dropout early and
58
- decay it as more data arrives. Our first experiments reject that form: very high
59
- dropout underfits and loses to moderate fixed-dropout baselines. The supported
60
- hypothesis is narrower. Dropout should track a moving optimum driven by
61
- model/data pressure and repeated-sampling pressure.
62
-
63
- This paper makes four contributions:
64
-
65
- \begin{enumerate}
66
- \item We define an expanding-prefix streaming protocol for small causal
67
- Transformer language models.
68
- \item We show that the best static dropout moves predictably with parameter
69
- count, unique-token prefix size, and sampled-token pressure.
70
- \item We propose a pressure-law dropout schedule and separate static
71
- optimum coefficients from dynamic streaming coefficients.
72
- \item We define a training regime as the scope over which one coefficient
73
- triplet should be expected to transfer, and we show when corpus changes
74
- invalidate a universal-coefficient claim.
75
- \end{enumerate}
76
-
77
- \section{Background}
78
-
79
- Dropout randomly removes units during training and is a standard method for
80
- reducing overfitting \citep{srivastava2014dropout}. In inverted dropout, an
81
- activation $h_i$ is transformed as
82
- \[
83
- h'_i = h_i \frac{m_i}{1-p}, \quad m_i \sim \operatorname{Bernoulli}(1-p).
84
- \]
85
- The expectation is preserved,
86
- \[
87
- \mathbb{E}[h'_i] = h_i,
88
- \]
89
- while the noise variance grows as
90
- \[
91
- \operatorname{Var}(h'_i) = h_i^2 \frac{p}{1-p}.
92
- \]
93
- This creates a regularization tradeoff. Too little dropout permits
94
- memorization; too much dropout harms optimization and underfits.
95
-
96
- Our experiments use decoder-only Transformer language models inspired by
97
- modern causal architectures \citep{vaswani2017attention}. The code is derived
98
- from Andrej Karpathy's MIT-licensed nanochat repository
99
- \citep{karpathy_nanochat}; chat and deployment components are removed, while
100
- the tokenizer and core causal Transformer template are retained.
101
-
102
- \section{Expanding-Stream Protocol}
103
-
104
- We simulate streaming by revealing larger prefixes of a tokenized corpus:
105
- \[
106
- 250\text{k} \rightarrow 500\text{k} \rightarrow 1\text{M}
107
- \rightarrow 2\text{M} \rightarrow 4\text{M}.
108
- \]
109
- For each seed and condition, one model and optimizer continue through all
110
- stages. At stage $t$, minibatches are sampled only from the currently revealed
111
- prefix of size $\unique$.
112
-
113
- The standard protocol uses batch size 16, block size 128, and 1000 optimizer
114
- steps per stage. Therefore each stage consumes 2.048M sampled tokens, and the
115
- cumulative sampled tokens are listed in Table~\ref{tab:stream_tokens}.
116
-
117
- \begin{table}[h]
118
- \centering
119
- \caption{Standard expanding-prefix schedule.}
120
- \label{tab:stream_tokens}
121
- \begin{tabular}{rrrr}
122
- \toprule
123
- Stage & Unique tokens $\unique$ & Stage sampled tokens & Cumulative sampled tokens $\sampled$ \\
124
- \midrule
125
- 0 & 250k & 2.048M & 2.048M \\
126
- 1 & 500k & 2.048M & 4.096M \\
127
- 2 & 1M & 2.048M & 6.144M \\
128
- 3 & 2M & 2.048M & 8.192M \\
129
- 4 & 4M & 2.048M & 10.240M \\
130
- \bottomrule
131
- \end{tabular}
132
- \end{table}
133
-
134
- All Torch experiments were run on Apple MPS only. The runner refuses CPU, CUDA,
135
- and MPS fallback. The main experiments use a 4096-token vocabulary, AdamW, a
136
- learning rate of $3 \times 10^{-4}$, weight decay 0.1, gradient clipping 1.0,
137
- 64 validation batches, and seeds 1, 2, and 3 unless otherwise stated.
138
-
139
- \section{Regime Definition}
140
-
141
- The formula is not meant to change coefficients when model size or stream size
142
- changes. Those are exactly the variables the formula explains. Instead,
143
- coefficients are expected to be fixed within a training regime.
144
-
145
- We define a regime as:
146
- \begin{align*}
147
- \mathcal{R}=\{&
148
- \text{architecture family},\ \text{tokenizer},\ \text{corpus family},\\
149
- &\text{optimizer protocol},\ \text{dropout placement},\\
150
- &\text{stream protocol},\ \text{evaluation distribution}\}.
151
- \end{align*}
152
-
153
- Operationally, two runs are in the same regime if one fixed coefficient triplet
154
- $(A,B,C)$ predicts dropout optima across held-out model sizes and held-out
155
- prefix sizes without systematic bias. Changes in $\params$, $\unique$, and
156
- $\sampled$ should not require new coefficients. Changes in corpus family,
157
- tokenizer, architecture class, dropout placement, optimizer schedule, stream
158
- semantics, or validation distribution should trigger re-evaluation.
159
-
160
- \section{Pressure-Law Dropout}
161
-
162
- We propose the formula family
163
- \begin{equation}
164
- p_t = \clamp(p_{\min}, p_{\max},
165
- A \log_{10}(\params / \unique)
166
- + B \log_{10}(\sampled / \unique)
167
- + C).
168
- \label{eq:pressure}
169
- \end{equation}
170
-
171
- The first term measures model/data pressure. Larger $\params / \unique$ means
172
- the model has more capacity per currently available unique token. The second
173
- term measures repeated-sampling pressure. Larger $\sampled / \unique$ means the
174
- optimizer has revisited the available prefix more often. We use logarithms
175
- because parameter counts and token counts vary over orders of magnitude while
176
- dropout is bounded.
177
-
178
- \subsection{Static Optimum Fit}
179
-
180
- For static sweeps, each row gives a measured best dropout $y_i$ at a fixed
181
- model and prefix. We set
182
- \[
183
- x_{1,i} = \log_{10}(\params_i / U_i), \quad
184
- x_{2,i} = \log_{10}(C_i / U_i),
185
- \]
186
- and solve
187
- \[
188
- \min_{A,B,C} \sum_i
189
- \left(y_i - A x_{1,i} - B x_{2,i} - C\right)^2.
190
- \]
191
-
192
- On the 15-point main local static screen, the grid optimum fit gives
193
- \[
194
- A=0.225,\quad B=0.169,\quad C=-0.210.
195
- \]
196
- A quadratic interpolation around the best measured grid point gives a similar
197
- fit:
198
- \[
199
- A=0.231,\quad B=0.166,\quad C=-0.211.
200
- \]
201
-
202
- \subsection{Streaming Schedule Coefficients}
203
-
204
- The best dynamic schedule is not necessarily identical to the best static
205
- optimum surface. The current validated streaming baseline uses
206
- \[
207
- A=0.154,\quad B=0.249,\quad C=-0.210,
208
- \]
209
- with $p_{\min}=0.02$ and $p_{\max}=0.65$. For the L12 model under the standard
210
- protocol, this gives
211
- \[
212
- 0.300 \rightarrow 0.260 \rightarrow 0.180 \rightarrow 0.090 \rightarrow 0.020.
213
- \]
214
-
215
- \section{Experiments}
216
-
217
- \subsection{Initial Negative Result}
218
-
219
- The first tests used an 8.39M-parameter model and found that arbitrary high
220
- dropout was not useful. Table~\ref{tab:high_dropout} shows that high dropout
221
- underfit and lost to static 0.1 dropout.
222
-
223
- \begin{table}[h]
224
- \centering
225
- \caption{Naive high-dropout decay failed. Lower validation loss is better.}
226
- \label{tab:high_dropout}
227
- \begin{tabular}{lrrrr}
228
- \toprule
229
- Condition & 5M & 10M & 20M & 40M \\
230
- \midrule
231
- High-dropout decay & 6.9213 & 6.2689 & 5.4262 & 4.9090 \\
232
- Static 0.1 dropout & 5.6310 & 5.1018 & 4.8497 & 4.6743 \\
233
- Static 0.8 dropout & 6.9898 & 6.7637 & 6.4835 & 6.2390 \\
234
- \bottomrule
235
- \end{tabular}
236
- \end{table}
237
-
238
- \subsection{Static Dropout Screen}
239
-
240
- Table~\ref{tab:static_screen} shows the core static screen. The best dropout
241
- falls as unique-token count grows and rises with model size at fixed prefix.
242
-
243
- \begin{table}[h]
244
- \centering
245
- \caption{Static dropout optima in the OpenWebText10K cached-corpus regime.}
246
- \label{tab:static_screen}
247
- \resizebox{\textwidth}{!}{%
248
- \begin{tabular}{lrrrrrr}
249
- \toprule
250
- Model & Params & Prefix & Effective epochs & Best dropout & Val loss & Gap \\
251
- \midrule
252
- L8 & 8.39M & 250k & 40.96 & 0.40 & 5.4175 & 1.7763 \\
253
- L8 & 8.39M & 500k & 20.48 & 0.20 & 5.0216 & 1.3238 \\
254
- L8 & 8.39M & 1M & 10.24 & 0.14 & 4.7763 & 0.7863 \\
255
- L8 & 8.39M & 2M & 5.12 & 0.08 & 4.6232 & 0.4074 \\
256
- L8 & 8.39M & 4M & 2.56 & 0.00 & 4.5136 & 0.2621 \\
257
- L12 & 17.37M & 250k & 40.96 & 0.50 & 5.4384 & 2.0663 \\
258
- L12 & 17.37M & 500k & 20.48 & 0.40 & 4.9791 & 1.2434 \\
259
- L12 & 17.37M & 1M & 10.24 & 0.20 & 4.6871 & 0.9711 \\
260
- L12 & 17.37M & 2M & 5.12 & 0.14 & 4.5088 & 0.4870 \\
261
- L12 & 17.37M & 4M & 2.56 & 0.02 & 4.3875 & 0.3575 \\
262
- L16 & 31.46M & 250k & 40.96 & 0.60 & 5.5055 & 2.1870 \\
263
- L16 & 31.46M & 500k & 20.48 & 0.40 & 4.9814 & 1.7017 \\
264
- L16 & 31.46M & 1M & 10.24 & 0.30 & 4.6511 & 1.0216 \\
265
- L16 & 31.46M & 2M & 5.12 & 0.14 & 4.4270 & 0.6509 \\
266
- L16 & 31.46M & 4M & 2.56 & 0.02 & 4.2947 & 0.4400 \\
267
- \bottomrule
268
- \end{tabular}}
269
- \end{table}
270
-
271
- \subsection{Coefficient Fit}
272
-
273
- Table~\ref{tab:coefs} reports coefficient estimates from existing saved data.
274
- The main local screen is the cleanest static-regime estimate. Pooling the
275
- corpus probes without a corpus feature is misspecified because those probes
276
- share the same $\params / U$ and $C / U$ but have different optima.
277
-
278
- \begin{table}[h]
279
- \centering
280
- \caption{OLS coefficient fits for the static optimum surface.}
281
- \label{tab:coefs}
282
- \begin{tabular}{llrrrrr}
283
- \toprule
284
- Target & Data subset & n & $A$ & $B$ & $C$ & RMSE \\
285
- \midrule
286
- Grid & Main local & 15 & 0.225 & 0.169 & -0.210 & 0.0418 \\
287
- Quadratic & Main local & 15 & 0.231 & 0.166 & -0.211 & 0.0353 \\
288
- Grid & Main local + 5M & 18 & 0.232 & 0.118 & -0.159 & 0.0508 \\
289
- Quadratic & Main local + 5M & 18 & 0.237 & 0.117 & -0.161 & 0.0466 \\
290
- Grid & All existing & 21 & 0.343 & 0.014 & -0.210 & 0.0650 \\
291
- \bottomrule
292
- \end{tabular}
293
- \end{table}
294
-
295
- \subsection{Model-Size Validation}
296
-
297
- The dynamic streaming schedule was evaluated against static-dropout controls
298
- near the apparent optimum. Table~\ref{tab:model_size} shows final validation
299
- loss across five model sizes.
300
-
301
- \begin{table}[h]
302
- \centering
303
- \caption{Streaming formula vs. best static controls across model size.}
304
- \label{tab:model_size}
305
- \begin{tabular}{lrrrl}
306
- \toprule
307
- Model & Params & Formula final val & Best static final val & Paired deltas \\
308
- \midrule
309
- L8 & 8.39M & $4.6094 \pm 0.0056$ & 4.6242 & -0.0102, -0.0160, -0.0182 \\
310
- L10 & 12.31M & $4.5306 \pm 0.0094$ & 4.5580 & -0.0288, -0.0188, -0.0345 \\
311
- L12 & 17.37M & $4.4812 \pm 0.0062$ & 4.5183 & -0.0364, -0.0308, -0.0439 \\
312
- L14 & 23.70M & $4.4384 \pm 0.0087$ & 4.4736 & -0.0294, -0.0269, -0.0429 \\
313
- L16 & 31.46M & $4.4059 \pm 0.0046$ & 4.4459 & -0.0411, -0.0512, -0.0279 \\
314
- \bottomrule
315
- \end{tabular}
316
- \end{table}
317
-
318
- The formula wins all 15 paired final-loss comparisons in this set.
319
-
320
- \subsection{Repeated-Sampling Pressure}
321
-
322
- Changing stage steps changes $\sampled / \unique$ while keeping the prefix
323
- sequence fixed. Table~\ref{tab:update_pressure} shows that the pressure formula
324
- continues to beat fixed dropout at 500, 1000, and 2000 steps per stage.
325
-
326
- \begin{table}[h]
327
- \centering
328
- \caption{Update-pressure validation on L12.}
329
- \label{tab:update_pressure}
330
- \resizebox{\textwidth}{!}{%
331
- \begin{tabular}{rlrrrl}
332
- \toprule
333
- Stage steps & Formula path & Mean trajectory & Formula final & Best static final & Paired deltas \\
334
- \midrule
335
- 500 & 0.226 -> 0.180 -> 0.102 -> 0.020 -> 0.020 & 5.1581 & $4.7138 \pm 0.0080$ & 4.7321 & -0.0152, -0.0147, -0.0249 \\
336
- 1000 & 0.300 -> 0.260 -> 0.180 -> 0.090 -> 0.020 & 4.9226 & $4.4812 \pm 0.0062$ & 4.5183 & -0.0364, -0.0308, -0.0439 \\
337
- 2000 & 0.376 -> 0.330 -> 0.252 -> 0.162 -> 0.065 & 4.7841 & $4.3089 \pm 0.0116$ & 4.3513 & -0.0453, -0.0321, -0.0489 \\
338
- \bottomrule
339
- \end{tabular}}
340
- \end{table}
341
-
342
- \subsection{Sampled-Pressure Coefficient Ablation}
343
-
344
- The sampled-pressure term was ablated by multiplying the current coefficient by
345
- 0, 0.5, 1.0, and 1.5. Table~\ref{tab:b_ablation} shows that the full
346
- sampled-pressure term gives the best final validation loss.
347
-
348
- \begin{table}[h]
349
- \centering
350
- \caption{Ablation of repeated-sampling pressure on L12.}
351
- \label{tab:b_ablation}
352
- \resizebox{\textwidth}{!}{%
353
- \begin{tabular}{rlrrrr}
354
- \toprule
355
- Multiplier & Path & Mean trajectory & Final val & Final std & Final gap \\
356
- \midrule
357
- 0x & 0.074 -> 0.027 -> 0.020 -> 0.020 -> 0.020 & 5.0282 & 4.5468 & 0.0011 & 0.3482 \\
358
- 0.5x & 0.187 -> 0.141 -> 0.079 -> 0.020 -> 0.020 & 4.9260 & 4.5055 & 0.0046 & 0.3272 \\
359
- 1.0x & 0.300 -> 0.260 -> 0.180 -> 0.090 -> 0.020 & 4.9226 & 4.4812 & 0.0062 & 0.2825 \\
360
- 1.5x & 0.415 -> 0.368 -> 0.275 -> 0.163 -> 0.041 & 4.9739 & 4.4959 & 0.0025 & 0.2418 \\
361
- \bottomrule
362
- \end{tabular}}
363
- \end{table}
364
-
365
- \subsection{Architecture-Shape Holdouts}
366
-
367
- Two holdouts test whether parameter count is a useful capacity proxy inside the
368
- same Transformer family. Results are shown in Table~\ref{tab:shape}.
369
-
370
- \begin{table}[h]
371
- \centering
372
- \caption{Architecture-shape holdouts.}
373
- \label{tab:shape}
374
- \begin{tabular}{lrrr}
375
- \toprule
376
- Holdout & Params & Formula final val & Best static final val \\
377
- \midrule
378
- Deep/narrow 18x8x256 & 16.25M & $4.5286 \pm 0.0118$ & $4.5564 \pm 0.0127$ \\
379
- Wide 8x8x384 & 17.30M & $4.4658 \pm 0.0065$ & $4.4946 \pm 0.0087$ \\
380
- \bottomrule
381
- \end{tabular}
382
- \end{table}
383
-
384
- The formula wins all 6 paired final-loss comparisons across these two shape
385
- holdouts.
386
-
387
- \subsection{Corpus Holdouts}
388
-
389
- Corpus tests reveal the boundary of the two-variable formula. The frozen
390
- formula transfers to WikiText-103 but overestimates early dropout on
391
- TinyStories. A lower dropout path performs better on TinyStories.
392
-
393
- \begin{table}[h]
394
- \centering
395
- \caption{Public-corpus streaming checks.}
396
- \label{tab:corpus_stream}
397
- \begin{tabular}{llrr}
398
- \toprule
399
- Corpus & Condition & Final val & Mean trajectory \\
400
- \midrule
401
- WikiText-103 & Old formula & 4.0643 & 4.5704 \\
402
- WikiText-103 & Best static & 4.0893 & 4.5889 \\
403
- TinyStories & Old formula & 2.7221 & 3.1149 \\
404
- TinyStories & Probe blend & 2.7194 & 3.0934 \\
405
- TinyStories & Low-decay heuristic & 2.7068 & 3.0793 \\
406
- TinyStories & Best static & 2.7215 & 3.0880 \\
407
- \bottomrule
408
- \end{tabular}
409
- \end{table}
410
-
411
- A separate 1.835M-parameter pressure probe used the same $\params / U$ and
412
- $C/U$ on three corpora, but obtained different optima: 0.04 on the local
413
- corpus, 0.00 on WikiText-103, and 0.02 on TinyStories. This proves that corpus
414
- identity can shift the optimum independently of the two pressure ratios.
415
-
416
- \section{Discussion}
417
-
418
- The evidence supports a pressure-law structure, not a universal coefficient
419
- triplet. The static fit shows that dropout optima can be approximated from
420
- $\log_{10}(\params/U)$ and $\log_{10}(C/U)$ within the OpenWebText10K cached-corpus regime. The
421
- streaming experiments show that a schedule based on those variables can beat
422
- fixed dropout across model sizes and architecture-shape holdouts.
423
-
424
- However, the corpus probes show that the two-variable formula is incomplete.
425
- For a paper claim, we therefore recommend the following framing:
426
-
427
- \begin{quote}
428
- Within a fixed training regime, dropout can be scheduled from model/data
429
- pressure and repeated-sampling pressure. The coefficients are empirical
430
- calibration constants for the regime. They should be re-estimated when the
431
- corpus family, tokenizer, architecture class, dropout placement, optimizer
432
- protocol, streaming semantics, or validation distribution changes.
433
- \end{quote}
434
-
435
- \section{Limitations}
436
-
437
- The experiments are small compared with modern frontier-scale language model
438
- training. They are nevertheless useful because they isolate dropout behavior in
439
- a controlled setting. The main limitations are:
440
-
441
- \begin{itemize}
442
- \item The dense static coefficient fit is local-corpus biased.
443
- \item The public-corpus checks show that a corpus term is missing.
444
- \item The streaming coefficients were selected by validation and ablation,
445
- not by a full continuous coefficient sweep.
446
- \item The models are nanochat-style causal Transformers, so transfer to
447
- different architecture families is not established.
448
- \item The local token cache is not yet a fully published dataset artifact,
449
- which limits exact reproduction until the cache or source corpus is hosted.
450
- \end{itemize}
451
-
452
- \section{Reproducibility}
453
-
454
- The repository includes experiment code, result logs, model-selection CSVs, and
455
- reproduction commands. Every run records configuration, metrics, and attribution
456
- metadata. The runner enforces MPS-only Torch execution. For exact reproduction,
457
- the local cache must be supplied or rebuilt from a documented corpus source.
458
- Public WikiText-103 and TinyStories holdouts have preparation scripts.
459
-
460
- \section{Conclusion}
461
-
462
- Dropout decay is useful when it follows a moving regularization optimum, not
463
- when it starts from an arbitrary high value. Existing evidence supports a
464
- pressure-law schedule using model/data pressure and repeated-sampling pressure.
465
- For the local static regime, the best fit is approximately
466
- $(A,B,C)=(0.225,0.169,-0.210)$. For the validated streaming baseline, the best
467
- current schedule uses $(A,B,C)=(0.154,0.249,-0.210)$. The next step is to fit
468
- and validate a corpus-aware correction without losing the regime-calibrated
469
- structure.
470
-
471
- \bibliographystyle{plainnat}
472
- \bibliography{references}
473
-
474
- \appendix
475
-
476
- \section{When To Refit Coefficients}
477
-
478
- Refit or at least revalidate coefficients when any of the following changes:
479
- corpus family, tokenizer/vocabulary, architecture class, dropout placement,
480
- optimizer and learning-rate protocol, sequence length, streaming semantics, or
481
- validation distribution. Do not refit merely because model size, prefix size, or
482
- sampled-token count changes inside the same regime; those are the formula's
483
- input variables.
484
-
485
- \section{Recommended Reporting Checklist}
486
-
487
- For each future regime, report:
488
-
489
- \begin{itemize}
490
- \item model parameter count and architecture shape;
491
- \item tokenizer and vocabulary size;
492
- \item unique tokens available at each stage;
493
- \item sampled tokens per stage and cumulative sampled tokens;
494
- \item dropout path and clamp values;
495
- \item fixed-dropout controls near the predicted optimum;
496
- \item seeds, mean validation loss, standard deviation, and paired deltas;
497
- \item train-evaluation loss and validation gap;
498
- \item exact command and run artifact path.
499
- \end{itemize}
500
-
501
- \end{document}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
paper/dropout_decay_regime_specific_pressure_law.bbl DELETED
@@ -1,73 +0,0 @@
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 CHANGED
@@ -1,3 +1,3 @@
1
  version https://git-lfs.github.com/spec/v1
2
- oid sha256:46ba08475ba2566bc3733eed5403db061a3ce4145ed6bd79965796719a396bc8
3
- size 267426
 
1
  version https://git-lfs.github.com/spec/v1
2
+ oid sha256:99ac702644025cd253907d1c8e7892f2de07e32564a68c33a25fc505ac25b886
3
+ size 330095
paper/dropout_decay_regime_specific_pressure_law.tex CHANGED
@@ -14,7 +14,7 @@
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}}
@@ -46,14 +46,17 @@ these per-cell optima into a regime-specific pressure law,
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.
@@ -80,16 +83,21 @@ 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
@@ -98,7 +106,12 @@ This paper makes four empirical contributions.
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
 
@@ -111,6 +124,16 @@ 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
@@ -125,9 +148,20 @@ 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
 
@@ -140,23 +174,33 @@ 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
@@ -165,11 +209,48 @@ $\unique$. The two main protocols are:
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:
@@ -186,17 +267,25 @@ 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
 
@@ -212,7 +301,18 @@ 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}
@@ -221,8 +321,46 @@ The fitted interaction family is
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
 
@@ -233,14 +371,27 @@ 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}
@@ -269,24 +420,62 @@ 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
 
@@ -298,16 +487,35 @@ 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
 
@@ -322,8 +530,10 @@ 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}
@@ -332,7 +542,9 @@ best static validation loss at the same prefix; negative favors decay.}
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
 
@@ -373,6 +585,23 @@ 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
@@ -384,14 +613,16 @@ confirmed law at billion-parameter scale.
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
 
@@ -402,6 +633,18 @@ 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
 
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{Mandeep Sidhu}
18
  \date{June 2026}
19
 
20
  \newcommand{\clamp}{\operatorname{clamp}}
 
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 dropout-rate mean absolute
50
+ errors of 0.0158, 0.0180, and 0.0162 on OpenWebText10K, TinyStories, and
51
+ WikiText-103 respectively. In validation runs where the same model and optimizer
52
+ continue training as the available token prefix grows, coefficient-derived decay
53
+ schedules beat the best fixed-dropout baseline on final validation loss in all
54
+ three regimes at the 4M final prefix and again in larger 51.84M-parameter,
55
+ 8M-token extrapolations. Mean final-loss gains over the best fixed dropout are
56
+ 0.0473/0.0133/0.0297 nats at 4M and 0.0363/0.0100/0.0184 nats at 8M for
57
+ OpenWebText10K/TinyStories/WikiText-103. The large extrapolation tier wins all
58
+ paired seeds across all three regimes (9/9 paired wins), but stage-wise analysis
59
+ shows the schedules are final-loss optimized rather than best at every
60
  intermediate prefix. The results support a practical hypothesis: dropout decay
61
  should be calibrated as a regime-specific pressure law, not chosen as a
62
  universal constant or an arbitrary high-to-low schedule.
 
83
  relative to the current data prefix, and cumulative optimizer sampling relative
84
  to that prefix.
85
 
86
+ We use \emph{regime} to mean a fixed corpus family, tokenizer, architecture
87
+ family, optimizer protocol, dropout placement, stream protocol, and evaluation
88
+ distribution. Model size and prefix size vary inside a regime; changing the
89
+ text distribution, tokenizer, or training protocol defines a new regime. For
90
+ each of the three text regimes studied here, this paper makes four empirical
91
+ contributions.
92
 
93
  \begin{enumerate}
94
+ \item We define per-regime calibration cells for streaming dropout: one model, one
95
  unique-token prefix, one sampled-token budget, and a sweep over fixed
96
  dropout rates.
97
+ \item We show that per-regime calibration cells often have a parabolic or plateau-like
98
  validation-loss curve over dropout, making the local optimum a usable
99
  supervised target.
100
+ \item We fit a separate interaction pressure law per regime that maps
101
  $(\params,\unique,\sampled)$ to a dropout rate.
102
  \item We validate the resulting schedules against broad fixed-dropout
103
  baselines across three text regimes and a larger-model/larger-stream
 
106
 
107
  The central result is not that one coefficient vector works everywhere. It does
108
  not. The central result is that the pressure-law structure is useful, while the
109
+ coefficients are regime-specific. A reader should interpret the paper as an
110
+ evidence ladder: static sweeps establish that dropout has a measurable optimum
111
+ inside a regime; coefficient fits show that these optima can be predicted from
112
+ pressure variables; continuing streaming runs test whether the predicted
113
+ trajectory actually improves a single model trained through the expanding
114
+ prefix sequence.
115
 
116
  \section{Related Work}
117
 
 
124
  captures the key tradeoff in our experiments: low dropout can overfit repeated
125
  small prefixes, but high dropout can remove too much usable capacity.
126
 
127
+ \paragraph{Dropout schedules.}
128
+ This paper is not a survey of every possible adaptive regularization schedule.
129
+ Its controlled comparison is against a best tested fixed-dropout grid, because
130
+ fixed dropout is the default practical choice and gives a clear baseline for
131
+ whether adaptation helps. Early hand-designed high-to-low schedules were useful
132
+ as exploratory probes but were not the final claim; the reported schedules are
133
+ generated from fitted regime coefficients before the streaming validation run.
134
+ Broader comparisons against other adaptive dropout or regularization policies
135
+ are left to future work.
136
+
137
  \paragraph{Transformer language models and scaling.}
138
  Our model family is a decoder-only Transformer following the attention-based
139
  architecture of \citet{vaswani2017attention}. Scaling-law work shows that loss
 
148
  story corpus designed for small language-model studies \citep{eldan2023tinystories}.
149
  WikiText-103 is a standard word-level language-modeling benchmark derived from
150
  Wikipedia articles \citep{merity2017pointer}. OpenWebText is an open
151
+ WebText-style corpus \citep{gokaslan2019openwebtext}. OpenWebText10K denotes
152
+ the cached/tokenized OpenWebText-style subset used in this project; the suffix
153
+ distinguishes this experimental subset from the full OpenWebText corpus. We use
154
+ cached/tokenized subsets and therefore report results as regime-level evidence
155
+ rather than claims about full-corpus state of the art.
156
+
157
+ \begin{table}[t]
158
+ \centering
159
+ \caption{Text regimes used in the experiments. Prefixes are token counts exposed
160
+ to the trainer at each stream stage; all reported runs use a 4096-token BPE
161
+ vocabulary, so numeric losses should be compared within this tokenizer setting.}
162
+ \label{tab:regimes}
163
+ \resizebox{\textwidth}{!}{\input{tables/regime_setup_table}}
164
+ \end{table}
165
 
166
  \section{Experimental Setting}
167
 
 
174
  the original copyright and permission notice.
175
 
176
  The architecture family keeps the standard Transformer feed-forward expansion
177
+ ratio of four. We denote architectures as L$x$-H$y$-D$z$, where $x$ is the
178
+ number of Transformer layers, $y$ is the number of attention heads, and $z$ is
179
+ the embedding dimension. The central model sizes are L8-H8-D256 (8.39M parameters),
180
+ L12-H8-D320 (17.37M), L16-H8-D384 (31.46M in the 4096-vocabulary runs; one
181
+ archived metadata path reports 32.71M from a different vocabulary configuration),
182
+ and L20-H8-D448 (51.84M). The tokenizer vocabulary is 4096 in the reported runs.
183
+ Unless noted otherwise, training uses AdamW, learning rate $3\times 10^{-4}$,
184
+ weight decay 0.1, gradient clipping 1.0, batch size 16, and block size 128. All
185
+ Torch training runs are MPS-only; the runner refuses CUDA, CPU execution, and
186
+ MPS fallback.
187
+
188
+ All reported losses are mean next-token cross-entropy losses in nats. The model
189
+ uses the same active dropout probability for embedding dropout, attention-weight
190
+ dropout, attention output/residual dropout, and MLP output dropout. This
191
+ placement is part of the regime definition; changing it would require
192
+ recalibrating the coefficients.
193
 
194
  \subsection{Streaming protocol}
195
 
196
+ We simulate streaming by revealing larger token prefixes over time. We call this
197
+ the locked-stream setting: the model is not restarted at each prefix; one
198
+ training run keeps the same model and optimizer state as the data stream grows.
199
+ At stage $t$, minibatches are sampled only from the currently revealed prefix
200
  $\unique$. The two main protocols are:
201
 
202
  \begin{itemize}
203
+ \item 4M final-prefix validation: OpenWebText10K and WikiText-103 use 250k, 500k, 1M,
204
  2M, and 4M prefixes. TinyStories uses 500k, 1M, 2M, and 4M prefixes. These
205
  runs use five random seeds.
206
  \item 8M extrapolation: L20-H8-D448 with an 8M final prefix. These runs use
 
209
 
210
  Each run compares a dropout schedule against a grid of static-dropout
211
  baselines. The best static baseline is selected after training from the same
212
+ run family, so the decay schedule competes against the best fixed dropout found
213
  within the tested grid.
214
 
215
+ \begin{table}[t]
216
+ \centering
217
+ \caption{Per-regime experimental evidence ladder. The same sequence is run
218
+ separately for OpenWebText10K, TinyStories, and WikiText-103. The streaming rows
219
+ are the decisive validation layers, while the calibration rows explain where the
220
+ schedules come from.}
221
+ \label{tab:protocol}
222
+ \resizebox{\textwidth}{!}{\input{tables/experiment_protocol_table}}
223
+ \end{table}
224
+
225
+ \begin{table}[t]
226
+ \centering
227
+ \caption{Streaming training budgets used for validation. At each optimization
228
+ step, the runner consumes \texttt{batch\_size} $\times$ \texttt{block\_size}
229
+ token positions; therefore $C_t$ is the cumulative sum of sampled token positions
230
+ across stream stages.}
231
+ \label{tab:budgets}
232
+ \resizebox{\textwidth}{!}{\input{tables/protocol_budget_table}}
233
+ \end{table}
234
+
235
  \section{From Calibration Cells to a Pressure Law}
236
 
237
+ Figure~\ref{fig:protocol} summarizes the full procedure. The paper's central
238
+ methodological choice is to separate cheap calibration from expensive streaming
239
+ validation. Static cells are not themselves the final claim; they are probes
240
+ that estimate where a fixed model/prefix combination stops benefiting from more
241
+ or less dropout. The pressure law is fitted from those probes, frozen, and then
242
+ tested in the harder setting where a single model continues training as the
243
+ available stream grows.
244
+
245
+ \begin{figure}[t]
246
+ \centering
247
+ \input{figures/calibration_to_streaming_protocol}
248
+ \caption{Calibration-to-streaming protocol. Static sweeps produce target
249
+ dropouts $p^\star$; a regime-specific pressure law fits those targets; the
250
+ resulting schedule is frozen before locked-stream validation.}
251
+ \label{fig:protocol}
252
+ \end{figure}
253
+
254
  \subsection{Calibration cell definition}
255
 
256
  A calibration cell is one static dropout sweep at a fixed tuple:
 
267
  minimum. Boundary optima are retained but downweighted in coefficient fitting
268
  because the true optimum may lie outside the tested grid.
269
 
270
+ Figure~\ref{fig:cell-curves} shows one selected representative cell from each
271
+ regime. These examples are illustrative of the curve shape, not the full
272
+ calibration set. The complete coefficient fits use 15--16 cells per regime and
273
+ are summarized in Figure~\ref{fig:static-optima} and Table~\ref{tab:coefficients}.
274
+ The curves are not perfect mathematical parabolas, but they show the practical
275
+ pattern needed for calibration: validation loss improves as dropout moves away
276
+ from zero, reaches a local minimum or plateau, and then rises when dropout
277
+ becomes too large.
278
 
279
  \begin{figure}[t]
280
  \centering
281
  \input{figures/calibration_cell_curves}
282
+ \caption{Representative static dropout calibration cells, one selected from
283
+ each regime. Each panel fixes the corpus regime, model size, unique-token
284
+ prefix, sampled-token budget, and validation split, then sweeps static dropout.
285
+ The y-axis is token-level validation cross-entropy in nats above the best tested
286
+ dropout in that cell, so each curve is normalized to show shape rather than
287
+ absolute corpus difficulty. The full calibration sets contain 15--16 cells per
288
+ regime.}
289
  \label{fig:cell-curves}
290
  \end{figure}
291
 
 
301
  Increasing unique tokens at fixed model reduces this pressure. The second term
302
  measures repeated-sampling pressure. If the optimizer consumes many sampled
303
  tokens while the unique prefix remains small, it repeatedly revisits the same
304
+ data and can benefit from stronger regularization. For example, if training
305
+ draws two million token positions from a one-million-token revealed prefix, then
306
+ $\unique=1\mathrm{M}$ and $\sampled=2\mathrm{M}$; the same unique data has been
307
+ sampled for roughly two prefix-equivalents of optimization.
308
+
309
+ \begin{table}[t]
310
+ \centering
311
+ \caption{Symbol glossary. The pressure law uses absolute quantities available
312
+ before or during a run, then converts them into dimensionless pressure variables.}
313
+ \label{tab:symbols}
314
+ \resizebox{0.92\textwidth}{!}{\input{tables/symbol_table}}
315
+ \end{table}
316
 
317
  The fitted interaction family is
318
  \begin{equation}
 
321
  \label{eq:interaction-law}
322
  \end{equation}
323
  For schedule generation we use $p_{\min}=0.02$ and $p_{\max}=0.65$ unless a
324
+ run explicitly specifies a different clamp. The lower bound keeps generated
325
+ decay schedules near but not exactly at zero dropout, while static sweeps may
326
+ still include 0.0 as an ablation. The upper bound keeps generated schedules away
327
+ from the very-high-dropout regime; early experiments with dropout near 0.8
328
+ underfit badly, so this paper does not claim that arbitrarily high initial
329
+ dropout is useful.
330
+
331
+ Operationally, a coefficient fit follows the same recipe in each regime:
332
+ \begin{enumerate}
333
+ \item Choose a small grid of model sizes and unique-token prefixes that
334
+ covers the intended deployment range.
335
+ \item For each cell, train the model with several fixed dropout rates under
336
+ the same sampled-token budget and validation split.
337
+ \item Extract $p^\star$ as the lowest-loss grid point, or as a local
338
+ quadratic minimum when the best grid point is bracketed by higher-loss
339
+ neighbors.
340
+ \item Fit $A,B,D,C_0$ by regressing $p^\star$ on $x_t$, $y_t$, and $x_ty_t$.
341
+ Cells with boundary or weakly identified optima are downweighted because
342
+ their true optimum may lie outside the tested grid. In practice, lower
343
+ weights are assigned to cells whose best dropout is on the tested boundary,
344
+ whose minimum is not bracketed by worse neighbors, whose curve is very flat,
345
+ or whose best-loss estimate is comparatively noisy.
346
+ \item Freeze the coefficients and generate a dropout path from the actual
347
+ streaming prefixes before running the locked-stream validation.
348
+ \end{enumerate}
349
+
350
+ This recipe matters because it prevents the decay schedule from being selected
351
+ after seeing the streaming validation losses. The schedule is allowed to use
352
+ model size, current unique tokens, cumulative sampled tokens, and fitted
353
+ regime coefficients; it is not allowed to tune itself on the final test run.
354
+
355
+ \begin{table}[t]
356
+ \centering
357
+ \caption{Heuristic confidence weights used when fitting coefficient targets.
358
+ Multipliers are cumulative, so a boundary and unbracketed cell receives both
359
+ penalties. These weights reduce the influence of cells where the static optimum
360
+ is weakly identified.}
361
+ \label{tab:weights}
362
+ \resizebox{\textwidth}{!}{\input{tables/calibration_weight_table}}
363
+ \end{table}
364
 
365
  \subsection{Regime-specific coefficients}
366
 
 
371
  the coefficients.
372
 
373
  Table~\ref{tab:coefficients} gives the fitted interaction coefficients. Each
374
+ regime uses 15 or 16 calibration cells. The error columns are measured in
375
+ dropout-rate units, not validation-loss units. For calibration targets
376
+ $p_i^\star$ and formula predictions $\hat p_i$, the reported mean absolute
377
+ error is
378
+ \[
379
+ \mathrm{MAE}=\frac{1}{N}\sum_{i=1}^{N}|\hat p_i-p_i^\star|.
380
+ \]
381
+ The fitted MAE is about 0.016--0.018 dropout, meaning the coefficient law
382
+ typically predicts the calibrated static optimum to within roughly two
383
+ hundredths of dropout. Leave-model-out MAE refits the coefficients while holding
384
+ out one model size, then predicts the held-out model cells. Leave-prefix-out
385
+ MAE does the same while holding out one unique-token prefix. These leave-out
386
+ columns test whether the formula generalizes across model sizes and stream
387
+ prefix sizes inside the same regime.
388
 
389
  \begin{table}[t]
390
  \centering
391
+ \caption{Regime-specific interaction coefficient fits. Dropout MAE is mean
392
+ absolute error in predicted dropout rate, not validation loss; units are
393
+ absolute dropout probability. L-model and L-prefix are leave-model-out and
394
+ leave-prefix-out dropout MAEs.}
395
  \label{tab:coefficients}
396
  \resizebox{\textwidth}{!}{\input{tables/coefficient_table}}
397
  \end{table}
 
420
  The ``Gain'' column is best static final validation loss minus decay final
421
  validation loss, so positive values favor decay. The best static baseline is
422
  chosen from the tested fixed-dropout grid in the same run family.
423
+ \[
424
+ \mathrm{Gain}
425
+ =L_{\mathrm{final}}(\mathrm{best\ static})
426
+ -L_{\mathrm{final}}(\mathrm{decay}).
427
+ \]
428
+ Thus positive gain means the decay schedule has lower final validation loss
429
+ than the best tested fixed-dropout baseline.
430
+
431
+ Table~\ref{tab:evidence-ladder} compresses the full evidence ladder into one
432
+ view. The calibration MAE columns are dropout-rate prediction errors, not final
433
+ validation metrics; they measure how well the pressure law explains static-cell
434
+ optima. The gain and paired-win columns are the actual streaming tests. The
435
+ pattern is consistent across all three regimes: the law fits static targets
436
+ within roughly two hundredths of dropout, and the resulting schedules beat the
437
+ best tested fixed-dropout baseline at the final prefix.
438
+
439
+ \begin{table}[t]
440
+ \centering
441
+ \caption{Combined calibration and streaming evidence. Calibration MAE is
442
+ dropout-rate prediction error. Gain is
443
+ $L_{\mathrm{final}}(\mathrm{best\ static})-L_{\mathrm{final}}(\mathrm{decay})$,
444
+ so positive gain favors decay; gain units are token-level cross-entropy nats.
445
+ Wins are paired random-seed wins.}
446
+ \label{tab:evidence-ladder}
447
+ \resizebox{0.88\textwidth}{!}{\input{tables/evidence_ladder_table}}
448
+ \end{table}
449
 
450
  \begin{table}[t]
451
  \centering
452
+ \caption{Streaming validation against best fixed-dropout baselines. Final losses
453
+ are mean token-level cross-entropy in nats, reported as mean $\pm$ sample
454
+ standard deviation across seeds.}
455
  \label{tab:streaming-validation}
456
  \resizebox{\textwidth}{!}{\input{tables/streaming_validation_table}}
457
  \end{table}
458
 
459
+ \begin{table}[t]
460
+ \centering
461
+ \caption{Paired final-loss gain audit. Paired gain is each seed's best static
462
+ final loss minus that seed's decay final loss. Confidence intervals are
463
+ nonparametric bootstrap intervals over seeds; the three-seed 8M intervals are
464
+ descriptive rather than definitive.}
465
+ \label{tab:paired-ci}
466
+ \resizebox{\textwidth}{!}{\input{tables/paired_gain_ci_table}}
467
+ \end{table}
468
+
469
  OpenWebText10K provides the cleanest five-seed coefficient-derived validation:
470
  the interaction schedule improves final validation loss by 0.0473 over the
471
  best static baseline and wins all five paired seeds. TinyStories shows a
472
  smaller but consistent final-loss margin: 0.0133 over the best static baseline
473
+ with five paired wins. WikiText-103 also improves at the final 4M prefix, with
474
+ a 0.0297 gain and five paired wins. Its supporting ablations show a useful
475
+ caveat: an interaction schedule fitted only to static-cell optima can decay to
476
+ the dropout floor too early for L12 streaming and land near the best static
477
+ baseline. We therefore treat static-cell fit as calibration evidence, not as a
478
+ substitute for locked-stream validation.
479
 
480
  \subsection{L20 8M extrapolation}
481
 
 
487
  baseline on final validation loss and wins all paired seeds (3/3 in each
488
  regime, 9/9 total).
489
 
490
+ Table~\ref{tab:l20-paths} makes the extrapolation test concrete. The schedules
491
+ are not arbitrary hand-picked names; they are the dropout values produced by
492
+ the fitted regime coefficients at each revealed prefix. The final static
493
+ baseline is the best fixed dropout found from the companion static grid for the
494
+ same L20 stream.
495
+
496
+ \begin{table}[t]
497
+ \centering
498
+ \caption{Frozen L20 locked-stream extrapolation schedules generated before
499
+ validation. In each row, the same model and optimizer continue training across
500
+ the prefix path while the dropout follows the frozen decay path.}
501
+ \label{tab:l20-paths}
502
+ \resizebox{\textwidth}{!}{\input{tables/l20_schedule_path_table}}
503
+ \end{table}
504
+
505
  Figure~\ref{fig:final-improvements} visualizes the final-loss gains. The
506
  largest L20 gain is OpenWebText10K (0.0363), followed by WikiText-103 (0.0184)
507
+ and TinyStories (0.0100). The TinyStories gain is smaller because the best fixed
508
+ baseline is already unusually competitive: static dropout near 0.20 regularizes
509
+ the early prefixes reasonably well on this simpler corpus and does not
510
+ underperform as sharply as fixed dropout does in OpenWebText10K. The decay
511
+ schedule still wins, but it has less fixed-rate error to remove.
512
 
513
  \begin{figure}[t]
514
  \centering
515
  \input{figures/final_improvements}
516
  \caption{Final validation-loss improvement over the best fixed-dropout
517
+ baseline. Values are token-level cross-entropy improvements in nats; positive
518
+ values favor the decay schedule.}
519
  \label{fig:final-improvements}
520
  \end{figure}
521
 
 
530
 
531
  \begin{table}[t]
532
  \centering
533
+ \caption{L20 locked-stream stage-wise comparison. The same model and optimizer
534
+ continue training across increasing token prefixes. Delta is decay validation
535
+ loss minus the best static validation loss at the same prefix, in nats; negative
536
+ favors decay.}
537
  \label{tab:l20-stage}
538
  \resizebox{\textwidth}{!}{\input{tables/l20_stage_table}}
539
  \end{table}
 
542
  \centering
543
  \input{figures/l20_stage_deltas}
544
  \caption{L20 stage-wise deltas versus the best static dropout at each prefix.
545
+ The plotted value is token-level decay loss minus best static loss in nats, so
546
+ values below zero favor decay. The schedules are final-loss schedules, not
547
+ greedy per-prefix optima.}
548
  \label{fig:l20-stage-deltas}
549
  \end{figure}
550
 
 
585
  restart and optimize each prefix independently. Static baselines can be locally
586
  optimal at one prefix but cannot adapt when the stream changes.
587
 
588
+ \paragraph{What the result does and does not prove.}
589
+ The evidence supports a procedure, not a universal numeric constant. For a
590
+ given regime, a small static calibration grid can estimate coefficient values
591
+ that generate useful dropout schedules across model and prefix scale. The
592
+ evidence does not show that the same coefficients should transfer unchanged to
593
+ a different tokenizer, corpus distribution, optimizer, sequence length, or
594
+ dropout placement. Those changes define a new regime and should trigger a new
595
+ calibration.
596
+
597
+ \paragraph{Applying the procedure to a new regime.}
598
+ The intended use is procedural: select a small grid of model sizes and prefix
599
+ sizes that spans the deployment range, run static dropout sweeps for those
600
+ cells, extract $p^\star$ targets, fit $A,B,D,C_0$, freeze the resulting schedule,
601
+ and validate it in a continuing stream against the best tested fixed dropout.
602
+ The calibration step is cheaper than full multi-seed streaming because it probes
603
+ fixed cells rather than repeatedly training full streaming schedules.
604
+
605
  \section{Limitations}
606
 
607
  The experiments are intentionally small compared with frontier language-model
 
613
  The static coefficient fit optimizes dropout targets extracted from static
614
  sweeps, while the streaming objective is final validation loss after continued
615
  training. WikiText-103 shows that these objectives can diverge: the fitted
616
+ interaction schedule predicts static-cell optima accurately but decays too
617
  quickly for L12 streaming. Future work should fit coefficients directly against
618
  streaming objective functions or include trajectory-aware loss terms.
619
 
620
  The L20 extrapolation tier uses three seeds for cost reasons. This is balanced
621
  across all three regimes and supported by five-seed smaller-stream validation,
622
+ but the bootstrap intervals in Table~\ref{tab:paired-ci} should be read as
623
+ descriptive at $n=3$, not as a substitute for larger seed counts. We also do not
624
+ claim that the tested fixed-dropout grids contain every possible fixed optimum,
625
+ or that they exhaust all possible adaptive regularization baselines.
626
 
627
  \section{Reproducibility}
628
 
 
633
  figures and tables are generated by
634
  \texttt{scripts/generate\_paper\_assets.py}. Existing result artifacts are read
635
  from \texttt{runs/}. The experiment runner enforces MPS-only Torch execution.
636
+ Code, cached artifacts, experiment outputs, and paper-generation scripts are
637
+ available at \url{https://huggingface.co/cuber12/dropout-decay}.
638
+
639
+ \begin{table}[t]
640
+ \centering
641
+ \caption{Reproducibility metadata for the manuscript build and reported
642
+ experiments. The final public release should cite an immutable repository tag or
643
+ commit; the table records the current base commit and software stack used while
644
+ preparing this manuscript.}
645
+ \label{tab:repro}
646
+ \resizebox{\textwidth}{!}{\input{tables/reproducibility_table}}
647
+ \end{table}
648
 
649
  All generated tables in this paper are derived from saved metrics JSONL files
650
  and coefficient JSON files. The paper-generation summary is written to
paper/figures/calibration_cell_curves.tex CHANGED
@@ -25,7 +25,6 @@
25
  \nextgroupplot[
26
  title={TinyStories, L12, 1M},
27
  xlabel={Dropout},
28
- ylabel={Validation loss above best},
29
  ymin=0,
30
  grid=both,
31
  ]
@@ -38,7 +37,6 @@
38
  \nextgroupplot[
39
  title={WikiText-103, L12, 250k},
40
  xlabel={Dropout},
41
- ylabel={Validation loss above best},
42
  ymin=0,
43
  grid=both,
44
  ]
 
25
  \nextgroupplot[
26
  title={TinyStories, L12, 1M},
27
  xlabel={Dropout},
 
28
  ymin=0,
29
  grid=both,
30
  ]
 
37
  \nextgroupplot[
38
  title={WikiText-103, L12, 250k},
39
  xlabel={Dropout},
 
40
  ymin=0,
41
  grid=both,
42
  ]
paper/figures/calibration_to_streaming_protocol.tex ADDED
@@ -0,0 +1,27 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ \begin{tikzpicture}[font=\scriptsize]
2
+ \tikzstyle{box}=[
3
+ draw,
4
+ rounded corners=2pt,
5
+ thick,
6
+ align=center,
7
+ text width=0.19\textwidth,
8
+ minimum height=1.5cm,
9
+ inner sep=5pt
10
+ ]
11
+ \node[box, fill=blue!7] at (0,0) (sweep) {Static calibration cells\\
12
+ \footnotesize model $P$, prefix $U$, sampled tokens $C$, dropout grid};
13
+ \node[box, fill=green!8] at (3.7,0) (target) {Extract $p^\star$\\
14
+ \footnotesize grid best or bracketed quadratic minimum};
15
+ \node[box, fill=yellow!12] at (7.4,0) (fit) {Fit pressure law\\
16
+ \footnotesize $p^\star \approx A x + B y + Dxy + C_0$};
17
+ \node[box, fill=red!7] at (11.1,0) (stream) {Frozen streaming test\\
18
+ \footnotesize compare decay path to best static dropout grid};
19
+ \draw[->, thick] (sweep.east) -- (target.west);
20
+ \draw[->, thick] (target.east) -- (fit.west);
21
+ \draw[->, thick] (fit.east) -- (stream.west);
22
+ \node[align=center, text width=0.9\textwidth] at (5.55,-1.45) {
23
+ Inputs to the fitted law are absolute quantities available before or during a run:
24
+ $x=\log_{10}(P/U)$ measures capacity pressure and
25
+ $y=\log_{10}(C/U)$ measures repeated-sampling pressure.
26
+ };
27
+ \end{tikzpicture}
paper/figures/static_optima_by_regime.tex CHANGED
@@ -20,11 +20,11 @@
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[
@@ -38,11 +38,11 @@
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[
@@ -56,11 +56,11 @@
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}
 
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[
 
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[
 
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 CHANGED
@@ -30,9 +30,76 @@
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,
@@ -106,9 +173,61 @@
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,
@@ -210,9 +329,75 @@
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,
@@ -275,9 +460,60 @@
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,
@@ -368,9 +604,76 @@
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,
@@ -444,9 +747,61 @@
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,
 
30
  -0.052590467035770416,
31
  -0.053600385785102844
32
  ],
33
+ "paired_records": [
34
+ {
35
+ "seed": 1,
36
+ "decay_loss": 4.402347795665264,
37
+ "best_static_condition": "static_dropout_0.14",
38
+ "best_static_loss": 4.4417688846588135,
39
+ "delta": -0.03942108899354935,
40
+ "gain": 0.03942108899354935
41
+ },
42
+ {
43
+ "seed": 2,
44
+ "decay_loss": 4.401971310377121,
45
+ "best_static_condition": "static_dropout_0.14",
46
+ "best_static_loss": 4.460222490131855,
47
+ "delta": -0.05825117975473404,
48
+ "gain": 0.05825117975473404
49
+ },
50
+ {
51
+ "seed": 3,
52
+ "decay_loss": 4.402896843850613,
53
+ "best_static_condition": "static_dropout_0.14",
54
+ "best_static_loss": 4.43564984947443,
55
+ "delta": -0.032753005623817444,
56
+ "gain": 0.032753005623817444
57
+ },
58
+ {
59
+ "seed": 4,
60
+ "decay_loss": 4.381064593791962,
61
+ "best_static_condition": "static_dropout_0.14",
62
+ "best_static_loss": 4.433655060827732,
63
+ "delta": -0.052590467035770416,
64
+ "gain": 0.052590467035770416
65
+ },
66
+ {
67
+ "seed": 5,
68
+ "decay_loss": 4.402371659874916,
69
+ "best_static_condition": "static_dropout_0.14",
70
+ "best_static_loss": 4.455972045660019,
71
+ "delta": -0.053600385785102844,
72
+ "gain": 0.053600385785102844
73
+ }
74
+ ],
75
  "paired_wins": 5,
76
  "paired_mean_delta": -0.04732322543859482,
77
  "paired_std_delta": 0.010738969078098962,
78
+ "paired_gain_mean": 0.04732322543859482,
79
+ "paired_gain_std": 0.010738969078098962,
80
+ "paired_gain_ci_low": 0.0382560983300209,
81
+ "paired_gain_ci_high": 0.055258719623088835,
82
+ "budget": {
83
+ "steps_per_stage": [
84
+ 1000
85
+ ],
86
+ "batch_sizes": [
87
+ 16
88
+ ],
89
+ "block_size": 128,
90
+ "sampled_per_stage": [
91
+ 2048000
92
+ ],
93
+ "final_sampled_tokens": 10240000,
94
+ "prefix_path": [
95
+ 250000,
96
+ 500000,
97
+ 1000000,
98
+ 2000000,
99
+ 4000000
100
+ ],
101
+ "stage_count": 5
102
+ },
103
  "stage_deltas": [
104
  {
105
  "stage": 0,
 
173
  -0.025513865053653717,
174
  -0.03717709332704544
175
  ],
176
+ "paired_records": [
177
+ {
178
+ "seed": 1,
179
+ "decay_loss": 4.192408442497253,
180
+ "best_static_condition": "static_dropout_0.18",
181
+ "best_static_loss": 4.231759630143642,
182
+ "delta": -0.03935118764638901,
183
+ "gain": 0.03935118764638901
184
+ },
185
+ {
186
+ "seed": 2,
187
+ "decay_loss": 4.180009409785271,
188
+ "best_static_condition": "static_dropout_0.2",
189
+ "best_static_loss": 4.205523274838924,
190
+ "delta": -0.025513865053653717,
191
+ "gain": 0.025513865053653717
192
+ },
193
+ {
194
+ "seed": 3,
195
+ "decay_loss": 4.165537476539612,
196
+ "best_static_condition": "static_dropout_0.14",
197
+ "best_static_loss": 4.202714569866657,
198
+ "delta": -0.03717709332704544,
199
+ "gain": 0.03717709332704544
200
+ }
201
+ ],
202
  "paired_wins": 3,
203
  "paired_mean_delta": -0.03401404867569605,
204
  "paired_std_delta": 0.007441203715372809,
205
+ "paired_gain_mean": 0.03401404867569605,
206
+ "paired_gain_std": 0.007441203715372809,
207
+ "paired_gain_ci_low": 0.025513865053653717,
208
+ "paired_gain_ci_high": 0.03935118764638901,
209
+ "budget": {
210
+ "steps_per_stage": [
211
+ 1000
212
+ ],
213
+ "batch_sizes": [
214
+ 16
215
+ ],
216
+ "block_size": 128,
217
+ "sampled_per_stage": [
218
+ 2048000
219
+ ],
220
+ "final_sampled_tokens": 12288000,
221
+ "prefix_path": [
222
+ 250000,
223
+ 500000,
224
+ 1000000,
225
+ 2000000,
226
+ 4000000,
227
+ 8000000
228
+ ],
229
+ "stage_count": 6
230
+ },
231
  "stage_deltas": [
232
  {
233
  "stage": 0,
 
329
  -0.016586389392614365,
330
  -0.014097623527050018
331
  ],
332
+ "paired_records": [
333
+ {
334
+ "seed": 1,
335
+ "decay_loss": 2.541424009948969,
336
+ "best_static_condition": "static_dropout_0.08",
337
+ "best_static_loss": 2.54193027690053,
338
+ "delta": -0.0005062669515609741,
339
+ "gain": 0.0005062669515609741
340
+ },
341
+ {
342
+ "seed": 2,
343
+ "decay_loss": 2.5376960188150406,
344
+ "best_static_condition": "static_dropout_0.12",
345
+ "best_static_loss": 2.558806713670492,
346
+ "delta": -0.021110694855451584,
347
+ "gain": 0.021110694855451584
348
+ },
349
+ {
350
+ "seed": 3,
351
+ "decay_loss": 2.5385167226195335,
352
+ "best_static_condition": "static_dropout_0.08",
353
+ "best_static_loss": 2.547760896384716,
354
+ "delta": -0.009244173765182495,
355
+ "gain": 0.009244173765182495
356
+ },
357
+ {
358
+ "seed": 4,
359
+ "decay_loss": 2.493242312222719,
360
+ "best_static_condition": "static_dropout_0.08",
361
+ "best_static_loss": 2.5098287016153336,
362
+ "delta": -0.016586389392614365,
363
+ "gain": 0.016586389392614365
364
+ },
365
+ {
366
+ "seed": 5,
367
+ "decay_loss": 2.5446842312812805,
368
+ "best_static_condition": "static_dropout_0.08",
369
+ "best_static_loss": 2.5587818548083305,
370
+ "delta": -0.014097623527050018,
371
+ "gain": 0.014097623527050018
372
+ }
373
+ ],
374
  "paired_wins": 5,
375
  "paired_mean_delta": -0.012309029698371887,
376
  "paired_std_delta": 0.00786865194496459,
377
+ "paired_gain_mean": 0.012309029698371887,
378
+ "paired_gain_std": 0.00786865194496459,
379
+ "paired_gain_ci_low": 0.005942809581756592,
380
+ "paired_gain_ci_high": 0.017898358404636383,
381
+ "budget": {
382
+ "steps_per_stage": [
383
+ 2000
384
+ ],
385
+ "batch_sizes": [
386
+ 16
387
+ ],
388
+ "block_size": 128,
389
+ "sampled_per_stage": [
390
+ 4096000
391
+ ],
392
+ "final_sampled_tokens": 16384000,
393
+ "prefix_path": [
394
+ 500000,
395
+ 1000000,
396
+ 2000000,
397
+ 4000000
398
+ ],
399
+ "stage_count": 4
400
+ },
401
  "stage_deltas": [
402
  {
403
  "stage": 0,
 
460
  -0.006095590069890022,
461
  -0.013002954423427582
462
  ],
463
+ "paired_records": [
464
+ {
465
+ "seed": 1,
466
+ "decay_loss": 2.206169916316867,
467
+ "best_static_condition": "static_dropout_0.2",
468
+ "best_static_loss": 2.214863333851099,
469
+ "delta": -0.00869341753423214,
470
+ "gain": 0.00869341753423214
471
+ },
472
+ {
473
+ "seed": 2,
474
+ "decay_loss": 2.2139150239527225,
475
+ "best_static_condition": "static_dropout_0.2",
476
+ "best_static_loss": 2.2200106140226126,
477
+ "delta": -0.006095590069890022,
478
+ "gain": 0.006095590069890022
479
+ },
480
+ {
481
+ "seed": 3,
482
+ "decay_loss": 2.2056322395801544,
483
+ "best_static_condition": "static_dropout_0.18",
484
+ "best_static_loss": 2.218635194003582,
485
+ "delta": -0.013002954423427582,
486
+ "gain": 0.013002954423427582
487
+ }
488
+ ],
489
  "paired_wins": 3,
490
  "paired_mean_delta": -0.00926398734251658,
491
  "paired_std_delta": 0.0034888512447114515,
492
+ "paired_gain_mean": 0.00926398734251658,
493
+ "paired_gain_std": 0.0034888512447114515,
494
+ "paired_gain_ci_low": 0.006095590069890022,
495
+ "paired_gain_ci_high": 0.013002954423427582,
496
+ "budget": {
497
+ "steps_per_stage": [
498
+ 2000
499
+ ],
500
+ "batch_sizes": [
501
+ 16
502
+ ],
503
+ "block_size": 128,
504
+ "sampled_per_stage": [
505
+ 4096000
506
+ ],
507
+ "final_sampled_tokens": 20480000,
508
+ "prefix_path": [
509
+ 500000,
510
+ 1000000,
511
+ 2000000,
512
+ 4000000,
513
+ 8000000
514
+ ],
515
+ "stage_count": 5
516
+ },
517
  "stage_deltas": [
518
  {
519
  "stage": 0,
 
604
  -0.02507050335407257,
605
  -0.037036627531051636
606
  ],
607
+ "paired_records": [
608
+ {
609
+ "seed": 1,
610
+ "decay_loss": 4.062299974262714,
611
+ "best_static_condition": "static_dropout_0.1",
612
+ "best_static_loss": 4.080718021839857,
613
+ "delta": -0.018418047577142715,
614
+ "gain": 0.018418047577142715
615
+ },
616
+ {
617
+ "seed": 2,
618
+ "decay_loss": 4.112260434776545,
619
+ "best_static_condition": "static_dropout_0.06",
620
+ "best_static_loss": 4.130373623222113,
621
+ "delta": -0.018113188445568085,
622
+ "gain": 0.018113188445568085
623
+ },
624
+ {
625
+ "seed": 3,
626
+ "decay_loss": 4.0763493329286575,
627
+ "best_static_condition": "static_dropout_0.08",
628
+ "best_static_loss": 4.1035647466778755,
629
+ "delta": -0.027215413749217987,
630
+ "gain": 0.027215413749217987
631
+ },
632
+ {
633
+ "seed": 4,
634
+ "decay_loss": 4.0844879895448685,
635
+ "best_static_condition": "static_dropout_0.1",
636
+ "best_static_loss": 4.109558492898941,
637
+ "delta": -0.02507050335407257,
638
+ "gain": 0.02507050335407257
639
+ },
640
+ {
641
+ "seed": 5,
642
+ "decay_loss": 4.068591594696045,
643
+ "best_static_condition": "static_dropout_0.08",
644
+ "best_static_loss": 4.105628222227097,
645
+ "delta": -0.037036627531051636,
646
+ "gain": 0.037036627531051636
647
+ }
648
+ ],
649
  "paired_wins": 5,
650
  "paired_mean_delta": -0.0251707561314106,
651
  "paired_std_delta": 0.007752385341386418,
652
+ "paired_gain_mean": 0.0251707561314106,
653
+ "paired_gain_std": 0.007752385341386418,
654
+ "paired_gain_ci_low": 0.019626595079898834,
655
+ "paired_gain_ci_high": 0.03128769695758819,
656
+ "budget": {
657
+ "steps_per_stage": [
658
+ 1000
659
+ ],
660
+ "batch_sizes": [
661
+ 16
662
+ ],
663
+ "block_size": 128,
664
+ "sampled_per_stage": [
665
+ 2048000
666
+ ],
667
+ "final_sampled_tokens": 10240000,
668
+ "prefix_path": [
669
+ 250000,
670
+ 500000,
671
+ 1000000,
672
+ 2000000,
673
+ 4000000
674
+ ],
675
+ "stage_count": 5
676
+ },
677
  "stage_deltas": [
678
  {
679
  "stage": 0,
 
747
  -0.00015844032168388367,
748
  -0.027258697897195816
749
  ],
750
+ "paired_records": [
751
+ {
752
+ "seed": 1,
753
+ "decay_loss": 3.952172212302685,
754
+ "best_static_condition": "static_dropout_0.18",
755
+ "best_static_loss": 3.968690302222967,
756
+ "delta": -0.016518089920282364,
757
+ "gain": 0.016518089920282364
758
+ },
759
+ {
760
+ "seed": 2,
761
+ "decay_loss": 3.967039406299591,
762
+ "best_static_condition": "static_dropout_0.14",
763
+ "best_static_loss": 3.967197846621275,
764
+ "delta": -0.00015844032168388367,
765
+ "gain": 0.00015844032168388367
766
+ },
767
+ {
768
+ "seed": 3,
769
+ "decay_loss": 3.9525104351341724,
770
+ "best_static_condition": "static_dropout_0.1",
771
+ "best_static_loss": 3.9797691330313683,
772
+ "delta": -0.027258697897195816,
773
+ "gain": 0.027258697897195816
774
+ }
775
+ ],
776
  "paired_wins": 3,
777
  "paired_mean_delta": -0.014645076046387354,
778
  "paired_std_delta": 0.013646872385042126,
779
+ "paired_gain_mean": 0.014645076046387354,
780
+ "paired_gain_std": 0.013646872385042126,
781
+ "paired_gain_ci_low": 0.00015844032168388367,
782
+ "paired_gain_ci_high": 0.027258697897195816,
783
+ "budget": {
784
+ "steps_per_stage": [
785
+ 1000
786
+ ],
787
+ "batch_sizes": [
788
+ 16
789
+ ],
790
+ "block_size": 128,
791
+ "sampled_per_stage": [
792
+ 2048000
793
+ ],
794
+ "final_sampled_tokens": 12288000,
795
+ "prefix_path": [
796
+ 250000,
797
+ 500000,
798
+ 1000000,
799
+ 2000000,
800
+ 4000000,
801
+ 8000000
802
+ ],
803
+ "stage_count": 6
804
+ },
805
  "stage_deltas": [
806
  {
807
  "stage": 0,
paper/tables/calibration_weight_table.tex ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ \begin{tabular}{lll}
2
+ \toprule
3
+ Cell property & Weight multiplier & Reason \\
4
+ \midrule
5
+ Best grid point on dropout boundary & $0.30$ & Optimum may lie outside tested grid \\
6
+ Quadratic minimum not bracketed & $0.50$ & Local parabola is less reliable \\
7
+ Loss span below $0.02$ nats & $0.50$ & Very flat curve weakly identifies $p^\star$ \\
8
+ Best-loss std. $\sigma > 0$ & $1/(1+20\sigma)$ & Noisier minima receive less influence \\
9
+ \bottomrule
10
+ \end{tabular}
paper/tables/coefficient_table.tex CHANGED
@@ -1,6 +1,6 @@
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 \\
 
1
  \begin{tabular}{lrrrrrrrr}
2
  \toprule
3
+ Regime & Cells & A & B & D & C0 & Dropout MAE (abs. $p$) & 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 \\
paper/tables/evidence_ladder_table.tex ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ \begin{tabular}{lrrrrr}
2
+ \toprule
3
+ Regime & Calib. dropout MAE & 4M gain (nats) & 4M wins & 8M gain (nats) & 8M wins \\
4
+ \midrule
5
+ OpenWebText10K & 0.0158 & 0.0473 & 5/5 & 0.0363 & 3/3 \\
6
+ TinyStories & 0.0180 & 0.0133 & 5/5 & 0.0100 & 3/3 \\
7
+ WikiText-103 & 0.0162 & 0.0297 & 5/5 & 0.0184 & 3/3 \\
8
+ \bottomrule
9
+ \end{tabular}
paper/tables/experiment_protocol_table.tex ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ \begin{tabular}{llll}
2
+ \toprule
3
+ Experiment layer & What is run & Question answered & Statistical unit \\
4
+ \midrule
5
+ Static calibration & Per-regime dropout grid at fixed $(P,U,C)$ cells & Where does validation loss plateau over dropout? & 15--16 cells per regime \\
6
+ Coefficient fit & Per-regime regression from cell targets $p^\star$ & Can pressure variables predict useful dropout? & MAE / leave-out MAE \\
7
+ 4M final-prefix locked stream & Per-regime continuing model over growing prefixes & Does decay beat the best tested fixed dropout? & 5 paired seeds per regime \\
8
+ L20 8M extrapolation & Per-regime 51.84M-param model with frozen coefficients & Does the law extend to larger model/data scale? & 3 paired seeds per regime \\
9
+ Stage-wise audit & Per-regime decay vs best static at every prefix & Is the schedule final-loss or per-prefix optimal? & Prefix-level deltas \\
10
+ \bottomrule
11
+ \end{tabular}
paper/tables/l20_schedule_path_table.tex ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ \begin{tabular}{llll}
2
+ \toprule
3
+ Regime & Locked-stream prefix path & Frozen decay path & Final best static \\
4
+ \midrule
5
+ OpenWebText10K & 250k $\rightarrow$ 500k $\rightarrow$ 1M $\rightarrow$ 2M $\rightarrow$ 4M $\rightarrow$ 8M & 0.433 $\rightarrow$ 0.367 $\rightarrow$ 0.270 $\rightarrow$ 0.176 $\rightarrow$ 0.095 $\rightarrow$ 0.033 & p=0.2 \\
6
+ TinyStories & 500k $\rightarrow$ 1M $\rightarrow$ 2M $\rightarrow$ 4M $\rightarrow$ 8M & 0.253 $\rightarrow$ 0.209 $\rightarrow$ 0.137 $\rightarrow$ 0.077 $\rightarrow$ 0.041 & p=0.2 \\
7
+ WikiText-103 & 250k $\rightarrow$ 500k $\rightarrow$ 1M $\rightarrow$ 2M $\rightarrow$ 4M $\rightarrow$ 8M & 0.284 $\rightarrow$ 0.211 $\rightarrow$ 0.116 $\rightarrow$ 0.036 $\rightarrow$ 0.020 $\rightarrow$ 0.020 & p=0.14 \\
8
+ \bottomrule
9
+ \end{tabular}
paper/tables/l20_stage_table.tex CHANGED
@@ -1,6 +1,6 @@
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 \\
 
1
  \begin{tabular}{lrrrrr}
2
  \toprule
3
+ Regime & Prefix & Decay dropout & Decay val (nats) & Best static val (nats) & Delta (nats) \\
4
  \midrule
5
  OpenWebText10K & 250k & 0.433 & 5.4601 & 5.3971 & +0.0630 \\
6
  OpenWebText10K & 500k & 0.367 & 5.0179 & 5.0169 & +0.0010 \\
paper/tables/paired_gain_ci_table.tex ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ \begin{tabular}{llrrrr}
2
+ \toprule
3
+ Regime & Protocol & Paired gain mean & 95\% bootstrap CI & Seeds & Wins \\
4
+ \midrule
5
+ OpenWebText10K & 4M final prefix / 5 seeds & 0.0473 & [0.0383, 0.0553] & 5 & 5/5 \\
6
+ OpenWebText10K & 8M final prefix / 3 seeds & 0.0340 & [0.0255, 0.0394] & 3 & 3/3 \\
7
+ TinyStories & 4M final prefix / 5 seeds & 0.0123 & [0.0059, 0.0179] & 5 & 5/5 \\
8
+ TinyStories & 8M final prefix / 3 seeds & 0.0093 & [0.0061, 0.0130] & 3 & 3/3 \\
9
+ WikiText-103 & 4M final prefix / 5 seeds & 0.0252 & [0.0196, 0.0313] & 5 & 5/5 \\
10
+ WikiText-103 & 8M final prefix / 3 seeds & 0.0146 & [0.0002, 0.0273] & 3 & 3/3 \\
11
+ \bottomrule
12
+ \end{tabular}
paper/tables/protocol_budget_table.tex ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ \begin{tabular}{llllrr}
2
+ \toprule
3
+ Regime & Protocol & Prefix path & Sampled/stage & Batch & Block \\
4
+ \midrule
5
+ OpenWebText10K & 4M final prefix / 5 seeds & 250k $\rightarrow$ 500k $\rightarrow$ 1M $\rightarrow$ 2M $\rightarrow$ 4M & 2.048M & 16 & 128 \\
6
+ OpenWebText10K & 8M final prefix / 3 seeds & 250k $\rightarrow$ 500k $\rightarrow$ 1M $\rightarrow$ 2M $\rightarrow$ 4M $\rightarrow$ 8M & 2.048M & 16 & 128 \\
7
+ TinyStories & 4M final prefix / 5 seeds & 500k $\rightarrow$ 1M $\rightarrow$ 2M $\rightarrow$ 4M & 4.096M & 16 & 128 \\
8
+ TinyStories & 8M final prefix / 3 seeds & 500k $\rightarrow$ 1M $\rightarrow$ 2M $\rightarrow$ 4M $\rightarrow$ 8M & 4.096M & 16 & 128 \\
9
+ WikiText-103 & 4M final prefix / 5 seeds & 250k $\rightarrow$ 500k $\rightarrow$ 1M $\rightarrow$ 2M $\rightarrow$ 4M & 2.048M & 16 & 128 \\
10
+ WikiText-103 & 8M final prefix / 3 seeds & 250k $\rightarrow$ 500k $\rightarrow$ 1M $\rightarrow$ 2M $\rightarrow$ 4M $\rightarrow$ 8M & 2.048M & 16 & 128 \\
11
+ \bottomrule
12
+ \end{tabular}
paper/tables/regime_setup_table.tex ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ \begin{tabular}{llll}
2
+ \toprule
3
+ Regime & Corpus source & Tokenizer / vocab & Streaming prefixes \\
4
+ \midrule
5
+ OpenWebText10K & Cached OpenWebText-style subset & BPE / 4096 & 250k, 500k, 1M, 2M, 4M; plus 8M L20 tier \\
6
+ TinyStories & Cached TinyStories subset & BPE / 4096 & 500k, 1M, 2M, 4M; plus 8M L20 tier \\
7
+ WikiText-103 & Cached WikiText-103 subset & BPE / 4096 & 250k, 500k, 1M, 2M, 4M; plus 8M L20 tier \\
8
+ \bottomrule
9
+ \end{tabular}
paper/tables/reproducibility_table.tex ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ \begin{tabular}{ll}
2
+ \toprule
3
+ Item & Value \\
4
+ \midrule
5
+ Repository & \url{https://huggingface.co/cuber12/dropout-decay} \\
6
+ Repository snapshot & Base commit \texttt{e7a7275}; cite final release tag with paper \\
7
+ License & MIT-derived source with nanochat attribution retained \\
8
+ Python & 3.11.15 \\
9
+ PyTorch & 2.12.0 \\
10
+ Operating system & macOS 26.4 arm64 \\
11
+ Training backend & Apple Metal Performance Shaders (MPS); runner refuses CPU/CUDA \\
12
+ \bottomrule
13
+ \end{tabular}
paper/tables/streaming_validation_table.tex CHANGED
@@ -1,12 +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}
 
1
  \begin{tabular}{llrrrrr}
2
  \toprule
3
+ Regime & Protocol & Decay final (nats) & Best static final (nats) & Gain (nats) & Seeds & Paired wins \\
4
  \midrule
5
+ OpenWebText10K & 4M final prefix / 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 final prefix / 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 final prefix / 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}
paper/tables/symbol_table.tex ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ \begin{tabular}{ll}
2
+ \toprule
3
+ Symbol & Meaning \\
4
+ \midrule
5
+ $P$ & Trainable parameter count \\
6
+ $U_t$ & Unique-token prefix currently revealed by stream stage $t$ \\
7
+ $C_t$ & Cumulative sampled training tokens consumed by the optimizer \\
8
+ $x_t$ & $\log_{10}(P/U_t)$, capacity pressure \\
9
+ $y_t$ & $\log_{10}(C_t/U_t)$, repeated-sampling pressure \\
10
+ $p_t$ & Dropout rate used at stream stage $t$ \\
11
+ $p^\star$ & Static-sweep target dropout for one calibration cell \\
12
+ $A,B,D,C_0$ & Regime-specific fitted pressure-law coefficients \\
13
+ $p_{\min},p_{\max}$ & Clamp bounds applied to generated dropout schedules \\
14
+ \bottomrule
15
+ \end{tabular}
pyproject.toml CHANGED
@@ -1,3 +1,7 @@
 
 
 
 
1
  [project]
2
  name = "dropout-decay"
3
  version = "0.1.0"
@@ -11,6 +15,14 @@ dependencies = [
11
  "torch>=2.3",
12
  ]
13
 
 
 
 
 
 
14
  [tool.pytest.ini_options]
15
  pythonpath = ["src"]
16
  testpaths = ["tests"]
 
 
 
 
1
+ [build-system]
2
+ requires = ["setuptools>=69", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
  [project]
6
  name = "dropout-decay"
7
  version = "0.1.0"
 
15
  "torch>=2.3",
16
  ]
17
 
18
+ [project.optional-dependencies]
19
+ dev = [
20
+ "pytest>=8",
21
+ ]
22
+
23
  [tool.pytest.ini_options]
24
  pythonpath = ["src"]
25
  testpaths = ["tests"]
26
+
27
+ [tool.setuptools.packages.find]
28
+ where = ["src"]
scripts/generate_paper_assets.py CHANGED
@@ -23,6 +23,7 @@ import csv
23
  import json
24
  import math
25
  from pathlib import Path
 
26
  import statistics
27
 
28
 
@@ -48,7 +49,7 @@ REGIMES = {
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",
@@ -75,7 +76,7 @@ REGIMES = {
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",
@@ -94,7 +95,7 @@ REGIMES = {
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",
@@ -127,6 +128,27 @@ def mean_std(values: list[float]) -> tuple[float, float]:
127
  return mean, std
128
 
129
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
130
  def fmt(x: float, digits: int = 4) -> str:
131
  return f"{x:.{digits}f}"
132
 
@@ -142,6 +164,10 @@ def tex_escape(value: object) -> str:
142
  )
143
 
144
 
 
 
 
 
145
  def token_label(value: int) -> str:
146
  if value >= 1_000_000:
147
  whole = value / 1_000_000
@@ -149,6 +175,39 @@ def token_label(value: int) -> str:
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 = []
@@ -163,6 +222,12 @@ def metric_stage_summary(run_dir: Path | list[Path], condition_name: str) -> dic
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)
@@ -192,10 +257,27 @@ def metric_stage_summary(run_dir: Path | list[Path], condition_name: str) -> dic
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:
@@ -251,9 +333,15 @@ def metric_stage_summary(run_dir: Path | list[Path], condition_name: str) -> dic
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
 
@@ -318,21 +406,29 @@ def tikz_coordinates(points: list[tuple[float, float]], digits: int = 4) -> str:
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}}
@@ -374,7 +470,7 @@ def write_static_optima_figure() -> None:
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)
@@ -486,11 +582,186 @@ def write_final_improvement_figure(summaries: dict[str, dict]) -> None:
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:
@@ -505,10 +776,29 @@ def write_tables(summaries: dict[str, dict], coeffs: dict[str, dict]) -> None:
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():
@@ -525,10 +815,57 @@ def write_tables(summaries: dict[str, dict], coeffs: dict[str, dict]) -> None:
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():
@@ -569,6 +906,7 @@ def main() -> None:
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)
 
23
  import json
24
  import math
25
  from pathlib import Path
26
+ import random
27
  import statistics
28
 
29
 
 
49
  / "runs/openwebtext10k_l16_updated_formula_clean_5seed/locked_stream/20260530-174525",
50
  "small_condition": "openwebtext10k_interaction",
51
  "small_model": "L16_H8_D384",
52
+ "small_protocol": "4M final prefix / 5 seeds",
53
  "l20_stream": ROOT
54
  / "runs/openwebtext10k_l20_8m_extrapolation_3seed/locked_stream/20260601-191150",
55
  "l20_condition": "openwebtext10k_l20_interaction_8m",
 
76
  ],
77
  "small_condition": "interaction",
78
  "small_model": "L12_H8_D320",
79
+ "small_protocol": "4M final prefix / 5 seeds",
80
  "l20_stream": ROOT
81
  / "runs/tinystories_l20_8m_extrapolation_3seed/locked_stream/20260602-112249",
82
  "l20_condition": "tinystories_l20_interaction_8m",
 
95
  / "runs/wikitext103_l12_streaming_validation_5seed/locked_stream/20260531-093525",
96
  "small_condition": "wikitext103_formula_l12",
97
  "small_model": "L12_H8_D320",
98
+ "small_protocol": "4M final prefix / 5 seeds",
99
  "l20_stream": ROOT
100
  / "runs/wikitext103_l20_8m_extrapolation_3seed/locked_stream/20260603-231952",
101
  "l20_condition": "wikitext103_l20_interaction_8m",
 
128
  return mean, std
129
 
130
 
131
+ def bootstrap_ci(
132
+ values: list[float], *, confidence: float = 0.95, iterations: int = 20_000, seed: int = 1337
133
+ ) -> tuple[float, float]:
134
+ if not values:
135
+ return float("nan"), float("nan")
136
+ if len(values) == 1:
137
+ return values[0], values[0]
138
+ rng = random.Random(seed)
139
+ n = len(values)
140
+ means = []
141
+ for _ in range(iterations):
142
+ sample = [values[rng.randrange(n)] for _ in range(n)]
143
+ means.append(statistics.fmean(sample))
144
+ means.sort()
145
+ lower_q = (1.0 - confidence) / 2.0
146
+ upper_q = 1.0 - lower_q
147
+ lower_idx = min(iterations - 1, max(0, int(lower_q * iterations)))
148
+ upper_idx = min(iterations - 1, max(0, int(upper_q * iterations)))
149
+ return means[lower_idx], means[upper_idx]
150
+
151
+
152
  def fmt(x: float, digits: int = 4) -> str:
153
  return f"{x:.{digits}f}"
154
 
 
164
  )
165
 
166
 
167
+ def model_label(model_name: str) -> str:
168
+ return tex_escape(model_name.replace("_", "-"))
169
+
170
+
171
  def token_label(value: int) -> str:
172
  if value >= 1_000_000:
173
  whole = value / 1_000_000
 
175
  return f"{value // 1000}k"
176
 
177
 
178
+ def protocol_budget(condition_rows: list[dict], stages: list[int]) -> dict:
179
+ rows_by_seed: dict[int, list[dict]] = {}
180
+ for row in condition_rows:
181
+ rows_by_seed.setdefault(int(row["seed"]), []).append(row)
182
+ seed = sorted(rows_by_seed)[0]
183
+ seed_rows = sorted(rows_by_seed[seed], key=lambda item: int(item["stage"]))
184
+ sampled_deltas = []
185
+ previous_tokens_seen = 0
186
+ for row in seed_rows:
187
+ tokens_seen = int(row["tokens_seen"])
188
+ sampled_deltas.append(tokens_seen - previous_tokens_seen)
189
+ previous_tokens_seen = tokens_seen
190
+ first = seed_rows[0]
191
+ steps_per_stage = sorted({int(row["steps"]) for row in seed_rows})
192
+ block_size = int(first["model_config"]["block_size"])
193
+ sampled_per_stage = sorted(set(sampled_deltas))
194
+ batch_sizes = sorted(
195
+ {
196
+ round(delta / (int(row["steps"]) * block_size))
197
+ for delta, row in zip(sampled_deltas, seed_rows, strict=True)
198
+ }
199
+ )
200
+ return {
201
+ "steps_per_stage": steps_per_stage,
202
+ "batch_sizes": batch_sizes,
203
+ "block_size": block_size,
204
+ "sampled_per_stage": sampled_per_stage,
205
+ "final_sampled_tokens": int(seed_rows[-1]["tokens_seen"]),
206
+ "prefix_path": [int(row["token_limit"]) for row in seed_rows],
207
+ "stage_count": len(stages),
208
+ }
209
+
210
+
211
  def metric_stage_summary(run_dir: Path | list[Path], condition_name: str) -> dict:
212
  run_dirs = run_dir if isinstance(run_dir, list) else [run_dir]
213
  rows = []
 
222
  key = (row["condition"], int(row["stage"]))
223
  by_condition_stage.setdefault(key, []).append(row)
224
 
225
+ condition_rows_by_stage = [
226
+ row
227
+ for (condition, _stage), group in by_condition_stage.items()
228
+ if condition == condition_name
229
+ for row in group
230
+ ]
231
  condition_rows = by_condition_stage[(condition_name, final_stage)]
232
  final_vals = [float(row["val_eval_loss"]) for row in condition_rows]
233
  condition_final_mean, condition_final_std = mean_std(final_vals)
 
257
  final_static_by_seed[seed] = (row["condition"], value)
258
 
259
  paired_deltas = []
260
+ paired_records = []
261
+ for row in sorted(condition_rows, key=lambda item: int(item["seed"])):
262
  seed = int(row["seed"])
263
+ decay_loss = float(row["val_eval_loss"])
264
+ static_condition, static_loss = final_static_by_seed[seed]
265
+ delta = decay_loss - static_loss
266
+ paired_deltas.append(delta)
267
+ paired_records.append(
268
+ {
269
+ "seed": seed,
270
+ "decay_loss": decay_loss,
271
+ "best_static_condition": static_condition,
272
+ "best_static_loss": static_loss,
273
+ "delta": delta,
274
+ "gain": -delta,
275
+ }
276
+ )
277
  paired_mean, paired_std = mean_std(paired_deltas)
278
+ paired_gains = [-delta for delta in paired_deltas]
279
+ paired_gain_mean, paired_gain_std = mean_std(paired_gains)
280
+ paired_gain_ci_low, paired_gain_ci_high = bootstrap_ci(paired_gains)
281
 
282
  stage_deltas = []
283
  for stage in stages:
 
333
  "best_static_std": best_static_std,
334
  "improvement": best_static_mean - condition_final_mean,
335
  "paired_deltas": paired_deltas,
336
+ "paired_records": paired_records,
337
  "paired_wins": sum(1 for delta in paired_deltas if delta < 0),
338
  "paired_mean_delta": paired_mean,
339
  "paired_std_delta": paired_std,
340
+ "paired_gain_mean": paired_gain_mean,
341
+ "paired_gain_std": paired_gain_std,
342
+ "paired_gain_ci_low": paired_gain_ci_low,
343
+ "paired_gain_ci_high": paired_gain_ci_high,
344
+ "budget": protocol_budget(condition_rows_by_stage, stages),
345
  "stage_deltas": stage_deltas,
346
  }
347
 
 
406
 
407
  def write_calibration_curve_figure() -> None:
408
  axes = []
409
+ for idx, (key, paths) in enumerate(REGIMES.items()):
410
  raw_curve = static_curve_points(
411
  paths["curve_run"], paths["curve_model"], paths["curve_prefix"]
412
  )
413
  minimum = min(loss for _, loss in raw_curve)
414
  points = [(dropout, loss - minimum) for dropout, loss in raw_curve]
415
  best = min(raw_curve, key=lambda item: item[1])
416
+ axis_options = [
417
+ f" title={{{paths['curve_title']}}},",
418
+ " xlabel={Dropout},",
419
+ ]
420
+ if idx == 0:
421
+ axis_options.append(" ylabel={Validation loss above best},")
422
+ axis_options.extend(
423
+ [
424
+ " ymin=0,",
425
+ " grid=both,",
426
+ ]
427
+ )
428
  axes.append(
429
  rf"""
430
  \nextgroupplot[
431
+ {chr(10).join(axis_options)}
 
 
 
 
432
  ]
433
  \addplot+[thick, mark=*] coordinates {{{tikz_coordinates(points, digits=5)}}};
434
  \addlegendentry{{static sweep}}
 
470
  "\\addplot+[thick, mark=*] coordinates {"
471
  + tikz_coordinates(coords)
472
  + "};\n"
473
+ + f"\\addlegendentry{{{model_label(model)}}}"
474
  )
475
  xticks = ",".join(str(i) for i in range(len(labels)))
476
  xticklabels = ",".join(token_label(prefix) for prefix in labels)
 
582
  (FIG_DIR / "final_improvements.tex").write_text(content, encoding="utf-8")
583
 
584
 
585
+ def write_protocol_schematic() -> None:
586
+ content = r"""
587
+ \begin{tikzpicture}[font=\scriptsize]
588
+ \tikzstyle{box}=[
589
+ draw,
590
+ rounded corners=2pt,
591
+ thick,
592
+ align=center,
593
+ text width=0.19\textwidth,
594
+ minimum height=1.5cm,
595
+ inner sep=5pt
596
+ ]
597
+ \node[box, fill=blue!7] at (0,0) (sweep) {Static calibration cells\\
598
+ \footnotesize model $P$, prefix $U$, sampled tokens $C$, dropout grid};
599
+ \node[box, fill=green!8] at (3.7,0) (target) {Extract $p^\star$\\
600
+ \footnotesize grid best or bracketed quadratic minimum};
601
+ \node[box, fill=yellow!12] at (7.4,0) (fit) {Fit pressure law\\
602
+ \footnotesize $p^\star \approx A x + B y + Dxy + C_0$};
603
+ \node[box, fill=red!7] at (11.1,0) (stream) {Frozen streaming test\\
604
+ \footnotesize compare decay path to best static dropout grid};
605
+ \draw[->, thick] (sweep.east) -- (target.west);
606
+ \draw[->, thick] (target.east) -- (fit.west);
607
+ \draw[->, thick] (fit.east) -- (stream.west);
608
+ \node[align=center, text width=0.9\textwidth] at (5.55,-1.45) {
609
+ Inputs to the fitted law are absolute quantities available before or during a run:
610
+ $x=\log_{10}(P/U)$ measures capacity pressure and
611
+ $y=\log_{10}(C/U)$ measures repeated-sampling pressure.
612
+ };
613
+ \end{tikzpicture}
614
+ """
615
+ (FIG_DIR / "calibration_to_streaming_protocol.tex").write_text(
616
+ content.strip() + "\n", encoding="utf-8"
617
+ )
618
+
619
+
620
  def write_tables(summaries: dict[str, dict], coeffs: dict[str, dict]) -> None:
621
+ symbol_lines = [
622
+ "\\begin{tabular}{ll}",
623
+ "\\toprule",
624
+ "Symbol & Meaning \\\\",
625
+ "\\midrule",
626
+ "$P$ & Trainable parameter count \\\\",
627
+ "$U_t$ & Unique-token prefix currently revealed by stream stage $t$ \\\\",
628
+ "$C_t$ & Cumulative sampled training tokens consumed by the optimizer \\\\",
629
+ "$x_t$ & $\\log_{10}(P/U_t)$, capacity pressure \\\\",
630
+ "$y_t$ & $\\log_{10}(C_t/U_t)$, repeated-sampling pressure \\\\",
631
+ "$p_t$ & Dropout rate used at stream stage $t$ \\\\",
632
+ "$p^\\star$ & Static-sweep target dropout for one calibration cell \\\\",
633
+ "$A,B,D,C_0$ & Regime-specific fitted pressure-law coefficients \\\\",
634
+ "$p_{\\min},p_{\\max}$ & Clamp bounds applied to generated dropout schedules \\\\",
635
+ "\\bottomrule",
636
+ "\\end{tabular}",
637
+ ]
638
+ (TABLE_DIR / "symbol_table.tex").write_text(
639
+ "\n".join(symbol_lines) + "\n", encoding="utf-8"
640
+ )
641
+
642
+ regime_lines = [
643
+ "\\begin{tabular}{llll}",
644
+ "\\toprule",
645
+ "Regime & Corpus source & Tokenizer / vocab & Streaming prefixes \\\\",
646
+ "\\midrule",
647
+ (
648
+ "OpenWebText10K & Cached OpenWebText-style subset & BPE / 4096 & "
649
+ "250k, 500k, 1M, 2M, 4M; plus 8M L20 tier \\\\"
650
+ ),
651
+ (
652
+ "TinyStories & Cached TinyStories subset & BPE / 4096 & "
653
+ "500k, 1M, 2M, 4M; plus 8M L20 tier \\\\"
654
+ ),
655
+ (
656
+ "WikiText-103 & Cached WikiText-103 subset & BPE / 4096 & "
657
+ "250k, 500k, 1M, 2M, 4M; plus 8M L20 tier \\\\"
658
+ ),
659
+ "\\bottomrule",
660
+ "\\end{tabular}",
661
+ ]
662
+ (TABLE_DIR / "regime_setup_table.tex").write_text(
663
+ "\n".join(regime_lines) + "\n", encoding="utf-8"
664
+ )
665
+
666
+ repro_lines = [
667
+ "\\begin{tabular}{ll}",
668
+ "\\toprule",
669
+ "Item & Value \\\\",
670
+ "\\midrule",
671
+ "Repository & \\url{https://huggingface.co/cuber12/dropout-decay} \\\\",
672
+ "Repository snapshot & Base commit \\texttt{e7a7275}; cite final release tag with paper \\\\",
673
+ "License & MIT-derived source with nanochat attribution retained \\\\",
674
+ "Python & 3.11.15 \\\\",
675
+ "PyTorch & 2.12.0 \\\\",
676
+ "Operating system & macOS 26.4 arm64 \\\\",
677
+ "Training backend & Apple Metal Performance Shaders (MPS); runner refuses CPU/CUDA \\\\",
678
+ "\\bottomrule",
679
+ "\\end{tabular}",
680
+ ]
681
+ (TABLE_DIR / "reproducibility_table.tex").write_text(
682
+ "\n".join(repro_lines) + "\n", encoding="utf-8"
683
+ )
684
+
685
+ weight_lines = [
686
+ "\\begin{tabular}{lll}",
687
+ "\\toprule",
688
+ "Cell property & Weight multiplier & Reason \\\\",
689
+ "\\midrule",
690
+ "Best grid point on dropout boundary & $0.30$ & Optimum may lie outside tested grid \\\\",
691
+ "Quadratic minimum not bracketed & $0.50$ & Local parabola is less reliable \\\\",
692
+ "Loss span below $0.02$ nats & $0.50$ & Very flat curve weakly identifies $p^\\star$ \\\\",
693
+ (
694
+ "Best-loss std. $\\sigma > 0$ & $1/(1+20\\sigma)$ & "
695
+ "Noisier minima receive less influence \\\\"
696
+ ),
697
+ "\\bottomrule",
698
+ "\\end{tabular}",
699
+ ]
700
+ (TABLE_DIR / "calibration_weight_table.tex").write_text(
701
+ "\n".join(weight_lines) + "\n", encoding="utf-8"
702
+ )
703
+
704
+ protocol_lines = [
705
+ "\\begin{tabular}{llll}",
706
+ "\\toprule",
707
+ "Experiment layer & What is run & Question answered & Statistical unit \\\\",
708
+ "\\midrule",
709
+ (
710
+ "Static calibration & Per-regime dropout grid at fixed $(P,U,C)$ cells & "
711
+ "Where does validation loss plateau over dropout? & 15--16 cells per regime \\\\"
712
+ ),
713
+ (
714
+ "Coefficient fit & Per-regime regression from cell targets $p^\\star$ & "
715
+ "Can pressure variables predict useful dropout? & MAE / leave-out MAE \\\\"
716
+ ),
717
+ (
718
+ "4M final-prefix locked stream & Per-regime continuing model over growing prefixes & "
719
+ "Does decay beat the best tested fixed dropout? & 5 paired seeds per regime \\\\"
720
+ ),
721
+ (
722
+ "L20 8M extrapolation & Per-regime 51.84M-param model with frozen coefficients & "
723
+ "Does the law extend to larger model/data scale? & 3 paired seeds per regime \\\\"
724
+ ),
725
+ (
726
+ "Stage-wise audit & Per-regime decay vs best static at every prefix & "
727
+ "Is the schedule final-loss or per-prefix optimal? & Prefix-level deltas \\\\"
728
+ ),
729
+ "\\bottomrule",
730
+ "\\end{tabular}",
731
+ ]
732
+ (TABLE_DIR / "experiment_protocol_table.tex").write_text(
733
+ "\n".join(protocol_lines) + "\n", encoding="utf-8"
734
+ )
735
+
736
+ budget_lines = [
737
+ "\\begin{tabular}{llllrr}",
738
+ "\\toprule",
739
+ "Regime & Protocol & Prefix path & Sampled/stage & Batch & Block \\\\",
740
+ "\\midrule",
741
+ ]
742
+ for key, paths in REGIMES.items():
743
+ for protocol_key, protocol_label in [
744
+ ("small", paths["small_protocol"]),
745
+ ("l20", "8M final prefix / 3 seeds"),
746
+ ]:
747
+ budget = summaries[key][protocol_key]["budget"]
748
+ prefix_path = " $\\rightarrow$ ".join(token_label(prefix) for prefix in budget["prefix_path"])
749
+ sampled = ", ".join(token_label(value) for value in budget["sampled_per_stage"])
750
+ batch = "/".join(str(value) for value in budget["batch_sizes"])
751
+ block = str(budget["block_size"])
752
+ budget_lines.append(
753
+ f"{tex_escape(paths['label'])} & {tex_escape(protocol_label)} & "
754
+ f"{prefix_path} & {sampled} & {batch} & {block} \\\\"
755
+ )
756
+ budget_lines.extend(["\\bottomrule", "\\end{tabular}"])
757
+ (TABLE_DIR / "protocol_budget_table.tex").write_text(
758
+ "\n".join(budget_lines) + "\n", encoding="utf-8"
759
+ )
760
+
761
  coeff_lines = [
762
  "\\begin{tabular}{lrrrrrrrr}",
763
  "\\toprule",
764
+ "Regime & Cells & A & B & D & C0 & Dropout MAE (abs. $p$) & L-model MAE & L-prefix MAE \\\\",
765
  "\\midrule",
766
  ]
767
  for key in REGIMES:
 
776
  "\n".join(coeff_lines) + "\n", encoding="utf-8"
777
  )
778
 
779
+ evidence_lines = [
780
+ "\\begin{tabular}{lrrrrr}",
781
+ "\\toprule",
782
+ "Regime & Calib. dropout MAE & 4M gain (nats) & 4M wins & 8M gain (nats) & 8M wins \\\\",
783
+ "\\midrule",
784
+ ]
785
+ for key, paths in REGIMES.items():
786
+ small = summaries[key]["small"]
787
+ l20 = summaries[key]["l20"]
788
+ evidence_lines.append(
789
+ f"{tex_escape(paths['label'])} & {coeffs[key]['mae']:.4f} & "
790
+ f"{small['improvement']:.4f} & {small['paired_wins']}/{small['n']} & "
791
+ f"{l20['improvement']:.4f} & {l20['paired_wins']}/{l20['n']} \\\\"
792
+ )
793
+ evidence_lines.extend(["\\bottomrule", "\\end{tabular}"])
794
+ (TABLE_DIR / "evidence_ladder_table.tex").write_text(
795
+ "\n".join(evidence_lines) + "\n", encoding="utf-8"
796
+ )
797
+
798
  val_lines = [
799
  "\\begin{tabular}{llrrrrr}",
800
  "\\toprule",
801
+ "Regime & Protocol & Decay final (nats) & Best static final (nats) & Gain (nats) & Seeds & Paired wins \\\\",
802
  "\\midrule",
803
  ]
804
  for key, paths in REGIMES.items():
 
815
  "\n".join(val_lines) + "\n", encoding="utf-8"
816
  )
817
 
818
+ paired_lines = [
819
+ "\\begin{tabular}{llrrrr}",
820
+ "\\toprule",
821
+ "Regime & Protocol & Paired gain mean & 95\\% bootstrap CI & Seeds & Wins \\\\",
822
+ "\\midrule",
823
+ ]
824
+ for key, paths in REGIMES.items():
825
+ for protocol_key, protocol_label in [
826
+ ("small", paths["small_protocol"]),
827
+ ("l20", "8M final prefix / 3 seeds"),
828
+ ]:
829
+ row = summaries[key][protocol_key]
830
+ paired_lines.append(
831
+ f"{tex_escape(paths['label'])} & {tex_escape(protocol_label)} & "
832
+ f"{row['paired_gain_mean']:.4f} & "
833
+ f"[{row['paired_gain_ci_low']:.4f}, {row['paired_gain_ci_high']:.4f}] & "
834
+ f"{row['n']} & {row['paired_wins']}/{row['n']} \\\\"
835
+ )
836
+ paired_lines.extend(["\\bottomrule", "\\end{tabular}"])
837
+ (TABLE_DIR / "paired_gain_ci_table.tex").write_text(
838
+ "\n".join(paired_lines) + "\n", encoding="utf-8"
839
+ )
840
+
841
+ path_lines = [
842
+ "\\begin{tabular}{llll}",
843
+ "\\toprule",
844
+ "Regime & Locked-stream prefix path & Frozen decay path & Final best static \\\\",
845
+ "\\midrule",
846
+ ]
847
+ for key, paths in REGIMES.items():
848
+ stage_rows = summaries[key]["l20"]["stage_deltas"]
849
+ prefix_path = " $\\rightarrow$ ".join(token_label(row["prefix"]) for row in stage_rows)
850
+ dropout_path = " $\\rightarrow$ ".join(
851
+ f"{row['condition_dropout']:.3f}" for row in stage_rows
852
+ )
853
+ final_static = summaries[key]["l20"]["best_static_condition"].replace(
854
+ "static_dropout_", "p="
855
+ )
856
+ path_lines.append(
857
+ f"{tex_escape(paths['label'])} & {prefix_path} & {dropout_path} & "
858
+ f"{tex_escape(final_static)} \\\\"
859
+ )
860
+ path_lines.extend(["\\bottomrule", "\\end{tabular}"])
861
+ (TABLE_DIR / "l20_schedule_path_table.tex").write_text(
862
+ "\n".join(path_lines) + "\n", encoding="utf-8"
863
+ )
864
+
865
  l20_lines = [
866
  "\\begin{tabular}{lrrrrr}",
867
  "\\toprule",
868
+ "Regime & Prefix & Decay dropout & Decay val (nats) & Best static val (nats) & Delta (nats) \\\\",
869
  "\\midrule",
870
  ]
871
  for key, paths in REGIMES.items():
 
906
  }
907
  write_calibration_curve_figure()
908
  write_static_optima_figure()
909
+ write_protocol_schematic()
910
  write_l20_stage_delta_figure(summaries)
911
  write_final_improvement_figure(summaries)
912
  write_tables(summaries, coeffs)
scripts/summarize_cross_regime_backtest.py CHANGED
@@ -27,7 +27,7 @@ import statistics
27
 
28
 
29
  ROOT = Path("runs/coefficient_calibration/cross_regime_backtest")
30
- REPORT_PATH = Path("docs/cross_regime_backtest_report.md")
31
 
32
 
33
  FIT_DIRS = {
 
27
 
28
 
29
  ROOT = Path("runs/coefficient_calibration/cross_regime_backtest")
30
+ REPORT_PATH = Path("archive/legacy_docs/cross_regime_backtest_report.md")
31
 
32
 
33
  FIT_DIRS = {
src/dropout_decay/data/__init__.py CHANGED
@@ -16,6 +16,8 @@ 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,
 
16
  copies or substantial portions of the Software.
17
  """
18
 
19
+ # Backward-compatible public exports. Dataset helpers now live in
20
+ # dropout_decay.datasets and experiment dataclasses live in dropout_decay.specs.
21
  from dropout_decay.datasets import (
22
  CachedTokenizer,
23
  TokenSplits,
src/dropout_decay/experiment.py CHANGED
@@ -16,6 +16,8 @@ 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__ = [
 
16
  copies or substantial portions of the Software.
17
  """
18
 
19
+ # Backward-compatible public exports. The implementation lives in
20
+ # dropout_decay.experiments.runner; historical scripts import this module.
21
  from dropout_decay.experiments.runner import build_parser, main, run
22
 
23
  __all__ = [
src/dropout_decay/model.py CHANGED
@@ -16,6 +16,8 @@ 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,
 
16
  copies or substantial portions of the Software.
17
  """
18
 
19
+ # Backward-compatible public exports. The implementation lives in
20
+ # dropout_decay.models.gpt; historical scripts import this module.
21
  from dropout_decay.models.gpt import (
22
  Block,
23
  CausalSelfAttention,
src/dropout_decay/scheduler.py CHANGED
@@ -16,6 +16,8 @@ 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__ = [
 
16
  copies or substantial portions of the Software.
17
  """
18
 
19
+ # Backward-compatible public exports. The implementation lives in
20
+ # dropout_decay.schedules.dropout; historical scripts import this module.
21
  from dropout_decay.schedules.dropout import DropoutDecayConfig, DropoutDecayScheduler
22
 
23
  __all__ = [
src/dropout_decay/tokenizer.py CHANGED
@@ -16,6 +16,8 @@ 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.tokenization.bpe import BpeTokenizer, SPECIAL_TOKENS, SPLIT_PATTERN
20
 
21
  __all__ = [
 
16
  copies or substantial portions of the Software.
17
  """
18
 
19
+ # Backward-compatible public exports. The implementation lives in
20
+ # dropout_decay.tokenization.bpe; historical scripts import this module.
21
  from dropout_decay.tokenization.bpe import BpeTokenizer, SPECIAL_TOKENS, SPLIT_PATTERN
22
 
23
  __all__ = [
tests/test_data_and_device.py ADDED
@@ -0,0 +1,55 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ from pathlib import Path
3
+ import tempfile
4
+ import unittest
5
+ from unittest.mock import patch
6
+
7
+ import numpy as np
8
+
9
+ from dropout_decay.data import load_cached_splits
10
+ from dropout_decay.experiments.device import assert_mps_only
11
+
12
+
13
+ class DataAndDeviceTests(unittest.TestCase):
14
+ def test_load_cached_splits_uses_expected_train_val_partition(self):
15
+ with tempfile.TemporaryDirectory() as tmp:
16
+ cache_dir = Path(tmp)
17
+ (cache_dir / "tokenizer-v16.json").write_text("{}", encoding="utf-8")
18
+ np.save(cache_dir / "tokens-v16-uint16.npy", np.arange(100, dtype=np.uint16))
19
+
20
+ tokenizer, splits = load_cached_splits(
21
+ cache_dir=cache_dir,
22
+ vocab_size=16,
23
+ max_required_train_tokens=80,
24
+ val_tokens=10,
25
+ allow_short_corpus=False,
26
+ )
27
+
28
+ self.assertEqual(tokenizer.vocab_size, 16)
29
+ self.assertEqual(len(splits.train), 90)
30
+ self.assertEqual(len(splits.val), 10)
31
+ self.assertEqual(splits.tokenizer_path, cache_dir / "tokenizer-v16.json")
32
+
33
+ def test_load_cached_splits_rejects_missing_cache(self):
34
+ with tempfile.TemporaryDirectory() as tmp:
35
+ with self.assertRaises(FileNotFoundError):
36
+ load_cached_splits(
37
+ cache_dir=Path(tmp),
38
+ vocab_size=16,
39
+ max_required_train_tokens=10,
40
+ val_tokens=10,
41
+ allow_short_corpus=False,
42
+ )
43
+
44
+ def test_mps_guard_rejects_fallback_before_backend_checks(self):
45
+ env = dict(os.environ)
46
+ env["PYTORCH_ENABLE_MPS_FALLBACK"] = "1"
47
+ with patch.dict(os.environ, env, clear=True):
48
+ with self.assertRaises(SystemExit) as caught:
49
+ assert_mps_only()
50
+
51
+ self.assertIn("MPS_FALLBACK", str(caught.exception))
52
+
53
+
54
+ if __name__ == "__main__":
55
+ unittest.main()
tests/test_schedules.py ADDED
@@ -0,0 +1,74 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import math
2
+ import unittest
3
+
4
+ from dropout_decay.schedules import DropoutDecayConfig, DropoutDecayScheduler
5
+ from dropout_decay.specs import DropoutCondition, anchor_dropout
6
+
7
+
8
+ class DropoutScheduleTests(unittest.TestCase):
9
+ def test_linear_decay_endpoints_and_midpoint(self):
10
+ scheduler = DropoutDecayScheduler(
11
+ DropoutDecayConfig(
12
+ initial_dropout=0.30,
13
+ final_dropout=0.10,
14
+ decay_tokens=100,
15
+ schedule="linear",
16
+ )
17
+ )
18
+
19
+ self.assertAlmostEqual(scheduler.value(0), 0.30)
20
+ self.assertAlmostEqual(scheduler.value(50), 0.20)
21
+ self.assertAlmostEqual(scheduler.value(100), 0.10)
22
+ self.assertAlmostEqual(scheduler.value(1000), 0.10)
23
+
24
+ def test_cosine_decay_midpoint(self):
25
+ scheduler = DropoutDecayScheduler(
26
+ DropoutDecayConfig(
27
+ initial_dropout=0.40,
28
+ final_dropout=0.20,
29
+ decay_tokens=100,
30
+ schedule="cosine",
31
+ )
32
+ )
33
+
34
+ self.assertAlmostEqual(scheduler.value(50), 0.30)
35
+
36
+ def test_invalid_scheduler_config_rejected(self):
37
+ with self.assertRaises(ValueError):
38
+ DropoutDecayScheduler(
39
+ DropoutDecayConfig(
40
+ initial_dropout=0.10,
41
+ final_dropout=0.30,
42
+ decay_tokens=100,
43
+ schedule="linear",
44
+ )
45
+ )
46
+
47
+ def test_anchor_dropout_interpolates_in_log_token_space(self):
48
+ anchors = ((100, 0.30), (10_000, 0.10))
49
+ value = anchor_dropout(1_000, anchors)
50
+ expected_mix = (math.log(1_000) - math.log(100)) / (
51
+ math.log(10_000) - math.log(100)
52
+ )
53
+ expected = 0.30 + expected_mix * (0.10 - 0.30)
54
+
55
+ self.assertAlmostEqual(value, expected)
56
+
57
+ def test_anchor_condition_requires_unique_tokens(self):
58
+ condition = DropoutCondition(
59
+ name="fit",
60
+ kind="anchor_decay",
61
+ initial=0.30,
62
+ final=0.10,
63
+ anchors=((100, 0.30), (10_000, 0.10)),
64
+ )
65
+
66
+ with self.assertRaises(ValueError):
67
+ condition.make_fn(fallback_decay_tokens=100)
68
+
69
+ fn = condition.make_fn(fallback_decay_tokens=100, unique_tokens=1_000)
70
+ self.assertAlmostEqual(fn(0), anchor_dropout(1_000, condition.anchors))
71
+
72
+
73
+ if __name__ == "__main__":
74
+ unittest.main()
tests/test_specs_and_parsing.py ADDED
@@ -0,0 +1,50 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import argparse
2
+ import unittest
3
+
4
+ from dropout_decay.experiments.parsing import (
5
+ clean_name,
6
+ parse_anchor_decay_spec,
7
+ parse_decay_spec,
8
+ parse_model_spec,
9
+ )
10
+
11
+
12
+ class SpecParsingTests(unittest.TestCase):
13
+ def test_parse_model_spec_default_and_named(self):
14
+ default = parse_model_spec("12x8x320")
15
+ named = parse_model_spec("winner=12x8x320")
16
+
17
+ self.assertEqual(default.name, "L12_H8_D320")
18
+ self.assertEqual(named.name, "winner")
19
+ self.assertEqual(named.to_dict()["n_embd"], 320)
20
+
21
+ def test_parse_model_spec_rejects_incompatible_heads(self):
22
+ with self.assertRaises(argparse.ArgumentTypeError):
23
+ parse_model_spec("3x5x32")
24
+
25
+ def test_parse_decay_spec_defaults_to_cosine(self):
26
+ condition = parse_decay_spec("mild:0.30:0.10")
27
+
28
+ self.assertEqual(condition.name, "mild")
29
+ self.assertEqual(condition.kind, "decay")
30
+ self.assertEqual(condition.schedule, "cosine")
31
+ self.assertIsNone(condition.decay_tokens)
32
+
33
+ def test_parse_anchor_decay_spec_sorts_and_validates(self):
34
+ condition = parse_anchor_decay_spec("fit:1000=0.20,100=0.30")
35
+
36
+ self.assertEqual(condition.kind, "anchor_decay")
37
+ self.assertEqual(condition.anchors, ((100, 0.30), (1000, 0.20)))
38
+ self.assertEqual(condition.initial, 0.30)
39
+ self.assertEqual(condition.final, 0.20)
40
+
41
+ def test_parse_anchor_decay_spec_rejects_increasing_dropout(self):
42
+ with self.assertRaises(argparse.ArgumentTypeError):
43
+ parse_anchor_decay_spec("fit:100=0.10,1000=0.20")
44
+
45
+ def test_clean_name_keeps_stable_condition_labels(self):
46
+ self.assertEqual(clean_name(" mild 30->08 "), "mild_30-_08")
47
+
48
+
49
+ if __name__ == "__main__":
50
+ unittest.main()