A technical report on the MOSS voice-acting line: how a general-purpose text-to-speech base model was turned, over two full fine-tunes and roughly a thousand adapters, into a model that takes stage directions — and what happened when the project then tried to give it the two things a director actually gives an actor, timing and intensity. One of them is now solved to within 0.08 s. The other has resisted four training objectives, and the reason turns out to be measurable.
How to read this. Every number below was measured, and the measurement is named. Where a result is negative it is reported as a result, because in this project the diagnosed failures have been more useful than the successes: several of them changed how the work is evaluated at all. Where something is a hypothesis rather than a finding, it says so. Where two sources disagree, §23.2 records the disagreement instead of smoothing it over.
One caveat applies to the whole document and is repeated where it bites: no human listening study has been run. Every quality figure here is the output of a learned scorer. §6 explains what those scorers are, how they were trained, how far they can be trusted, and the measured cases in which they were confidently wrong.
MOSS Voice-Acting is a text-to-speech model that is directed rather than configured. Instead of exposing a handful of knobs — speed, pitch, an “emotion” enum — it takes a written stage direction in natural language, of the kind a director would give a voice actor, and performs the line accordingly. It speaks English and German, outputs 48 kHz audio, can clone a voice from a reference recording, and can produce non-verbal vocal bursts — sighs, gasps, chuckles, groans — inside a spoken line.
The production checkpoint is laion/moss-tts-local-transformer-4.55b-voice-acting-v2, 4.55 billion parameters. It did not appear in one step. §3 reconstructs the road: an off-the-shelf OpenMOSS base model, a full fine-tune on distilled voice-acting data plus a rank-256 adapter merged into it (“v1”), then a second six-dataset full fine-tune selected on generated audio rather than on loss (“v2”).
Around that model sits an ecosystem this report describes end to end:
The short version of the result. Timing control works. Ask for a sentence that takes 3.4 seconds followed by a 0.6-second pause and a 0.3-second sigh, and you get it — the median error on the total clip length is 0.08 s and essentially every clip lands within 0.5 s. Intensity control does not work yet. Ask for an emotion in the 90th-to-98th percentile of how strongly that emotion appears in the corpus, and the model delivers something around the 34th percentile. Four training objectives have been aimed at that gap. Three things have moved it, all of them by less than a hundredth: data — a small adapter trained on the most intense and most genuine 8 % of the corpus (§13.3); overdriving that adapter past its trained strength (§13.4); and a better-constructed contrast in the preference corpus, which as of 28 August produces the best model in the project and is the first preference tuning here that raises intensity without costing intelligibility (§15.4).
| TTS | Text-to-speech. |
| LoRA | Low-rank adaptation. Instead of retraining billions of
weights, you train a small pair of low-rank matrices whose product is added to selected weight
matrices. A “rank-16” adapter here is ~34 M trainable parameters against 4.13 B
frozen ones. The added delta is scaled by alpha / r; multiplying that by a
merge weight or dose λ dials the adapter up or down at inference. |
| SFT | Supervised fine-tuning: ordinary next-token training on (prompt, target) pairs. |
| DPO | Direct preference optimisation: training on pairs of outputs labelled better/worse, pushing up the likelihood of the preferred one relative to the other. |
| GRPO | Group-relative policy optimisation: generate several outputs for the same prompt, score them, push the policy toward the ones that scored above the group average. |
| WER | Word error rate: run a speech recogniser on the generated audio and compare its transcript to the words that were requested. Used here as an intelligibility and hallucination detector, not as an ASR benchmark. |
| RVQ codec / audio token | Residual vector quantisation. The audio is not generated as a waveform but as discrete symbols from a learned codec, decoded back to sound. |
| Vocal burst | A non-verbal vocalisation inside speech: a laugh, sob, gasp, sigh, groan, scoff. |
| Best-of-N | Generate N candidate takes for one prompt, score them all, keep the best. Used both as an inference strategy and as a data-filtering strategy. |
| ECDF percentile | Empirical cumulative distribution function. A raw score is replaced by its rank within the whole corpus, so that scores from incomparable heads become comparable. §6.7 explains why this was necessary and how it was still wrong at first. |
The production model was not trained from scratch and did not arrive in one step. It is the end of a four-stage chain, and every stage constrains what the later ones could do. The chain is worth laying out before anything else, because the emotional-intensity ceiling that Part II keeps running into is partly inherited from it.
OpenMOSS-Team/MOSS-TTS-Local-Transformer-v1.5 — a general-purpose
autoregressive TTS base model, 12-codebook RVQ at 48 kHz.laion/moss-tts-local-transformer-4.55b-voice-acting.…-voice-acting-v2. This is the model every adapter in §4 is attached to.MOSS-TTS-Local-Transformer-v1.5 is an autoregressive local-transformer TTS model: a large semantic backbone predicts one hidden state per audio frame, and a small second transformer expands that hidden state into the codec tokens for the frame. It has three parts:
| Component | Size | Role |
|---|---|---|
| Semantic transformer (Qwen3-style, 36 layers) | ~4 B | Reads the prompt — instruction, reference audio, language, length budget, text — and produces one hidden state per audio frame. |
| Local “talker” transformer (GPT-2-style decoder) | ~550 M | Expands each frame's hidden state into the 12 codec tokens that make up that frame. |
| 12 audio LM heads + 1 text head | — | One output head per codebook. These are weight-tied to the corresponding audio embedding tables — a detail that later turns out to matter a great deal (§19.2). |
4.13 B of the 4.55 B parameters are trainable; the remainder is the frozen codec-side embedding. The audio tokenizer is MOSS-Audio-Tokenizer-v2: a residual vector-quantised codec with 12 codebooks of 1024 entries at 12.5 frames per second, decoding to native 48 kHz audio. Two consequences run through everything below:
floor(d × 12.5) frames. This is the unit in which length is requested and in which
length error is measured.The base model's prompt surface, which v1 and v2 both inherit, is two fields:
instruction carrying a GENERAL: voice description and a
SCRIPT: block with inline parenthesised delivery cues and [pause]
markers, and text carrying the same spoken words plain. Recommended sampling with a
reference clip: temperature 0.8–0.9, top-p 0.9, top-k 40–50.
Two things happened here, and they are often conflated because the public artefact is their merge.
(a) A full-parameter fine-tune of v1.5 on DramaBox-style voice-acting data.
“DramaBox-style” means the data is formatted as a structured director's instruction
plus the spoken line, and it was filtered for vocal bursts and strong emotion. The checkpoint,
with its checkpoint-cont-* continuations, lives in the private repository
TTS-AGI/moss-dramabox-ft.
(b) A rank-256 voice-acting LoRA on top of that, α = 256, over all
attention and MLP projections, trained on 1 million samples (the adapter
repository's root is the 100 k checkpoint; checkpoint-1m/ is the full run), then
merged into the weights. Training: 8×GPU DDP, bf16, gradient checkpointing, global batch 128,
warmup 0.03 with linear decay.
The public release laion/moss-tts-local-transformer-4.55b-voice-acting is that merged model — “the voice-acting LoRA is already folded into the base fine-tune”, in the card's own words. Its card names four data sources, and this mix is the first place where the balance between synthetic and real material is set:
| Source | What it is |
|---|---|
| DramaBox distillation | Voice-acting performances distilled from DramaBox-style prompts — structured director's instructions plus spoken lines — filtered for vocal bursts and strong emotion. |
| Gemini TTS distillation | Audio-matched fixed prompts paired with MOSS RVQ tokens. This is the synthetic arm: material generated by a commercial TTS system and re-tokenised. |
| Emolia | German emotional speech — real recorded material. |
| Podcast snippets | Publicly available podcast audio — the “audio snippets” corpus, also real. |
(c) A vocal-burst LoRA. Rank 256 again, trained on a 100 k inline-burst
dataset and resumed from the 1 M voice-acting adapter rather than started fresh. Its
training data (TTS-AGI/moss-inline-vocal-bursts, private) writes the bursts into both
the instruction and the text, which is what teaches the model that a burst tag corresponds to an
event in the audio. The merged result was called merged_inline, and a further
all-parameter fine-tune was run from it. A separate library of per-class burst adapters — chuckle,
breathy giggle, growl, fearful gasp, contented sigh, and so on — is published as
laion/vocal-burst-lora-adapters.
Burst hit rate — how often a requested burst actually lands in the audio —
was 23.6 % from the prompt alone, 16.7 % with only an emotion adapter, and
71.9 % with the burst adapter at full dose. The dose knee is
λ = 0.75: it delivers 91 % of the full-merge burst presence at a much better blend.
Across 2,304 generations the single best cell was burst @ 1.0 + emotion @ 0.5;
half-burst plus half-emotion was the worst combination of the grid, because the two
adapters interfere. Placement rules that came out of the same study: put the tag inline in the
SCRIPT: block, never open or close a part with a burst, and never place silence
directly after one.
This is the production model. It started on 2026-07-22 and ran roughly 14–15 hours on 8×A100-80GB — not on JUPITER. Its training scripts lived on an ephemeral RAM disk that has since been wiped; the surviving record is the run notes, the published repositories and the live results pages, which is why this section is reconstructed rather than quoted from a script.
| Setting | Value |
|---|---|
| Data | 185,503 rows across six datasets —
prep_top3_neutral, prep_emolia_elise, prep_a1plus,
our_top3, our_emolia, our_adult — materialised to
va_data/*.parquet and sampled with equal probability per dataset,
not proportionally to size. |
| Schedule | 6 epochs · learning rate 1e-5 linear · warmup = the first half of epoch 1. (The architecture's official default is 2e-5; 1e-5 was chosen to match the earlier runs so the two would be comparable.) |
| Batch | Per-device batch 1 · gradient accumulation 8 → effective batch 64 · channel-wise loss weight (1, 32) — the text channel counts 1, the twelve audio channels count 32. |
| Augmentation | Per-sample caption dropout: 15 % drop the GENERAL:
block, 15 % drop the inline cues, 70 % drop both (and half of those also
drop a third of the remaining inline cues and pauses). Reference-audio dropout keeps 66 %.
|
| Evaluation | Every half epoch: per-dataset validation loss plus five generated clips per dataset, each paired with its ground truth. |
| Outputs | laion/moss-tts-local-transformer-4.55b-voice-acting-v2;
rolling checkpoint backup in the private TTS-AGI/moss-voiceacting-ft-checkpoints. |
Where the six datasets come from, and what they are. The prep_*
half is prepared external material: prep_top3_neutral is the top-3
reward-selected takes of a neutral-delivery set, prep_emolia_elise is a cut of the
Emolia German emotional-speech corpus, and prep_a1plus is an additional voice-acting
collection (published tokenised, without per-clip scores, as
TTS-AGI/additional-data-a1-plus, 31,306 rows). The our_* half is the
model family's own generations, kept by a best-of-N reward:
our_top3 the top-3 takes per prompt, our_emolia the Emolia-conditioned
generations, our_adult the adult-register set. The Gemini-generated named-character
material (TTS-AGI/gemini-adult-voices, 30 named characters × 1,000 clips) sits in the
same family of synthetic sources.
Not by validation loss. The released weights are the best checkpoint by cumulative genuineness + vocal-burst blend on a held-out set — that is, by scoring generated audio with the two learned proxies of §6.2–6.3. Every half-epoch evaluation therefore produced audio as well as a loss, and the audio decided.
This is the same rule the current round follows, and §18 is four separate demonstrations of why it is not optional: in this project a training-time number has been clear, consistent, monotone and wrong on at least four occasions.
Randomness lives entirely in the autoregressive token sampling (the torch RNG, fixed by a seed); the codec decoder is deterministic. The same seed therefore gives byte-identical audio despite a non-zero sampling temperature — verified at MAE 0.0 across two seeded generations. That is what makes the paired sweeps of §13.4 possible at all.
Serving throughput was characterised with SGLang-Omni on a JUPITER GH200 (aarch64):
27.7× realtime at concurrency 32 and 31.2× at concurrency 128 on a 5-second
sweep, against 18.8× for the plain transformers baseline — a 1.47–1.66× speedup,
saturating around concurrency 96–128 at roughly 6.4 clips/s. On 30-second clips it peaks at
85.3× realtime at concurrency 64. The lesson in that pair of numbers is to always
quote a realtime factor together with the clip length it was measured on; the advertised
~35×-per-A100 figure was not reproduced locally.
forward(). It is a base model that returns
only last_hidden_state (architecture MossTTSLocalModel). The channel-wise
loss has to be computed externally, by re-projecting the hidden states through the local
transformer and the text_lm_head / audio_lm_heads — the reference
implementation is compute_supervised_loss_from_hidden in
moss_tts_local_v1.5/finetuning/sft.py. Labels must be packed with the
13-channel MossTTSLocalV15SFTDataset, not the delay-pattern dataset of the sibling
architecture.eps is raised to 1e-4.
At the default 1e-8 the term underflows in bf16 and the loss is NaN after step 1.configure_gradient_checkpointing(scope="global", use_reentrant=False, impl="module_wrapper"),
not model.gradient_checkpointing_enable(), which yielded NaN gradients.forward tolerates CPU input; the manual loss uses the labels directly and does
not.save_pretrained for the best copy), so they were written to a
504 GB RAM-backed /tmp, keeping the last three and the best two.Two of these — bf16 eps and the checkpointing path — are the same class of failure:
a numerical default that is correct in fp32 and silently wrong in bf16. The sibling diffusion-model
project in the same group hit the identical class from the other side, where bf16's unit in the
last place exceeded a typical Adam update and training silently froze; the fix there was fp32
master weights. The transferable rule is that bf16 needs its optimizer numerics checked
explicitly, not inherited.
Before the current research round, the project's main mechanism for control was
adapters. Every one is a PEFT LoRA attached to the same frozen base model, and —
this is what makes them stackable — they all declare the same 23 target-module patterns: the
global Qwen3 stack's q, k, v, o,
gate, up, down_proj; the local GPT-2 decoder's
c_attn, c_proj, fc_in, fc_out; and all twelve
audio_lm_heads.0…11. They differ only in rank and in what they were trained on.
| Family | Adapters | What it moves | Typical dose λ |
|---|---|---|---|
| voice profiles (pilot) | 10 (+60 ablation) | Who is speaking — one identity across the whole expressive range | 1.0 |
| voice profiles (production) | 500 | The same, for the full synthetic voice corpus; rank 4 | 1.0 |
| emotion LoRAs v3 | 40 | One adapter per emotion, rank 32 / α 64 | 0.35–0.75 |
| VoiceNet dimension LoRAs | 114 | 57 timbre/prosody dimensions × {high, low} | 0.25–1.25, per dimension and direction |
| vocal-burst LoRAs | 64 | One per burst class — sobs, sharp inhale, chuckle, scream… | 0.5–0.75 |
| character LoRAs, genuine / refined / 12-cluster | 120 / 120 / 12 | Character archetypes (orc, dragon, fairy, …) | 1.0 |
| explicitness LoRAs (gated) | 6 | Adult / unguarded register; access-restricted behind an age check | 0.2–0.8 |
| German broadcast · sports commentary | 9 · 6 | Domain registers | 0.25 · 1.0 |
v1 — the rescue result (2026-07-26). Evolutionary search over natural-language prompts had failed outright to elicit Anger and Amusement from the base model. Training a per-emotion rank-64 LoRA on emotion-heavy audio rescued them. The ablation used the best checkpoint and the same emotional prompt in both arms, so the LoRA is the only variable (EmoNet z; higher = more of the emotion):
| Emotion | Baseline v2, prompt only | + rank-64 LoRA | Note |
|---|---|---|---|
| Amusement | −0.51 | +2.50 | the prompt failed completely; rank 64 ≫ rank 128 (2.50 vs 0.99) |
| Anger | −0.37 | +0.62 | ranks roughly tie; rank 128 keeps speaker similarity higher (0.53) |
| Fear | +0.34 | +1.20 | rank 64, best at an intermediate checkpoint (~6 epochs) |
| Sadness | +0.80 | +0.57 | the LoRA does not help — the prompt already sufficed |
Rank 64 became the default (rank 128 overfits the ~1,000-clip buckets), and the best adapter was consistently an intermediate checkpoint — 20 epochs drives the loss to 0.03–0.4 and overfits. Speaker similarity was preserved at 0.41–0.58.
Overnight scale-up and the mixture study (2026-07-27). 34 emotions were trained on natural EmoLia top-1000 subsets. Two things came out. First, there is no single best rank: Anger and Fear peak at rank 128, Sadness at 64, Amusement only moves at all at rank 32. Second, and more consequential, a seven-way mixture study over three sources — D cross-folder DACVAE, E natural EmoLia, G LAION's Got Talent (high-emotion synthetic voice acting) — found that Got-Talent wins: pure Got-Talent is best for Fear (z = +2.62), Distress, Sadness and Anger; only Amusement preferred a three-way mix. High-emotion synthetic data became the primary emotion-LoRA training source. Clear intensity lifts at this stage: Amusement +1.29, Sadness +0.59, Anger +0.52, Fear +0.47 — with many emotions near zero, because natural speech is subtler.
v3 — 40 of 40, and the synthetic character dialled back. The shipped set, TTS-AGI/moss-emotion-loras-v3, is 40 adapters at rank 32, α 64, single-phase, trained against v2. Its distinguishing change is a cap: Got-Talent material is limited to ≤ 25 % per bucket, so at least ~60 % of every mix is natural or expressive non-Got-Talent material — EmoLia, DACVAE, the Gemini-generated adult-voice set, and the mitermix collection. The card states the reason plainly: this “removes the synthetic Got-Talent character that v2 had”. So the trajectory of this line is a full round trip — synthetic data was found to be the strongest single source, and then had to be capped because it stamped its own character on the output.
One adapter per emotion, best of ranks {16, 32, 64}, ~1,000–1,400 clips per emotion (natural
EmoLia topped up with Got-Talent, and about 25 % of rows carrying a same-voice
contrasting-emotion reference). The best adapter per emotion was chosen by a gated composite,
z + 0.15·blend + 0.5·spk + 0.1·genu — note that the emotion term is gated and the
quality terms are already there, which is the ancestor of every reward used later in this
report.
Amusement +1.99 · Helplessness +1.79 · Intoxication +1.58 · Fatigue +1.47 · Sexual Lust +1.38 · Distress +1.35 · Anger +1.34 · Fear +1.26 · Sadness and Teasing +1.16 · Malevolence +1.15
High-valence and subtle states: Awe, Elation, Pride, Contentment, Pleasure, Hope, Thankfulness, Astonishment, Infatuation, Contempt… Two are self-reducers and are usable as minimising directions: Contemplation (+0.06 → −0.56) and Concentration (−0.30 → −0.40).
Three findings from that sweep have kept their value:
An emotion LoRA raises the target emotion and lowers genuineness, vocal-burst blend, audio quality and intelligibility. The best total reward is therefore frequently prompt-only, or the LoRA merged at about 50 %, rather than the LoRA at full strength. This trade-off — not raw emotion maximum — drives every recipe in the public manual, and it recurs unchanged in the current round (§13).
A reference-audio ablation on the same sweep is worth one line because it is so emotion-specific: attaching a same-voice, contrasting-emotion reference to ~50 % of rows helps Amusement enormously (+2.93 z, beating even the best reference-free mixture) and is mixed or small for everything else, with no improvement in speaker similarity. It is a per-emotion option at around 25 % of rows, not a default.
| Set | Count | Method |
|---|---|---|
| Genuine | 120 | Mined character voices from v2 generations plus Gemini-written casting profiles; rank-32, from scratch, ≥ 50 DNSMOS-filtered clips per class (a further 46 warm-started adapters cover the smallest classes). |
| Refined | 120 | Self-distillation: 640 candidates per character → filter → SIDON restoration → keep the top 20 % by profile reward → warm-start fine-tune for 3 epochs. Ships three epoch checkpoints each. |
| 12-cluster representatives | 12 | The nearest genuine adapter to each KMeans centroid over the 57 VoiceNet voice-quality dimensions. |
Voice space is a continuum, not a set of types. The 567 character reference voices, z-scored over their 57 VoiceNet dimensions and clustered at k = 12 and k = 18, give a silhouette score below 0.2 everywhere; k = 18 adds only about 5 % of variance and is less separated than k = 12. Twelve is a workable practical taxonomy; eighteen just subdivides the same families. The poles of the space are bright / high / feminine at one end and dark / low / gravelly / masculine at the other. An honest caveat travels with it: the 120 creature adapters skew masculine and monstrous, so the feminine and clean clusters get loose matches — the “High-Bright-Feminine” centroid maps to a guttural imp, which is a conceptual mismatch. Good matches: Playful-Feminine → elf, Full-Chesty → ogre, Silken → titan, Gravel → skeleton.
A cross-lingual reference does not clone a voice; it invents one. Over 1,280 generations conditioning German and English output on Japanese anime references, mean speaker similarity to the reference was 0.188 against an unrelated-speaker floor of 0.105 and a self-similarity of 1.000. Pitch tracks (r = 0.591) but compresses toward the model's own register (median F0 182 → 157 Hz). English transfers better than German (0.217 vs 0.158), and best-of-8 lifts it to 0.294. The recommendation is to use cross-lingual references to mine new character voices, never to clone — a limit that returns in §20 as a constraint on dubbing.
| Dataset | Size | What |
|---|---|---|
| scientifi-papers/chavo-annotated | 750,242 | Game character clips with the full 57 VoiceNet dimensions, 40 emotions
and genuineness/blend, tokenised for MOSS v2. Heavily skewed: 668 k of the rows are
creature, across 807 races. |
| laion/moss-character-voices-top3-captioned | ≈38,400 | Designed takes, 128 shards, ~5,500 per archetype. |
TTS-AGI/gemini-adult-voices (private) | 30,000 | 30 named characters × 1,000 clips — the Gemini-generated synthetic arm; tokenised, no per-clip scores. |
TTS-AGI/additional-data-a1-plus (private) | 31,306 | Voice-acting clips; tokenised, no per-clip scores. This is prep_a1plus in §3.3. |
One rank-32 adapter per pole of each VoiceNet dimension, trained on v2. The published evolution study covers 86 dimensions across high and low poles. Δ100 below is the measured shift at full dose:
Ranting style S_RANT high +0.78 · Tension TENS high
+0.67 · Dynamic arc DARC high +0.65 · Respiration RESP high +0.65
Mixed resonance R_MIXD low +0.04 · Roughness ROUG low
+0.06 · Mixed resonance high +0.06
Averaged over all of them, λ = 0.5 delivers about 36 % of the full-strength shift (mean in-direction shift +0.350 at λ = 1.0 against +0.157 at λ = 0.5) — so a half-strength merge buys a large fraction of the effect at much lower risk to speaker identity. That matches the search agent's independent canary finding that tempered doses of 0.4–0.5 preserve identity where 1.0 and above maximise the target and destroy it.
A practical trick from the manual that has no obvious analogue elsewhere:
use VoiceNet adapters as stabilisers. Adding vn_CLRT_high @ 0.4
and vn_STRU_high @ 0.3 (plus vn_S_NARR_high for storytelling)
protects intelligibility when a strong emotion or style adapter would otherwise push word error
rate up; adding +0.5·ESTH to the reward biases toward “nice to listen
to”.
Bucket feasibility, measured on game-voice data. Per-dimension buckets at three
poles — high (≥ 5), mid (3–4), low (≤ 1), capped at 1,500 each — give 154 trainable
buckets (≥ 200 clips) across 57 dimensions. Sixteen dimensions have no high
bucket at all in game voices (S_TECH, VALN, S_NEWS,
STRU, SMTH, R_ORAL, R_MASK,
R_MIXD, R_HEAD, EXPL, DFLU, ESTH,
COGL, CHNK, BRGT, BKGN) and need podcast or
empathic-speech fill; EXPL has essentially no variation in that data at all.
Six adapters, published both publicly and in a gated form requiring manual approval, an 18+
confirmation and name/email collection:
TTS-AGI/moss-explicitness-loras.
The set is aesthetic_mix_r32 (the recommended default), adult_r32,
a1_r32, and raw_r16 / raw_r32 / raw_r64, of
which raw_r32 gives the largest Sexual-Lust lift. The recommended recipe is the
aesthetic adapter at 0.2–0.8 with the emotion adapter held at 0.35–0.75.
An attempt to bypass natural language entirely. Ninety-nine numbers — the 40 EmoNet emotions,
the 57 VoiceNet dimensions, genuineness and vocal-burst blend — were each projected into the
model's 2560-dimensional latent space by a tiny per-score projector, and the 99 resulting rows were
spliced into the prompt between the backbone's <|vision_start|> and
<|vision_end|> tokens, using the otherwise-unused
<|image_pad|> token as a placeholder. A rank-128 LoRA and the 99 projectors were
trained jointly, warm-started from an existing adapter.
The first thing measured was the ceiling: how much of a conditioning signal survives a round trip through the codec at all. Encode a clip, decode it, re-score it, and correlate:
| Signal | Codec round-trip correlation | Reading |
|---|---|---|
| Overall | 0.993 | — |
| 40 emotions | 0.993 | Emotion survives the codec almost perfectly. |
| 57 VoiceNet dimensions | 0.995 | So does timbre. |
| Genuineness | 0.951 | Good. |
| Vocal-burst blend | 0.823 | The hardest of the 99. Bursts are fine, transient events that a 12.5 Hz codec partly smears — so the achievable control on blend is intrinsically lower than on anything else. |
The outcome of the experiment itself was negative: the projected-score conditioning path came out slightly below plain v2 on the two proxies in a head-to-head evaluation (genuineness −0.12, blend −0.44). Numeric conditioning is promising and did not beat text in this iteration. The correlation ceilings, though, are permanently useful, and the 0.823 for blend explains a recurring difficulty later.
Merge weight is an intensity dial, and it is roughly linear. Averaged over adapters and multiple seeds, λ = 0.5 recovers about 40 % of the full-strength gain, with no snap-on, no collapse and naturalness held. (With a fixed seed the same curve looks like a staircase rather than a smooth line — an artefact of sampling, not of the adapter.)
And it destroys speaker identity, monotonically. Measured as ECAPA speaker- embedding similarity to the reference clip, on cross-lingual Japanese → German/English generations:
| Dose λ | Similarity to reference | Reading |
|---|---|---|
| 0.0 | 0.62 | baseline cloning fidelity |
| 0.5 | 0.57 | usable |
| 1.0 | 0.50 | degrading |
| 1.5 | −0.03 | below the 0.105 unrelated-speaker floor — the output voice has no relationship to the reference at all |
Anchors from the same encoder: a reference against itself scores 1.000, two different speakers score 0.105. Published guidance: keep λ ≤ 0.5 if reference fidelity matters, 0.75–1.0 if only the emotion matters. Overdriving also truncates: in a controlled stacking run, one sentence and one seed produced 3.84 s with the voice adapter alone and 1.68 s — under half — with an emotion adapter at dose 1.9 stacked on top. An overdriven stack does not merely sound wrong; it stops early.
Difference vectors. Taking Effect = mean z at λ = 1.0 minus mean z at λ = 0, and recording for each measured dimension which adapter raises it most and which lowers it most, turns the adapter library into a set of directions rather than a set of labels. Highlights: the Amusement adapter raises Intoxication by +1.19 as well as Amusement by +1.80; the Sadness adapter is a broad negative lever, raising Embarrassment +1.08, Disappointment +0.85 and Fear +0.66; and subtracting Teasing (−1.70 on Concentration) is a “more concentrated” direction. Hope at −0.83 on Contentment behaves the same way.
An in-sentence merge-dose curve over 1,536 generations, 8 families × 64 classes:
| Burst dose λ | Burst present | Blend | Genuineness | Composite |
|---|---|---|---|---|
| 0.25 | 27.3 % | 5.73 | 1.48 | 0.080 |
| 0.50 | 50.5 % | 4.76 | 1.89 | 0.146 |
| 0.75 — the knee | 64.8 % | 4.17 | 1.97 | 0.199 |
| 1.00 | 71.4 % | 3.97 | 1.94 | 0.226 |
λ = 0.75 gives 91 % of the full-dose burst presence at the peak of genuineness and a better blend; presence is bought against blend roughly one for one. Two exceptions: Sexual Lust peaks at 0.75 and then drops, Fear is best at 1.0. Five classes never fire inside a sentence at all — hiss, kissing, lip-smack, playful whistle, slurping. Rank barely matters (composite 0.416 / 0.424 / 0.427 for ranks 16 / 32 / 64).
The bare-burst prior is the opposite: in isolated-burst sweeps λ = 1.0 won every class. Do not carry that setting into sentence generation.
The locator-plus-classifier pipeline could not recognise 26 of 77 burst classes even in real human recordings. On the 45 measurable classes, 84 % improved (mean +0.132); pooled over all 77 that dilutes to +0.060. A separate Gemini listening judge confirmed 64 of 77 classes at mean ≥ 1.5 — including 19 of the 26 the metric evaluator cannot see (controls: real human audio 1.874, base model with no adapter 0.411, adapter 1.755). The same label filter discarded 83 % of the available data, 97 k rows down to 16.7 k usable.
The transferable point: when a metric reports a small effect, check whether the metric can see the effect at all before concluding the effect is small.
“Contained” means the emotion is present but held in, which is what most adult
dramatic performance actually is. The mechanism that works is specific: keep the emotion and
drop vulnerability — a light vn_VULN_low at 0.06–0.18 plus a parenthetical
restraint cue, over a character-adapter scaffold with the emotion at about 0.4. What does
not work: turning the emotion down, adding tension, or applying heavy Emotional-Numbness
or vn_TENS_high — all of those armour the delivery into a news-anchor flatness.
A deep paired re-run over 1,878 clips measured ΔVULN as the suppressed cohort's mean minus the free cohort's; negative means the masking worked:
| Verdict | Emotions, with ΔVULN at moderate / intense |
|---|---|
| Masks cleanly | Fatigue −0.17 / −0.27 · Embarrassment −0.11 / −0.25 · Pain −0.18 / −0.01 |
| Masks at intense only | Emotional Numbness −0.01 / −0.55 · Intoxication +0.02 / −1.05 · Helplessness +0.18 / −0.23 |
| Reverses | Sadness +1.29 / +0.45 · Pride +1.09 / +1.14 · Distress +0.43 / +0.82 · Affection +0.18 / +0.59 |
| Erasure, not masking | Fear (intense −0.28, and the emotion itself −0.32) · Doubt (−0.75, −0.56) |
The rule that falls out: masking works when the audible signal is effort, arousal or social surface and vulnerability is incidental; it fails or reverses when vulnerability is the emotion's core — for Sadness, Pride, Distress and Affection, ship the free take. This result overturned an earlier “moderate contains, intense flattens” story that turned out to be an artefact of four anchors and sparse unpaired data.
A large ablation — 106 growing to 124 arms, up to 3,968 candidates — found that several apparently “vocal” dimensions are in fact properties of the written line, and no adapter moves them.
DFLU from 0.44 to 2.07, at a
cost of +0.176 WER. Ellipses drive the dimension harder (span 1.54 vs 1.07) while
[pause] tags are gentler on intelligibility (0.122 vs 0.238 WER) — so ship both.
[pause] and [long pause] were verified not to be spoken
aloud.EXPL
register dimension is essentially not adapter-drivable (ladder span −0.006), and vulgar text gives
only a modest span (0.26–0.43). The explicitness LoRA in the adult-intimacy sense is real:
adult text plus inline burst tags — without a burst adapter — lifts blend from 1.72 to
6.77; adding a burst adapter lowered it.Inline cues and bursts are written in round brackets, pauses in square
brackets, tags in lower case. One pipeline emitted <sobs> instead. The
angle-bracket form was not matched by the WER stripper, so the word “sobs” stayed in
the reference string and every burst-carrying take was charged with failing to pronounce it. The
ranker then selected against tagged takes, and round 2's conclusion that “tags
lose” was an artefact. With the corrected stripper
(\([^)]*\)|\[[^\]]*\]|<[^>]{1,40}>), round brackets beat angle brackets on
emotion, blend and genuineness alike.
Three further ranking decisions were each measured on 28,698 stored candidates:
reward = (genu + blend) × (1 − WER) × identity × length-fit.The production matrix at the end of this line was 169 distinct adapter keys — 13 character, 40 emotion, 114 VoiceNet, plus explicitness and sports.
| Candidates N per group | 1 | 8 | 16 | 32 | 64 |
|---|---|---|---|---|---|
| Mean reward of the winner | 0.383 | 0.520 | 0.550 | 0.574 | 0.596 |
The 16 → 32 step is worth +4.46 % reward, −8.95 % duration error and +0.254 blend; 32 → 64 costs 3.4× for the same increment. Crucially, generation time is flat from batch 16 to 32 (20.34 → 20.54 s on a GH200) because the decode loop is latency-bound, so a batch of 16 wastes half the GPU. N = 32 is the operating point, and peak VRAM there is about 23 GB.
The dominant cost is then the speech recogniser, and it is swappable: Parakeet-v3 runs at 52× realtime and is 12 % of a 32-candidate group's cost; Whisper-turbo 12.7× and 32 %; CrisperWhisper 3.1× and 66 %. Rank on Parakeet and re-transcribe only the top three with CrisperWhisper — on a 40 × 3,000 production run that took the bill from 2,952 to 1,148 GPU-hours.
Two more rules from the same body of work, both of which recur later: rank whole assemblies, not parts — across 108 rounds the top-ranked assembly used the best candidate of every part in only 11 rounds (10.2 %) — and n = 8 per cell is noise; use n ≥ 30 with the base model in the same batch.
The voice-profile pilot trained each of ten voices at ranks 16, 8 and 4 in a single process against one shared frozen base, stepping on the same micro-batches with the same seed — so rank is the only difference between the three adapters. On 1,920 held-out clips per arm:
| Comparison | Δ speaker similarity | 95 % CI | p |
|---|---|---|---|
| base → rank 4 (stage 2) | +0.2102 | [+0.2030, +0.2175] | < 1e−300 |
| rank 16 → rank 4 | +0.0023 | [−0.0014, +0.0060] | 0.22 |
| rank 16 → rank 8 | +0.0017 | [−0.0018, +0.0052] | 0.35 |
The base→rank-4 effect is about ninety times the rank-16→rank-4 gap, and the inter-rank gaps are
indistinguishable from zero on every axis measured. Nine of the ten voices ship at rank 4 — 8.6 M
trainable parameters. (Speaker similarity here is cosine distance between ECAPA embeddings from
speechbrain/spkrec-ecapa-voxceleb.)
Across the acting corpus, emotion-adapter dose correlates −0.226 with the measured emotion peak and +0.084 with genuineness. In the manual's words: “you cannot buy a turn by raising λ.” 98.6 % of the 492 doses actually used across the corpus sit in the 0.35–0.75 band. What carries the result is the written line (§4.10).
The manual's own emotion index reports that only 22 of the 40 emotion LoRAs produce a positive lift over a well-written prompt with no adapter. Biggest wins: Sexual Lust +0.25, Fatigue +0.21, Helplessness +0.18, Jealousy and Envy +0.17. Biggest losses: Distress −0.18, Concentration −0.12, Anger −0.12, Relief −0.11, Disappointment −0.10. Eight emotions — Interest, Concentration, Impatience and Irritability, Amusement, Hope, Doubt, Disgust, Pride — are best served by a neutral prompt and no adapter at all.
Because every family targets the same 23 modules, a voice adapter and an emotion adapter are always fighting over the same weights, and at equal λ the larger-rank adapter dominates. Voice adapters are rank 4; emotion, burst, VoiceNet and character adapters are rank 32. That asymmetry — not any target-module difference — is why every non-voice λ is below 1 unless that adapter is the condition.
Two library-level traps are documented with their symptoms, and both killed multi-hour runs:
PeftModel.active_adapter is a plain attribute that base_model.set_adapter()
does not update, so it stays pinned to the first adapter ever loaded and raises
KeyError forever once that adapter is evicted; and a snapshot of the trained scalings
goes stale the moment another adapter is loaded, so a dose computation must consult both
dictionaries and record the trained value on first sight. A third surfaced today and is recorded in
§14.
A large part of the training material for the current round is not recorded speech but voice profiles: synthetic speakers, each generated by the v2 model with the adapters of §4, each exercised across the whole expressive range — 40 emotions, 57 VoiceNet dimensions, acting edge cases, character clusters, vocal bursts, English and German — and each filtered by the scoring models before anything was kept.
40,143 non-empty clips per voice · 108.4 h of playtime per voice · 842 condition groups · 30,869 unique utterances · mean clip 9.72 s. Projected to 500 voices: ~20.1 M clips, ~54,200 h — about 6.2 years of audio.
31.2 GPU-hours per voice billed → ~16,150 GPU-h ≈ 1.16 M core-hours for 500 voices. Throughput held at scale: 1,354–1,451 generations per GPU-hour against a pilot mean of 1,405.
The selection rule that produced them is the direct ancestor of the reward used in Part II:
reward = (genuineness + w_blend·blend + 1.25·target) × (1 − WER)
score = reward × durationFloor × lengthCeiling × identityRank × floorPenalty
Three of its design choices were each measured on 28,698 stored candidates, and each is a small lesson (the first two are §4.11 seen from the data side):
WER × quality filter put 602 of 1,000 candidates at exactly 0 and then
selected the half with worse word error rate.43.4 % of the voice-profile corpus falls below the project's own 0.40 speaker-similarity floor — and 57.1 % within the intense-emotion block. Identity was ranked, not gated, so the adapters were trained on takes that partly drift off the reference voice.
That number needs one caveat and one consequence. The caveat: re-scored with an independent WavLM timbral embedder that played no role in selection, 75.4 % of the takes classified as failures sit above that model's own published same-speaker threshold, and the two embedders correlate at only r = 0.66. So “43.4 % is not recognisably the same speaker” is a claim about one similarity scale, not an established perceptual fact. The consequence: strong emotion and stable identity are, on this system, in direct tension — which is §8.
The largest published artefact from this stage is laion/laion-voice-profiles-annotated — 28,212,933 rows, 5.8 TB.
Nothing in this project can be evaluated by listening at the scale at which it is generated — a single GRPO run produces 1,400–1,700 fully scored clips per hour. So the project runs on a set of learned scoring models. They do three different jobs, and it is worth keeping those apart: selection (which of 32 or 64 candidate takes to keep), reward (what an RL objective maximises) and evaluation (the numbers in this report). The same models do all three, which is a known circularity and is discussed in §6.8.
VoiceCLAP is a CLAP-style (Contrastive Language–Audio Pretraining) dual-tower model: an audio encoder and a text encoder trained so that a clip and a sentence describing how it sounds land close together in one shared vector space. The workhorse here is laion/voiceclap-commercial:
| Part | What it is |
|---|---|
| Audio tower | BUD-E-Whisper-Small — 12 layers × 768 dim × 12 heads, 80-mel at 16 kHz |
| Text tower | all-MiniLM-L6-v2, 6 layers × 384 dim, mean-pooled |
| Joint space | 768-d, L2-normalised; ~110 M parameters total |
| Loss | SigLIP sigmoid contrastive plus a prototypical-contrastive auxiliary term — 39 learned emotion prototypes, weight 0.2, cross-entropy against z-scored pseudo-labels derived from Emolia's emotion scalars |
| Training data | Only commercially licensable sources: Emilia-YODAS (the CC-BY-4.0 subset of Emilia), LAION's Got Talent, and the in-house Majestrino corpus. The CC-BY-NC clips are filtered out at training time by clip id; Expresso and EARS are not used. |
A controlled ablation reported on the card shows that removing the non-commercial data costs nothing — the commercial-only model wins 4 of 5 benchmarks against the full-mix model, trailing only on fine-grained intensity ranking of synthetic audio. Two recovery experiments were tried and discarded: adding VoxCeleb1/2 hurt, and upweighting the safe corpora was flat.
A larger sibling exists,
laion/voiceclap-large-v2 — a rank-16
LoRA (α 32) on the 7 B LCO-Embedding-Omni-7B (Qwen2.5-Omni thinker), producing a
3584-d embedding and setting the better numbers on the human-annotated VoiceNet benchmark (emotion
balanced accuracy 0.7069 against voiceclap-small's 0.6754). It is not what the
pipeline runs on, because the 768-d commercial model comes within a hair of it on the two heads
that matter at 1/4.7 the embedding width.
The architectural fact that matters most here is that one VoiceCLAP encode feeds three
separate instruments. In the production scorer (pp_scores_fast.FastScorer) a
clip is encoded exactly twice in total — once by VoiceCLAP-commercial and once by BUD-E-Whisper —
and every downstream head reads one of those two vectors.
There are two generations of this predictor, and conflating them is easy, so both are described.
The original genuineness signal is one of the expert heads in the
Empathic-Insight-Voice
suite: the repository ships 55 checkpoints, of which 40 are emotions and 15 are
demographic/vocal/psychological attributes — and Authenticity and
Arousal are two of those 15. It is trained exactly like an emotion expert: a
BUD-E-Whisper encoder is fine-tuned once
and then frozen, and a single MLP is trained on its flattened sequence embedding. This is the head
occupying slot 97 of the project's 99-dimensional scorer
(va_rescore.NinetyNineScorer), and it is what filtered the data for the v1/v2-era
emotion buckets. Its published private siblings are laion/genuineness and
laion/vocal-burst-blend.
One number from that generation is worth carrying forward: inter-rater agreement on authenticity is very low — Cronbach's α ≈ 0.185 on n = 300, far weaker than the emotion labels. The head is modelling a genuinely subjective target. That is precisely why genuineness is down-weighted (≈ 0.1 in the selection score of §4.2) and used as a gate rather than as a primary objective.
laion/voiceclap-commercial-genuineness predicts a single continuous score in [0, 6] for how much a clip sounds like a real, lived-in spoken moment rather than a rehearsed or synthetic read. Anchors from its own card: 0–2 reads as rehearsed, flat or synthetic; 3–4 plausibly natural; 5–6 sounds like a genuine, unscripted moment. It explicitly is not about audio fidelity — it is about natural timing, breath, micro-imperfections and emotional grounding.
| Aspect | Value |
|---|---|
| Architecture | Frozen VoiceCLAP-commercial 768-d embedding → L2-normalise →
standardise with stored μ/σ → Linear(768,50) → GELU → Dropout(0.2) → Linear(50,1),
about 38 k parameters. The backbone is never fine-tuned. |
| Training data | ~10,000 speech clips from a mix of various TTS systems and the Emolia expressive-speech collection, spanning a wide range of naturalness. |
| Labels | Each clip scored 0–6 by Gemini-3.1-Pro. |
| Loss | Huber (δ 1.5), Adam, standardised inputs. |
| Heads shipped | Two: full (default, trained on the whole label
distribution) and balanced (retrained on a class-balanced subset — flatter per-bucket
error, similar overall MAE). |
| Validation | 140 held-out clips, 20 per integer score 0–6, stratified, seed 1234. Full head: MAE 1.00 · Pearson r 0.77 · RMSE 1.33. |
| License / packaging | CC-BY-4.0; fully standalone (~450 MB) — the frozen embedder is bundled, so nothing is fetched at inference. |
laion/voiceclap-commercial-vocalburst-blend predicts, on a 0–10 scale, how naturally a non-verbal burst blends into the speech around it. Anchors from its card: 0 = disconnected — spliced or pasted in, robotic, or the wrong emotion for the context, and also assigned to clips with no genuine burst at all; 5 = fits the context but sounds performed or stagey; 10 = totally organic, indistinguishable from a natural human reaction. Intermediate values interpolate; output is clamped to [0, 10].
Architecturally it is the same shape as the genuineness head — frozen VoiceCLAP-commercial
768-d → L2-normalise → standardise → Linear(768,50) → GELU → Dropout(0.2) → Linear(50,1),
about 38 k parameters — which is why one encode yields both scores. Its card publishes a
comparison at equal head architecture across three embedders:
| Embedder | Width | Val MAE ↓ | Val corr ↑ |
|---|---|---|---|
| VoiceCLAP-commercial (shipped) | 768 | 2.057 | 0.625 |
| VoiceCLAP-small | 768 | 2.360 | 0.418 |
| VoiceCLAP-large-v2 | 3584 | 1.895 | 0.645 |
It gives no training-set size, no label source and no train/val split description — only the comparison above. Nothing here should be read as saying the blend head was labelled by Gemini or trained on Emolia; that is stated by the genuineness card about itself, and does not transfer. Neither card publishes a human-agreement figure. The blend card's own stated limits are that the frozen encoder caps accuracy, that mid-range scores (4–6) carry the most uncertainty and the model is most reliable at the extremes, that it is a fast proxy for ranking and reward shaping rather than a replacement for a strong multimodal judge or a human listener, and that its behaviour on music, noise-only or non-speech audio is undefined.
An earlier draft of this report asserted training details for this head that its card does not contain. They have been removed.
These two heads are why the project's data selection is not naive. As §13.1 shows, the most intense recordings in the corpus are on average the least genuine, so selecting on intensity alone selects for overacting. Genuineness and blend are the gate that stops that.
laion/Empathic-Insight-Voice-Plus is not one model but a suite of independent single-output MLP regression experts on top of a frozen BUD-E-Whisper encoder (a fine-tuned Whisper-Small producing a 1500 × 768 sequence).
Flatten(1,152,000) → Linear(…,64) → ReLU → Dropout → Linear(64,64) → … → Linear(16,1),
about 73.7 M parameters each, dominated by that first projection. Trained on
LAION's Got Talent (~5,000 h of multilingual synthetic voice acting) plus an in-the-wild voice-snippet
collection of similar size. The taxonomy and benchmark come from the EmoNet-Voice work.-Plus adds): pooled features instead
of the full sequence — mean + min + max + std over the sequence dimension, 3072-d — and therefore
only ~200 k parameters each. Three are distilled from Microsoft's DNSMOS (overall quality, speech
quality, background quality) and one from Meta's AudioBox content-enjoyment model. Validation on
200 samples each: overall MAE 0.26 / r 0.899; speech 0.30 / 0.517; background 0.35 / 0.865;
content enjoyment 0.34 / 0.691.The production scorer loads 44 heads — the 40 emotions and the 4 quality experts — and leaves the attribute heads unloaded, because the VoiceNet regressor (§6.5) covers those dimensions. Emotion outputs are unipolar, nominally 0 → 4 (0 = the emotion is absent, 4+ = extreme manifestation, card maximum 4.5), but the heads are unbounded regressors and real corpus values overshoot — the project's own normalisation statistics record a maximum of 8.20 on the Anger head.
The 40 emotions, verified against the repository's file listing, are the axis along which everything in this report is requested and measured:
Affection · Amusement · Anger · Astonishment/Surprise · Awe · Bitterness · Concentration · Confusion · Contemplation · Contempt · Contentment · Disappointment · Disgust · Distress · Doubt · Elation · Embarrassment · Emotional Numbness · Fatigue/Exhaustion · Fear · Helplessness · Hope/Enthusiasm/Optimism · Impatience and Irritability · Infatuation · Interest · Intoxication/Altered States of Consciousness · Jealousy & Envy · Longing · Malevolence/Malice · Pain · Pleasure/Ecstasy · Pride · Relief · Sadness · Sexual Lust · Shame · Sourness · Teasing · Thankfulness/Gratitude · Triumph
The heads are keyed by name, taken from the checkpoint filenames. In the predictor
repository the file is model_Jealousy_&_Envy_best.pth, so the runtime scorer's
emotion dictionary is keyed Jealousy_&_Envy — with an ampersand. Everything else
in the project — the corpus columns, the ECDF tables, the bucket names, the adapter directories —
uses Jealousy_and_Envy. Every evaluation therefore executes
scores["emonet"].get("Jealousy_and_Envy", 0.0), misses, and records a hard
0.0.
The effect is visible in the data: in the 17-adapter grid (§13.5) and in the merge-weight sweep
(§13.4), Jealousy_and_Envy reads exactly 0.000 on every single clip in every arm. Two
of the 80 evaluation prompts — the English and German Jealousy prompts, so 8 of 320 clips — are
pinned at zero in every model's emotion average.
Two consequences, stated separately because they differ. Comparisons between models are unaffected: the same 8 clips are zeroed in every arm, so it is a constant offset, and every ranking in this report stands. Absolute emotion percentiles are biased slightly low, by roughly one fortieth of whatever that emotion would have scored. An earlier draft of this report attributed the zeros to the percentile correction of §6.7 behaving as designed. That explanation was wrong; the cause is a key mismatch, and it is a recurrence of the same defect that once left this emotion out of the head list entirely (below).
That earlier defect is worth repeating as its own warning: the emotion list originally had
39 entries — Jealousy_&_Envy was simply missing, so its head was
never loaded and that emotion could never be predicted. Because heads are keyed by name and not by
index, the other 39 were still correctly labelled. It was a missing emotion, not a
misaligned one, and only a count caught it. The comment recording that fix is still in the scorer
source, three lines above the string that now causes the lookup to fail.
laion/voicenet-dimension-predictors-commercial is a bank of 114 small MLP heads — 57 dimensions × 2 (a regression head and a classification head) — on the same frozen 768-d VoiceCLAP-commercial embedding. One encode yields all 57 dimensions. Outputs are ordinal levels, 0–6 for most dimensions, 0–4 for background noise and 0–2 for content appropriateness.
How it was trained. Labels are laion/emolia-voicenet-gemini-annotations — Emolia clips each re-scored on a single VoiceNet dimension by Gemini-3.5-flash, combining three annotation rounds (round 3 topped up the most data-starved score levels of the hardest dimensions). Each unique clip is encoded once. For every dimension three class-balancing variants are trained (buckets capped at 3× / 4× / 5× the second-smallest usable bucket), and the shipped default is chosen per dimension on a frozen validation split — lowest MAE for the regression head, highest accuracy for the classification head, chosen independently. Regression uses Huber (δ 1.5), H = 64, dropout 0.33; classification uses cross-entropy, H = 48, dropout 0.33; Adam, 50 epochs, best-val checkpoint.
Best-per-dimension results: mean regression MAE 0.744, mean Pearson 0.79, mean within-one-level accuracy 0.870, mean classification accuracy 0.617. Per-dimension quality varies a great deal — head resonance reaches MAE 0.478 with r = 0.88, while cognitive load sits at 1.052.
| Group | Codes |
|---|---|
| Core affect & dynamics (10) | AROU arousal · ARSH
arousal shift · VALN valence · VALS valence shift · VOLT
volatility · TENS tension · VULN vulnerability · DARC
dynamic arc · FOCS focus · COGL cognitive load |
| Speaker identity (2) | AGEV voice age · GEND perceived gender |
| Prosody & delivery (9) | TEMP · VFLX velocity flux ·
RANG pitch range · REGS register · EMPH emphasis ·
ATCK attack · SMTH smoothness · CHNK chunking ·
RESP respiration |
| Articulation & discourse (3) | CLRT clarity · DFLU
disfluency · STRU structure |
| Timbre & voice quality (8) | BRGT · WARM ·
FULL · ROUG · HARM · METL · ESTH
esthetics · STNC stance |
| Resonance placement (7) | R_CHST · R_HEAD ·
R_MASK · R_NASL · R_ORAL · R_THRT ·
R_MIXD |
| Speaking styles (15) | S_ASMR · S_AUTH · S_CART
· S_CASU · S_CONV · S_DRAM · S_FORM ·
S_MONO · S_NARR · S_NEWS · S_PLAY ·
S_RANT · S_STRY · S_TECH · S_WHIS |
| Recording & content (3) | RCQL recording quality · BKGN
background noise · EXPL content appropriateness |
Two polarity traps, and a bug they caused. GEND runs feminine at 0
to masculine at 6, and BKGN runs noisy at 0 to clean at 4 — higher means less
noise. Both are counter-intuitive, and both were inverted in an earlier version of the
procedural caption generator, so that corpus-wide the prose descriptions of gender and background
noise ran backwards while the numeric columns were correct throughout. The fix was verified end to
end on the shipped strings rather than on the ladder tables: across 40 sampled voices, zero
captions contradict the voice's measured gender class.
Bursts are handled by two models in sequence, because “where is it” and “what is it” are different problems.
laion/vocalburst-locator does
frame-level binary segmentation at 20 ms resolution: a Whisper-small encoder adapted with a rank-8
(α 16) LoRA on q_proj/v_proj and then merged, followed by
Linear(768→384) → GELU → Conv1d(384, kernel 7) → Linear(384→1) → sigmoid, giving 1500
per-frame probabilities over a 30-second window, post-processed into (start, end, confidence)
events. Event F1 at IoU 0.5 on 992 real in-the-wild expressive-speech clips: 0.607
for v2 against 0.152 for v1 — and v1 fails in a specific way, reaching usable
precision only at threshold 0.80 where recall collapses to 0.21.
Ground-truth bursts have a median duration of about 180 ms, so the v1-era default minimum-duration filter of 0.5 s discards roughly 96 % of real bursts before matching ever happens. On one identical checkpoint, changing only the post-processing moved event F1 from 0.243 to 0.598 — a larger effect than any training change made for v2. Three subsequent attempts to beat 0.607 by training all failed, one of them (1,044,713 edge-case windows that were 100 % positive) dropping F1 to 0.482 because the extractor had thrown away the negatives.
laion/vocal-burst-detector-v2
then classifies each located span: an MLP
(Linear(768,256) → BatchNorm → GELU → Dropout(0.3) → Linear(256,83), ~219 k parameters)
on the VoiceCLAP-commercial embedding of the cut, over an 83-class taxonomy — 82 vocalisations plus
no_burst at index 82. Argmax accuracy on the official 1,940-clip validation set is
58.1 % overall: 70.4 % on clips with a single pure burst, 39.4 % on composites.
Five classes that are hand or body impacts rather than vocalisations — blowing a kiss, finger
snaps, hand scratching head, hand slaps, slap face — are folded into no_burst and
masked at inference, so the recognised label space is 77 burst classes plus
no_burst. A burst is additionally vetoed when P(no_burst) ≥ 0.50.
The project's reward pipeline runs the locator at threshold 0.5, merge gap 0.2 s, minimum
duration 0.08 s, pads each span by 0.05 s before classification, and drops anything under 0.10 s
(LOC_THR, LOC_GAP, LOC_MINDUR = 0.5, 0.2, 0.08; BURST_PAD, MIN_BURST,
NOBURST_GATE = 0.05, 0.10, 0.50). The card's own recommendation is slightly tighter — merge
gap 0.10 and minimum duration 0.10 — and the difference has not been ablated.
Everything above produces numbers on incomparable scales. Measured on the project corpus, the
Interest head has a median of 2.082 and never outputs zero, while the
Infatuation head has a median of −0.017 and outputs zero on 87.7 % of clips. A reward
built on raw values would have been an Interest detector.
The answer is the corpus ECDF: every raw score is mapped to its percentile within the full 3,147,802-row corpus, per head. Four intensity bands are defined on that percentile scale, and — this is the point — the same four cutoffs are used by the prompt builder, by the reward function and by the evaluation harness, so what the prompt asks for is exactly what gets measured.
| Band | Percentile | Adverbs used in the prompt |
|---|---|---|
| faint | 0.40 – 0.70 | barely, faintly, only slightly, just a little |
| moderate | 0.70 – 0.90 | clearly, plainly, noticeably, unmistakably |
| intense | 0.90 – 0.98 | strongly, intensely, very, deeply |
| extreme | 0.98 – 1.00 | overwhelmingly, extremely, utterly, completely |
A plain ECDF percentile turned out to be still not comparable across the 40 heads, for a reason that is obvious in retrospect. A clip with a raw score of exactly zero — no measurable emotion at all — lands at a percentile that depends entirely on how often that head outputs zero:
| Head | Percentile of raw 0 | Head | Percentile of raw 0 |
|---|---|---|---|
| Awe | 0.822 | Contempt | 0.510 |
| Infatuation | 0.766 | Amusement | 0.487 |
| Pain | 0.703 | Jealousy and Envy | 0.004 |
| Distress | 0.628 | Affection | 0.002 |
Asking for band 0.90–0.98 was therefore a different task for every emotion: on Awe the model starts 0.08 below the target having produced nothing, while on Affection it starts at zero. Averaged over 40 heads, the reported figure was measuring where each head's zero point happens to fall, not how well the emotion was performed.
The fix is a per-head affine rescale of the tail above the zero point,
p' = max(0, (p − p₀) / (1 − p₀)) where p₀ is the percentile of a raw score
of zero. It was verified to map raw 0 to exactly 0 on all 40 heads while remaining monotone within
each head — so every comparison between two clips of the same emotion, which is what DPO
pairs and GRPO group rankings consist of, is unchanged. Only absolute targets and cross-head
averages move, which is precisely what was broken.
Evaluations run before this correction and after it are on two different scales. The same audio,
scored both ways, differs by exactly this much: emotion percentile 0.5095 → 0.3494,
composite reward 0.4900 → 0.4584; word error rate, blend, genuineness, burst
metrics and every duration field are byte-identical between the two files. Sorting all 22 stored
evaluations by timestamp gives a clean cut at 2026-08-25 12:00 UTC: every run before it reports an
emotion percentile ≥ 0.5095, every run after ≤ 0.3572. Six runs are on the old scale and
sixteen on the new one, and the two sets must not share a ranking column. The superseded
SFT-3 directory is kept under an explicit _UNCORRECTED_ECDF name so it cannot be
quoted by accident. No cross-model number from before that point appears in this report.
This is the section that ought to be read before any number below is quoted elsewhere.
Two mitigations have actually worked here and are recommended rather than merely stated: always run a control arm that isolates the claimed cause, and verify identity from an independent namespace — never infer “this file is that record” from a shared key.
The shipped v2 model is documented in a public manual: MOSS Voice-Acting — Manual & Studies Hub. It is 25 chapters: a hub, an emotion-conditioning manual with one page per emotion, twelve recipe chapters, and an eight-page tree recording an autonomous casting agent's four generations of runs. It is unusually forthright — a large fraction of it is a record of things that did not work, with the measurements attached.
Two fields, exactly:
instruction =
GENERAL: <the standing description of the voice and the situation>
SCRIPT:
(delivery cue) "the spoken line, (burst) with tags inline, [pause] and pauses in squares"
text =
<exactly the same spoken words, plain — no cues, no brackets>
The words appear in both fields — plain in text, cue-annotated inside
SCRIPT: — and that redundancy is deliberate and matches the training data. The
manual's notation rules, verbatim:
(quietly, with lethal control), (screams). This is the only inline
notation the model was trained on.[pause], [long pause].
Never put one directly after a burst — that is the truncation shape.(SCREAM) is read out as “ess see arr
ee ay em”. Lower-case everything inside brackets.A worked example, copied verbatim from the manual's casting-agent prompt archive (adapters Scream @ 0.9, Anger @ 0.4, Distress @ 0.35):
GENERAL: Start wounded and breathless, then rebuild into nearly screaming but controlled
rage. The final words should land cold and absolute, with the voice hardened by pain. Place
the vocal burst inline and continue speaking immediately afterward.
SCRIPT:
(a broken breath turning into a controlled eruption) "Don't you dare touch me." (Scream)
"You don't get to cry now. You don't get to make this about your guilt. Pack your things,
and get out before I say something neither of us can take back."
There are no duration tags in the shipped prompt format. Length is controlled
only indirectly, through the tokens field (target frames, ≈ words × 6) and
max_new_frames. The manual gives a specific warning for non-verbal parts:
tokens defaults to the number of words, and a scream's text has almost none, so the
model is told to make ~5 tokens and stops after 0.3 s — for non-verbal material you must set about
12.5 tokens per intended second by hand.
This is exactly the gap the current research round closes. Everything from §9 onward is about training a prompt surface in which duration is stated explicitly, per sentence, per pause and per burst.
Intensity in the shipped format is not a tag and not a number. It is requested three ways, and the manual is emphatic about their ranking:
One finding here anticipates the whole of §12: a maximise objective cannot produce a moderate emotion. The manual states it directly — “a maximize objective always drifts to intense; a believable moderate/subtle emotion scores low against ‘maximize’ and dies out of the population.” The fix used there is a target-band fitness function, which is precisely the shape the current reward uses.
The manual also independently reports the cross-head incomparability of §6.7, in its own units: “Anger/Sadness/Fear reach ~2.4–3.7 on the detector; Amusement tops out ~1.7 even when strong — do not compare raw emotion scores across emotions; compare each against its own free take.”
Because emotion strength, burst placement and prosody are seed-dependent, the recommended way to use this model is to generate many candidates and rank them. The economics are in §4.12; the practical consequences the manual adds are:
The manual documents an agent built on the v2 model: it writes a ~30-second performance as 2–4 parts, generates each best-of-16, assembles the best combination across parts, ranks with the local scorer stack, and has a supervisor listen afterwards and give feedback for the next round. Seven generations of it were run — four complete, three aborted — over nine challenges, roughly 2,000 generations. A separate autonomous LoRA-plus-prompt search agent (LAION-AI/voice-acting-search-agent) ran an overnight programme of 163 agent generations and ~10 k scored takes on a single A100; its notable finding is about the brain, not the speech model — a Gemma-4-31B QAT policy won decisively on single-dimension missions using only 22 tool calls, while a Gemini supervisor was an effective active director (tracking the objective at r = 0.72) and a local audio judge was usable only as a lenient score-only gate, because active steering by it was harmful.
That agent can merge the LoRAs, and the audio it produces is more emotional than what the current SFT/DPO line produces. It is also, on the project's own measurements, not reliable on character consistency. This section states the limits because they are the honest state of the system and the manual documents every one of them.
And the thresholds themselves are wrong when set high. The casting playbook originally used similarity gates of 0.82 / 0.75 / 0.68. The voice-consistency chapter overrides them: regenerate below 0.58, repair with voice conversion below 0.45, reject below 0.40 — and calls that correction “the single most important thing on this page”. The evidence: among 170 human/model-confirmed same-speaker pairs the median ECAPA similarity is 0.632, 55 % fall below 0.68 and 84 % below 0.82, while the highest score reached by a genuinely different speaker was 0.816. Four embedding models were tried (ECAPA AUC 0.847, VoiceNet 0.814, WavLM timbre 0.794, VoiceCLAP 0.770) and none gives a clean threshold. The conclusion: ranking on identity is fine, gating on it is not — and never raise the floor “to be safe”, because the failure that creates (flat, over-clean delivery) is less visible than a voice change but affects every take rather than a few.
(SCREAM) becomes
“ess-see-arr-ee-ay-em”. The corpus's single clip whose entire purpose was to be a
scream transcribed as “Age.” / “Atch! Itch!” / “Akish.”narration = ½(S_NARR + S_NEWS) − ½(S_DRAM + S_CONV).“Short answer: on spoken scenes, a little — ρ ≈ 0.21, about 4 % of the variance. On non-verbal scenes, nothing, and the sign is wrong. There is currently no configuration in which the local stack can replace the listener.”
Two of the reward's own components point the wrong way against listener preference — genuineness at −0.143 and blend at −0.148; only emotion peak (+0.179) is useful. An optimally weighted linear combination of everything the local stack measures explains under 3 % of a listener's within-round preference. The manual's conclusion is that re-weighting cannot fix this, because the information is not in the feature set.
Two further findings sharpen it. The casting score in use was measured to correlate +0.91 to +0.98 with its own (1 − WER) multiplier and +0.00 to +0.04 with genuineness — it ranks assemblies by intelligibility and speaker continuity and is close to blind to everything else it claims to measure. And three of its eight terms were dead: the emotion-peak term was saturated at its cap in 92.4 % of assemblies, and the arc term sat at exactly 1.0 in 84 % and took three distinct values ever.
It produces more emotional performances than the current supervised line does. It is not production-reliable on character consistency: it drifts, and it drifts hardest exactly where the performance is strongest and on non-verbal material. Under strong conditioning it can garble, truncate, or quietly stop acting. The practical way to use it is the way the manual recommends — large candidate groups (32 is the economic sweet spot), reward-ranking with hard exclusion rather than penalty on identity, anchor-plus-tail continuation to hold a voice, byte-identical voice descriptions across parts, and a human in the loop for the final pick, because the local reward will reliably reject the worst takes and will not reliably find the best.
Listening to the round-1 supervised-plus-preference model, two faults dominated:
Both are the same missing signal: the prompt never stated how long anything may take.
The current round exists to supply it. All of it runs on JUPITER: GH200, 4 GPUs per node, 102 GB
each, SLURM account reformo, partition booster.
The instruction block is unchanged in shape from the shipped format — the same
Reference(s) / Instruction / Tokens / Quality / Sound Event / Ambient Sound / Language / Text
template — but the SCRIPT: block is now a rendered timed script:
[0.9 seconds pause] [3.4 seconds duration] Erster Satz. [0.6 seconds pause]
(contented sigh, 0.4 seconds) [4.1 seconds duration] Zweiter Satz.
There are exactly four kinds of tag, told apart by their brackets:
| Tag | Means | Rule |
|---|---|---|
[3.9 seconds duration] | The next sentence must take this long | Square brackets. One per speech segment, standing before the text it applies to. |
[0.8 seconds pause] | Silence of this length | Square brackets. Every gap of 0.2 s or more, including before the first word and after the last. |
(contented sigh, 0.2 seconds) | A non-speech vocalisation of this length | Round brackets with a duration. Label first, then the seconds. |
(clearly amused, warm and open, unguarded) | How to perform the next sentence | Round brackets without a duration. Stands before the duration tag. |
The disambiguation rule in one line: square bracket = a number of seconds; round bracket
with a number = a vocal burst; round bracket without a number = a delivery direction. That
is the only thing separating a burst from a direction, which is why directions are always emitted
with no number at all. There is one soft spot: in the untimed 30 % of samples nothing carries a
number, so a burst renders as a bare (chuckle) — the same shape as a direction. What
still separates them there is length: a burst label is one or two words, a direction is a
phrase.
Segmentation rules, all measured against the corpus: split at sentence ends;
split again at a vocal burst, at the nearest word gap; split any remaining segment over 12 s at its
largest internal gap provided both halves stay above 2 s. A duration is measured from the first
word onset to the last word offset of that segment, so two sentences of 12 s and 8 s produce
[12.0 seconds duration] and [8.0 seconds duration], never a single
20-second tag. Gaps under 0.20 s are folded into the neighbouring speech rather than printed, so
the printed numbers still sum to the clip length. Bursts under 0.05 s are dropped, and a burst that
overlaps speech prints only the part that does not overlap.
| Trap in building it | Measured cost |
|---|---|
| Rebuilding the transcript by joining word tokens — the forced aligner drops numerals (“154 Euro” → “Euro”) | would lose text on 9.9 % of real-speech rows |
| Taking bursts from the parenthetical cues in the source text instead of from detections | 39.4 % of those cues were never confirmed by the detector |
| Printing a burst's full length when it overlaps speech | 49.0 % of detected bursts overlap a word |
| Printing sub-threshold gaps | would break the “numbers sum to the clip length” invariant |
Verification, done the right way. The emitted string is re-parsed by a separate function with no access to the source data, and the recovered numbers compared against the true clip length. Residual median 0.010 s on 8,614 real-speech rows and 0.020 s on 14,359 voice-profile rows; within rounding tolerance on 99.9 % / 98.7 %; the transcript comes back byte-identical on 100 % of rows. This is the pattern §6.8 recommends: make at least one leg of a check independent of the pipeline that produced the thing being checked.
The round-2 format dropped the parenthesised delivery direction that round 1 had carried — by accident, and the accident became the round's central negative result (§10.2). Round 3 puts it back, inside the script, before each duration tag. A direction is composed of five pieces:
| Piece | Source | Example |
|---|---|---|
| Intensity adverb | The requested percentile band | overwhelmingly |
| Emotion name | The clip's own label — from its identifier or from the caption's
reads as … clause. Never guessed. | amused |
| Optional second emotion | Same source, as a shading | with a hint of bitterness |
| Performance anchor | Harvested from the corpus, not written. 187,528 voice-profile rows were scanned and the leading direction of each grouped by emotion. | letting it out / not hiding it, warm and open, emotionally reachable, unguarded |
| Manner words | The VoiceNet ladders the row's own caption already states | slightly bright, relaxed |
Only the first segment gets the full direction; later segments get a short reminder
((still clearly amused)), and segments under 1.6 s often get none — repeating a
thirty-word note in front of a 0.6-second line buries the line it is meant to shape. When a row has
no emotion label at all — a VoiceNet-axis row, for instance — the opening segment gets the corpus's
own most common direction instead.
A limitation kept deliberately rather than silently repaired. All 40 emotions resolve to exactly two harvested phrasings each — one expressed, one suppressed — drawn from only 10 distinct family-level texts. Some pairings read oddly: intoxication, pain and shame all inherit the fear family, so an intoxication direction can say “the fear plain in the voice”. This is not a bug in the generator — it is exactly what the round-1 corpus contains (425 of the intoxication rows use that phrasing verbatim), and the model was already trained on those pairings. It is kept for comparability and flagged as the first thing to revisit if intensity control still lags for those emotions.
Two bugs in this generator were found and fixed on 25 August, seventeen minutes before the round-3 run launched — one that would have made every row render the same intensity word, and one that left the untimed 30 % of samples with no direction at all. Both would have made the round meaningless without changing any training-time number. §10.4 gives them in full, with the pre-fix code.
Nothing is fixed; each sample draws from a seeded generator, so the model sees the whole range and never requires any single element to be present. Measured end to end on 62,672 real corpus rows through the training loader:
| Setting | Probability | Why |
|---|---|---|
| Timed script | 0.70 | The other 30 % render the same script with no numbers, so the model stays usable when a caller supplies plain text. (Realised: 71.1 %.) |
| At least one direction present | 0.85 | The rest train direction-free, so a plain prompt stays in distribution. (Realised: 79.5–80.2 %.) |
| Direction dropped per segment | 0.15 | So the model never learns that a duration tag must be preceded by one. |
| Bursts dropped | 0.10 | Each burst's span folds into the surrounding pause so the numbers still add up. |
| Reference audio used | 0.50 | Of rows that have one; the rest fall back to a speaker name or to nothing. |
The Instruction field itself takes one of four shapes:
gen_both (a GENERAL: line and a SCRIPT: block) at 45 % for
voice profiles and 75 % for real speech; caption_tpl (a natural-language instruction
instead of the GENERAL: line) at 30 % for voice profiles only;
gen_general at 15 %; gen_script at 10 %.
Realised band mix over those 62,672 rows: 58.8 % extreme, 23.3 % intense, 10.1 % moderate, 7.8 % faint. That skew is not an accident — it comes from training on the “extreme subset” — and §10.11 argues it is a prime suspect for the failure that follows.
| Artefact | Size | Composition |
|---|---|---|
| Corpus v2 | 3,147,802 rows · 128 shards · 17 GB | 1,200,530 voice-profile + 1,947,272 real-speech rows, with word timelines, burst spans, packed 40-emotion and 57-VoiceNet float32 blobs, blend, genuineness and emotion strength. The burst sidecar matched 1,200,530 of 1,200,530 voice-profile rows. |
| The “extreme” subset | 398,282 rows (12.65 %) | Core 331,902 + a blend arm of 66,380. Voice profiles 179,617 / real speech 218,665; German 176,705 / English 221,577. |
The extreme subset is worth one paragraph because of how its definition had to change. The original rule was “the top 10 % of each of the 40 emotions plus the top and bottom 10 % of each VoiceNet dimension”. Measured, that rule selects essentially the whole corpus: the emotion top-10 % union alone is 89.01 %, the VoiceNet top-plus-bottom is 99.37 %, and the combination is 99.84 % of the full corpus. With 154 overlapping axes, a per-axis decile is not a filter. It was re-implemented as a fixed quota per axis; at 6,000 rows per emotion each pick is the top 0.19 % of its axis.
Band composition of the subset — 59.5 % extreme, 23.8 % intense, 9.9 % moderate, 6.8 % faint — against the full corpus's 45.6 / 32.4 / 12.8 / 9.3 %.
Every model comparison in the rest of this report uses the same harness, so the rows are comparable to each other and to nothing else:
The evaluation deliberately uses a stricter yardstick than training. Training needs a gradient, so its band score ramps; evaluation asks a different question — did the clip land in the band that was requested — and answers it with a plateau that pays nothing outside the band. Keeping the two apart is what makes numbers published before and after a reward change comparable.
Burst realisation deserves a note, because it is not an obvious metric. It is
an F1 over prompted-versus-detected bursts in which each match is weighted by how well its
duration matches — a matched class scores 1.0 and a mismatched one 0.35, times
exp(−|Δduration| / 0.5), with a 1.5 s tolerance on position. Recall alone would reward
emitting a burst everywhere and precision alone would reward emitting none; the original listening
complaint was bursts that are too long, so the duration term is what carries the signal,
and a matched pair with the wrong length scores near zero.
Three supervised rounds have run. Round 1 established the line: all 4.13 B parameters, 3 epochs over the 3,147,802-row corpus, global batch 4,096, 2,232 steps, 64 nodes × 4 GH200 in 1 h 53 min, held-out loss improving monotonically 4.7076 → 4.6314 across all twelve evaluations. Rounds 2 and 3 are much shorter runs on top, whose purpose is the prompt format rather than more data: each is 2 epochs over the 398,282-row extreme subset, 712 steps on 32 nodes.
The sequence is the most instructive thing in this report, because round 2 was a success and a failure in the same run, the failure was caused by a line that was deleted by accident, and round 3 exists only to put it back. The rest of this section is that story in order: what round 2 won, what it lost, how the loss was diagnosed, the two further bugs found seventeen minutes before round 3 launched, and exactly what round 3 changed.
| Run | Base | Steps | Wall | Nodes | LR | Validation loss | Tensors changed vs base |
|---|---|---|---|---|---|---|---|
| SFT round 2 | SFT-1 + DPO-1 (full) | 712 | 29 min | 32 | 5e-6 | 4.6278 → 4.6268 | 375 / 425, max relative 2.098e−02 |
| SFT round 3 | SFT-2 export | 712 | 31 min | 32 | 7e-6 | 4.6296 → 4.6264 | 378 / 425, max relative 2.740e−02 |
A comparability note that the protocol records rather than hides: round 2 ran at learning rate 5e-6 and round 3 at 7e-6, so their validation losses are not comparable to each other; nor are they comparable across the round-2 → round-3 prompt change, which altered the objective. §10.7 argues that in this regime the validation loss is barely comparable to itself.
320 generated clips per model, band intense requested:
| Model | Reward | Emotion pct | Quality | Burst | WER | Duration err. (median) | Within 0.5 s |
|---|---|---|---|---|---|---|---|
| SFT-2 | 0.4880 | 0.4905 | 0.9152 | 0.3751 | 0.1123 | 0.100 s | 99.4 % |
| + DPO-2, step 216 | 0.4766 | 0.4987 | 0.9013 | 0.3614 | 0.1205 | 0.100 s | 98.8 % |
| + DPO-2, step 864 | 0.4944 | 0.5080 | 0.9282 | 0.3602 | 0.1185 | 0.080 s | 100.0 % |
(These three rows are on the pre-correction percentile scale of §6.7 and are therefore comparable to each other but not to the tables from §11 onward.)
Timing is solved. Median duration error 0.08 s — one audio frame — and every one of 320 clips within half a second of the length its script's own numbers add up to. That is the capability the timed script was built to deliver, it was delivered in a single 29-minute run, and nothing since has degraded it.
Emotion is not. Asked for percentile 0.90–0.98, every round-2 model lands at 0.49–0.51 — the corpus median, which is what a model produces when it is not being told anything at all. Three models with three different amounts of preference tuning all sitting on the median is not a training-strength problem; it is the signature of a missing input.
The cause was found by reading the diff between the round-1 and round-2 prompt renderers. Round 2 had dropped the parenthesised delivery direction entirely.
Round 1 had put a direction in front of the spoken text on 55 % of voice-profile samples and 85 %
of real-speech samples — (intensely amused: letting it out / not hiding it, warm and open,
unguarded) and the like. Round 2 replaced that slot with the timed script, and the direction
did not survive the replacement. Nobody removed it deliberately; the new renderer simply did not
carry it forward.
What was left was description, not instruction. The emotion still appeared, in
the GENERAL: line, as a clause saying what the source recording sounded like —
“reads as contentment”. That tells the model what kind of clip this is. It does not tell
it what to do. The model was trained for two epochs on a format in which no one ever asked it to
perform anything, and it learned exactly that: produce the corpus median.
If the direction is the missing input, then feeding a round-2 model a prompt that does carry one — a format it has never seen — should not merely fail to help. It should break the model, because the direction sits inside the script, in the same bracket space as the timing tags the model has learned to obey. It does.
| Evaluation | Model | WER with inline directions |
|---|---|---|
eval_sft2_dir | plain SFT-2, no adapter at all | 0.5122 |
eval_dpo2_dir | SFT-2 + DPO-2, step 864 | 0.4828 |
eval_grpo1_dir | SFT-2 + GRPO run 1 | 0.4798 |
eval_grpo3_dir | SFT-2 + run-1 adapter + GRPO run 3 | 0.4836 |
eval_sft2_dirv3 | SFT-2, corrected-scale rerun | 0.4473 |
eval_sft3_dir | SFT-3 | 0.0987 |
Word error rate 0.48–0.51 for every round-2 model including the bare supervised
checkpoint, against 0.11 for the same checkpoint on direction-free prompts. Because the
bare model fails as badly as the tuned ones, this is not a property of any adapter. It is the format.
Verbatim from the evaluation log — clip k550_age5_bg1__E__Relief__C__en.c036, on a
script asking for “November was the only month the headline appeared: The last sporting
taboo”:
"wer": 4.545454545454546,
"hyp": " Ah, ah, ah, ah, ah, ah, ah, ah, ah, ah, ah, ah, ah, ah, ah, ah, ah, ah, ah, ah, …"
This matters well beyond the supervised round. Two GRPO runs had been spending hours optimising against exactly that collapse (§12.1), which is how a reward computed on out-of-distribution prompts ends up measuring the prompt instead of the policy.
Round 3's entire purpose is to put the delivery direction back and train on it, in the position the timed script leaves for it: in round brackets, without a number, standing immediately before the duration tag of the segment it applies to. §9.2 gives the five pieces a direction is built from and §9.1 gives the bracket rule that keeps a direction distinguishable from a vocal burst. Nothing else about the format changed.
Two consequences of that decision are worth stating before the results, because they bound what round 3 could possibly have achieved:
Both were found by rendering real corpus rows through the training loader and reading the output, which is a cheap check that this project now runs before every prompt-format change. Both would have made the round meaningless in a way that no training-time metric would have revealed. The backup files the fixes left behind are timestamped 09:54:21 and 09:55:19 on 25 August; the SFT-3 job started at 10:12:48 the same morning.
The band came from an in_extreme flag. That column exists in the preference
corpus, where it records membership of the extreme subset. It does not exist in
corpus_x, the supervised corpus round 3 trains on. The pre-fix line, preserved verbatim
in prompt_lib2.py.bak-20260825-band:
band = "intense" if row.get("in_extreme") else "moderate"
On every supervised row row.get("in_extreme") returns None, so
band is "moderate", and the adverb table maps moderate to
"clearly". Every single row of the round would have rendered
“clearly” — a model asked to be clearly angry, two epochs running, and never
once asked to be intensely or overwhelmingly anything.
The point of the round was to teach the model that the intensity word controls the delivery. With
one word on every row there is no contrast, and the round would have taught nothing while producing
a perfectly normal-looking training curve. The fix reads the row's own emo_strength
percentile with the reward's cutoffs; 83 % of rows then carry intense or extreme
wording, matching what the audio actually sounds like.
Directions attach to duration tags. 30 % of samples are rendered without numbers on purpose, so that the model stays usable when a caller supplies plain text — and an untimed script has no duration tags to attach to. The substitution therefore found nothing and that entire arm trained direction-free.
The failure mode is at inference, not in training: a caller who supplies a delivery direction but no durations — the most natural way to use the model, and the round-1 shape — would have been straight out of distribution, which §10.2 has just shown costs a factor of five in word error rate. The fix places one direction at the head of the line for untimed renderings, which is exactly the round-1 shape. Direction coverage rose from 57.7 % to 79.5 % of samples.
The 57.7 % → 79.5 % figures come from the protocol; the 79.5 % is independently confirmed by the loader measurement of §9.3 over 62,672 rows, the 57.7 % is not reproduced by any artefact still on disk.
The prompt-format hash moved from 64d956b8bb97f0d9 to
f90a7b16b4cea093 — the hash is written into every checkpoint's state file, so a
checkpoint can always be matched to the exact prompt renderer that produced its training data. That
is the mechanism by which the round-2 accident was findable at all.
| Setting | Value | Note |
|---|---|---|
| Base | the SFT-2 export | The full-parameter round-2 checkpoint, not a merge |
| Trained | all 4.13 B parameters | Not a LoRA. Rounds 2 and 3 are full fine-tunes. |
| Corpus | corpus_x — the 398,282-row extreme subset |
Materialised as 128 parquet shards, 401,345 rows, of which 3,063 are the validation holdout carried unchanged from round 1 |
| Epochs | 2 | |
| Steps | 712 | 356 optimizer updates per epoch |
| Global batch | 1,024 | = 4 micro × 2 accumulation × 128 ranks |
| World | 128 ranks — 32 nodes × 4 GH200 | |
| Learning rate | 7e-6 | Round 2 used 5e-6 |
| Warmup | 178 steps | Exactly half of epoch 1 |
| Wall clock | 30 min 46 s | SLURM start 10:12:48, end 10:43:34. The training window inside the log is 25 min 45 s; the rest is model load and export. |
| Prompt hash | f90a7b16b4cea093 | Recorded in the run header and in every checkpoint |
One correction to a figure that has circulated internally: round 3 was
not trained on 247,049 rows. That number is the emotion-LoRA corpus of
§13.2, built the same evening by a different selection script, and the coincidence of dates is the
likely source of the conflation. Round 3 trained on corpus_x.
| Step | 89 | 178 | 267 | 356 | 445 | 534 | 623 | 712 |
|---|---|---|---|---|---|---|---|---|
| Held-out loss | 4.6296 | 4.6300 | 4.6301 | 4.6294 | 4.6289 | 4.6276 | 4.6267 | 4.6264 |
The whole run moves the held-out loss by 0.0032 nats, and it moves the wrong way for the first three evaluations. Against a model whose negative log-likelihood is about 54 nats per position — each supervised position carries 13 channels (§3.1) — that is a relative change of six parts in a hundred thousand.
The reason is structural and it applies to every short fine-tune in this project. The loss is dominated by predicting audio codes, and a change to the prompt format changes almost nothing about how hard the audio is to predict. The whole intervention — putting a parenthesised phrase back in front of each segment — is a few dozen text tokens against several hundred audio frames per example. A metric that averages over both cannot see it.
So the honest reading of the validation series is: it confirms the run did not break, and it says nothing whatever about whether the run achieved its purpose. It is a smoke test, not a result. The 0.0032 improvement is not evidence that round 3 is better than round 2 — the two runs used different learning rates and different prompt formats and their losses are not on the same scale — and it is presented here in full precisely so that nobody mistakes a monotone tail for a finding. §18 collects four other cases in this project where a training-time number was clear, consistent and wrong; this is the mild version, where the number is merely uninformative.
Every claim in §10.8 comes instead from generating 320 clips and scoring them.
The export step compares every tensor against the base checkpoint, which is a cheap check that a run did what it claimed and touched what it should have:
| Run | Tensors changed | Max relative delta |
|---|---|---|
| SFT round 2, vs SFT-1 + DPO-1 | 375 / 425 | 2.098e−02 |
| SFT round 3, vs the SFT-2 export | 378 / 425 | 2.740e−02 |
Two things are worth reading off that. Round 3 moved more of the model than round 2 did, both in count and in magnitude — 378 tensors against 375, and a peak relative change 31 % larger — which is consistent with the higher learning rate and with the fact that round 3 introduced a new token pattern into every sample rather than merely reshuffling an existing one. And roughly 47 tensors were untouched by both runs, which is the expected shape for a fine-tune that leaves embeddings and certain normalisation parameters alone.
This is a sanity check, not a result. A run that reported zero tensors changed, or all 425, would have been a bug; 378 with a peak delta of 2.7 % is an ordinary short fine-tune. The figure is recorded because a later run in this project did report a suspicious tensor count, and it was a bug (§19.9).
Both models on the same 320 prompts, all carrying inline directions:
| Metric | SFT-2 | SFT-3 |
|---|---|---|
| Reward | 0.3939 | 0.4900 |
| WER | 0.4473 | 0.0987 |
| WER, English / German | 0.574 / 0.320 | 0.070 / 0.127 |
| Duration error, median | 0.100 s | 0.080 s |
| Within 0.5 s | 92.8 % | 100 % |
| Burst hit rate | 0.516 | 0.666 |
| Burst realisation | 0.224 | 0.356 |
| Blend / genuineness (raw) | 4.737 / 3.367 | 4.646 / 3.304 |
| Emotion percentile (uncorrected scale) | 0.5496 | 0.5095 |
The collapse is gone. Word error rate falls from 0.447 to 0.099 — a factor of 4.5 — and it falls on both languages, from 0.574 to 0.070 on English and from 0.320 to 0.127 on German. Timing improves at the same time (median duration error 0.100 → 0.080 s, and 100 % of clips inside half a second against 92.8 %), and so does burst realisation (0.224 → 0.356) and burst hit rate (0.516 → 0.666). One 31-minute run on 32 nodes bought all of that.
It is worth being precise about what was and was not demonstrated. Round 3 did not teach the model a new capability. It taught the model a format — and the size of the effect is a measure of how completely a transformer will fail on a prompt shape it has not seen, even when every individual element of that shape is familiar. The directions round 3 trains on are the same directions round 1 trained on. Two epochs of not seeing them was enough to make them poison.
Emotion control did not improve. SFT-2's higher emotion number (0.5496 against 0.5095) is not usable as a comparison: it was producing broken audio at word error rate 0.45, and broken audio scores on emotion heads — a screaming-noise clip reads as high arousal. The honest statement is that round 3 restored the format and left the intensity problem exactly where it was.
Rescored on the zero-point-corrected percentile scale of §6.7, with no regeneration, the picture is worse and more honest:
| Model | Emotion pct, raw | …corrected | Clips in the requested band, raw | …corrected |
|---|---|---|---|---|
| SFT-3 | 0.5095 | 0.349 | 9.4 % | 4.4 % |
| SFT-2 | 0.5496 | 0.402 | 15.9 % | 10.6 % |
4.4 % of clips land in the band that was requested. That number, not the composite reward, is the one that states the problem: asked 320 times for a specific intensity, the model delivers it fourteen times.
If the zero-point defect had also corrupted the training labels — the
emo_strength percentile from which each direction's intensity adverb is derived — the
whole round would have needed redoing. It did not. If the labels were a floor artefact, heads with a
high zero point would win almost always. They do not: the winning head has a real signal
(raw > 0.01) on 99.9 % of rows, including 99.9 % of the rows labelled at
percentile ≥ 0.90, and the 40 heads are evenly represented at 3–4 % each. Repeating the supervised
round on corrected labels would produce the same labels.
The training subset is 59.5 % extreme and 6.8 % faint. The model sees “overwhelmingly” almost always and “faintly” almost never — so there is little contrast from which to learn that the word controls anything. Note that this is the same failure as bug 1 of §10.4, one order of magnitude weaker: bug 1 would have given the model one intensity word on every row; the corpus as shipped gives it four words in a 59/24/10/7 mix. The fix for the bug was obvious. The fix for the skew is the same fix, and it has not been applied.
The full corpus holds roughly 290,000 faint and 400,000 moderate rows, so a band-balanced selection of the same size is feasible. This remains untested and is the most obvious unexplored lever in the project.
Preference tuning is the objective this project has spent the most compute on, and it is the one that produced the shipped model. It has also produced a completely consistent negative result on the capability it was aimed at. This section covers all of it: how the objective is configured, every run that was launched, how each of the four preference corpora was constructed and what each was meant to fix, which checkpoint is best and why, and what the training telemetry does and does not say.
DPO trains on pairs: a chosen audio sequence and a rejected one for the same prompt, pushing up the log-probability of the chosen relative to the rejected. Two configuration choices carry most of the weight, and both were arrived at by getting them wrong first.
| Setting | Value | Why |
|---|---|---|
| Sequence score | length-normalised log-probability | 85.6 % of the raw preference pairs differ from the chosen sequence mainly in length. An unnormalised summed log-probability reached 1.000 preference accuracy at step 102 of 1,194 by counting tokens — a perfect score for a model that has learned nothing. |
| β | 30 | The margin temperature. |
| Chosen-NLL anchor | 0.013 | An absolute term pinning the chosen sequence's likelihood. The model's negative log-likelihood is about 54 nats per position, because each supervised position carries 13 channels (§3.1). A first attempt at weight 0.25 put the anchor term at ≈13.6 against a DPO term of 0.69 — the anchor simply won and the preference run became a supervised pass. |
| Adapter | LoRA rank 64, α 128, dropout 0.05 | Targets the 23 modules of §4
including all twelve audio_lm_heads, which is why it ships unmerged (§19.2). |
| Learning rate | 1e-6 | |
| Scale | 8 nodes / 32 ranks, 256 preference pairs per optimizer step | Constant across every run on SFT-3, which is what makes step numbers comparable between runs: equal steps mean equal numbers of pairs seen. |
Seven runs exist on disk. Two of them are not results — one trained on the wrong base and is kept deliberately (§19.9), one was superseded before it was evaluated — and they are listed anyway, because a table of five successes is a different document from a table of seven runs.
| Run | Base | Corpus | Steps | Checkpoints kept | Final rew_chosen | Outcome |
|---|---|---|---|---|---|---|
dpo2 | SFT-2 | v1 | 864 / 864 | 419–864 | −0.7797 | The round-2 preference model. Best round-2 checkpoint at step 864. |
dpo_on_sft2_WRONGBASE | SFT-2 | v1 | 184 / 184 | 46–184 | −0.6348 | Ran on the wrong base — 32 nodes, global batch 1,024. Kept as a result (§19.9). |
dpo3 | SFT-3 | v1 | 864 / 864 | 419–864 | −1.3769 | Discarded; superseded by
dpo_sft3. |
dpo_sft3 | SFT-3 | v1 | 864 / 864 | 420–864 | −0.8162 | The v1 result. Reward 0.4668. |
dpo_sft3_v2 | SFT-3 | v2 | 4,772 / 7,135 | 3566, 3770, 3976, 4180, 4385, 4590 | −2.3037 | Wall clock. Best checkpoint was step 3216 — and its weights were deleted by checkpoint rotation (§19.7). |
dpo_sft3_cfg | SFT-3 | CFG | 5,006 / 8,953 | 3963, 4176, 4389, 4476, 4694, 4912 | −1.1211 | Six-hour timeout. Step 4912 was the best model in the project until 28 August. |
dpo_sft3_p2 | SFT-3 | contrastive families | 5,126 / 10,331 | 3906, 4132, 4355, 4579, 4800, 5022 | −1.0015 | Step 5022 is the best model this project has produced. |
None of the three long runs finished an epoch. All three hit a six-hour wall clock somewhere between a third and two thirds of the way through, and in all three cases the best checkpoint was the last one taken — which means nobody knows whether the remaining half helps, and it is an open task rather than a finding.
The preference corpus has five pair families. The rejected side is constructed, not sampled — which is the design decision that makes the whole approach cheap, since no second generation pass is needed:
| Family | Available pairs | How the rejected side is made |
|---|---|---|
vp_emotion | 1,064,594 | A stored lower-intensity take of the same voice-profile condition |
vp_truncation | 1,186,406 | The chosen codes cut short |
vp_continuation | 1,200,531 | The chosen codes with donor codes appended — running long |
rs_truncation | 2,011,920 | Stored upstream |
rs_continuation | 1,947,272 | Stored upstream |
Version 1 of that corpus came out at only 236,166 pairs across 128 shards (1.7 GB), and the reason is a good example of a cost that gets stated rather than buried: the builder looked word timings up in the supervised corpus, which contains only the rows selected for supervised training. Every voice-profile pair whose chosen clip was not in that selection was dropped — 946,266 pairs, 88.9 % of the emotion arm — and because the length arm is capped to the emotion arm's size, the whole corpus shrank with it. What survived:
| Family | Rows in v1 |
|---|---|
vp_emotion | 118,328 |
rs_continuation | 32,252 |
rs_truncation | 32,074 |
vp_continuation | 27,029 |
vp_truncation | 26,483 |
| total | 236,166 |
A correction to an earlier figure that propagated through the protocol and through an earlier draft of this report: v1 is 236,166 pairs, not 236,656. The parquet shards and the build log both give 236,166, and the family counts force it arithmetically — 118,328 on the emotion arm against 117,838 on the length arm. It appears to be a digit transposition that was then copied forward.
They were in the annotation product's tar files all along: <uid>.json members
carrying MMS-FA word alignments in exactly the {w, s, e} form the renderer expects. A
sidecar keyed by clip id was built in three stages on a single node — plan (3,451,531 references →
2,146,797 distinct ids over 2,000 shards, 85 s), bursts (1 s per task), words
(84 s per task).
| Coverage after the sidecar | Found | Of | Rate |
|---|---|---|---|
| Word timings | 2,137,175 | 2,146,797 | 99.55 % |
| Burst spans | 2,146,797 | 2,146,797 | 100 % |
The rebuild changes exactly one function, so pairs that already worked come out byte-identical and a v1/v2 comparison isolates the effect of data volume alone:
| v1 | v2 | |
|---|---|---|
| Emotion pairs | 118,328 | 1,061,613 |
| Total pairs | 236,166 | 1,853,486 |
| Shards / size | 128 · 1.7 GB | 128 · 13 GB |
| Dropped for missing annotation | 946,266 (88.9 %) | 3,784 (0.36 %) |
| Arm ratio (emotion : length) | 50 : 50 | 57 : 43 |
The v2 drop count splits as 2,981 rows with no annotation at all and 803 with an annotation but no usable duration; the protocol labels the 803 as the no-annotation count, which is the wrong one of the two. The 11.9 % pre-sidecar coverage figure quoted elsewhere could not be reproduced — the v1 auxiliary table was overwritten by the rebuild — so it is stated here as a protocol claim rather than a measurement.
The 57 : 43 ratio is a deliberate departure from the original brief's strict equality, kept because the length arm has already done its work — timing is solved — and the emotion arm is what remains unsolved.
What eight times the data bought. Reward 0.4668 → 0.4687, emotion percentile 0.3518 → 0.3401, word error rate 0.1117 → 0.1094, burst realisation 0.3973 → 0.3929. That is: a rounding-level gain in composite reward, a fall in the emotional intensity the corpus was enlarged to improve, and no material change anywhere else. Eight times the preference pairs on the arm that matters moved the target metric in the wrong direction. It is the cleanest evidence in this section that the emotion ceiling is not a data-volume problem, and it is the measurement that motivated changing the construction rather than the size.
Every contrast built so far pits an intense clip against a mild one — which a model can win by being generically expressive, without ever reading the instruction. The classifier-free-guidance (CFG) construction tries to make that impossible:
..., so the preference
cannot be decided from the words.A rendered CFG prompt, verbatim:
<user_inst>
- Reference(s):
<|audio|>
- Instruction:
GENERAL: A voice; timbre is very dark
- Tokens:
101
- Quality:
None
...
- Text:
(very dark, and hold it there; otherwise exactly as this voice normally speaks)
[5.5 seconds duration] ... [0.5 seconds pause] [2.0 seconds duration] ...
</user_inst>
Composition, counted over all 128 shards of the built artefact — 2,327,904 pairs, 17 GB:
| Family | Rows | Share |
|---|---|---|
vp_emotion | 1,061,613 | 45.60 % |
cfg_low | 237,209 | 10.19 % |
cfg_high | 237,209 | 10.19 % |
rs_continuation | 218,665 | 9.39 % |
rs_truncation | 217,536 | 9.34 % |
vp_continuation | 179,617 | 7.72 % |
vp_truncation | 176,055 | 7.56 % |
| CFG share | 474,418 | 20.38 % |
The two CFG halves are not merely balanced to within a tenth of a percentage point, as an earlier
write-up of this build reported — they are exactly equal, 237,209 each, which is
what a correct flipped-role duplication must produce and is therefore a stronger check than the
approximate one. The prompt-format hash moved to 073aeb09dc923376.
Preference accuracy on the CFG families rose from 0.5625 over the first 40
logged batches — chance — to 0.9750 over the last 40. With the words removed and the
roles flipped, the instruction is the only signal distinguishing chosen from rejected. A
model ignoring it would sit at 0.5 forever. This one started there and learned its way out.
Per-family means over the whole run: cfg_high 0.9093 (193 batches),
cfg_low 0.9136 (189), vp_emotion 0.9518 (342),
rs_truncation 0.9205 (153).
Two further families were added on top of the CFG corpus, aimed at two measured defects rather
than at a general idea. §15 gives their construction, their selection rules and the per-family
learning curves. In summary: p2_emox (309,128 rows) pits intense against
intense — same speaker, matched length, emotion A against emotion B, with the instruction naming
A — so that turning intensity up is worth nothing and only reading the instruction pays;
p2_len (59,389 rows) keeps speaker, text and emotion fixed and varies only the duration.
Mixed onto the CFG corpus: 2,327,904 + 368,517 = 2,696,421 pairs, the new families
13.7 %.
All rows on the same 80 prompts, 4 completions each, inline delivery directions, zero-point-corrected percentile scale, plateau yardstick — so they are comparable to each other and to nothing else:
| Model | Reward | WER | Emotion pct | Quality | Burst | Hit rate |
|---|---|---|---|---|---|---|
| SFT-3 + DPO contrastive families, step 5022 | 0.4757 | 0.0977 | 0.3541 | 0.9208 | 0.4180 | 0.762 |
| SFT-3 + DPO contrastive families, step 3906 | 0.4744 | 0.0916 | 0.3478 | 0.9231 | 0.4106 | 0.762 |
| SFT-3 + DPO CFG corpus, step 4912 | 0.4708 | 0.0950 | 0.3373 | 0.9235 | 0.4271 | 0.772 |
| SFT-3 + DPO CFG corpus, step 3963 | 0.4698 | 0.0953 | 0.3420 | 0.9235 | 0.3940 | 0.700 |
| SFT-3 + DPO corpus v2, step 3216 | 0.4687 | 0.1094 | 0.3401 | 0.9211 | 0.3929 | 0.709 |
| SFT-3 + DPO corpus v1 | 0.4668 | 0.1117 | 0.3518 | 0.9108 | 0.3973 | 0.694 |
| SFT-3 + emotion LoRA r16 (joint) | 0.4663 | 0.1239 | 0.3572 | 0.9189 | 0.4002 | 0.684 |
| SFT-3 + DPO corpus v2, step 4590 | 0.4633 | 0.1024 | 0.3427 | 0.9062 | 0.3930 | 0.719 |
| SFT-3, no adapter | 0.4584 | 0.0987 | 0.3494 | 0.9127 | 0.3564 | 0.666 |
| SFT-3 + GRPO v4 | 0.4551 | 0.1286 | 0.3468 | 0.8950 | 0.3837 | 0.694 |
| SFT-3 + GRPO v5 | 0.4512 | 0.1253 | 0.3275 | 0.9282 | 0.3586 | 0.647 |
Step 5022 of the contrastive-families run is the best model this project has produced, and the case for it rests on three things rather than on the composite reward alone, since the top four rows span 0.0059 in reward and this evaluation cannot resolve ±0.01.
Its neighbour at step 3906 is a statistical tie on reward, is better on word error rate (0.0916, the lowest of any model in the project), and is worse on emotion (0.3478) and on burst realisation. Step 5022 was chosen because emotion is the unsolved capability and 3906's WER advantage of 0.006 is inside noise. Both are published.
What none of them did. The target band is 0.90–0.98. The best model in the project reaches 0.354. Four preference corpora, seven runs and something over a hundred node-hours have moved that number from 0.3494 to 0.3541 — a gain of 0.005 on a scale where the goal is 0.55 away. The corpora are not what is wrong, and §11.9 argues that the objective may be.
rew_chosen: a metric that is uninformative across runs and anti-informative within oneThe DPO trainer logs two reward terms per evaluation: rew_chosen, the implicit reward
on the preferred sequence, and rew_rejected, the same on the rejected one. A healthy
preference run should raise the first while lowering the second.
rew_chosen| Run | Last logged eval | Val loss | Pref. acc | rew_chosen | rew_rejected |
|---|---|---|---|---|---|
dpo2 | 864 | 0.7435 | 0.9864 | −0.7797 | −15.47 |
dpo3 | 864 | 0.7576 | 0.9802 | −1.3769 | −18.69 |
dpo_on_sft2_WRONGBASE | 184 | 1.0274 | 0.8184 | −0.6348 | −8.34 |
dpo_sft3 | 864 | 0.7526 | 0.9805 | −0.8162 | −15.58 |
dpo_sft3_v2 | 3566 | 0.6499 | 1.0000 | −2.3037 | −57.85 |
dpo_sft3_cfg | 4476 | 0.7106 | 0.9844 | −1.1211 | −46.57 |
dpo_sft3_p2 | 2582 | 0.7277 | 0.9805 | −1.0015 | −38.85 |
The trainer emits an explicit <-- DAMAGE: policy suppresses the CHOSEN audio line
and writes "healthy": false into eval.jsonl whenever this happens. It has
happened on every evaluation row of every preference run in this project.
Within a run the metric moves the wrong way as the model improves. From
dpo_sft3:
| Step | Val loss | Preference accuracy | rew_chosen | rew_rejected |
|---|---|---|---|---|
| 216 | 0.9410 | 0.8652 | −0.2592 | −8.03 |
| 432 | 0.7931 | 0.9707 | −0.6095 | −13.02 |
| 648 | 0.7594 | 0.9766 | −0.8474 | −15.24 |
| 864 | 0.7526 | 0.9805 | −0.8162 | −15.58 |
Preference accuracy climbs from 0.865 to 0.981 while rew_chosen gets three
times worse — and step 864 is the checkpoint that generates best. The policy is suppressing the
chosen audio while learning the ranking: it makes the bad audio catastrophically less likely and the
good audio slightly less likely. Both go down; one goes down less. That satisfies the objective
exactly and is not what anyone wanted.
It is natural to compress the above into “the run whose rew_chosen was worst
generated the best”. That is a real claim, it is easy to state, and the data refute it. The
worst final rew_chosen belongs to dpo_sft3_v2 at −2.3037,
whose best checkpoint scores 0.4687 and ranks fifth. The best model in the project comes from
dpo_sft3_p2, whose −1.0015 is the second least negative of the
seven. Ordering the runs by final rew_chosen and ordering them by evaluated reward gives
essentially unrelated sequences.
The supported statement is the weaker and more useful one: rew_chosen is
anti-correlated with generation quality inside a run and carries no information
between runs. It is a diagnostic that the objective is behaving as written, not a
measure of model quality in either direction. This project therefore selects checkpoints by
generating 320 clips and scoring them, every time, at a cost of about a GPU-hour per candidate —
which is the conclusion §18 arrives at from four other directions.
Refuted: “the high side was only locally intense.” The obvious explanation is that pairing within a voice makes “high” a relative target — a voice profile that never gets angry has a top-1 % anger clip that is barely angry. Measured over 224,750 voice-profile rows in 500 voices, each voice's own top 1 % per emotion head scores a median 0.995 on the global zero-point-corrected scale, and 99.7 % reach the 0.90 band. The chosen side was genuinely extreme, and the training signal pointed at real extremes.
rew_chosen is −1.12 against rew_rejected
−46.6. The policy is not making intense audio more likely; it is making mild audio
catastrophically less likely under a “high” instruction and intense audio slightly less
likely. Pushing everything down, one thing less, does not move the sampling mode. Ranking two fixed
recordings is a discrimination task and the model solved it (0.5625 → 0.9750). Sampling draws from
wherever the probability mass actually sits.
p2_emox rows are the only ones where the spoken words are absent; at evaluation every
prompt has words. What generalises out of that regime is structural — the duration, pause
and burst tags mean the same thing with or without words — and that is exactly the profile of the
gains: burst hit rate 0.709 → 0.772, burst realisation 0.3929 → 0.4271, WER 0.1094 → 0.0950, all
structural. The semantic mapping from an intensity word to an acoustic target has no lexical content
to attach to when the direction sits next to silence.
A fourth possibility has since arrived from a different direction and is recorded in §16: measured inside the model, the direction that means “more of this emotion” and the direction that means “higher measured quality” have a cosine of −0.75 in mid-stack and −0.954 at the last state before audio codes are chosen. If those two are close to opposite instructions, then any objective that rewards emotion and quality together is asking for something the representation makes difficult, and every reward in this project does exactly that.
GRPO generates G completions for the same prompt with sampling, scores each with the
measurement models, normalises the rewards within the group
(A = (r − mean) / std), and pushes the policy towards the completions with positive
advantage. With one inner update per rollout the PPO ratio is exactly 1, the clipping term is
inert, and the loss reduces to −(A · logp).mean(). The whole learning signal
is therefore the spread of scores inside a group.
reward = ( 0.40·r_emo + 0.40·r_qual + 0.20·r_burst ) × wer_factor(WER)
r_emo band_score(percentile reached on the requested emotion head, requested band)
r_qual 0.5·percentile(vocal-burst blend) + 0.5·percentile(genuineness)
r_burst F1 over prompted vs detected bursts, weighted by duration match
WER Whisper-large-v3-turbo against the script's own plain text
| Run | Base | Prompt format | G | KL β | Hours | Steps | Rollout reward | Outcome |
|---|---|---|---|---|---|---|---|---|
| 1 | SFT-2 | round 2, no directions | 16 | off | 1.4 | 75 | 0.657 → 0.555 | reward fell |
| 2 | SFT-2 | round 2 | 16 | off | — | — | — | DDP deadlock, 35 GB of core dumps |
| 3 | SFT-2 + run-1 adapter | with directions | 32 | on | 2.7 | — | 0.409 → 0.347 | reward fell |
| 4 | SFT-3 | with directions | 16 | 0.02 | 2.00 | 88 | 0.503 → 0.527 | stable; bursts and WER improve |
| 5 | SFT-3 | with directions | 16 | 0.02 | 2.02 | 105 | 0.611 → 0.608 | emotion worse on the strict yardstick |
Both fell, and the cause was outside the optimiser. Run 3 was given prompts with inline delivery directions — the right idea — but the model had never been trained on that format, because round 2 had dropped directions by accident (§10.2). It collapsed, and GRPO spent 2.7 hours optimising against a reward dominated by that collapse. The conclusion is not that GRPO needs tuning; it is that a reward computed on out-of-distribution prompts measures the prompt, not the policy.
88 steps, 176 rollout groups, 2.00 h on one 4-GPU node, from SFT-3, KL β 0.02, corrected
percentile scale. skipped = 0 for the entire run: the rank-divergence fix (§19.1)
held.
| Third of the run | Reward | Emotion | Quality | Burst | WER |
|---|---|---|---|---|---|
| first | 0.503 | 0.259 | 0.840 | 0.561 | 0.128 |
| middle | 0.550 | 0.280 | 0.873 | 0.666 | 0.103 |
| last | 0.527 | 0.242 | 0.834 | 0.669 | 0.098 |
Burst realisation rises 0.561 → 0.669 and WER falls 0.128 → 0.098. Emotion is flat within noise. (An earlier reading of single steps had suggested an emotion collapse; the aggregate over 176 groups does not support it, and per-step numbers of two groups each are far too noisy to read as a trend — a small methodological correction that is recorded rather than quietly dropped.)
Splitting run 4 by the band the prompt asked for, where emo is the band score of the percentile achieved against the percentile band requested:
| Band asked for | Emotion, first third | Emotion, last third | Burst, first → last |
|---|---|---|---|
| faint (0.40–0.70) | 0.369 | 0.317 | 0.756 → 0.541 |
| moderate (0.70–0.90) | 0.279 | 0.384 | 0.506 → 0.671 |
| intense (0.90–0.98) | 0.234 | 0.194 | 0.543 → 0.741 |
| extreme (0.98–1.00) | 0.071 | 0.139 | 0.592 → 0.582 |
The model scores three to five times better when asked for a mild emotion than for an extreme one — and that is why GRPO cannot learn emotion here. At the extreme band the band score sits on its floor for essentially every completion, so inside a group of 16 completions of the same prompt the emotion term is nearly constant — and a constant term contributes exactly nothing to a group-normalised advantage. Half the prompt mix (intense + extreme, by design) therefore carries almost no emotion gradient, while burst realisation and WER vary freely across completions and dominate what the update can learn from.
Supporting numbers, run 4: mean within-group reward standard deviation 0.1063 —
that is the entire learning signal per step. Across groups,
corr(reward, emo) = +0.753, corr(reward, burst) = +0.310,
corr(reward, wer) = −0.432, with std(emo) = 0.292,
std(burst) = 0.339, std(wer) = 0.122. The reward is not ignoring emotion
— the policy cannot move it where it is being asked to.
Four levers were built and all four default off, so run 4 stays reproducible:
| Third of run | Reward | Emotion | Quality | Burst | WER |
|---|---|---|---|---|---|
| v4 first | 0.503 | 0.259 | 0.840 | 0.561 | 0.128 |
| v4 last | 0.527 | 0.242 | 0.834 | 0.669 | 0.098 |
| v5 first | 0.611 | 0.441 | 0.853 | 0.569 | 0.122 |
| v5 last | 0.608 | 0.424 | 0.834 | 0.599 | 0.119 |
On the strict evaluation yardstick, run 5 is the worst of the four models on emotion (0.3275 against SFT-3's 0.3494 — see §13.3). The training-time gain was the mechanical artefact it had been predicted to be: lever 1 inflates the number by paying for partial progress, lever 3 inflates it by asking easier questions. The realised band mix confirms the second: v4 ran 0.28 moderate / 0.28 intense / 0.22 extreme / 0.21 faint, v5 ran 0.36 / 0.30 / 0.15 / 0.20 — the curriculum moved the run onto the easier half and the run ended before it came back.
Hypothesis, stated as a hypothesis: a ramp that pays for partial progress supplies a gradient but removes the incentive to arrive. A completion that moves slightly toward the target now collects reward without reaching the band, and the strict yardstick only counts arrival. Levers 1 and 3 push in the same direction and it is the wrong one. A ramp that is steeper near the band, or a rank-within-group reward that keeps the target absolute, would test this.
Lever 4 is the one that paid. Run 5 saved the most emotional usable completion of every group: 204 clips at median emotion percentile 0.641 and median WER 0.049, median 148 frames — 73 moderate, 61 intense, 41 faint, 29 extreme. High emotion and clean speech in the same clip is the combination that has been missing from every evaluation in this project. The model can do it; it does not do it on command. Two hours of rollout on one 4-GPU node therefore yields supervised training material at roughly 100 usable clips per GPU-hour — and best-of-N supervised training on that harvest is currently a more promising route than further reward shaping.
Hyperparameters, for reproduction: group size 16 (32 in run 3), 2 groups per optimizer step, micro-batch 2 completions, LoRA rank 32 / α 64 (68.7 M trainable of 4.27 B), learning rate 2e-6 constant after 20 warmup steps, KL β 0.02 with the k3 estimator against the same model with the adapter disabled (so no second copy in memory — measured KL stayed at 2–3e−4 and the constraint was never binding), sampling temperature 1.0 / top-p 0.95 / top-k 50, max 340 new frames (27.2 s), prompt pool 4,000 rows, checkpoint every 25 steps, 2-hour stopping rule. Prompt mix: 50 % carry vocal bursts, 35 % carry two emotions, 50 % carry a reference clip, 85 % carry a delivery direction. Throughput: one 4-GPU node produces 45–52 optimizer steps per hour at G = 16, i.e. 1,440–1,680 generated and fully scored clips per hour. Loading the three measurement models costs about 40 s per rank.
Measured on a 360-clip listening sample decoded from the corpus's own audio codes — one clip per emotion per tier:
| Tier | Emotion percentile range | Burst blend, median | Genuineness, median |
|---|---|---|---|
| top 10 % | 0.550 – 1.000 | 0.451 | 0.391 |
| top 5 % | 0.782 – 1.000 | 0.443 | 0.360 |
| top 1 % | 0.959 – 1.000 | 0.463 | 0.360 |
Genuineness falls as intensity rises, and both quality axes sit below the corpus median of 0.50 throughout. The most intense recordings are on average the least genuine. An adapter trained on intensity alone would be taught to overact.
AROU, VALN, ARSH, VALS — high valence is
elation, low valence is despair, and both are extremes. Top only for TENS,
VOLT, VULN, EXPL, RANG, EMPH,
VFLX, and the ranting / ASMR / whisper / cartoon / drama style axes.| Step | Rows |
|---|---|
| Top-1 % union, emotion heads only, no gate | 731,948 (23.3 % of the corpus) |
| Emotion arm, gated | 174,368 |
| VoiceNet arm, gated | 111,326, of which 69,618 are new |
| Total | 243,986 (7.8 %) — materialised at 247,049 rows including the unchanged validation holdout |
Two things fall out of this. First, the VoiceNet arm added about 40 % more material than the emotion heads alone would have found, and the axes that contributed are the affective ones — high valence (16,726 rows qualifying on that axis alone), explosiveness (14,437), arousal shift (9,890), valence shift (8,164), velocity flux (4,680), low arousal (3,323), low valence (2,383), high arousal (1,972). The stylistic axes — ranting, ASMR, whispering, cartoonish — contributed nothing in the top eight: their top 1 % was either already in the emotion arm or failed the quality gate.
Second, the source mix flipped. The gated selection is 65.4 % real speech, 34.6 % voice profiles — the reverse of the ungated listening sample (273 synthetic clips to 87). The gate removes synthetic extremes disproportionately, which supports the reading that the synthetic “top 1 %” is often overacted.
Three adapters on the same 247,049-row corpus, 5 epochs, cosine 1e-4 → 5e-6, α fixed at 2 × rank so the effective scaling is identical and rank is the only variable, global batch 256 (4 × 2 × 32 on 8 nodes), 4,395 steps, 2 h 35 to 2 h 40 each. Base SFT-3, evaluated alone on it.
| Rank | α | Trainable | Share of model |
|---|---|---|---|
| 16 | 32 | 34.4 M | 0.825 % |
| 32 | 64 | 68.7 M | 1.637 % |
| 64 | 128 | 137.4 M | 3.220 % |
Below is every evaluation in the project that is on the corrected percentile scale — eighteen runs, all on the same 80 prompts, the same 4 completions per prompt, the same strict plateau yardstick, ranked by composite reward. This table replaces every partial comparison published earlier. The top two rows were added on 28 August, after the run of §15 finished; the emotion-term column is not recorded for them.
| # | Model | Reward | WER | Emotion pct | Emotion term | Quality | Burst | Burst hit rate |
|---|---|---|---|---|---|---|---|---|
| 1 | SFT-3 + DPO contrastive families, step 5022 (published) | 0.4757 | 0.0977 | 0.3541 | — | 0.9208 | 0.4180 | 0.7625 |
| 2 | SFT-3 + DPO contrastive families, step 3906 | 0.4744 | 0.0916 | 0.3478 | — | 0.9231 | 0.4106 | 0.7625 |
| 3 | SFT-3 + CFG-DPO, step 4912 (published) | 0.4708 | 0.0950 | 0.3373 | 0.1269 | 0.9235 | 0.4271 | 0.7719 |
| 4 | SFT-3 + CFG-DPO, step 3963 | 0.4698 | 0.0953 | 0.3420 | 0.1397 | 0.9235 | 0.3940 | 0.7000 |
| 5 | SFT-3 + DPO corpus v2, step 3216 (weights deleted, §19.7) | 0.4687 | 0.1094 | 0.3401 | 0.1370 | 0.9211 | 0.3929 | 0.7094 |
| 6 | SFT-3 + DPO corpus v1, step 864 | 0.4668 | 0.1117 | 0.3518 | 0.1457 | 0.9108 | 0.3973 | 0.6937 |
| 7 | SFT-3 + emotion LoRA r16 | 0.4663 | 0.1239 | 0.3572 | 0.1472 | 0.9189 | 0.4002 | 0.6844 |
| 8 | SFT-3 + emotion LoRA r64 | 0.4640 | 0.1298 | 0.3345 | 0.1393 | 0.9218 | 0.3867 | 0.6844 |
| 9 | SFT-3 + DPO corpus v2, step 4590 | 0.4633 | 0.1024 | 0.3427 | 0.1385 | 0.9062 | 0.3930 | 0.7188 |
| 10 | SFT-3 + emotion LoRA r16 @ 0.75 | 0.4622 | 0.1055 | 0.3390 | 0.1412 | 0.9052 | 0.3895 | 0.6969 |
| 11 | SFT-3, no adapter | 0.4584 | 0.0987 | 0.3494 | 0.1394 | 0.9127 | 0.3564 | 0.6656 |
| 12 | SFT-3 + emotion LoRA r16 @ 1.25 | 0.4584 | 0.1130 | 0.3315 | 0.1392 | 0.9050 | 0.3929 | 0.6844 |
| 13 | SFT-3 + emotion LoRA r16 @ 0.25 | 0.4579 | 0.1174 | 0.3533 | 0.1359 | 0.9136 | 0.3827 | 0.6656 |
| 14 | SFT-3 + emotion LoRA r16 @ 0.5 | 0.4554 | 0.1345 | 0.3365 | 0.1324 | 0.9150 | 0.3712 | 0.6656 |
| 15 | SFT-3 + GRPO v4 | 0.4551 | 0.1286 | 0.3468 | 0.1486 | 0.8950 | 0.3837 | 0.6937 |
| 16 | SFT-3 + GRPO v5 | 0.4512 | 0.1253 | 0.3275 | 0.1202 | 0.9282 | 0.3586 | 0.6469 |
| 17 | SFT-3 + emotion LoRA r16 @ 1.5 | 0.4509 | 0.1325 | 0.3358 | 0.1298 | 0.9260 | 0.3611 | 0.6312 |
| 18 | SFT-3 + emotion LoRA r32 | 0.4494 | 0.1315 | 0.3209 | 0.1292 | 0.9126 | 0.3640 | 0.6719 |
Three readings of that table:
Six further evaluations exist and are deliberately excluded from that ranking, because they
predate the percentile correction (§6.7): eval_sft2_dir,
eval_sft2_dirv3, eval_dpo2_dir, eval_grpo1_dir,
eval_grpo3_dir and eval_sft3_dir_UNCORRECTED_ECDF. Their word error rates
(0.45–0.51 for the SFT-2-based ones) are comparable and genuinely bad; their reward and
emotion columns are not.
An obvious next question is whether merging an adapter more strongly pushes emotion further. Two sweeps have now been run, and they came out differently. Reported together, because the difference between them is the finding.
The single pooled emotion LoRA (rank 16, trained on all 247,049 gated rows) evaluated at seven merge weights on the standard harness. Those are rows 5, 8, 10, 11, 12, 15 and the baseline of the table above, gathered here:
| Merge weight | Reward | WER | Emotion pct | Quality | Burst | Burst hit rate |
|---|---|---|---|---|---|---|
| 0 (SFT-3) | 0.4584 | 0.0987 | 0.3494 | 0.9127 | 0.3564 | 0.666 |
| 0.25 | 0.4579 | 0.1174 | 0.3533 | 0.9136 | 0.3827 | 0.666 |
| 0.50 | 0.4554 | 0.1345 | 0.3365 | 0.9150 | 0.3712 | 0.666 |
| 0.75 | 0.4622 | 0.1055 | 0.3390 | 0.9052 | 0.3895 | 0.697 |
| 1.00 | 0.4663 | 0.1239 | 0.3572 | 0.9189 | 0.4002 | 0.684 |
| 1.25 | 0.4584 | 0.1130 | 0.3315 | 0.9050 | 0.3929 | 0.684 |
| 1.50 | 0.4509 | 0.1325 | 0.3358 | 0.9260 | 0.3611 | 0.631 |
Emotion percentile spans 0.3315–0.3572 with no monotone structure and a peak at the trained value. Reward spans 0.015 — inside what these evaluations show between arms that ought to be equivalent. All seven runs are complete (320 clips each). On this axis, with this adapter, at this band, merge weight is not a dial for intensity.
The second sweep is a different object. It takes the per-emotion bucket adapters of §13.5, each on prompts naming its own emotion at the extreme band with vocal bursts requested, and scales each adapter's contribution over six weights. Design details that make it a strong measurement: the same eight prompts are used at every weight for a given adapter, the torch seed is reset to a fixed value before each weight, and weight 0 is the same loaded model with the LoRA scaling zeroed rather than a separately loaded baseline — so a difference cannot come from anything but the adapter. 31 adapters × 6 weights × 8 clips = 1,488 scored generations.
| Merge weight | Emotion pct | Genuineness pct | Blend pct | Burst realisation | WER (mean) | WER (median) | Mean |duration error| |
|---|---|---|---|---|---|---|---|
| 0 | 0.4079 | 0.8169 | 0.9251 | 0.2605 | 0.1669 | 0.000 | 0.127 s |
| 0.25 | 0.4068 | 0.8444 | 0.9549 | 0.2734 | 0.1790 | 0.000 | 0.118 s |
| 0.5 | 0.4297 | 0.8331 | 0.9229 | 0.3035 | 0.1464 | 0.000 | 0.115 s |
| 1.0 | 0.4407 | 0.8364 | 0.9536 | 0.2307 | 0.1298 | 0.000 | 0.133 s |
| 1.5 | 0.4714 | 0.8460 | 0.9613 | 0.2547 | 0.0956 | 0.000 | 0.120 s |
| 2.0 | 0.4923 | 0.8796 | 0.9688 | 0.2227 | 0.1842 | 0.030 | 0.129 s |
Emotion percentile rises monotonically from weight 0.5 onward and is +0.084 higher at weight 2.0 than at weight 0 (paired over 248 clips, bootstrap 95 % CI [+0.048, +0.123]); at weight 1.5 the paired gain is +0.064, CI [+0.030, +0.098]. 27 of 31 adapters are higher at the top weight than at zero — the three that are not are Helplessness (−0.185), Bitterness (−0.104) and Contempt (−0.024) — and the fourth, Jealousy and Envy, is the dead metric of §6.4 and reads 0.000 everywhere. At weight 1.5 the split is 25 up, 5 down, 1 tied. Per-adapter argmax weight: 2.0 for 13 adapters, 1.5 for 7, 1.0 for 6, 0.5 for 2, 0.25 for 2 and 0.0 for 1.
Timing is untouched at every weight — mean absolute duration error stays inside 0.115–0.133 s across the whole sweep, so overdriving an adapter does not cost the capability that was actually solved.
Word error rate is best at weight 1.5 (mean 0.0956, the minimum of the sweep) and worst
at 2.0 (0.1842). But that is not a broad collapse — it is a tail. Exactly five of 248 clips at
weight 2.0 have WER ≥ 1.0, and two of them are the same failure: an
Astonishment_Surprise clip and a Fear clip that both derail into
" Ha ha ha ha ha ha ha …" repeat loops and score 11.11. Dropping just those
two brings the mean at 2.0 to 0.0954 — indistinguishable from 1.5. Two other clips transcribe as
silence and one as a single stray word.
There is also a small broad-base degradation underneath the tail: at weight 2.0 the median WER moves off zero for the first time (0.030), and the number of clips with exactly zero word errors falls to 111 from 128–135 at every other weight. Meanwhile the count of moderately bad clips (WER ≥ 0.5) is actually at its minimum at weight 2.0 (9, against 12 at 1.5 and 25 at weight 0). So the honest statement is: 2.0 makes the typical clip slightly worse and adds a small population of catastrophically derailed ones; 1.5 buys most of the emotion gain with the best intelligibility in the sweep. 1.5 is the published recommendation.
Three caveats belong with sweep B, none of which changes the direction of the result:
Why the two sweeps disagree, and which to believe for what. They are not the same experiment. Sweep A scales a single general adapter trained on all 154 axes at once, on the intense band, against a prompt set that mostly does not request bursts; sweep B scales per-emotion specialists on the extreme band, on prompts that name the adapter's own emotion and request a burst. The reconciling reading is that a general adapter has no single direction to overdrive — scaling it amplifies an average of 154 directions, which is close to noise — while a specialist adapter has exactly one, and scaling that one works. Both results stand; sweep A is the reason not to expect band-dependent scaling of a general adapter to work, and sweep B is the reason the per-emotion adapters ship with a recommended weight above 1.
Coverage note: sweep B was launched when 31 of the 40 per-emotion adapters existed. Amusement, Embarrassment, Intoxication, Relief, Sadness, Sexual Lust, Shame, Thankfulness and Triumph are absent from it. Re-running it now would cover all 40.
The natural next step is 40 adapters, one per emotion, rather than one general one. All 40 have now been trained (rank 16, α 32, 5 epochs, cosine 1e-4 → 5e-6, 34.4 M trainable parameters, each on the gated top 1 % of its own emotion, all finishing with zero non-finite batches). Training set sizes run from Sourness at 2,663 rows to Intoxication at 15,725; step counts from 3,330 to 19,660; wall time from 42 to 224 minutes per adapter.
Seventeen of them have been measured against the best general checkpoint —
SFT-3 + CFG-DPO — on identical prompts, identical sampling and identical seeds. The seventeen are
Affection, Anger, Astonishment/Surprise, Awe, Bitterness, Concentration, Contempt, Contentment,
Disgust, Elation, Emotional Numbness, Jealousy and Envy, Longing, Malevolence/Malice,
Pleasure/Ecstasy, Pride and Sourness. That set is not a chosen subset: the generator shards
adapters across ranks with names[RANK::WORLD], and the adapter directory was still
growing while the workers launched, so ranks duplicated each other and skipped others. Seventeen is
where the job landed.
The design of that evaluation is the interesting part. A specialist adapter should do two things: make its own emotion stronger when it is asked for, and change nothing when it is not. So each adapter gets ten prompts naming its emotion at the intense band and ten prompts naming no emotion at all — the corpus's own neutral direction. A rise in the first is the adapter working. A rise in the second is the adapter leaking into plain speech.
| Measurement | Value | Adapters improving |
|---|---|---|
| Emotion when asked for | +0.0466 | 11 of 17 (5 down, 1 exactly zero) |
| Emotion when not asked for | +0.0327 | 12 of 17 (4 down, 1 zero) |
| Selectivity ratio | 1.43 : 1 | — |
| Word error rate, with adapter vs baseline | 0.1896 vs 0.1215 | — |
| Burst realisation, with adapter vs baseline | 0.2547 vs 0.3252 | worse for 16 of 17 |
So the adapters do push their emotion — and they push nearly as hard when nobody asked. They also cost about half again as much transcription error and about a fifth of the burst realisation. As they stand they are not a drop-in improvement. They are tinting, not control.
| Emotion | Δ when asked | Δ when neutral | WER with LoRA | WER baseline | Training rows |
|---|---|---|---|---|---|
| Elation | +0.159 | −0.292 | 0.203 | 0.295 | 6,046 |
| Emotional Numbness | +0.158 | +0.129 | 0.059 | 0.136 | 4,633 |
| Concentration | +0.150 | +0.161 | 0.294 | 0.167 | 5,400 |
| Bitterness | +0.131 | +0.010 | 0.334 | 0.096 | 3,364 |
| Pride | +0.092 | −0.058 | 0.038 | 0.086 | 6,784 |
| Contempt | +0.087 | +0.053 | 0.293 | 0.310 | 3,105 |
| Contentment | +0.068 | +0.105 | 0.443 | 0.170 | 3,445 |
| Anger | +0.053 | +0.132 | 0.266 | 0.083 | 6,142 |
| Astonishment / Surprise | +0.037 | +0.055 | 0.219 | 0.024 | 4,899 |
| Malevolence / Malice | +0.036 | −0.037 | 0.118 | 0.212 | 6,628 |
| Longing | +0.034 | +0.038 | 0.194 | 0.092 | 7,981 |
| Jealousy and Envy | 0.000 | 0.000 | 0.150 | 0.269 | 4,503 |
| Sourness | −0.003 | +0.162 | 0.064 | 0.010 | 2,663 |
| Pleasure / Ecstasy | −0.023 | +0.042 | 0.039 | 0.042 | 2,976 |
| Awe | −0.056 | +0.061 | 0.209 | 0.057 | 3,247 |
| Disgust | −0.061 | +0.008 | 0.101 | 0.086 | 3,271 |
| Affection | −0.070 | −0.013 | 0.214 | 0.061 | 5,235 |
Word error rates here are for the emotion-naming (matched) prompts only. Different adapters draw different slices of the prompt pool, so cross-adapter comparison of the WER columns is not prompt-matched. The prompts are drawn from three source corpora — the run log's “three voices” label is a fallback string, not three verified speakers — so the design controls for corpus, not for speaker identity, which is weaker than it looks.
Counted over 3,144,739 rows, with 154 buckets (40 emotions, plus 57 VoiceNet dimensions × 2 tails):
| Group | Buckets | Ungated | Gated | Median | Smallest |
|---|---|---|---|---|---|
| 40 emotions | 40 | 1,258,097 | 283,275 | 6,706 | 2,663 |
| VoiceNet, expressive axes | 32 | 1,006,528 | 272,053 | 7,854 | 426 |
| VoiceNet, the rest | 82 | 2,579,188 | 552,066 | 5,084 | 0 |
Feasible — a median of 6,700 rows is comfortable for a rank-16 adapter — but badly skewed, and skewed by exactly the effect that motivated the gate. The four smallest emotion buckets are Sourness (2,663), Pleasure/Ecstasy (2,976), Contempt (3,105) and Awe (3,247), and their genuineness median in the top 1 % is 0.23–0.30: the most intense recordings of those four are almost entirely inauthentic, and the gate removes three quarters of them. Intoxication (15,725 rows, genuineness median 0.804) and Amusement (13,272, 0.774) have ample genuine material. A 40-adapter programme therefore needs either unequal training sets or a relaxation to the top 2 % for the four problem emotions.
One caveat on the VoiceNet half of that table, stated because it would otherwise mislead: its
largest buckets are S_ASMR bottom (23,817) and S_CART bottom (18,934) —
that is, “not ASMR” and “not cartoonish”. Those are the unremarkable
default, not an expressive extreme, and their high genuineness (0.87) reflects that. Only the four
two-sided affective axes have a meaningful lower tail.
The same bucket machinery was pointed at the VoiceNet axes on 26 August at 16:47. The policy is
the one §13.2 established: both tails for AROU, VALN,
ARSH and VALS; the top only for the rest — the survey found that
the largest “bottom” buckets of the style axes are simply ordinary unremarkable speech,
so there is no such thing as an extreme absence of ranting.
Twenty candidate buckets went in; three were dropped by a row-count floor of 1,500 applied after the top-1 % tail selection and the blend-and-genuineness quality gate, and 17 came out: 146,175 rows in total, median 8,216, minimum 1,922, maximum 22,849.
| Bucket | Gated rows | Bucket | Gated rows |
|---|---|---|---|
VALN_high | 22,849 | VULN_high | 8,216 |
EXPL_high | 22,378 | AROU_low | 7,492 |
ARSH_high | 13,219 | AROU_high | 4,104 |
VALS_high | 13,037 | EMPH_high | 3,306 |
ARSH_low | 10,365 | S_DRAM_high | 2,883 |
VALS_low | 10,161 | S_RANT_high | 2,608 |
VFLX_high | 9,849 | TENS_high | 2,532 |
VALN_low | 9,286 | VOLT_high | 1,968 |
S_ASMR_high — 1,922 |
dropped: RANG_high 1,147 ·
S_WHIS_high 939 · S_CART_high 680 | ||
The three dropped axes are exactly the ones where the quality gate bites hardest: high pitch range keeps 1,147 of 31,453 candidates, whisper-talk 939 of 31,452, and cartoonish 680 of 31,450 — a 2.2 % survival rate for cartoonish, whose genuineness median in its own top 1 % is 0.109. That is the §13.1 finding again, in its sharpest form: the most cartoonish recordings in the corpus are almost entirely inauthentic.
Training of those 17 adapters started at 16:51 on 26 August (rank 16, α 32, 5 epochs, AdamW lr 1e-4, cosine with 10 % warmup, batch 4, non-DDP, one bucket per GPU stream) and was still running when this report was written. They are not yet published.
All 40 emotion adapters, all 500 voice adapters and the 17 new VoiceNet adapters are trained on
SFT-3 — never on a merged SFT-3 + DPO checkpoint. The reason is structural, not a
preference: the DPO adapter targets audio_lm_heads.*, and those twelve tensors are
weight-tied to audio_embeddings.*. They are literally the same tensor. Merging
the head delta therefore rewrites the embedding that produced it, and the model is corrupted (§19.2
gives the measurement).
The adapters are stacked at inference instead, each with its own scaling factor. The published recommendation is DPO at 1.0, the voice adapter at 1.0 and the emotion adapter at 1.5 — the emotion weight from the sweep of §13.4; the voice weight has not been swept and 1.0 is simply its trained value.
The round's artefacts went public on 26 August as four repositories under the
laion organisation, and three more followed on 28 August: the preference adapter of
§15, the public subset of the trajectory corpus (§17), and a listening page for the
merge-weight ablation of §13.4. All four were uploaded from the login node (compute nodes have no route to the
internet) and byte-verified after upload — the uploader re-reads the repository
metadata with HfApi.repo_info(files_metadata=True), size-compares every file, and for
the strictest of the scripts downloads and SHA-256-hashes anything the Hub reports no size for,
exiting non-zero on a single disagreement. The reason is recorded in that script's docstring:
“upload_folder has returned a success URL for a rejected upload in this
project before, so nothing is trusted.”
| Repository | Contents | Key numbers on the card |
|---|---|---|
| laion/moss-tts-local-transformer-4.55b-voice-acting-v2-sft3 | The full SFT round-3 model — weights, remote code, processor, provenance. CC-BY-4.0, EN/DE. | Trained on the 398,282-row extreme subset, 2 epochs, 712 steps on 32 nodes. WER on direction-carrying prompts 0.447 → 0.099; median duration error 0.100 → 0.080 s; clips within 0.5 s 92.8 → 100 %; burst hit rate 0.516 → 0.666. |
| laion/moss-va-sft3-dpo-lora | The CFG-DPO adapter — rank 64, α 128, dropout 0.05, targeting the 23 modules of §4 including
all twelve audio_lm_heads. This is ckpt-step4912. |
Trained on 2,327,904 preference pairs, 20.5 % of them classifier-free-guidance pairs; preference accuracy on those rose 0.562 → 0.975. Reward 0.4708, WER 0.0950, quality 0.9235, burst 0.4271, hit rate 0.772. |
| laion/moss-va-sft3-emotion-loras | 40 per-emotion adapters, one subdirectory each, with a bucket.json
recording rows, steps, minutes, non-finite count and prompt hash. |
Rank 16, α 32, 5 epochs, cosine 1e-4 → 5e-6, 34.4 M trainable (0.83 %). All 40 finished with zero non-finite batches. Carries both the selectivity result (§13.5) and the merge-weight sweep (§13.4) with its recommended operating point of 1.5. |
| laion/moss-va-sft3-voice-loras | 500 per-voice adapters — emolia_*, refvoice_*,
mediathek_* — each trained on every clip of that voice minus the worst decile by mean
of blend and genuineness. |
Rank 16, α 32, 5 epochs, same schedule. Rows per voice: median 2,163, min 1,971, max 2,251. Steps per adapter: median 2,705. 500 of 500 with zero non-finite batches. |
| laion/moss-va-sft3-dpo-lora-p2 — 28 Aug | The current best checkpoint — the preference adapter trained on the corpus with
the two contrastive pair families. Rank 64, α 128. This is ckpt-step5022. Supersedes
moss-va-sft3-dpo-lora, which stays up as the CFG-corpus result. |
Trained on 2,696,421 preference pairs, 13.7 % of them the new families. Reward 0.4757, WER 0.0977, emotion percentile 0.3541 — the highest of any preference-tuned model in this line — quality 0.9208, burst 0.4180, hit rate 0.762. |
| datasets/laion/moss-va-trajectory-corpus — 28 Aug | The public subset of the trajectory corpus (§17). Specifications only; no audio. CC-BY-4.0. | 7,638,961 trajectories over 17,722,101 distinct clips and 1,214,241 speakers; 74,858 h of referenced audio; 419 MB. Three source datasets held back. |
The cards are unusually candid, and three of their statements are worth reproducing because they are the honest limits of what was shipped:
The base-model card also carries a decoding warning that is worth repeating outside it: use
proc.decode_audio_codes(..., return_stereo=False). The direct tokenizer path yields
half-speed audio. In the card's words, “this project lost a whole corpus to that once”
— see §19.6.
model.add_weighted_adapter(["dpo","voice","emo"], [1.0,1.0,1.5], "combined",
combination_type="linear"). The DPO adapter is rank 64 and the emotion and voice adapters
are rank 16, and PEFT refuses: “All adapters must have the same r value when using
combination_type linear, ties, dare_ties or dare_linear.” That exact refusal is in
today's generation log. The project's own working route — recorded in the study runner as
"route": "multi-active adapters, per-adapter scaling" — is to activate all three
adapters and set each one's scaling factor individually, which the cards do show elsewhere but
never connect to the failure. The cards should be corrected.merge_and_unload(). It does not. Grepping all four READMEs for
merge_and_unload, weight-tied or audio_embeddings returns
nothing; the DPO card carries no merge warning at all. The warning is real and the measurement
behind it is real (§19.2) — it simply lives in this report and in the training code, not on the
Hub. That is a documentation gap, and it is recorded here rather than quietly fixed in the
prose.These are aimed directly at two measured defects — the leakage of §13.5 and an emotion-blind length signal — and they are the only intervention in this project that has raised the emotion percentile above the supervised baseline without costing word error rate.
Family A — emotion-contrastive, for selectivity. Every contrast built before this pits an intense clip against a mild one, so a model can win by being generically expressive — which is exactly the 1.4 : 1 tinting the per-emotion adapters showed. Family A pits intense against intense: same speaker, same length, emotion A against emotion B, with the instruction naming A. Turning the intensity up is then worth nothing; only reading the instruction is. The builder's own docstring states the motivation in one line: “A ratio of 1.4 : 1 is not control, it is tinting.”
The selection rule is strict on both sides. The chosen clip must sit at percentile ≥ 0.90 on the head being asked for and have that head as its own top emotion; the partner must be ≤ 0.50 on that head and ≥ 0.90 on its own top head. So both sides really are intense, on different emotions. Lengths are matched to within 10 % of frames. Each base pair is emitted twice, the mirror naming the partner's own top emotion. Because chosen and rejected are different recordings with different words and a DPO pair shares one prompt, the spoken words are removed, as in the CFG construction.
Family B — length-contrastive, conditioned on the emotion. The existing truncation and continuation families know nothing about emotion, so they teach “the right length is better” in the abstract. Family B keeps the speaker, the text and the emotion fixed and varies only the length: chosen is the clip at its true duration, rejected is the same clip cut to 50–75 % or run to 125–150 %. Here the words are in the prompt, with the full timed script, because the timing tags are exactly what the rejected side violates.
Both families take the emotion from the measured value, never from the prompt
that generated the clip. Constants: HI_PCT 0.90, LO_PCT 0.50,
FRAME_TOL 0.10, 8 emotion-contrastive and 6 length-contrastive pairs per cell, cut range
0.50–0.75, extension range 1.25–1.50, RNG seed 4711.
| Quantity | Value |
|---|---|
| Emotion-contrastive cells (voice × emotion) | 19,884 |
| Emotion-contrastive base pairs → rows (each emitted twice) | 154,564 → 309,128 |
| Length-contrastive: cut / extended | 29,564 / 29,825 = 59,389 |
| New rows written | 368,517 over 342,284 distinct clips |
| Mixed onto the CFG corpus | 2,327,904 + 368,517 = 2,696,421 |
| New share | 13.7 % |
The base is the CFG corpus rather than the older one, and the mixer says why: because the CFG corpus produced the best checkpoint measured so far. Schema equality between the new rows and the base was checked field by field before the merge — 30 fields each, none missing, none extra — and all 342,284 referenced clips were found. An earlier build with a smaller per-cell budget produced 138,088 rows and was superseded.
The run is dpo_sft3_p2: 32 ranks over 8 nodes, LoRA rank 64 (137.4 M trainable of
4.267 B, 3.220 %), β 30, chosen-NLL anchor 0.013, learning rate 1e-6, 10,331 steps per epoch with
1,033 warmup, 256 preference pairs per step, arm-balanced sampling. It ran at about 62 pairs per
second and hit its six-hour wall clock at step 5,126 of 10,331 — half an epoch.
Checkpoints were kept at 3906, 4132, 4355, 4579, 4800 and 5022.
Preference accuracy per family, aggregated from the step log (the trainer emits an instantaneous per-family accuracy for whichever families a batch happened to contain, so these are means over logged batches, not a single running statistic):
| Window | emotion-contrastive | length-contrastive | cfg_high | cfg_low |
|---|---|---|---|---|
| steps 1–1771 (first half) | 0.864 | 0.786 | 0.825 | 0.766 |
| steps 1772–3543 (second half) | 0.938 | 1.000 | 0.888 | 0.938 |
| post-warmup (1033–3543) | 0.951 | 0.950 | 0.913 | 0.946 |
| cumulative to step 2582 | 0.889 | 0.842 | 0.835 | 0.813 |
| cumulative to step 3543 | 0.902 | 0.885 | 0.854 | 0.851 |
The model learned the harder contrast. Emotion-contrastive accuracy rises from 0.864 to 0.938 across the two halves and sits at 0.951 post-warmup, above the older CFG families' 0.82–0.84 at the corresponding cumulative point. That is the intended shape: a pair whose two sides are both intense can only be told apart by reading the instruction.
The length-contrastive family saturated. It reads 1.000 over the second half — the task is solved and it is no longer informative, which is consistent with timing having been solved since round 2. It is contributing nothing to the gradient by the halfway point and could be dropped from a future mix in favour of more emotion-contrastive material.
And the run failed the health metric, exactly as every previous DPO run has. The
mid-run evaluation at step 2582 reported val loss 0.7277, preference accuracy 0.9805,
rew_chosen −1.0015 and rew_rejected −38.851, and the
trainer wrote "healthy": false with the annotation “policy suppresses the
CHOSEN audio” (§11.8).
Both surviving checkpoints were evaluated on the standard harness. Step 5022 is now the best model this project has produced.
| Model | Reward | WER | Emotion pct | Quality | Burst | Hit rate |
|---|---|---|---|---|---|---|
| SFT-3, no adapter | 0.4584 | 0.0987 | 0.3494 | 0.9127 | 0.3564 | 0.666 |
| + DPO, CFG corpus, step 4912 (previous best) | 0.4708 | 0.0950 | 0.3373 | 0.9235 | 0.4271 | 0.772 |
| + DPO, contrastive families, step 3906 | 0.4744 | 0.0916 | 0.3478 | 0.9231 | 0.4106 | 0.762 |
| + DPO, contrastive families, step 5022 | 0.4757 | 0.0977 | 0.3541 | 0.9208 | 0.4180 | 0.762 |
Because both this run and the CFG run use a global batch of 256 preference pairs, equal step numbers mean equal numbers of pairs seen — so step 5022 here and step 4912 there are a nearly matched comparison, and the difference is the corpus rather than the budget.
The emotion percentile is 0.3541 — the highest of any preference-tuned model in this line, and the first one above the supervised baseline of 0.3494. The two families were built to raise exactly that number and they raised it. The gain is +0.005, against a requested band of 0.90–0.98.
Both statements are true and the second one is the important one. This is the correct construction — it removes the shortcut, the model demonstrably learns the harder discrimination, and the metric moves in the right direction for the first time in four preference corpora. It moves by less than one percentage point. §11.9's first standing hypothesis predicts precisely this: a preference objective can teach a model to recognise which of two recordings matches an instruction without teaching it to produce one, and high emotion-contrastive accuracy is evidence of the former only. This run does not distinguish the two, and the experiment that would is still not instrumented.
The adapter is published as laion/moss-va-sft3-dpo-lora-p2. The remaining half epoch was never run; whether it helps is unknown.
Everything up to this point measures the model by generating audio and scoring it. That is the right way to select a checkpoint and it says nothing about why four objectives have failed on the same capability. A separate line of work therefore opened the model up: hidden states were extracted while it followed real recordings, probes were trained to read attributes out of them, and the resulting directions were injected back into generation to see whether the model could be steered. The full account is a companion page: Steering and probes. This section is the summary and the two findings that bear on the rest of the report.
One teacher-forced forward pass per corpus row — the row's own audio on the assistant side, packed byte-identically to a training example — and the mean hidden state over the tail of the assistant audio span, kept at 38 taps: the embeddings, 35 layer outputs, the final norm, and the frame-slot state of the local transformer that the twelve audio heads actually read. 387,999 rows across four extractions, 75.5 GB of float16, on the SFT-3 export with the step-5022 adapter stacked. Five probing tasks were then trained per tap and scored by macro R² on a held-out, speaker-disjoint split.
| Task | What it reads out of one layer | Best tap | Macro R² | Metadata-only baseline |
|---|---|---|---|---|
| C | all 57 VoiceNet dimensions at once | h12 | 0.8159 | 0.2042 |
| D | genuineness | h23 | 0.6685 | 0.1965 |
| E | vocal-burst blend | h22 | 0.4972 | 0.1049 |
| B | all 40 emotion scores at once | h20 | 0.4328 | 0.0507 |
| A | the score of the one dimension the clip was selected for | h14 | 0.1321 | 0.0254 |
Task A is under-specified by construction — the probe is not told which dimension it is being asked about, and 54.8 % of its target variance sits between bucket families it cannot see. Its 0.13 is a statement about the question, not about the representation, and it should not be quoted as a result. The companion page explains why in full.
All of it survives the controls: a fully speaker-disjoint split costs under 0.007 R² on four of five tasks, and stripping the emotion caption from the prompt before extraction costs at most 0.015 R² anywhere. The probes are reading the performance, not the speaker and not the prompt.
Under one probe, on one model: how a voice sounds is recoverable from a single mid-stack layer at R² 0.82, and what it feels at 0.43. Even the worst of the 57 VoiceNet dimensions reaches 0.675, while the 40 emotion heads spread from 0.181 to 0.666. Voice quality also peaks eight layers earlier and is already at 0.39 in the embeddings.
This is the first evidence for the report's running finding — that this model line controls timbre, pace and timing far better than it controls affect — that does not come from generating audio and scoring it. It is not proof of a causal story: a representation can be legible and unused, which is what §16.3 goes on to demonstrate. One further number sharpens it. On the emotion task, the final tap retains 0.262 against 0.433 at h20, so 39 % of the decodable emotion variance is gone by the time the representation reaches the acoustic decoder.
The steering vectors are differences of means: the mean activation of clips high on a dimension minus the mean of clips low on it. Comparing the emotion direction against the quality direction built the same way gives a signed cosine of −0.75 at h20 and −0.954 at the frame slot — the last state before audio codes are chosen. For VoiceNet axes the same comparison is +0.46: the opposite sign.
This is §13.1's corpus finding — the most intense recordings are on average the least genuine — restated as geometry, and it is sharper inside the model than outside it. At the point where the model commits to audio, asking for more emotion is close to asking for less measured quality. Every reward in this project asks for both at once (0.40·emotion + 0.40·quality), every merge-weight sweep scales along one of them, and this says the two terms are pulling against each other in the representation rather than merely trading off in the output. It is the strongest available argument for the quality gate §13.2 imposes on training data, and it arrives from a completely different direction.
If a direction in activation space is the model's handle on an attribute, adding a multiple of it
during generation should move that attribute. The injection is
h ← h + α·(v/‖v‖)·‖h‖, so α is the fraction of the current hidden state's own magnitude
added along the direction. 393 clip records over 27 grid cells: seven strengths on the dimension
vector, a quality vector, a quality-orthogonalised dimension vector, multi-tap stacks, three
single-tap probes, and — the control that makes the result interpretable — a random vector of
matched magnitude.
| Condition | WER | Duration error | Emotion pct | Reward |
|---|---|---|---|---|
| α = 0 — no steering | 0.094 | 0.01 s | 0.458 | 0.4925 |
| α = 0.5, best tap | 1.139 | 0.27 s | 0.422 | 0.2425 |
| α = 1.0, best tap | 1.082 | 1.27 s | 0.362 | 0.2266 |
| α = 2.0, best tap | 0.989 | 7.66 s | 0.391 | 0.2196 |
| quality vector, α = 2.0 | 1.030 | 13.76 s | 0.548 | 0.2946 |
| random vector, α = 1.0 | 2.065 | 0.04 s | 0.398 | 0.1788 |
At every non-zero strength, in both directions, the output collapses. Unsteered, the model transcribes at word error rate 0.094 and lands within 0.01 s of its requested length. At α = 0.5 the word error rate is already 1.14 — more errors than there are words. The emotion percentile does not rise at any positive strength. The probe directions are not merely noise: a random vector at α = 1.0 does about twice the damage. But no cell of the grid shows a dimension vector moving its own attribute in a way a matched random vector does not, and that is the result.
The quality vector at α = 2.0 gives the highest emotion percentile of the grid and, in the reward's terms, a perfect 1.000 on both genuineness and vocal-burst blend. Read alone, a triumph. The same clips have a word error rate of 1.030 and are 13.76 seconds away from their requested duration, and the underlying raw scores are genuineness 1.31 out of 6 and blend 1.97 out of 10 — near the bottom of both scales. The percentile mapping, built on a corpus of speech, has no idea what to do with audio that is not speech and maps it to the top. Every quality-vector cell in the grid reports exactly 1.000 on both terms, at every strength and every tap; that constancy is the giveaway.
This belongs in §18 alongside the other cases of a metric lying, and it is the most extreme of them. A steering result reported on quality scores alone, without word-error and duration controls beside it, would have been published as a win.
Three readings of the failure are consistent with the grid and none is tested: the intervention may be in the wrong place (a difference of window-averaged means is not an activation the model ever holds); the regimes may not match (extracted teacher-forced, injected during free generation, which is exactly the gap the extraction code warns about); or the discrete 13-channel audio-code output may simply be more brittle under perturbation than a text model's. The practical conclusion is narrower and firmer than any of them: activation steering is not currently a route to intensity control here.
One provenance note carried over from the companion page: the aggregated grid
artefacts under report/ hold an eight-clip smoke test, not the executed grid, and the
project's own protocol still quotes the smoke test's numbers. The tables above are aggregated from
the 393 raw per-clip records. The protocol's conclusion — that the causal claim is not
established — is unchanged and is if anything strengthened.
Every measurement in this report is of one utterance in isolation. That is the right unit for “can this model perform contempt at the 95th percentile” and the wrong unit for almost everything a director actually asks for, because direction is about change: start polite and let it curdle, come down off it slowly, angrier with every sentence. None of those is a single target percentile, none can be scored by running an instrument on one clip, and nothing in this project has ever trained on or evaluated one.
A trajectory is the smallest object that makes that measurable: an ordered list of clips from one speaker in which one measured dimension moves monotonically. Because every clip in the corpus already carries the full 40-emotion and 57-VoiceNet annotation of §6, such sequences can be mined rather than recorded. The full account is a companion page: The trajectory corpus.
| Full corpus | Public subset | |
|---|---|---|
| Trajectories | 10,653,713 | 7,638,961 (71.7 %) |
| Distinct clips referenced | 24,922,343 | 17,722,101 |
| Speakers | 1,507,064 | 1,214,241 |
| Referenced audio | 114,920 h | 74,858 h |
| Clips per trajectory, mean | 3.52 | 3.51 |
| Contiguous in the source | 59.3 % | 82.7 % |
| File size | 581 MB | 419 MB |
A row is a specification and contains no audio. Twenty-four columns: the ordered clip identifiers, the speaker they share, the dimension being walked, the distance covered and the per-step size, the monotonicity and consistency thresholds and the step count, quality bounds, total duration, whether the clips are adjacent in the source recording, and selection bookkeeping. Roughly 55 bytes per trajectory, which is why 7.6 million of them fit in 419 MB.
| Family | Rule | What it walks | Full | Public |
|---|---|---|---|---|
emotion | B1 | one emotion head, monotonically | 5,069,820 | 3,545,392 |
voicenet | VN1 | one VoiceNet dimension | 3,072,000 | 2,160,000 |
proxy_taillift | PXR | a proxy dimension into its tail | 1,260,345 | 977,981 |
emotion_twosided | AB2 | two emotions at once, in opposite directions | 1,251,548 | 955,588 |
The two-sided family is the smallest and the most interesting. A single-dimension walk asks the model to do more of something; a two-sided walk asks it to trade — and every acting note about a turn, a realisation or a change of heart is a trade rather than an intensification.
Trajectories are short: two to five clips, and the step histogram sums exactly to the row total in
both versions, so there are none longer. The public release is a clean removal of three source
datasets — podcast, snippets and evasnippets — whose
transcripts are not part of the open real-speech release. A trajectory row names exactly which
segments were used, which is a weaker disclosure than publishing the transcripts but is not nothing.
The exclusion of snippets in particular was a cautious default rather than a settled
decision, and the companion page says so.
Nothing has been trained or evaluated on it. The corpus was built and published on 28 August 2026; its value is entirely unmeasured. It is included in this report because it is the most direct available attack on the standing hypothesis of §10.11 — that the model cannot learn what an intensity word controls because it has seen too little contrast. A trajectory is contrast by construction: the same speaker, at several points on the same axis, in order. Whether rendering one as a single training example teaches anything is the obvious next experiment and has not been run.
Published as laion/moss-va-trajectory-corpus (public subset, CC-BY-4.0); the unfiltered corpus is retained privately.
This project selects checkpoints, ranks adapters and decides whether a run worked by generating audio and scoring it, never by the training objective. That is more expensive — a 320-clip evaluation costs a GPU-hour or so — and it is not a stylistic preference. It is the conclusion of six separate cases in which a number was clear, consistent, and wrong — five of them training-time metrics, and one a measurement instrument saturating on audio that was not speech.
Preference accuracy is the metric a DPO run naturally reports, and it rises monotonically in
every run here — 0.865 → 0.981 in one, up to 0.994 in another. Over the same steps
reward(chosen), the implicit reward on the preferred sequence, falls monotonically.
The round-1 model card states the consequence outright: “preference accuracy is not a health
metric here. It was highest exactly where the model was most degraded.”
And the inverse holds too, within a run. The classifier-free-guidance run of §11.5 ends with
reward(chosen) at −1.12 — three times worse than at its first evaluation — while
generating the best audio in the project at the time: lowest word error rate, highest quality, best
bursts. Had that indicator been used to select, the best checkpoint would have been discarded.
One caveat that an earlier version of this section got wrong, corrected here rather
than quietly: −1.12 is not the worst reward(chosen) in the project, and the
compressed cross-run form of this story — “the worst indicator produced the best model” —
is false. The corpus-v2 run ends at −2.30 and produced the fifth-best model. §11.8 gives all seven
runs. The lesson survives in its within-run form, which is the form in which it was actually
observed.
The three emotion adapters of §13.3 differ by a factor of four in trainable parameters and by 0.036 in emotion percentile on generation. Their validation losses all sat at 4.652–4.654, flat from step 1,225 onward and separated only in the fourth decimal. The reason is structural: the holdout is the general corpus holdout, not the extreme subset, so an adapter specialising in intense emotion cannot improve it. The loss was not noisy — it was measuring a different question.
The cleanest case is from the voice-profile rank ablation. Held-out validation loss separates the ranks monotonically — r16 < r8 < r4 — for every one of the ten voices, without exception. On loss alone you would ship rank 16 ten times out of ten, with clean, consistent, unanimous evidence.
| Base validation loss | 3.94 – 4.20 nats |
| Rank 16, stage 2 | 3.73 – 3.95 nats |
| Mean base → r16 improvement | 0.217 nats |
| Mean r4 − r16 gap | +0.0084 nats |
| Gap as a share of the gain | 3.9 % (range 3.2–5.3 % across the ten voices) |
The generation-based evaluation turns that 3.9 % into no measurable difference at all on 1,920 held-out clips per arm (p = 0.22 on speaker similarity, 0.94 on reward, 0.32 on WER). The two measurements agree on the ordering and disagree only on whether the remaining gap is worth paying for. Selecting on loss would have cost four times the adapter parameters to buy nothing measurable.
The honest limit of that claim is worth stating with it: the generation evaluation is itself a learned scorer, so what has really been shown is that the gap is below the resolution of every instrument that was pointed at it. No human was asked.
The manual reports the same failure a third time, from a different direction: at rank 64 the validation loss rose from 4.4237 at epoch 1 to 5.4369 at epoch 8, while listeners rated epoch 1 and epoch 8 identically (1.969 both).
GRPO run 5 reported a training-time emotion term of 0.441 against run 4's 0.259 — a 70 % improvement. It was not one. Two of the four changes made for that run (§12.4) inflate that number directly: the ramp pays for partial progress that the strict yardstick does not count, and the curriculum asks easier questions. On the unchanged evaluation, run 5 is the worst of the four models on emotion. A training reward is only comparable across runs if the reward function did not change; when it does, the only honest comparison is on a yardstick that did not. This is why the evaluation here deliberately keeps the plateau band score even though training uses the ramp.
The round-3 supervised model was evaluated before the zero-point percentile correction of §6.7
and the preference and RL models after it. For a period the emotion column — and through it the
composite reward — was on two different scales, and SFT-3's 0.4900 was inflated relative to the
preference model's 0.4668. Every model was re-evaluated on the identical prompt set with the
corrected scale; the superseded directory is kept under an explicit _UNCORRECTED_ECDF
name so it cannot be quoted by accident. The two files are byte-identical on every field except
four (§6.7), which is what makes the size of the distortion exactly measurable rather than
estimated.
The rule this project now works to: ablate on the output, not on the objective — and when the objective changes, keep one yardstick that does not.
The sixth case is not a training metric but an evaluation instrument, and it is the most extreme of the six. In the steering grid of §16.3, the condition that injects the quality direction at strength 2.0 reports 1.000 on both genuineness and vocal-burst blend — the top of both reward terms — and the highest emotion percentile of the entire grid. Every other cell in that column reports 1.000 as well, at every strength and every tap.
The same clips transcribe at word error rate 1.030 and run 13.76 seconds away from the length their own script asks for. The underlying raw scores are genuineness 1.31 out of 6 and blend 1.97 out of 10 — near the bottom of both scales. The percentile mapping that turns a raw score into a reward term was fitted on a corpus of speech; handed something that is not speech, it has no basis for a judgement and returns the top of its range.
The generalisable form: a learned scorer's output is only meaningful inside the distribution it was fitted on, and a percentile mapping actively hides the moment you leave that distribution — a raw score of 1.31/6 looks obviously wrong, and its percentile of 1.000 looks like a triumph. Two cheap defences caught it here, and both are structural rather than clever: a word-error control and a duration control run beside every quality number, and a constancy check (a metric reading exactly 1.000 in twelve consecutive cells is reporting a saturation, not a result).
Several of these cost node-days, and all of them are the kind of failure that looks like something else. They are recorded because the diagnosis, not the fix, is the transferable part.
In the GRPO loop each rank decided independently whether to skip a rollout group:
if len(recs) < 4: skipped += 1; continue
if r.std() < 1e-6: skipped += 1; continue
if nseq == 0: continue # skips opt.step() too
So one rank ran three backward passes while another ran two, and the gradient all-reduces went
out of step. The watchdog fired after ten minutes:
BROADCAST, NumelIn=104 … ran for 600066 milliseconds, exit code 134, and
35 GB of core files. The fix is to make the number of backward passes a constant
of the configuration rather than of the data: a degenerate group is kept with
advantage 0, every group is padded to the full group size, and padding entries are excluded from
the KL term so they contribute no gradient. Runs 4 and 5 report skipped = 0 across 176
and 210 groups.
The same failure class had already appeared once as a heartbeat failure in a preference run, and
was reproduced by hand a third time while rewriting the bucket-adapter trainer. The general form:
any per-rank continue in a data-parallel training loop is a deadlock waiting
for the right batch.
The published DPO adapter ships unmerged. merge_and_unload()
corrupts the model, because audio_lm_heads.N.weight and
audio_embeddings.N.weight are the same tensor (they are tied at load), so merging the
head delta also rewrites the embedding that produced it.
Measured: both tensors changed by exactly 6.103515625e−05 while the text
embedding changed by 0.0, and a logit-equivalence probe reported
hidden: max|diff| = 3.33, rms = 1.07e−01 against a signal rms of 2.52. That constant is
2−14 — the bf16 quantum at that magnitude — which is exactly the signature of a single
shared tensor being written once and read twice.
Three independent corroborations sit on disk: the adapter configuration targets
audio_lm_heads.0…11 and carries "ensure_weight_tying": false; every
training log reports audio_lm_heads.{0…11}.weight | MISSING in the load report,
because those tensors are not serialised separately; and at save time PEFT prints
Removed shared tensor …audio_lm_heads.3.lora_A…. The adapters are stacked at inference
instead, which is also why every adapter in §13 is trained on the supervised checkpoint rather than
on the preference model.
Padding every channel with the text pad token puts an id around 151,643 into the audio channels,
whose embedding tables hold 1,025 entries. The gather runs out of bounds and reports
device-side assert triggered — vectorized gather kernel index out of bounds
19 seconds into the first rollout, taking the ranks down one by one and looking exactly like a
hardware fault. Text and audio channels must be padded separately.
The identical bug was then reproduced twice more, in different files. Once by omitting the label masking when the batch packing was reimplemented — an 8-node job died in three minutes on every rank. And again this morning, when the bucket-LoRA trainer was launched for the first time: all 32 ranks died simultaneously at 08:51 with the same assert. It is now copied verbatim with a comment saying why it is not optional, and validated on a single-node smoke test before spending an 8-node allocation.
This one is new, from this morning, and it is the most instructive of the recent batch. After
the padding fix, the bucket-LoRA jobs restarted and produced 1,305 loss=nan
lines across two 8-node jobs before they were cancelled 25 minutes in. A single-GPU smoke
test diagnosed it in six minutes — 21 non-finite losses in 1 steps — refusing to train on
garbage — and a second smoke test caught the worse variant: a finite loss with a
non-finite gradient, reported as non-finite grad norm at step 0, step
skipped. That run then printed DONE emotion/Affection rows=5235 steps=0 17.9m.
An adapter had been trained for eighteen minutes, taken zero optimizer steps, and
reported success.
The root cause: the vendored model's own gradient-checkpointing path is broken by late binding — every layer's wrapper closes over the last layer, so the recomputed activations do not match the forward pass and the gradients come back inf or NaN. The fix is to disable the vendored path entirely and wrap each decoder layer with a non-reentrant checkpoint wrapper. This is the third distinct gradient-checkpointing failure in this project (§3.5 has the first).
Two guards now stand in the trainer as a result: a non-finite-loss counter that refuses to continue, and a non-finite-gradient check that skips the step. The second one is what turned a silent zero-step success into a visible failure — but note that it still printed “DONE”. A guard that skips is not a guard that reports.
Real-speech captions state the source clip's measured genuineness X/6 and
vocal-burst blend Y/10 — exactly the two quantities the reward's quality term
optimises. On a low-scoring source row, the model was therefore rewarded for contradicting its
own instruction. Fixed by stripping those clauses in the RL prompt only; this is not out of
distribution, because most voice-profile caption templates carry no such clause.
The most instructive of the lot, and it predates the current round. The annotated corpus shipped
for weeks with the invariant moss_frames == round(dur_s × 12.5) passing on
100 % of rows. It passed because both terms were doubled together: a
decoder bug had been flattening stereo frames, producing half-speed audio, and the duration, the
frame count, every emotion score, all 57 VoiceNet dimensions, the WER and the word timestamps were
all computed on it. An earlier verification pass had reported that 100 % as proof of
correctness.
It was found only by decoding the audio and comparing against an independent reader. The rule that came out of it is the one §6.8 and §9.1 both apply:
A consistency check cannot detect a shared-mode error. Assert the invariant on the emitted artefact, and make at least one leg of the check independent of the pipeline that produced it.
The DPO run on corpus v2 produced its best checkpoint at step 3216 — reward 0.4687, at the time
the best model in the project. The trainer's --keep-last 2 policy then rotated it away.
The evaluation directory survives, complete, which is why the number can still be quoted here; the
weights do not. That checkpoint no longer exists and cannot be republished.
The lesson was applied afterwards: the CFG-DPO run has a sibling keep/ directory
holding every checkpoint from step 212 through 4912, twenty-four of them, alongside the six
the rotating directory kept — which is why step 4912 was still on disk to be published. The
contrastive-families run (§15) rotated again, with six retained and no keep/ directory
— it kept steps 3906 through 5022, the best of them survived, and the exposure was real rather than
theoretical.
128 ranks raced to populate the trust_remote_code module cache for a directory
nobody had loaded yet. Some read a half-written source file
(cannot import name MossQwen3Model); the survivors hung in the initialisation
collective. Fixed by a single-process pre-warm before the parallel launch — three lines in the
batch script. A related environment hazard on this filesystem: concurrent cold imports of large
Python packages put every worker into uninterruptible sleep for up to 25 minutes with the GPUs
idle, so launches are staggered by 45–150 s.
One preference run intended to start from the round-3 supervised checkpoint started from round 2
instead: the batch script reads its base from one variable, a different one was exported, and
line 21 silently replaced it with the default. The log stated it plainly and that is where it was
caught. The run is kept under the name dpo_on_sft2_WRONGBASE, because it is a valid
experiment in its own right — preference tuning on SFT-2 under the round-3 prompt format. Its
telemetry is the familiar shape: preference accuracy 0.709 → 0.818 while
reward(chosen) falls −0.071 → −0.635 and reward(rejected) collapses to
−8.34, with "healthy": false on every row. It was never run through the standard
evaluation harness, so it contributes no reward or WER number to §13.3.
A second detail from it is worth keeping: it used 32 nodes, giving a global batch of 1,024 and only 184 optimizer steps for the same data, where an earlier run at 8 nodes got 864. The corrected run uses 8 nodes so the two are comparable.
restricted to 3 voices: [''] — the speaker-extraction function
had fallen through and produced a single empty string, and the job ran anyway on an 884-row pool.
It was cancelled and re-run; the published comparison comes from the second job. Even in that
second job, the “three voices” are three source corpora
(other:emolia, other:mediathek, other:refvoice), not three
verified speakers — the same fallback branch, one level less broken. The control is weaker than the
label suggests, and §13.5 says so.peft_model.active_adapter raises TypeError: unhashable type: 'list' from
deep inside the library's activation check. Two generation jobs crashed on it today before the
workaround — read and set model.active_adapters, plural — was found. This is the third
documented trap in the same attribute (§4.13 has the other two).Observed seven times in three days, and worth reproducing as a table because each row is a different tool lying in a different way:
| Tool | What it said | What was true |
|---|---|---|
| Scheduler | ARRAY DONE … rc=0 | Workers died in under a second on a mis-quoted path |
| Scheduler | 3-line log, rc=0, 5.5 h elapsed | Actually fine — indistinguishable from failure |
| Hub upload | uploaded in 145s plus a URL | 8 commits rejected |
| Hub upload | “retrying in smaller chunks” | Retrying against an unfixable per-directory cap |
| Process kill | success | 2 processes still alive |
| Process pool | tasks RUNNING, no error | 3 Python processes where 24 should be, load average 0.12 |
| Transfer client | transfer progressing | Infinite retry on “disk quota exceeded” |
The remedy that works: process tables, repository metadata, parquet footers, decoded audio, controlled probes — never the status line. It is the reason the four repositories of §14 are byte-verified after upload rather than trusted, and the reason §19.4's “DONE … steps=0” was caught at all.
The capability that is solved has an obvious and immediate use, and it is worth spelling out because it is the part of this work that is ready now.
Dubbing has a hard constraint that ordinary TTS does not: the translated line must fit the time the original line occupied. A German utterance of 4.7 seconds has to become an English utterance of 4.7 seconds, including its pauses, or the picture and the sound come apart. Conventional pipelines fight this with post-hoc time-stretching, which audibly damages the voice, or by rewriting the translation until it happens to fit.
The timed-script format solves it at the source:
floor(d × 12.5) of the original duration.| Criterion | Instrument | Answers |
|---|---|---|
| Timing fidelity | Produced frame count vs. the script's own sum | Does it fit the picture? On the current model the median error is 0.08 s and essentially every clip is inside 0.5 s, so this is usually a tie-break rather than a filter. |
| Emotional match | The 40 Empathic-Insight heads, compared clip to clip on the same head | Does the English take carry the same feeling as the German original? This comparison is safe on the corrected percentile scale precisely because it is within-head (§6.7); cross-emotion comparison is not. |
| Overall closeness in the audio–text embedding space | VoiceCLAP — cosine similarity between the 768-d embedding of the source clip and of each candidate | Does it sound like the same performance — timbre, energy, style, affect together — rather than matching one named axis? Because VoiceCLAP is a dual-tower audio↔text model in one shared space, the same encoder also lets a director's sentence be used as the target instead of a reference clip. |
Add speaker similarity (ECAPA, speechbrain/spkrec-ecapa-voxceleb) as a fourth term, and apply §5's lesson: rank on it, do not gate on it — and if you must exclude, exclude below 0.40 rather than penalising.
A note on which VoiceCLAP. The audio–text contrastive models live in the
laion organisation —
voiceclap-commercial (768-d,
CC-BY-4.0, the one the scoring stack uses),
voiceclap-small-v2, and
voiceclap-large-v2 (3584-d). The
related artefact in the TTS-AGI organisation,
audio-audio-clap-maestrino-sae-32x-k5,
is a different kind of object: a sparse autoencoder trained on 155 M CLAP embeddings drawn
from six audio corpora — 512-d in, 16,384-d hidden (32× expansion), top-k 5, with 14,127 of 16,384
features alive. It is an interpretability and retrieval tool over the embedding space, not the
embedding model itself. For a dubbing ranker you want the embedder; the SAE is what you would reach
for if you wanted to ask which few features a pair of clips differ on.
Two limits belong with this recommendation. First, the emotional-intensity ceiling of §13 means that if the source performance is at the top of the human range, the dub will land below it — the ranker will pick the closest of what the model produced, and the model does not produce extremes on command. Second, cross-lingual reference conditioning does not clone (§4.3, §8.1: mean ECAPA 0.188 against a 0.105 floor), so a German→English dub that must preserve identity needs a trained voice adapter for that speaker, not just a reference clip. Both of the newly published adapter families (§14) are relevant here: the 500 voice adapters give per-speaker identity, and the 40 emotion adapters at weight ~1.5 give the intensity lift, stacked rather than merged.
| Work | State |
|---|---|
Preference run dpo_sft3_p2 — DPO on the 2,696,421-pair corpus with the two new
contrastive families (§15) | Finished on the six-hour wall clock at step 5,126 of 10,331. Step 5022 is the new best model (reward 0.4757) and is published. The remaining half epoch was never run. |
| Emotion and voice adapters (§13.5) | Complete and published — 40 of 40
emotion, 500 of 500 voice, every one reporting skipped_nonfinite: 0. 540 buckets,
1,362,516 rows, 7.3 GB. |
| VoiceNet axis adapters (§13.7) | 15 of 17 done;
VALN_high (22,849 rows) and EXPL_high (22,378) — the two largest buckets —
still training. Per-adapter wall time runs 30 to 262 minutes. Not yet uploaded: the set goes up as
one repository when it is complete. |
| Burst adapter | One bucket, blend_genuine_emotional, 29,594 rows, training
now. Not yet uploaded. |
| Merge-weight ablation (§13.4) | Done and published as a listening page, spaces/laion/moss-va-lora-scale. |
| Activation forensics and steering (§16) | Probes complete; steering grid executed and read. 393 clip records over 27 cells. The taps (75.5 GB) and the probe weights and vectors (254 MB) are on disk and unpublished, for the reasons the companion page gives. |
| Trajectory corpus (§17) | Built, split and published. Nothing has been trained or evaluated on it. |
| Four-factor study | 1,440 prompts = 6 emotions × 5 utterances × 2 languages × 2 punctuation styles × 2 direction settings × 6 voice conditions, in 36 cells — an orthogonal measurement of what actually drives measured emotion. Launched 26 August; the smoke run reached mean emotion percentile 0.94–0.99 on three cells with zero dropped clips, and every prompt's durations were arithmetically verified to sum to its frame budget before any GPU was requested. |
The bucket-adapter trainer deliberately uses no distributed data parallelism. The work is embarrassingly parallel over buckets, so each of a node's four GPUs takes its own stream and trains independently. That removes the failure class that has cost this project the most (§19.1) and lets each process load the 8.3 GB base model once and attach / train / detach adapters in sequence instead of reloading per bucket.
Credentials. Two Hugging Face tokens were pasted in cleartext in earlier sessions, and plaintext global-write tokens sit in a reference code directory. Rotation is outstanding.
Storage. The project crossed the inode soft limit on its primary data filesystem on 26 August and writes began failing outright — presenting, unhelpfully, as a generic system error rather than as a quota message. Everything new goes to scratch. The underlying quota problem is unresolved and affects more than this project.
Jealousy_&_Envy key mismatch (§6.4) and re-score. The
raw per-clip scores are stored, so this is an offline rescore, not a regeneration. It will move
absolute emotion percentiles slightly and no rankings.Every URL below was checked on 26 August 2026. Repositories marked private returned HTTP 401 to an anonymous request and are listed because they are the authoritative location of an artefact this report cites, not because they can be opened.
| Stage | Repository |
|---|---|
| Base checkpoint (OpenMOSS) | huggingface.co/OpenMOSS-Team/MOSS-TTS-Local-Transformer-v1.5 |
| Audio tokenizer, 12 codebooks @ 12.5 fps → 48 kHz | huggingface.co/OpenMOSS-Team/MOSS-Audio-Tokenizer-v2 |
| v1 — DramaBox full FT (internal) | huggingface.co/TTS-AGI/moss-dramabox-ft — private |
| v1 — public merged release (base FT + rank-256 LoRA) | huggingface.co/laion/moss-tts-local-transformer-4.55b-voice-acting |
| v2 — the six-dataset production model | huggingface.co/laion/moss-tts-local-transformer-4.55b-voice-acting-v2 |
| v2 rolling checkpoints | huggingface.co/TTS-AGI/moss-voiceacting-ft-checkpoints — private |
| Round-1 SFT (full fine-tune) | …-v2-sft |
| Round-1 SFT + DPO, full parameter | …-v2-sft-dpo |
| Round-1 DPO LoRA (ships unmerged — §19.2) | …-v2-dpo-lora |
| Round-3 SFT — published 26 Aug | huggingface.co/laion/moss-tts-local-transformer-4.55b-voice-acting-v2-sft3 |
| CFG-DPO adapter, step 4912 — published 26 Aug | huggingface.co/laion/moss-va-sft3-dpo-lora |
| Contrastive-families DPO adapter, step 5022 — the current best, published 28 Aug | huggingface.co/laion/moss-va-sft3-dpo-lora-p2 |
| 8 B delay-architecture sibling (24 kHz) | huggingface.co/laion/moss-tts-v1.5-8b-voice-acting |
| 40 emotion LoRAs on SFT-3, rank 16 — published 26 Aug | huggingface.co/laion/moss-va-sft3-emotion-loras |
| 500 voice LoRAs on SFT-3, rank 16 — published 26 Aug | huggingface.co/laion/moss-va-sft3-voice-loras |
| 40 emotion LoRAs on v2, rank 32 (v3 line) | huggingface.co/TTS-AGI/moss-emotion-loras-v3 |
| Emotion LoRAs v1 · v2 · the 40-emotion sweep · the filtered buckets | TTS-AGI/moss-emotion-loras · -v2 · -40 · moss-emotion-filtered-buckets — all private |
| Voice-profile LoRAs — 10 pilot voices, rank ablation | huggingface.co/TTS-AGI/moss-voice-profile-loras |
| Voice-profile LoRAs — 500 production voices, rank 4 | huggingface.co/laion/moss-voice-profile-loras-500 |
| 114 VoiceNet dimension LoRAs (57 × high/low) | huggingface.co/laion/moss-voicenet-dimension-loras |
| 64 vocal-burst LoRAs | huggingface.co/laion/vocal-burst-lora-adapters |
| Voice-acting LoRA (rank 256, 100 k + 1 M) · inline-burst training data | TTS-AGI/moss-local-transformer-voice-acting · TTS-AGI/moss-inline-vocal-bursts — both private |
| Character LoRAs — refined / genuine, public mirrors | …-refined-public · …-genuine-public |
| Character LoRAs — source repositories | TTS-AGI/moss-character-loras-genuine · -refined · moss-12-cluster-character-loras — all private |
| Explicitness LoRAs (public + age-gated) | huggingface.co/TTS-AGI/moss-explicitness-loras |
| German broadcast · sports commentary | laion/moss-mediathek-emotion-lora · laion/moss-sports-commentator-lora |
| VoiceCLAP-commercial — the shared 768-d audio↔text embedder | huggingface.co/laion/voiceclap-commercial |
| VoiceCLAP small · small-v2 · large-v2 (3584-d) | voiceclap-small · voiceclap-small-v2 · voiceclap-large-v2 |
| Genuineness predictor (0–6) | huggingface.co/laion/voiceclap-commercial-genuineness · large sibling voiceclap-large-v2-genuineness |
| Vocal-burst blend predictor (0–10) | huggingface.co/laion/voiceclap-commercial-vocalburst-blend |
| Earlier-generation genuineness / blend heads (slots 97 & 98 of the 99-vector scorer) | laion/genuineness · laion/vocal-burst-blend — both private |
| Empathic-Insight-Voice — 40 emotion + 15 attribute experts | huggingface.co/laion/Empathic-Insight-Voice-Small |
| Empathic-Insight-Voice-Plus — the same plus 4 audio-quality experts | huggingface.co/laion/Empathic-Insight-Voice-Plus |
| BUD-E-Whisper — the frozen encoder both suites read | huggingface.co/laion/BUD-E-Whisper |
| VoiceNet 57-dimension regressor + classifier (114 heads) | huggingface.co/laion/voicenet-dimension-predictors-commercial |
| …its Gemini-3.5-flash label set | huggingface.co/datasets/laion/emolia-voicenet-gemini-annotations |
| Vocal-burst locator · detector v2 | laion/vocalburst-locator · laion/vocal-burst-detector-v2 |
| Speaker embedding used for identity | huggingface.co/speechbrain/spkrec-ecapa-voxceleb |
| Sparse autoencoder over 155 M CLAP embeddings (32× expansion) | huggingface.co/TTS-AGI/audio-audio-clap-maestrino-sae-32x-k5 |
All audio on these pages is model-generated; none of it is source recording.
| Round-3 evaluation grid — nine models × 80 prompts × 4 completions, with ASR transcripts | huggingface.co/spaces/laion/moss-va-sft3-samples |
| Emotion LoRAs vs baseline — 17 adapters, matched and neutral prompts | huggingface.co/spaces/laion/moss-va-emotion-loras |
| Merge-weight ablation — 31 emotion adapters over six weights (§13.4) | huggingface.co/spaces/laion/moss-va-lora-scale |
| Round-2 baseline grid (timed script, no directions) | huggingface.co/spaces/laion/moss-va-sft2-samples |
| Round-1 grid (base / SFT / SFT+DPO / ground truth) | huggingface.co/spaces/laion/moss-va-sft-samples |
| This report, as a hosted page | huggingface.co/spaces/laion/moss-va-technical-report |
| Annotated voice-profile corpus — 28,212,933 rows, 5.8 TB | huggingface.co/datasets/laion/laion-voice-profiles-annotated |
| Voice-profile SFT — 1,200,531 rows | huggingface.co/datasets/laion/laion-voice-profiles-sft |
| Real-speech SFT, EN/DE — 1,947,272 rows | huggingface.co/datasets/laion/tts-realspeech-sft-en-de |
| Voice-profile DPO — 3,451,531 pairs | huggingface.co/datasets/laion/laion-voice-profiles-dpo |
| Real-speech DPO, EN/DE — 3,959,192 pairs | huggingface.co/datasets/laion/tts-realspeech-dpo-en-de |
| Trajectory corpus, public subset — 7,638,961 specifications, no audio (§17) | huggingface.co/datasets/laion/moss-va-trajectory-corpus |
| Trajectory corpus, full — 10,653,713 rows | huggingface.co/datasets/TTS-AGI/moss-va-trajectory-corpus-full — private |
| Voice-profile reference clips | huggingface.co/datasets/TTS-AGI/moss-voice-profile-references |
| Game-character corpus, 750,242 rows with full VoiceNet + emotion annotation | huggingface.co/datasets/scientifi-papers/chavo-annotated |
| Designed character takes, ≈38,400 | huggingface.co/datasets/laion/moss-character-voices-top3-captioned |
| Gemini-generated named-character voices (30 × 1,000) · additional voice-acting clips (31,306) | TTS-AGI/gemini-adult-voices · TTS-AGI/additional-data-a1-plus — both private |
| The voice-acting manual — 25 chapters, 40 emotion pages, 12 recipe chapters | projects.laion.ai/moss-voiceacting-manual/site/index.html (mirror: laion-ai.github.io/moss-voiceacting-manual) |
| The manual's emotions chapter | …/site/emotions/index.html |
Model home — inference, prompting guide, demos (default branch master) | github.com/LAION-AI/laion-moss-local-1.5-voice-acting-4.55b |
| This report, and its two companion pages | technical-report.html · steering-and-probes.html · trajectory-corpus.html |
| Experiment overview · the 13-voice showcase · the reinterpretation corpus | overview.html · voices.html · reinterpretations_25.html |
| 40-emotion evolution · prompt × LoRA study · edge-case evolution | moss-emotion-evolution-all · moss-emotion-prompt-lora-study · moss-emotion-edgecase-evolution |
| VoiceNet-LoRA evolution · explicitness dose grid | moss-voicenet-lora-evolution · moss-explicitness-grid |
| Autonomous LoRA + prompt search agent | github.com/LAION-AI/voice-acting-search-agent |
| Expressive best-of-N method | github.com/LAION-AI/chatterbox-voice-conversion/tree/main/expressive_bestofn |
| Manual source · VoiceNet taxonomy | LAION-AI/moss-voiceacting-manual · LAION-AI/voicenet |
| Canonical pipeline documentation (docs/01–23 + LEARNINGS) | github.com/LAION-AI/Voice-Acting-Pipeline-WIP — returns 404; much of Part I's measured detail is sourced from it via distilled run notes |
corpus_x — 398,282 selected
rows, materialised at 401,345 including the validation holdout. 247,049 is the emotion-LoRA
corpus of §13.2, built the same evening by a different selection script. Corrected in §10.5.cfg_high and cfg_low are not “balanced to within 0.1
percentage points” — they are exactly equal at 237,209 rows each, which is
what a correct flipped-role duplication must produce and is a stronger check than the approximate
one. Corrected in §11.5.rew_chosen was worst produced the best model”
is false. It is a tempting compression of a real within-run effect and the seven runs refute
it: the worst final rew_chosen (−2.3037) belongs to the corpus-v2 run, whose best
checkpoint ranks fifth, while the best model comes from the run with the second least
negative value. The supported claim is the within-run one. §11.8 gives both and says which is
which.report/steer_grid.parquet holds one grid row and eight clips; the real 27-cell,
393-clip grid ran the following evening into raw per-clip records and was never re-aggregated. The
running protocol still quotes the smoke test's numbers, which point the same way as the full grid
and reach the same conclusion by a different route: the protocol's figures show a random vector
raising the emotion metric more than the real direction, the full grid shows both falling
below the unsteered control. Either way the causal claim is not established. §16.3 uses the full
grid.vp_emotion_no_annotation, where the
build's plan file calls that count vp_emotion_dur_missing and puts
no_annotation at 2,981. §11.4 uses the plan file.moss-emotion-loras-v3 set is rank 32 / α 64 throughout; the
40-emotion sweep that preceded it chose the best of ranks {16, 32, 64} per emotion; the
new SFT-3 adapters of §14 are rank 16 / α 32. These are three different artefacts.our_* half of the six is the best-of-N-filtered
self-generated material the card describes and the prep_* half is prepared external
material, and the settings match line for line (8×GPU DDP, caption dropout, reference dropout,
linear LR with warmup, channel-wise loss weighting). §3.3 presents the union. The one point the two
do not settle jointly is whether the checkpoint the six-dataset run started from is byte-identical
to the public v1 release or to its unmerged predecessor.<moan>, <gasp>. Follow the
prompt-notation chapter.(Scream), (Gasp), (Chuckle). The manual says why:
the rule was written in a later round and nothing enforced it on the 808 texts that already existed.
The same defect shipped into the 500-voice build, where 135,580 of 135,630 burst tags
(99.96 %) are Title-Case — and Title-Case tags are spelled out letter by letter rather than
performed.Authenticity,
Arousal, Recording_Quality and Background_Noise, which the
prose table does not list, while the prose lists Talking Speed, for which no checkpoint
ships. The file listing was trusted.GEND runs feminine → masculine, BKGN runs
noisy → clean). The numeric columns were always correct; only the generated prose inverted. Anyone
who trained on the caption text from that period learned the opposite of the truth for those two
attributes.laion organisation, not in
TTS-AGI, which is worth stating because it is an easy assumption to make.
TTS-AGI holds a sparse autoencoder trained over CLAP embeddings — a different
object, described in §18.LAION-AI/Voice-Acting-Pipeline-WIP and
LAION-AI/voice-acting-swarm-artifacts both return 404. The model repository's default
branch is master, not main, so raw links built with main will
404 as well.The previous draft contained assertions that could not be traced to a source. They are listed here rather than silently deleted, because a reader who saw that draft should know what changed.
| Claim in the previous draft | Status |
|---|---|
| Training details asserted for the vocal-burst blend predictor beyond its head architecture and validation table | Removed. The card publishes no training-set size, no label source and no split description. §6.3 now says so explicitly. |
“The [DPO] model card says why: merge_and_unload() corrupts the
model.” | Corrected. No such warning exists in any of the four cards. The measurement is real; the card does not carry it (§14, §19.2). |
| “Jealousy and Envy reads 0.000 everywhere because that head sits at the very bottom of its own distribution.” | Corrected. The real cause is a key mismatch —
Jealousy_&_Envy in the predictor repository against
Jealousy_and_Envy everywhere else — so the head is never read (§6.4). |
| “There is no trend” presented as the merge-weight result | Scoped. True of the single general adapter at the intense band; false of the per-emotion adapters at the extreme band. Both are now reported (§13.4). |
| VoiceCLAP-large-v2 “scoring better on every benchmark reported” against “the small model”, quoted next to VoiceCLAP-commercial | Clarified.
The 0.7069 vs 0.6754 comparison on that card is against voiceclap-small, not against
voiceclap-commercial. |
| In-flight job states (“31 of 40 adapters written”, “268 adapters written”, “138,088 pairs”) | Superseded. 40 emotion and 500 voice adapters are finished and published; the pair build that shipped is 368,517 rows (§14, §15). |
| The 17 per-emotion adapters described as if the set were chosen | Corrected. It is where a sharding bug landed; 40 exist (§13.5). |
| “All prompts drawn from the same three speakers” | Weakened. They are three source corpora; the speaker-extraction function fell through to a corpus-prefix fallback (§13.5, §19.10). |
| “CFG-DPO is the best model this project has produced” | Superseded on 28 August. True when written; the contrastive-families adapter at step 5022 beats it on reward and emotion percentile (§15.4). The CFG checkpoint remains the best on burst realisation and hit rate. |
| Preference corpus v1 given as 236,656 pairs | Corrected to 236,166 against the shards and the build log (§11.3, §23.2). |
| Round-3 supervised fine-tuning described as using 247,049 rows | Corrected. That is the emotion-LoRA corpus; round 3 used corpus_x
(§10.5, §23.2). |
A general-purpose 4.55-billion-parameter TTS model was turned into a voice-acting model by two full fine-tunes — one on distilled director-instruction data with a rank-256 adapter merged in, one on six datasets of prepared and self-generated material, with the released checkpoint chosen by scoring generated audio rather than by validation loss — and then surrounded by roughly a thousand LoRA adapters for emotions, characters, voices, timbre dimensions and vocal bursts. The current round gave that model a prompt format stating, per sentence, how long it may take, where the silences are and how long each vocal burst lasts. That worked: median duration error 0.08 s, essentially every clip within 0.5 s, and adding the format also fixed a word-error collapse (0.447 → 0.099) and improved burst realisation. The same round tried to make the model perform an emotion at a requested intensity, and four objectives — supervised fine-tuning on an extreme subset, preference tuning on contrast pairs, preference tuning on symmetric instruction-conditioned pairs, and group-relative reinforcement learning — all failed to move it. The reason for the RL failure is measured and structural rather than a matter of tuning: at the intensity being asked for, every completion in a group scores the same near-zero value, and a constant term contributes nothing to a group-normalised advantage. What has moved the number is data, dose and a better contrast: a small adapter trained on the most intense and most genuine 8 % of the corpus (0.3572 against a 0.3494 baseline); overdriving the per-emotion adapters to weight 1.5, which lifts emotion percentile from 0.408 to 0.471 across 31 adapters with the best word error rate in the sweep and no cost in timing; and a preference corpus whose contrasts pit intense against intense so that generic expressiveness wins nothing, which produced the best model in the project (0.3541, and the first preference tuning here that raises intensity while also lowering word error rate). All three remain far from the 0.90–0.98 that was asked for. Looking inside the model rather than at its output found two things consistent with the failure and neither sufficient to explain it: emotion is roughly half as linearly legible in the activations as voice quality is and loses 39 % of that legibility before the acoustic decoder, and the internal direction meaning “more emotional” runs close to opposite to the one meaning “more genuine”; steering along those directions destroyed the output at every strength tested. Meanwhile the older LoRA-merging agent produces more emotional performances than any of this, and is not reliable on character consistency. Every one of those statements is the output of a learned scorer, and no human has listened.
Written 26 August 2026 and expanded on 28 August from the project's running experimental protocol, the daily status records, the on-disk evaluation summaries, training logs, build logs and corpus shards on JUPITER, the model cards published on 26 and 28 August, the model cards of every external instrument (fetched and read for this report), the distilled run notes covering the pre-JUPITER fine-tunes, and the public voice-acting manual. Every figure quoted has a named source in one of those. Where the brief for this report and the sources disagreed, the sources won and the disagreement is recorded in §23.2 — eight such disagreements were found and corrected during the 28 August expansion, including two of this project's own published numbers. Companion pages: steering and probes · the trajectory corpus. Claims that could not be sourced were removed and are listed in §23.3. Contact: LAION.