trainproof 0.1.0__tar.gz → 0.2.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- trainproof-0.2.0/PKG-INFO +138 -0
- trainproof-0.2.0/README.md +126 -0
- {trainproof-0.1.0 → trainproof-0.2.0}/pyproject.toml +1 -1
- {trainproof-0.1.0 → trainproof-0.2.0}/src/trainproof/epoch.py +10 -0
- {trainproof-0.1.0 → trainproof-0.2.0}/src/trainproof/rules.py +10 -0
- trainproof-0.2.0/src/trainproof.egg-info/PKG-INFO +138 -0
- {trainproof-0.1.0 → trainproof-0.2.0}/tests/test_epoch.py +7 -0
- trainproof-0.1.0/PKG-INFO +0 -62
- trainproof-0.1.0/README.md +0 -50
- trainproof-0.1.0/src/trainproof.egg-info/PKG-INFO +0 -62
- {trainproof-0.1.0 → trainproof-0.2.0}/setup.cfg +0 -0
- {trainproof-0.1.0 → trainproof-0.2.0}/src/trainproof/__init__.py +0 -0
- {trainproof-0.1.0 → trainproof-0.2.0}/src/trainproof/adapters.py +0 -0
- {trainproof-0.1.0 → trainproof-0.2.0}/src/trainproof/cli.py +0 -0
- {trainproof-0.1.0 → trainproof-0.2.0}/src/trainproof/report.py +0 -0
- {trainproof-0.1.0 → trainproof-0.2.0}/src/trainproof/speech/__init__.py +0 -0
- {trainproof-0.1.0 → trainproof-0.2.0}/src/trainproof/speech/data.py +0 -0
- {trainproof-0.1.0 → trainproof-0.2.0}/src/trainproof/speech/tokenizer.py +0 -0
- {trainproof-0.1.0 → trainproof-0.2.0}/src/trainproof.egg-info/SOURCES.txt +0 -0
- {trainproof-0.1.0 → trainproof-0.2.0}/src/trainproof.egg-info/dependency_links.txt +0 -0
- {trainproof-0.1.0 → trainproof-0.2.0}/src/trainproof.egg-info/entry_points.txt +0 -0
- {trainproof-0.1.0 → trainproof-0.2.0}/src/trainproof.egg-info/requires.txt +0 -0
- {trainproof-0.1.0 → trainproof-0.2.0}/src/trainproof.egg-info/top_level.txt +0 -0
- {trainproof-0.1.0 → trainproof-0.2.0}/tests/test_adapters.py +0 -0
- {trainproof-0.1.0 → trainproof-0.2.0}/tests/test_data.py +0 -0
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: trainproof
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: A deterministic linter for ML training runs: dataset, tokenizer, and epoch logs.
|
|
5
|
+
Author-email: "Panagiotis (Panos) Gkilis" <bedvibe@bedvibe.studio>
|
|
6
|
+
License: MIT
|
|
7
|
+
Requires-Python: >=3.10
|
|
8
|
+
Description-Content-Type: text/markdown
|
|
9
|
+
Requires-Dist: numpy
|
|
10
|
+
Requires-Dist: soundfile
|
|
11
|
+
Requires-Dist: ttsproof
|
|
12
|
+
|
|
13
|
+
# trainproof
|
|
14
|
+
|
|
15
|
+
[](https://pypi.org/project/trainproof/)
|
|
16
|
+
[](LICENSE)
|
|
17
|
+
|
|
18
|
+
**A deterministic linter for ML training runs.** Point it at your dataset, your
|
|
19
|
+
tokenizer, or your first-epoch logs — it returns a PASS / WARN / FAIL verdict
|
|
20
|
+
with named findings and cited evidence, before you burn days of GPU time on a
|
|
21
|
+
run that was doomed at step 50.
|
|
22
|
+
|
|
23
|
+
No ML judging ML. No invented "confidence 97%". Every rule is a deterministic
|
|
24
|
+
threshold in [one auditable module](src/trainproof/rules.py), and every finding
|
|
25
|
+
cites the numbers that triggered it.
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
pip install trainproof
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## See a verdict in 60 seconds
|
|
32
|
+
|
|
33
|
+
This repo ships the real logs of five QLoRA fine-tuning runs (Qwen2.5-3B,
|
|
34
|
+
RTX 5080 — see the gallery below). Judge one right now:
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
trainproof epoch examples/gallery/lr_hot/trainer_state.json --format hf
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
```text
|
|
41
|
+
[FAIL] Critical checks failed:
|
|
42
|
+
[FAIL] Loss curve is diverging.
|
|
43
|
+
Evidence: End loss 7.492 vs Min loss 1.398
|
|
44
|
+
[FAIL] Loss never improved over the run (dead run).
|
|
45
|
+
[WARN] Gradient norm spikes detected.
|
|
46
|
+
Evidence: Max gn 2649.75 > 10.0x median (0.55)
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## Why this exists
|
|
50
|
+
|
|
51
|
+
The author lost a real 11-hour fine-tune to a failure nothing warned about.
|
|
52
|
+
Pointed retroactively at that run's 1MB Coqui Trainer log (2,501 logged steps),
|
|
53
|
+
trainproof's verdict: **FAIL — diverging**. The loss reached its minimum at 82%
|
|
54
|
+
of the run and ended 1.9x above it. Translation: the final two hours of GPU
|
|
55
|
+
time made the model measurably worse, and the checkpoint worth keeping had
|
|
56
|
+
already existed for hours. No tool in the stack said a word.
|
|
57
|
+
|
|
58
|
+
## The fault-injection gallery
|
|
59
|
+
|
|
60
|
+
To validate the rules, the same QLoRA fine-tune (Qwen2.5-3B-Instruct, 4-bit,
|
|
61
|
+
LoRA r=16, 300 steps on Alpaca-cleaned) was run five times — once healthy, four
|
|
62
|
+
times with exactly one knob deliberately broken. Real runs, real logs, all
|
|
63
|
+
shipped in [`examples/gallery/`](examples/gallery/):
|
|
64
|
+
|
|
65
|
+
| Run | Sabotage | Verdict | Key evidence |
|
|
66
|
+
|---|---|---|---|
|
|
67
|
+
| `healthy` | none | **PASS** | loss 1.52 → 0.94, stable gradients |
|
|
68
|
+
| `lr_hot` | LR x100 (2e-2) | **FAIL** | diverging: end 7.49 vs min 1.40; grad spike 2650 vs median 0.55 |
|
|
69
|
+
| `lr_zero` | LR = 0 | **FAIL** | dead run: first-5 median 1.52 vs last-5 1.49 (<5% improvement); lr=0 on 100% of steps |
|
|
70
|
+
| `fp16_nan` | fp16 + hot LR, no clipping | **FAIL** | diverging: end 7.21 vs min 1.09 (grad scaling absorbed the intended NaN — the run diverged instead; reported as observed) |
|
|
71
|
+
| `bad_labels` | labels shuffled per-sequence | **WARN only** — see below | grad spike 23.3 vs median 1.09 |
|
|
72
|
+
|
|
73
|
+
### The honest finding: loss curves cannot see corrupted data
|
|
74
|
+
|
|
75
|
+
The `bad_labels` run — whose shuffled labels make real learning impossible —
|
|
76
|
+
*reduced its loss by 62%* (18.9 → 5.75). The model was genuinely learning: not
|
|
77
|
+
the task, but the marginal token statistics of the garbage. From its own loss
|
|
78
|
+
curve, that is indistinguishable from healthy training (neural networks
|
|
79
|
+
famously fit random labels). **No single-run, loss-only rule can catch this
|
|
80
|
+
class of failure** — its real signature is *relative*: a loss floor ~6x higher
|
|
81
|
+
than a known-good run of the same task (5.59 vs 0.94).
|
|
82
|
+
|
|
83
|
+
That finding sets the roadmap: v0.3 is `trainproof compare <run> <baseline>` —
|
|
84
|
+
deterministic ratio rules against the healthy baseline you already have.
|
|
85
|
+
See [ROADMAP.md](ROADMAP.md). (The gallery also improved v0.2 itself: the
|
|
86
|
+
dead-run rule exists because `lr_zero` initially escaped with only a WARN.)
|
|
87
|
+
|
|
88
|
+
## The three commands
|
|
89
|
+
|
|
90
|
+
```bash
|
|
91
|
+
# 1. Dataset preflight (speech/TTS pack): audio integrity, transcript quality,
|
|
92
|
+
# duplicates, text-vs-audio duration mismatches
|
|
93
|
+
trainproof data /path/to/dataset_or_manifest.jsonl
|
|
94
|
+
|
|
95
|
+
# 2. Tokenizer preflight: vocabulary coverage, OOV rate, sequence blowouts,
|
|
96
|
+
# suspicious splits on numbers/dates
|
|
97
|
+
trainproof tokenizer my_tokenizer.model transcripts.txt
|
|
98
|
+
|
|
99
|
+
# 3. Training-run verdict: NaN/divergence/dead-run detection, gradient spikes,
|
|
100
|
+
# LR sanity, throughput — from log files, any framework
|
|
101
|
+
trainproof epoch logs/run.jsonl # exit code 1 on FAIL: CI-ready
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
Each command prints the verdict, writes a self-contained HTML report, and sets
|
|
105
|
+
the process exit code — so it works as a CI gate out of the box.
|
|
106
|
+
|
|
107
|
+
## Supported log formats
|
|
108
|
+
|
|
109
|
+
- HuggingFace Trainer (`trainer_state.json`)
|
|
110
|
+
- Coqui Trainer text logs (ANSI-colored `trainer_0_log.txt`)
|
|
111
|
+
- Generic JSONL / CSV (columns: step, loss, lr, grad_norm, time — all optional)
|
|
112
|
+
|
|
113
|
+
Auto-detected; override with `--format hf|coqui|jsonl|csv`. TensorBoard event
|
|
114
|
+
files are planned (Lightning console captures are TTY dumps, not logs, and
|
|
115
|
+
will not be supported).
|
|
116
|
+
|
|
117
|
+
## Philosophy
|
|
118
|
+
|
|
119
|
+
1. **Deterministic.** A rule fires or it doesn't. Thresholds live in one
|
|
120
|
+
module, commented, tunable.
|
|
121
|
+
2. **Evidence-cited.** Every finding names the steps and values that triggered
|
|
122
|
+
it.
|
|
123
|
+
3. **Honest about limits.** What the tool cannot detect is documented in the
|
|
124
|
+
README, not discovered by the user in production.
|
|
125
|
+
|
|
126
|
+
## Family
|
|
127
|
+
|
|
128
|
+
trainproof judges training runs. Its sibling [ttsproof](https://github.com/Mormolykos/ttsproof)
|
|
129
|
+
judges TTS model *outputs* (structural audio checks, equivalence-aware WER/CER,
|
|
130
|
+
published method with DOI) — and trainproof builds on it for the speech
|
|
131
|
+
dataset checks.
|
|
132
|
+
|
|
133
|
+
## Author
|
|
134
|
+
|
|
135
|
+
Panagiotis (Panos) Gkilis — [portfolio](https://tts.bedvibe.studio/portfolio/) ·
|
|
136
|
+
[bedvibe.studio](https://bedvibe.studio/)
|
|
137
|
+
|
|
138
|
+
MIT license.
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
# trainproof
|
|
2
|
+
|
|
3
|
+
[](https://pypi.org/project/trainproof/)
|
|
4
|
+
[](LICENSE)
|
|
5
|
+
|
|
6
|
+
**A deterministic linter for ML training runs.** Point it at your dataset, your
|
|
7
|
+
tokenizer, or your first-epoch logs — it returns a PASS / WARN / FAIL verdict
|
|
8
|
+
with named findings and cited evidence, before you burn days of GPU time on a
|
|
9
|
+
run that was doomed at step 50.
|
|
10
|
+
|
|
11
|
+
No ML judging ML. No invented "confidence 97%". Every rule is a deterministic
|
|
12
|
+
threshold in [one auditable module](src/trainproof/rules.py), and every finding
|
|
13
|
+
cites the numbers that triggered it.
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
pip install trainproof
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## See a verdict in 60 seconds
|
|
20
|
+
|
|
21
|
+
This repo ships the real logs of five QLoRA fine-tuning runs (Qwen2.5-3B,
|
|
22
|
+
RTX 5080 — see the gallery below). Judge one right now:
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
trainproof epoch examples/gallery/lr_hot/trainer_state.json --format hf
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
```text
|
|
29
|
+
[FAIL] Critical checks failed:
|
|
30
|
+
[FAIL] Loss curve is diverging.
|
|
31
|
+
Evidence: End loss 7.492 vs Min loss 1.398
|
|
32
|
+
[FAIL] Loss never improved over the run (dead run).
|
|
33
|
+
[WARN] Gradient norm spikes detected.
|
|
34
|
+
Evidence: Max gn 2649.75 > 10.0x median (0.55)
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## Why this exists
|
|
38
|
+
|
|
39
|
+
The author lost a real 11-hour fine-tune to a failure nothing warned about.
|
|
40
|
+
Pointed retroactively at that run's 1MB Coqui Trainer log (2,501 logged steps),
|
|
41
|
+
trainproof's verdict: **FAIL — diverging**. The loss reached its minimum at 82%
|
|
42
|
+
of the run and ended 1.9x above it. Translation: the final two hours of GPU
|
|
43
|
+
time made the model measurably worse, and the checkpoint worth keeping had
|
|
44
|
+
already existed for hours. No tool in the stack said a word.
|
|
45
|
+
|
|
46
|
+
## The fault-injection gallery
|
|
47
|
+
|
|
48
|
+
To validate the rules, the same QLoRA fine-tune (Qwen2.5-3B-Instruct, 4-bit,
|
|
49
|
+
LoRA r=16, 300 steps on Alpaca-cleaned) was run five times — once healthy, four
|
|
50
|
+
times with exactly one knob deliberately broken. Real runs, real logs, all
|
|
51
|
+
shipped in [`examples/gallery/`](examples/gallery/):
|
|
52
|
+
|
|
53
|
+
| Run | Sabotage | Verdict | Key evidence |
|
|
54
|
+
|---|---|---|---|
|
|
55
|
+
| `healthy` | none | **PASS** | loss 1.52 → 0.94, stable gradients |
|
|
56
|
+
| `lr_hot` | LR x100 (2e-2) | **FAIL** | diverging: end 7.49 vs min 1.40; grad spike 2650 vs median 0.55 |
|
|
57
|
+
| `lr_zero` | LR = 0 | **FAIL** | dead run: first-5 median 1.52 vs last-5 1.49 (<5% improvement); lr=0 on 100% of steps |
|
|
58
|
+
| `fp16_nan` | fp16 + hot LR, no clipping | **FAIL** | diverging: end 7.21 vs min 1.09 (grad scaling absorbed the intended NaN — the run diverged instead; reported as observed) |
|
|
59
|
+
| `bad_labels` | labels shuffled per-sequence | **WARN only** — see below | grad spike 23.3 vs median 1.09 |
|
|
60
|
+
|
|
61
|
+
### The honest finding: loss curves cannot see corrupted data
|
|
62
|
+
|
|
63
|
+
The `bad_labels` run — whose shuffled labels make real learning impossible —
|
|
64
|
+
*reduced its loss by 62%* (18.9 → 5.75). The model was genuinely learning: not
|
|
65
|
+
the task, but the marginal token statistics of the garbage. From its own loss
|
|
66
|
+
curve, that is indistinguishable from healthy training (neural networks
|
|
67
|
+
famously fit random labels). **No single-run, loss-only rule can catch this
|
|
68
|
+
class of failure** — its real signature is *relative*: a loss floor ~6x higher
|
|
69
|
+
than a known-good run of the same task (5.59 vs 0.94).
|
|
70
|
+
|
|
71
|
+
That finding sets the roadmap: v0.3 is `trainproof compare <run> <baseline>` —
|
|
72
|
+
deterministic ratio rules against the healthy baseline you already have.
|
|
73
|
+
See [ROADMAP.md](ROADMAP.md). (The gallery also improved v0.2 itself: the
|
|
74
|
+
dead-run rule exists because `lr_zero` initially escaped with only a WARN.)
|
|
75
|
+
|
|
76
|
+
## The three commands
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
# 1. Dataset preflight (speech/TTS pack): audio integrity, transcript quality,
|
|
80
|
+
# duplicates, text-vs-audio duration mismatches
|
|
81
|
+
trainproof data /path/to/dataset_or_manifest.jsonl
|
|
82
|
+
|
|
83
|
+
# 2. Tokenizer preflight: vocabulary coverage, OOV rate, sequence blowouts,
|
|
84
|
+
# suspicious splits on numbers/dates
|
|
85
|
+
trainproof tokenizer my_tokenizer.model transcripts.txt
|
|
86
|
+
|
|
87
|
+
# 3. Training-run verdict: NaN/divergence/dead-run detection, gradient spikes,
|
|
88
|
+
# LR sanity, throughput — from log files, any framework
|
|
89
|
+
trainproof epoch logs/run.jsonl # exit code 1 on FAIL: CI-ready
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
Each command prints the verdict, writes a self-contained HTML report, and sets
|
|
93
|
+
the process exit code — so it works as a CI gate out of the box.
|
|
94
|
+
|
|
95
|
+
## Supported log formats
|
|
96
|
+
|
|
97
|
+
- HuggingFace Trainer (`trainer_state.json`)
|
|
98
|
+
- Coqui Trainer text logs (ANSI-colored `trainer_0_log.txt`)
|
|
99
|
+
- Generic JSONL / CSV (columns: step, loss, lr, grad_norm, time — all optional)
|
|
100
|
+
|
|
101
|
+
Auto-detected; override with `--format hf|coqui|jsonl|csv`. TensorBoard event
|
|
102
|
+
files are planned (Lightning console captures are TTY dumps, not logs, and
|
|
103
|
+
will not be supported).
|
|
104
|
+
|
|
105
|
+
## Philosophy
|
|
106
|
+
|
|
107
|
+
1. **Deterministic.** A rule fires or it doesn't. Thresholds live in one
|
|
108
|
+
module, commented, tunable.
|
|
109
|
+
2. **Evidence-cited.** Every finding names the steps and values that triggered
|
|
110
|
+
it.
|
|
111
|
+
3. **Honest about limits.** What the tool cannot detect is documented in the
|
|
112
|
+
README, not discovered by the user in production.
|
|
113
|
+
|
|
114
|
+
## Family
|
|
115
|
+
|
|
116
|
+
trainproof judges training runs. Its sibling [ttsproof](https://github.com/Mormolykos/ttsproof)
|
|
117
|
+
judges TTS model *outputs* (structural audio checks, equivalence-aware WER/CER,
|
|
118
|
+
published method with DOI) — and trainproof builds on it for the speech
|
|
119
|
+
dataset checks.
|
|
120
|
+
|
|
121
|
+
## Author
|
|
122
|
+
|
|
123
|
+
Panagiotis (Panos) Gkilis — [portfolio](https://tts.bedvibe.studio/portfolio/) ·
|
|
124
|
+
[bedvibe.studio](https://bedvibe.studio/)
|
|
125
|
+
|
|
126
|
+
MIT license.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "trainproof"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.2.0"
|
|
4
4
|
description = "A deterministic linter for ML training runs: dataset, tokenizer, and epoch logs."
|
|
5
5
|
authors = [{name = "Panagiotis (Panos) Gkilis", email = "bedvibe@bedvibe.studio"}]
|
|
6
6
|
readme = "README.md"
|
|
@@ -67,6 +67,16 @@ def check_epoch(log_path: str | Path, fmt: str = "auto") -> dict[str, Any]:
|
|
|
67
67
|
findings.append({"level": "FAIL", "message": "Loss curve is diverging.", "evidence": f"End loss {valid_losses[-1]:.3f} vs Min loss {min_loss:.3f}"})
|
|
68
68
|
verdict = "FAIL"
|
|
69
69
|
|
|
70
|
+
# Check no-improvement (dead run): robust start-vs-end median comparison
|
|
71
|
+
if len(valid_losses) >= rules.MIN_POINTS_FOR_IMPROVEMENT_CHECK:
|
|
72
|
+
w = rules.LOSS_IMPROVEMENT_WINDOW
|
|
73
|
+
start_med = sorted(valid_losses[:w])[w // 2]
|
|
74
|
+
end_med = sorted(valid_losses[-w:])[w // 2]
|
|
75
|
+
if start_med > 0 and end_med >= start_med * (1 - rules.MIN_LOSS_IMPROVEMENT):
|
|
76
|
+
findings.append({"level": "FAIL", "message": "Loss never improved over the run (dead run).",
|
|
77
|
+
"evidence": f"median of first {w} losses {start_med:.3f} vs last {w} {end_med:.3f} (needs >={rules.MIN_LOSS_IMPROVEMENT*100:.0f}% improvement)"})
|
|
78
|
+
verdict = "FAIL"
|
|
79
|
+
|
|
70
80
|
# Check Grad Norm
|
|
71
81
|
valid_gns = [g for g in grad_norms if not math.isnan(g) and not math.isinf(g)]
|
|
72
82
|
if valid_gns and len(valid_gns) > 5:
|
|
@@ -51,3 +51,13 @@ MAX_GRAD_NORM_SPIKE_RATIO = 10.0
|
|
|
51
51
|
|
|
52
52
|
# If the learning rate is strictly zero for more than this fraction of the epoch, it's a warning.
|
|
53
53
|
MAX_ZERO_LR_FRACTION = 0.1
|
|
54
|
+
|
|
55
|
+
# Dead-run detection (added in v0.2 after fault-injection testing showed that
|
|
56
|
+
# zero-LR and garbage-label runs — which can never learn — only earned WARNs):
|
|
57
|
+
# the median of the last LOSS_IMPROVEMENT_WINDOW losses must be at least
|
|
58
|
+
# MIN_LOSS_IMPROVEMENT (relative) below the median of the first window, else
|
|
59
|
+
# the run never learned. Note: this targets FIRST-EPOCH linting; a model
|
|
60
|
+
# resumed at its convergence floor would also trip it.
|
|
61
|
+
MIN_LOSS_IMPROVEMENT = 0.05
|
|
62
|
+
LOSS_IMPROVEMENT_WINDOW = 5
|
|
63
|
+
MIN_POINTS_FOR_IMPROVEMENT_CHECK = 10
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: trainproof
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: A deterministic linter for ML training runs: dataset, tokenizer, and epoch logs.
|
|
5
|
+
Author-email: "Panagiotis (Panos) Gkilis" <bedvibe@bedvibe.studio>
|
|
6
|
+
License: MIT
|
|
7
|
+
Requires-Python: >=3.10
|
|
8
|
+
Description-Content-Type: text/markdown
|
|
9
|
+
Requires-Dist: numpy
|
|
10
|
+
Requires-Dist: soundfile
|
|
11
|
+
Requires-Dist: ttsproof
|
|
12
|
+
|
|
13
|
+
# trainproof
|
|
14
|
+
|
|
15
|
+
[](https://pypi.org/project/trainproof/)
|
|
16
|
+
[](LICENSE)
|
|
17
|
+
|
|
18
|
+
**A deterministic linter for ML training runs.** Point it at your dataset, your
|
|
19
|
+
tokenizer, or your first-epoch logs — it returns a PASS / WARN / FAIL verdict
|
|
20
|
+
with named findings and cited evidence, before you burn days of GPU time on a
|
|
21
|
+
run that was doomed at step 50.
|
|
22
|
+
|
|
23
|
+
No ML judging ML. No invented "confidence 97%". Every rule is a deterministic
|
|
24
|
+
threshold in [one auditable module](src/trainproof/rules.py), and every finding
|
|
25
|
+
cites the numbers that triggered it.
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
pip install trainproof
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## See a verdict in 60 seconds
|
|
32
|
+
|
|
33
|
+
This repo ships the real logs of five QLoRA fine-tuning runs (Qwen2.5-3B,
|
|
34
|
+
RTX 5080 — see the gallery below). Judge one right now:
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
trainproof epoch examples/gallery/lr_hot/trainer_state.json --format hf
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
```text
|
|
41
|
+
[FAIL] Critical checks failed:
|
|
42
|
+
[FAIL] Loss curve is diverging.
|
|
43
|
+
Evidence: End loss 7.492 vs Min loss 1.398
|
|
44
|
+
[FAIL] Loss never improved over the run (dead run).
|
|
45
|
+
[WARN] Gradient norm spikes detected.
|
|
46
|
+
Evidence: Max gn 2649.75 > 10.0x median (0.55)
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## Why this exists
|
|
50
|
+
|
|
51
|
+
The author lost a real 11-hour fine-tune to a failure nothing warned about.
|
|
52
|
+
Pointed retroactively at that run's 1MB Coqui Trainer log (2,501 logged steps),
|
|
53
|
+
trainproof's verdict: **FAIL — diverging**. The loss reached its minimum at 82%
|
|
54
|
+
of the run and ended 1.9x above it. Translation: the final two hours of GPU
|
|
55
|
+
time made the model measurably worse, and the checkpoint worth keeping had
|
|
56
|
+
already existed for hours. No tool in the stack said a word.
|
|
57
|
+
|
|
58
|
+
## The fault-injection gallery
|
|
59
|
+
|
|
60
|
+
To validate the rules, the same QLoRA fine-tune (Qwen2.5-3B-Instruct, 4-bit,
|
|
61
|
+
LoRA r=16, 300 steps on Alpaca-cleaned) was run five times — once healthy, four
|
|
62
|
+
times with exactly one knob deliberately broken. Real runs, real logs, all
|
|
63
|
+
shipped in [`examples/gallery/`](examples/gallery/):
|
|
64
|
+
|
|
65
|
+
| Run | Sabotage | Verdict | Key evidence |
|
|
66
|
+
|---|---|---|---|
|
|
67
|
+
| `healthy` | none | **PASS** | loss 1.52 → 0.94, stable gradients |
|
|
68
|
+
| `lr_hot` | LR x100 (2e-2) | **FAIL** | diverging: end 7.49 vs min 1.40; grad spike 2650 vs median 0.55 |
|
|
69
|
+
| `lr_zero` | LR = 0 | **FAIL** | dead run: first-5 median 1.52 vs last-5 1.49 (<5% improvement); lr=0 on 100% of steps |
|
|
70
|
+
| `fp16_nan` | fp16 + hot LR, no clipping | **FAIL** | diverging: end 7.21 vs min 1.09 (grad scaling absorbed the intended NaN — the run diverged instead; reported as observed) |
|
|
71
|
+
| `bad_labels` | labels shuffled per-sequence | **WARN only** — see below | grad spike 23.3 vs median 1.09 |
|
|
72
|
+
|
|
73
|
+
### The honest finding: loss curves cannot see corrupted data
|
|
74
|
+
|
|
75
|
+
The `bad_labels` run — whose shuffled labels make real learning impossible —
|
|
76
|
+
*reduced its loss by 62%* (18.9 → 5.75). The model was genuinely learning: not
|
|
77
|
+
the task, but the marginal token statistics of the garbage. From its own loss
|
|
78
|
+
curve, that is indistinguishable from healthy training (neural networks
|
|
79
|
+
famously fit random labels). **No single-run, loss-only rule can catch this
|
|
80
|
+
class of failure** — its real signature is *relative*: a loss floor ~6x higher
|
|
81
|
+
than a known-good run of the same task (5.59 vs 0.94).
|
|
82
|
+
|
|
83
|
+
That finding sets the roadmap: v0.3 is `trainproof compare <run> <baseline>` —
|
|
84
|
+
deterministic ratio rules against the healthy baseline you already have.
|
|
85
|
+
See [ROADMAP.md](ROADMAP.md). (The gallery also improved v0.2 itself: the
|
|
86
|
+
dead-run rule exists because `lr_zero` initially escaped with only a WARN.)
|
|
87
|
+
|
|
88
|
+
## The three commands
|
|
89
|
+
|
|
90
|
+
```bash
|
|
91
|
+
# 1. Dataset preflight (speech/TTS pack): audio integrity, transcript quality,
|
|
92
|
+
# duplicates, text-vs-audio duration mismatches
|
|
93
|
+
trainproof data /path/to/dataset_or_manifest.jsonl
|
|
94
|
+
|
|
95
|
+
# 2. Tokenizer preflight: vocabulary coverage, OOV rate, sequence blowouts,
|
|
96
|
+
# suspicious splits on numbers/dates
|
|
97
|
+
trainproof tokenizer my_tokenizer.model transcripts.txt
|
|
98
|
+
|
|
99
|
+
# 3. Training-run verdict: NaN/divergence/dead-run detection, gradient spikes,
|
|
100
|
+
# LR sanity, throughput — from log files, any framework
|
|
101
|
+
trainproof epoch logs/run.jsonl # exit code 1 on FAIL: CI-ready
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
Each command prints the verdict, writes a self-contained HTML report, and sets
|
|
105
|
+
the process exit code — so it works as a CI gate out of the box.
|
|
106
|
+
|
|
107
|
+
## Supported log formats
|
|
108
|
+
|
|
109
|
+
- HuggingFace Trainer (`trainer_state.json`)
|
|
110
|
+
- Coqui Trainer text logs (ANSI-colored `trainer_0_log.txt`)
|
|
111
|
+
- Generic JSONL / CSV (columns: step, loss, lr, grad_norm, time — all optional)
|
|
112
|
+
|
|
113
|
+
Auto-detected; override with `--format hf|coqui|jsonl|csv`. TensorBoard event
|
|
114
|
+
files are planned (Lightning console captures are TTY dumps, not logs, and
|
|
115
|
+
will not be supported).
|
|
116
|
+
|
|
117
|
+
## Philosophy
|
|
118
|
+
|
|
119
|
+
1. **Deterministic.** A rule fires or it doesn't. Thresholds live in one
|
|
120
|
+
module, commented, tunable.
|
|
121
|
+
2. **Evidence-cited.** Every finding names the steps and values that triggered
|
|
122
|
+
it.
|
|
123
|
+
3. **Honest about limits.** What the tool cannot detect is documented in the
|
|
124
|
+
README, not discovered by the user in production.
|
|
125
|
+
|
|
126
|
+
## Family
|
|
127
|
+
|
|
128
|
+
trainproof judges training runs. Its sibling [ttsproof](https://github.com/Mormolykos/ttsproof)
|
|
129
|
+
judges TTS model *outputs* (structural audio checks, equivalence-aware WER/CER,
|
|
130
|
+
published method with DOI) — and trainproof builds on it for the speech
|
|
131
|
+
dataset checks.
|
|
132
|
+
|
|
133
|
+
## Author
|
|
134
|
+
|
|
135
|
+
Panagiotis (Panos) Gkilis — [portfolio](https://tts.bedvibe.studio/portfolio/) ·
|
|
136
|
+
[bedvibe.studio](https://bedvibe.studio/)
|
|
137
|
+
|
|
138
|
+
MIT license.
|
|
@@ -34,3 +34,10 @@ def test_epoch_flat():
|
|
|
34
34
|
report = check_epoch(FIXTURES_DIR / "flat.jsonl")
|
|
35
35
|
assert report["verdict"] == "FAIL"
|
|
36
36
|
assert any("completely flat" in str(f) for f in report["findings"])
|
|
37
|
+
|
|
38
|
+
def test_epoch_dead_noisy():
|
|
39
|
+
# noisy-but-never-improving loss with healthy lr: only the no-improvement
|
|
40
|
+
# rule (v0.2) can catch this dead run
|
|
41
|
+
report = check_epoch(FIXTURES_DIR / "dead_noisy.jsonl")
|
|
42
|
+
assert report["verdict"] == "FAIL"
|
|
43
|
+
assert any("never improved" in str(f) for f in report["findings"])
|
trainproof-0.1.0/PKG-INFO
DELETED
|
@@ -1,62 +0,0 @@
|
|
|
1
|
-
Metadata-Version: 2.4
|
|
2
|
-
Name: trainproof
|
|
3
|
-
Version: 0.1.0
|
|
4
|
-
Summary: A deterministic linter for ML training runs: dataset, tokenizer, and epoch logs.
|
|
5
|
-
Author-email: "Panagiotis (Panos) Gkilis" <bedvibe@bedvibe.studio>
|
|
6
|
-
License: MIT
|
|
7
|
-
Requires-Python: >=3.10
|
|
8
|
-
Description-Content-Type: text/markdown
|
|
9
|
-
Requires-Dist: numpy
|
|
10
|
-
Requires-Dist: soundfile
|
|
11
|
-
Requires-Dist: ttsproof
|
|
12
|
-
|
|
13
|
-
# Trainproof
|
|
14
|
-
|
|
15
|
-
A deterministic linter for ML training runs. Run it on your dataset, tokenizer, and first-epoch logs — it gives a deterministic PASS/WARN/FAIL verdict with named findings and suggested fixes, BEFORE you burn weeks of GPU time.
|
|
16
|
-
|
|
17
|
-
## Installation
|
|
18
|
-
```bash
|
|
19
|
-
pip install .
|
|
20
|
-
```
|
|
21
|
-
|
|
22
|
-
## Usage
|
|
23
|
-
|
|
24
|
-
Exactly three subcommands:
|
|
25
|
-
|
|
26
|
-
### 1. Dataset Preflight
|
|
27
|
-
```bash
|
|
28
|
-
trainproof data /path/to/dataset
|
|
29
|
-
```
|
|
30
|
-
Checks audio integrity (clipping, silence, duration distribution) and transcript quality (unnormalized text, charset audit, duration correlation).
|
|
31
|
-
|
|
32
|
-
### 2. Tokenizer Preflight
|
|
33
|
-
```bash
|
|
34
|
-
trainproof tokenizer my_model.model transcripts.txt
|
|
35
|
-
```
|
|
36
|
-
Checks vocabulary coverage, tokens-per-second, and suspicious splits on numbers/dates.
|
|
37
|
-
|
|
38
|
-
### 3. First-Epoch Verification
|
|
39
|
-
```bash
|
|
40
|
-
trainproof epoch logs/epoch1.jsonl
|
|
41
|
-
```
|
|
42
|
-
Analyzes loss curves (divergence, flatlines, NaN), grad norms, learning rate response, and throughput.
|
|
43
|
-
|
|
44
|
-
## Supported log formats
|
|
45
|
-
- **Generic JSONL / CSV**
|
|
46
|
-
- **HuggingFace** (`trainer_state.json`)
|
|
47
|
-
- **Coqui TTS Trainer** (plain text `trainer_0_log.txt`)
|
|
48
|
-
|
|
49
|
-
*Roadmap: TensorBoard event files planned (v0.2) — Lightning console captures are TTY dumps, not logs, and will not be supported.*
|
|
50
|
-
|
|
51
|
-
## Verdict Rules
|
|
52
|
-
All verdict rules are deterministic thresholds defined centrally in `src/trainproof/rules.py`. Examples include:
|
|
53
|
-
- `MAX_CLIPPING_PEAK = 0.99`
|
|
54
|
-
- `MAX_LOSS_DIVERGENCE_RATIO = 1.5`
|
|
55
|
-
- `MIN_VOCAB_COVERAGE = 0.999`
|
|
56
|
-
|
|
57
|
-
## Explicit Non-Goals
|
|
58
|
-
- No live learning-rate auto-adjustment.
|
|
59
|
-
- No PyTorch/Lightning callbacks or framework hooks (log files only).
|
|
60
|
-
- No MCP server, no LLM integration.
|
|
61
|
-
- No dashboards/wandb-style UI.
|
|
62
|
-
- No extra features beyond this spec.
|
trainproof-0.1.0/README.md
DELETED
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
# Trainproof
|
|
2
|
-
|
|
3
|
-
A deterministic linter for ML training runs. Run it on your dataset, tokenizer, and first-epoch logs — it gives a deterministic PASS/WARN/FAIL verdict with named findings and suggested fixes, BEFORE you burn weeks of GPU time.
|
|
4
|
-
|
|
5
|
-
## Installation
|
|
6
|
-
```bash
|
|
7
|
-
pip install .
|
|
8
|
-
```
|
|
9
|
-
|
|
10
|
-
## Usage
|
|
11
|
-
|
|
12
|
-
Exactly three subcommands:
|
|
13
|
-
|
|
14
|
-
### 1. Dataset Preflight
|
|
15
|
-
```bash
|
|
16
|
-
trainproof data /path/to/dataset
|
|
17
|
-
```
|
|
18
|
-
Checks audio integrity (clipping, silence, duration distribution) and transcript quality (unnormalized text, charset audit, duration correlation).
|
|
19
|
-
|
|
20
|
-
### 2. Tokenizer Preflight
|
|
21
|
-
```bash
|
|
22
|
-
trainproof tokenizer my_model.model transcripts.txt
|
|
23
|
-
```
|
|
24
|
-
Checks vocabulary coverage, tokens-per-second, and suspicious splits on numbers/dates.
|
|
25
|
-
|
|
26
|
-
### 3. First-Epoch Verification
|
|
27
|
-
```bash
|
|
28
|
-
trainproof epoch logs/epoch1.jsonl
|
|
29
|
-
```
|
|
30
|
-
Analyzes loss curves (divergence, flatlines, NaN), grad norms, learning rate response, and throughput.
|
|
31
|
-
|
|
32
|
-
## Supported log formats
|
|
33
|
-
- **Generic JSONL / CSV**
|
|
34
|
-
- **HuggingFace** (`trainer_state.json`)
|
|
35
|
-
- **Coqui TTS Trainer** (plain text `trainer_0_log.txt`)
|
|
36
|
-
|
|
37
|
-
*Roadmap: TensorBoard event files planned (v0.2) — Lightning console captures are TTY dumps, not logs, and will not be supported.*
|
|
38
|
-
|
|
39
|
-
## Verdict Rules
|
|
40
|
-
All verdict rules are deterministic thresholds defined centrally in `src/trainproof/rules.py`. Examples include:
|
|
41
|
-
- `MAX_CLIPPING_PEAK = 0.99`
|
|
42
|
-
- `MAX_LOSS_DIVERGENCE_RATIO = 1.5`
|
|
43
|
-
- `MIN_VOCAB_COVERAGE = 0.999`
|
|
44
|
-
|
|
45
|
-
## Explicit Non-Goals
|
|
46
|
-
- No live learning-rate auto-adjustment.
|
|
47
|
-
- No PyTorch/Lightning callbacks or framework hooks (log files only).
|
|
48
|
-
- No MCP server, no LLM integration.
|
|
49
|
-
- No dashboards/wandb-style UI.
|
|
50
|
-
- No extra features beyond this spec.
|
|
@@ -1,62 +0,0 @@
|
|
|
1
|
-
Metadata-Version: 2.4
|
|
2
|
-
Name: trainproof
|
|
3
|
-
Version: 0.1.0
|
|
4
|
-
Summary: A deterministic linter for ML training runs: dataset, tokenizer, and epoch logs.
|
|
5
|
-
Author-email: "Panagiotis (Panos) Gkilis" <bedvibe@bedvibe.studio>
|
|
6
|
-
License: MIT
|
|
7
|
-
Requires-Python: >=3.10
|
|
8
|
-
Description-Content-Type: text/markdown
|
|
9
|
-
Requires-Dist: numpy
|
|
10
|
-
Requires-Dist: soundfile
|
|
11
|
-
Requires-Dist: ttsproof
|
|
12
|
-
|
|
13
|
-
# Trainproof
|
|
14
|
-
|
|
15
|
-
A deterministic linter for ML training runs. Run it on your dataset, tokenizer, and first-epoch logs — it gives a deterministic PASS/WARN/FAIL verdict with named findings and suggested fixes, BEFORE you burn weeks of GPU time.
|
|
16
|
-
|
|
17
|
-
## Installation
|
|
18
|
-
```bash
|
|
19
|
-
pip install .
|
|
20
|
-
```
|
|
21
|
-
|
|
22
|
-
## Usage
|
|
23
|
-
|
|
24
|
-
Exactly three subcommands:
|
|
25
|
-
|
|
26
|
-
### 1. Dataset Preflight
|
|
27
|
-
```bash
|
|
28
|
-
trainproof data /path/to/dataset
|
|
29
|
-
```
|
|
30
|
-
Checks audio integrity (clipping, silence, duration distribution) and transcript quality (unnormalized text, charset audit, duration correlation).
|
|
31
|
-
|
|
32
|
-
### 2. Tokenizer Preflight
|
|
33
|
-
```bash
|
|
34
|
-
trainproof tokenizer my_model.model transcripts.txt
|
|
35
|
-
```
|
|
36
|
-
Checks vocabulary coverage, tokens-per-second, and suspicious splits on numbers/dates.
|
|
37
|
-
|
|
38
|
-
### 3. First-Epoch Verification
|
|
39
|
-
```bash
|
|
40
|
-
trainproof epoch logs/epoch1.jsonl
|
|
41
|
-
```
|
|
42
|
-
Analyzes loss curves (divergence, flatlines, NaN), grad norms, learning rate response, and throughput.
|
|
43
|
-
|
|
44
|
-
## Supported log formats
|
|
45
|
-
- **Generic JSONL / CSV**
|
|
46
|
-
- **HuggingFace** (`trainer_state.json`)
|
|
47
|
-
- **Coqui TTS Trainer** (plain text `trainer_0_log.txt`)
|
|
48
|
-
|
|
49
|
-
*Roadmap: TensorBoard event files planned (v0.2) — Lightning console captures are TTY dumps, not logs, and will not be supported.*
|
|
50
|
-
|
|
51
|
-
## Verdict Rules
|
|
52
|
-
All verdict rules are deterministic thresholds defined centrally in `src/trainproof/rules.py`. Examples include:
|
|
53
|
-
- `MAX_CLIPPING_PEAK = 0.99`
|
|
54
|
-
- `MAX_LOSS_DIVERGENCE_RATIO = 1.5`
|
|
55
|
-
- `MIN_VOCAB_COVERAGE = 0.999`
|
|
56
|
-
|
|
57
|
-
## Explicit Non-Goals
|
|
58
|
-
- No live learning-rate auto-adjustment.
|
|
59
|
-
- No PyTorch/Lightning callbacks or framework hooks (log files only).
|
|
60
|
-
- No MCP server, no LLM integration.
|
|
61
|
-
- No dashboards/wandb-style UI.
|
|
62
|
-
- No extra features beyond this spec.
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|