# Seconds per step — three instruments, and the counting rule for each

The article says this run trained in **39 minutes and 56 seconds** and at **5.73 seconds per
optimiser step**. The first figure is the unit's own wall clock; the second is not derived from
it, and a reader who divides one by 415 will land on a different number. That is a counting
rule, not a measurement error, and this file gives all of them so the reader knows which is
which.

**The whole disagreement is 17.8 seconds of wall clock across a forty-minute run**, which is
what a run with almost no overhead looks like. Both ends of it are true of different things.

The run: `20260906T092541Z-house0`, 331 training tracks, 5 epochs × 83 steps = **415 optimiser
steps**.

| rule | seconds counted | ÷ steps | what the denominator includes | where you can recompute it | authoritative? |
|---|---:|---:|---|---|---|
| **A — the epoch lines** | **2,378.2** | **5.7306** | the five `[OK] Epoch n/5 in …s` lines summed (475.2 + 475.7 + 475.8 + 475.8 + 475.7). **Excludes** model load, the dataset scan and the five checkpoint writes, because the pass clock does not run during them. | `train.log` | **yes** |
| **B — the event stream** | 2,294.048 over **400** steps | 5.7351 | the wall time between the first logged step (10) and the last (410), divided by the 400 steps between them. A mid-run rate: it excludes **both** ends. | `train-lr-series.csv`, first and last `logged_at_utc` | no |
| **C — the unit's wall clock** | 2,396 | 5.7735 | `train-start.txt` to `train-end.txt` — the whole process, load and writes and exit included. This is the 39 m 56 s the article prints. | `run-timeline.json` (`stamps.train_start`, `stamps.train_end`) | no |

At two decimals those are **5.73**, **5.74** and **5.77**, which is how the article prints
them. Spread A→C is 0.043 s/step — **0.75 %**.

## A fourth reading, named so it does not ambush anyone

The trainer's own banner inside `train.log` reads `Total time .......... 39m 40s`. That is
**2,380 s**, and 2,380 ÷ 415 = **5.7349** — a fourth number, and at two decimals it collides
with rule A. The banner starts its clock after the model is loaded and stops before the process
exits, which is the 16 seconds between it and rule C. It is printed here rather than left for a
reader to trip over.

The same banner also reports `Epochs .............. 0 / 5`, which is wrong on its face — five
epochs ran, and the five `[OK] Epoch` lines and five checkpoint directories are in the same
file. It is a display bug in the trainer's summary and it is left exactly as written, because
editing a number inside a verbatim log is worse than explaining it. Its `Loss` and `Best loss`
lines are the last and lowest **logged step** losses (0.7743 and 0.4190 at steps 410 and 400),
not epoch means; the epoch means are the `[OK] Epoch` lines, and they are what the article and
the checkpoint names use.

## How to recompute all three

    # rule A
    grep '^\[OK\] Epoch' train.log
    # rule B
    head -2 train-lr-series.csv; tail -1 train-lr-series.csv
    # rule C
    python3 -c "import json;d=json.load(open('run-timeline.json'));print(d['stamps']['train_start'],d['stamps']['train_end'])"

Nothing in `train.log` was altered except the filesystem paths; `provenance.json` says how many
times each substitution fired, per file. The card is published by name — an RTX 3090 — because
it is consumer hardware a reader might own and the page's argument is partly about that.
