What was built, and every choice the protocol left open
This page documents the executed harness: the model, the data pipeline, the
training recipe, the frozen sampler, the evaluation stack, the statistics, and the
orchestration — plus each implementation decision that configs/grid.json and
protocol.html did not fix, with the reason it cannot bias H-D2/H-D3.
Theory: motivation.html. Binding thresholds:
protocol.html. Measured results:
results.html.
configs/grid.json → protocol.html → task.md. Where a value
was absent from all three, the choice is recorded here and in LOG.md
(entries P0-1…P0-8, D-00x), applied identically to every
config, seed and T.§1Environment as executed
| Component | As run |
|---|---|
| Hardware | 8 × NVIDIA B200 (183 GB, sm_100), 192 CPU cores, 1,996 GB RAM, driver 595.71.05 / CUDA 13.2 |
| Framework | PyTorch 2.11 (CUDA 13 wheels), torchaudio 2.11, transformers,
bf16 autocast, SDPA attention, eager (no torch.compile, see P0-7) |
| Codec | kyutai/mimi via transformers.MimiModel,
12.5 Hz, 8 of 32 codebooks, 2,048 entries each (+MASK, +PAD) |
| Text front end | espeak-ng 1.51 via phonemizer (en-us, stress marks,
punctuation preserved) |
| ASR | openai/whisper-large-v3, greedy (num_beams=1),
Whisper EnglishTextNormalizer |
| Speaker similarity | WavLM-large + ECAPA-TDNN SV head
(wavlm_large_finetune.pth, the seed-tts-eval / UniSpeech checkpoint) — the
grid.json primary; cosine against the ORIGINAL prompt waveform (SIM-o) |
| MOS proxy | UTMOS22-strong (SaruLab weights, fairseq-free packaging via
tarepan/SpeechMOS) |
§2Model — src/model.py
Bidirectional (non-causal) pre-LN transformer over Mimi tokens, exactly
grid.json → backbone: RMSNorm, RoPE, GELU MLP ratio 4, head_dim 64, no weight
tying, one embedding table per codebook summed at the input, one linear output head per
codebook. Non-embedding parameter count
verified for sampled configs to within 0.04 % of grid.json (the residual is the
RMSNorm gains, which the formula excludes) — src/gates.py params.
Sequence layout (decision P0-1)
One sequence is [phonemes | audio frames]. At an audio position the input
embedding is the sum of the eight codebook embeddings of that frame (MASK where masked, PAD
where padded); phoneme positions use a separate table. RoPE positions are i for
phoneme i and 512 + f for frame f. The fixed offset
matters: audio positions must not shift with the phoneme padding length, otherwise a run's
numerics would depend on micro-batch composition, and micro-batch size varies with model size
under a fixed effective batch of 256. Padding is masked out of attention on the key axis.
μP realisation (decision P0-5)
Base width 256, multiplier m = w/256. Embeddings and RMSNorm gains: init std
0.02, LR = base LR. Hidden matrices: init std 0.02·√(256/fan_in), LR = base LR / m.
Output heads: init std 0.02/√m, logits multiplied by 1/m, LR = base LR / m. Residual
projections (attention out, MLP down) are additionally scaled by 1/√(2d) at init
(grid.json residual_scale). The attention softmax keeps the standard
1/√head_dim because head_dim is fixed at 64 for every config in the grid, so the μP
attention-scale correction would be a constant. Width transfer is measured at gate G1
and depth transfer at G1b — not assumed.
§3Data — src/data.py
Four idempotent stages: scan → select → phonemize →
encode (plus eval_audio for the SIM-o references).
| Stage | What it does |
|---|---|
scan | Reads every JSON sidecar out of the Emilia-EN tar shards in
parallel → meta.parquet (id, speaker, duration, text, dnsmos, shard, member). |
select | Deterministic, RNG 1234. Filters English clips with ≥8
characters; training pool is 4–18 s (prompt 3 s + max_target 15 s); 200 held-out speakers are
drawn from speakers that own both a 2.5–3.5 s clip and a 4–15 s clip; the training set is built
by speaker-stratified round-robin (one clip per speaker per pass, clips ordered by id) until the
2,000 h target is hit exactly; val is 2,000 held-in clips disjoint from the training clips;
eval_zs is 400 cross-sentence items from the held-out speakers. sec_per_char is the
median of duration/characters over the first 10,000 training clips. |
phonemize | espeak-ng with stress marks and preserved punctuation,
96 processes; word boundaries become an explicit <sp> token. The vocabulary
is built from the TRAIN split only and then frozen; anything unseen maps to
<unk> and is counted. If espeak ever returns a different number of
utterances than it was given, that chunk is redone one-by-one so phoneme rows can never
mis-align with clip rows. |
encode | Per-shard workers, one GPU and one Mimi instance pinned per
worker process (see D-003). Each clip: mp3 → 24 kHz mono → peak-normalise to −1 dBFS
(task.md §10) → Mimi encode with num_quantizers=8 → int16 [frames, 8]
appended to a per-shard array with offsets in the index. Clips are grouped by length and
right-padded; Mimi's encoder is causal, so padding and batching were verified to leave the codes
bit-identical (agreement 1.0000 on the check). |
Prompt / target construction (decision P0-2)
Training. Each clip contributes its full phoneme transcript (always visible) and its
full frame grid; the first 37 frames (int(3 s × 12.5 Hz)) are the always-visible
prompt at all eight levels, the rest is the target region subject to masking. Clips longer than
18 s are excluded rather than truncated — truncating audio while keeping the transcript would
break the text↔length correspondence the length conditioning depends on.
Evaluation (zero-shot cloning). The prompt is a complete held-out-speaker clip
of 2.5–3.5 s together with its own transcript; the target text comes from a different clip
(4–15 s) of the same held-out speaker. Visible phonemes are prompt-phonemes +
<sp> + target-phonemes; the generated length is
round(chars(target_text) · sec_per_char · 12.5) frames, capped at 187. This is the
standard cross-sentence zero-shot protocol and needs no word-level alignment, which the corpus
does not provide.
§4Training — src/train.py
SoundStorm-style coarse-to-fine masking, verbatim from
grid.json → backbone.training_objective: per example a level
l ~ U{1..8} and a ratio t ~ U(0,1]; levels below l stay
visible, level l is masked i.i.d. with probability t, levels above
l are entirely MASK; cross-entropy on the masked cells of level l
only. Phonemes and the prompt frames are never masked. 30,000 steps, effective batch 256
sequences, AdamW(0.9, 0.95), weight decay 0.1 (on matrices; RMSNorm gains excluded), gradient
clip 1.0, 600 warmup steps then cosine to 10 % of peak, bf16 autocast with fp32 master weights.
Config-independence of everything except (w, d, heads, seed) — decision P0-6
- Data order. A per-epoch permutation seeded by
[seed, epoch]; within each group of eight batches the examples are sorted by frame count to cut padding waste. The order depends only on the seed, never on the model. - Masking draws. Level, ratio and per-cell uniforms are drawn once for the whole
256-sequence effective batch from a generator seeded by
(seed, step), before any forward pass — so two different shapes see bit-identical masks at every step. - Loss normalisation. The masked-cell count of the entire effective batch is computed
first; every micro-batch contributes
Σ CE / total_cells. The gradient is therefore independent of how the batch was split into micro-batches (micro-batch size is chosen per config from a memory proxy, accumulating to exactly 256).
Validation every 1,000 steps is the protocol's definition: masked CE at the fixed ratios {0.25, 0.5, 0.75}, averaged over all eight levels, over the full 2,000-clip val split, with a fixed val-masking RNG (seed 999) shared by every run — so val losses are comparable across shapes. Checkpoints (model + optimizer + val history) are written every 1,000 steps and runs resume from them. Divergence uses the gate-G3 definition: val loss above 1.2× its running minimum at three consecutive evaluations.
§5Frozen sampler — src/sample.py
MaskGIT confidence decoding, level by level 1…8, exactly T refinement
steps per level (NFE = 8T). Within a level, all target cells start as MASK; at each step the
model scores every target position, a token is drawn categorically at temperature 1.0 (Gumbel-max
on the log-probs), the confidence of the drawn token is its log-probability plus Gumbel noise
scaled by 1 − i/(T−1) (annealed 1 → 0 across the level's steps), and the cosine
schedule fixes how many cells remain masked after the step:
The highest-confidence cells are committed and stay committed; the rest revert to MASK.
At T = 1 the schedule commits every cell in the single step, so the
confidence ordering (and hence the noise) cannot affect the output — which is why the
degenerate 1 − i/(T−1) case at T=1 is harmless (P0-4). Levels above the current
level are fed as MASK, matching training; prompt frames and phonemes stay visible and unmodified
throughout; padding positions can never be committed (their confidence is −∞).
Generation RNG matched across T (decision P0-3)
eval_zs items are processed in a fixed order (sorted by item id) with a fixed batch
composition (50 items per batch) that is identical for every run and every T — item target
lengths depend only on the text and the frozen sec_per_char, so even the padded
tensor shapes match. All sampler randomness comes from a generator seeded by
20260805 + batch·10⁶ + level·10³ + step. Consequently a given item sees the
same noise at the first step of every level for every T, every config and every seed,
and no result depends on batch composition. An exactly matched stream for all steps is
impossible in principle, because T changes how many draws happen.
Integrity (protocol §6.3). The token grids are saved per (run, T), and T=1 vs T=16 must differ on more than 20 % of cells before any metric is computed — the check that separates "steps genuinely don't help" (a finding) from "T isn't wired through" (a bug). The same check runs on an untrained model at gate G0(e).
§6Evaluation — src/evaluate.py
- WER. Whisper-large-v3, greedy, both reference and hypothesis passed through the
Whisper English normalizer; per-item WER via
jiwer, plus the corpus-level WER as a robustness column. Primary metrics are computed over all items — degenerates included (protocol §1); non-degenerate variants are reported separately. - SIM-o. Cosine similarity between the SV embedding of the generated target audio and of the ORIGINAL prompt waveform (not the codec reconstruction).
- Degenerate rule (§6.4, frozen). Empty normalised transcript, or fewer than 30 % of the target character count, or some 4-gram repeated at least six times consecutively.
- UTMOS22-strong as a tertiary, droppable metric.
- Crash policy (task.md §10). An item that cannot be read scores WER = 1.0, degenerate = true, is excluded from SIM/UTMOS, and is counted in a crash-rate column.
- c_layer(width) (§6.5). Measured per distinct width at batch 1 on the eval GPU
(200 phoneme + 224 frame positions), reported as ms per layer per step; the design-rule table
uses
latency = 8·T·d·c_layer(w)with these measured values only.
§7Statistics — src/fit.py
Weighted non-linear least squares (scipy least_squares, trust-region reflective)
with 32 multi-starts from RNG 42 and the protocol's bounds; Part A on the 15 config means at
T=16, Part B on the full 75-point surface; weights 1/SE² with SE taken over seeds and the pooled
within-config SD substituted where a config has a single seed (floored at 1e-3 so a chance
agreement between two seeds cannot dominate the fit). AICc uses the weighted RSS with
k = parameters + 1. The bootstrap is run-level as the protocol requires: for each
config its runs (seeds) are resampled with replacement, every T point is recomputed from the
resampled runs, and both parts are refitted — 2,000 replicates, RNG 7331, percentile CIs. The
decision block then applies H-D1…H-D4 and the S1/S2/F1/F2 routing mechanically, after gate G4.
protocol.html bounds E, A, B, C and the exponents α, β, γ, τ, but not κ. A
one-sided bound (κ ≥ 0) would make the H-D3 test "κ > 0 with CI excluding 0" partly
self-fulfilling, so κ is bounded symmetrically to [−3, 3]. Part B weights use the same 1/SE²
scheme as Part A (the protocol specifies weights only for Part A); an unweighted refit is
reported as an exploratory sensitivity check.§8Orchestration — src/orchestrate.py
A thread-pool scheduler places each run entirely on one GPU (task.md §2 forbids sharding a
run) and can place more than one run per GPU where utilisation is low — the narrow configs use a
small fraction of a B200. GPU-hours charged against gate G5 are the sum of per-run wall time,
which over-counts under co-tenancy and is therefore conservative; the exact per-GPU
occupancy (union of intervals) is also recorded in state.json.
state.json carries phase, active grid, active recipe, T semantics, chosen LR,
completed/failed runs, gate results, cut history and compute; every gate evaluation with its
measured values is appended to LOG.md.
§9File map and reproduction
| File | Role |
|---|---|
src/model.py | Backbone, μP parameter groups, param-count accessors. |
src/data.py | Corpus → selection → phonemes → Mimi token shards; TokenStore loader. |
src/train.py | Masking, training loop, validation, checkpoints, divergence. |
src/sample.py | Frozen sampler, synthesis, integrity check, c_layer. |
src/evaluate.py | WER / SIM-o / UTMOS / degenerate rule, G0(c), runs.csv. |
src/fit.py | Part A/B fits, AICc, run-level bootstrap, H-D1…H-D4, gate G4. |
src/gates.py | G0 (a) Mimi roundtrip, (b) overfit, (e) sampler integrity, param test. |
src/figures.py | The four protocol §10 figures, SVG + PDF. |
src/report.py | Generates results.html from artifacts only. |
src/paper.py | Populates aspect-d-paper/main.tex from fits.json/runs.csv. |
src/push_hf.py | Pushes every trained run to the private HF model repo. |
src/orchestrate.py | Scheduler, state.json, gate bookkeeping. |
python src/data.py all # scan, select, eval_audio, phonemize, encode python src/gates.py all # G0 (a), (b), (e) + param-count test python src/evaluate.py gt # G0 (c) on ground-truth audio python src/orchestrate.py phase1 --per-gpu 3 # muP sweeps -> G1, G1b python src/orchestrate.py train --phase 2 --configs A3,B3,C3 --seeds 0 python src/orchestrate.py train --phase 3 # remaining mandatory grid python src/orchestrate.py phase4 # synthesis at every T python src/orchestrate.py score # WER / SIM-o / UTMOS python src/evaluate.py collect # results/artifacts/runs.csv python src/fit.py # results/artifacts/fits.json (+ G4, H-D1..4) python src/figures.py && python src/report.py # figures + docs/results.html python src/paper.py # populate aspect-d-paper/main.tex
§10Deviations and fallbacks (full list in LOG.md)
fairseq (unavailable for
Python 3.12), attempt 2 on s3prl calling torchaudio.set_audio_backend
and torchaudio.sox_effects, both removed in torchaudio 2.x. Rather than fall back
to the weaker listed alternative, the two removed no-op APIs are stubbed at import time; the
checkpoint then loads with zero missing keys (one unexpected key: the training-time loss
projection). The primary metric model is therefore what was used, and gate G0(c)
independently validates it on ground-truth audio.encode pass assigned a GPU per job; because pool workers outlive
individual jobs, several processes opened contexts on the same device and Mimi's wide 24 kHz
activations exhausted it. Fixed by pinning one GPU and one Mimi instance per worker
process and encoding in groups of 8 clips. Data-affecting? No: the codes are
padding- and batch-invariant (verified), and the affected shards were re-encoded from scratch.ASPECT-D · implementation reference · generated during
the execution run; the authoritative trail is LOG.md and
state.json.