Building a 240-million-sample voice-acting corpus

The plan, the mechanics, and what it costs — measured on this hardware, against the actual compute budgets.

What this is, in plain language

A text-to-speech model can be asked to say a sentence angrily, quietly angry, very fast, with a lot of vocal fry. This project builds the training data for that, systematically: for each of 6,000 distinct voices it renders every cell of a 832-condition grid, in English and German, and stores every sample with full measurements attached.

The end goal is a speech model that can be steered — and, further out, a speech-to-speech translation model that keeps the speaker and the emotional level across languages. That second goal is why every text exists as an EN/DE pair of the same sentence.

samples
240 M
6,000 voices × 832 conditions
audio
523k h
at 7.86 s mean take length
compute
15.5 M
core-hours, speech generation
of budget
15 %
of both budgets combined

Hear what a "condition group" actually is

Everything below is counted in "condition groups" and "samples", which means nothing until you have heard one. Three complete voice profiles are published — the same reference voice rendered through all 832 conditions, with the winning take of each group playable and its scores shown:

Compare A against RW on the same emotion — Bitterness or Contempt are the clearest — and the argument for building the corpus this way stops being a table of numbers. In A the speaker is reciting a neutral sentence about a gym membership in a bitter tone of voice. In RW the sentence gives them something to actually be bitter about.

What comes out at the end, concretely

For an outsider: the deliverable is not a model. It is a dataset — roughly a quarter of a billion short speech clips, each one labelled with what it was asked to be and measured for what it actually turned out to be. Somebody else then trains on it.

Each row of that dataset is: a voice (one of 6,000 reference speakers), a sentence (English or German, paired with its counterpart in the other language), a condition (one of 832 — "intense, freely expressed anger", "very high vocal fry", "a fearful gasp mid-sentence"), the audio, and about a hundred numbers describing the result. Nothing is thrown away: the takes that scored badly are stored too, because a later change of mind about what "good" means should never require regenerating anything.

Two subsets, built for two different jobs

The corpus is deliberately split. The two halves teach opposite things, and a model needs both.

Subset A — 32 samples per condition, every one a different sentence

For each of the 832 conditions, 32 samples are generated from 32 different texts, each written so its content justifies that exact condition. This is the breadth half: it stops the model from associating "anger" with one particular sentence, and gives a pre-training corpus real lexical variety.

Why this matters more than it sounds. A controlled experiment on this pipeline showed that a dozen emotions produced almost no measurable emotion at all — Bitterness sat at −0.005 — not because the conditioning was broken but because the sentences, drawn from a translation corpus, gave the speaker nothing to feel. Rewriting the text so its content carries the emotion raised emotion strength +22 %, genuineness +27 % and vocal-burst blend +39 %, with audio quality flat and word error rate down 12 %. Text is the single most effective lever measured on this system, so the corpus is built around it.

Subset B — 16 samples per condition, all sharing one text

For each of the 125 parent groups (40 emotions, 57 VoiceNet dimensions, edge cases, character clusters, sports, explicitness), 16 texts are each rendered through all four sub-conditions. For Anger that is intense/free-flowing, intense/contained, moderate/free-flowing and moderate/contained — 16 × 4 = 64 samples per language, the same words every time.

This is the controllability half. Holding the sentence fixed and varying only the intensity gives a model minimal pairs: the only thing that differs between two samples is the dimension you want to control. That is exactly what a later fine-tune needs in order to learn "say this same line, but angrier" — and it is impossible to learn from subset A, where text and condition vary together.

Where the texts come from

Every text is written by a Gemma model before any speech is generated, and every one is seeded rather than invented from nothing — a model asked to write freely collapses each emotion onto its most convenient topic (every bitterness line about a betrayal), destroying the domain balance the corpus is supposed to have.

hard domain (1 % each)what breaksseed keywords
Mathematics & formulaeequations, Greek letters, operators, fractions, exponents, sub/superscriptsα, ∑, x², 3/4, ∫, ≠, ±, matrix, derivative, lemma
Numbers, units & measureslarge numbers, decimals, ordinals, ranges, currencies, units1,250,000, 3.14, 21st, 1990-1995, €4.99, 37 °C, 5 km/h
Dates, times & durationsformats that expand differently depending on locale and context3/4/25, 1900-1945, 14:05, Q3, 2h30, fortnight
Abbreviations & acronymsletter-by-letter vs word-like, and the ones read as their expansionNASA, e.g., i.e., Dr., GmbH, z.B., ISO 9001, PhD
Homographs & heteronymssame spelling, different pronunciation, disambiguated only by senseread/read, lead/lead, live/live, bow, tear, German 'ubersetzen', 'Modern'
Proper nouns & loanwordspersonal, place and brand names, including foreign-origin spellingsXiaomi, Worcestershire, Nguyen, Reykjavik, Bourgogne, Oaxaca
Code-switching EN↔DEa mid-sentence language switch -- directly load-bearing for this project's goal'Ich habe das feature schon deployed', 'the Umlaut is tricky'
URLs, emails, paths & codestrings that are not words and must not be read as wordshttps://a.io/x?q=1, snake_case, C:\\Users, <div>, npm i -g
Slang, profanity & net registerregisters a filtered web corpus under-represents, including censored formsswear words, f***, lol, innit, Digga, emoji-adjacent text
Technical, medical & legal jargonlong low-frequency terms with irregular stressacetylsalicylic acid, §823 BGB, pneumonoultramicroscopic, myocardial infarction
Caveat on that list, stated rather than hidden. It is assembled from the standard non-standard-word taxonomy used in text-normalisation work and from known failure modes — not from a fresh literature review, because the web search tool was unavailable when this page was written. The categories are uncontroversial, but the specific choice of ten and their relative weighting deserve a review before committing compute.

Texts are written to a target length, on purpose

This is not cosmetic — it is worth real money, for a reason specific to how batched generation works. See the batching section.

Why batching cares about sentence length

Speech is generated autoregressively in batches of 64. A batch runs until every sequence in it has finished, so one long take holds up the other 63. Today all 64 samples in a batch are the same sentence, so they finish together. Give each sample a different text and the batch waits for the longest one.

Measured on 25,850 real takes (mean 7.86 s against a 32 s generation cap, so takes genuinely do stop early — if they all ran to the cap this would cost nothing):

batch compositionexpected longest take relative cost
64 × the same text (today)12.46 s1.00×
64 × different texts, unsorted23.20 s1.86×
64 × different texts, sorted by word count16.77 s1.35×
+ texts written to a target length (estimate) ~1.15×

Different token counts per sample cost almost nothing on their own — that only affects prefill, one forward pass, against ~96 sequential decode steps. The entire penalty is the spread in durations. Which is why the text generator is asked for a target length: it turns a 1.86× tax into something near 1.15×.

And per useful sample, diverse texts are far cheaper, not dearer. Today a group spends 32 generations to yield one selected take. Under this plan those same 32 generations yield 32 distinct usable samples. That is roughly 17× less compute per usable pre-training sample, at a ~35 % batching premium. The trade is that each sample is a single attempt rather than the best of 32, so per-clip quality sits at the distribution's mean instead of its maximum — the right trade for pre-training breadth, the wrong one for a showcase.

Vocal bursts, placed deliberately

A gasp, a sigh, a shaky breath, a laugh. There are 64 trained burst adapters, and the corpus should contain them inside sentences rather than as isolated sound effects.

The measured recipe is specific, and it constrains the design:

burst adapter doseburst occurs rest of the line survivesblendWER
none (inline tag in the script only)21.9 %1.006.630.05
λ = 0.50 ← the operating point50.3 % 0.904.930.15
λ = 1.0072.1 %0.454.070.57

At full merge more than half the words after the burst are never spoken. λ = 0.5 is the measured optimum for "burst and sentence and blend".

Design rules that follow:

A consequence that has to be accepted up front. With one take per text and no best-of-N selection, a tagged burst fires about half the time. So of the 50 % of sentences tagged, roughly 25 % of subset A — about 40 M samples — will actually contain a burst, and the rest will be ordinary speech whose script mentioned one. That is still an enormous burst corpus, but the metadata must record requested and detected separately, or every downstream user will mistake the tag for a label. The manual's 4–8-candidate selection recipe reaches ~97 %, at 4–8× the cost; it is the right choice for a curated subset, not for all 160 M.

Every burst class, equally often

The obvious way to assign bursts — let each emotion draw the classes that fit it — produces a wildly skewed corpus. Sighs and gasps fit almost every emotion and would appear constantly; wolf_whistle, hiccup or gurgling fit almost none and would nearly vanish. A class with a few thousand examples is not learnable next to one with several million.

So the allocation is inverted: the target count per class is fixed first, and emotion-fit only decides where each class's quota gets spent.

  1. Fix a global budget of burst-carrying samples and divide it equally: 64 classes → 0.63 M samples each.
  2. For each class, take the set of emotions it is plausible in, and spread that class's quota across them — rather than letting each emotion pick freely.
  3. Where a class fits only one or two emotions, it still gets its full quota; it simply concentrates there. Coverage of the class is what matters, not its spread across emotions.
All 64 shipped classes qualify. In the manual's evaluation every one of the 64 scored 2 on the blind Gemini judge — the same score as the real recorded audio control, against 0.667 for the base model with no adapter. There is no subset of "classes that work"; they all cleared the bar, so all 64 are in the balanced rotation.

Two caveats carried over from that evaluation: training-set sizes are very uneven (75 items for swallows against 1,396 for the largest, median 178), and 19 of the 64 were scored without a blind metric. Equal representation in the corpus is therefore not a claim of equal quality — per-class burst-detection rates should be measured on the output and published alongside, so a downstream user can weight them.
Order the work queue by adapter, not by condition. Each sample needs an emotion adapter and possibly a burst adapter. With 40 emotions × 64 burst classes, a naive ordering thrashes the adapter cache — and adapter eviction has already cost this project four failed runs. Sorting the queue by (emotion adapter, burst adapter) loads each combination once.

What it costs

1. Writing the texts

modelsubset A textssubset B poolGPU-hcore-hshare of total job
Gemma 4 E4B79.9 M pairs200k pairs3,767271,2221.7 %
Gemma 12B bf1679.9 M pairs200k pairs11,301813,6655.0 %
Take the 12B. Going from E4B to 12B costs an extra 542,443 core-h — 3.3 % of the whole job. Text generation is a rounding error against speech generation either way, so there is no reason to economise on the component that determines what every sample actually says. Use E4B only if a side-by-side shows the two are genuinely indistinguishable on this task.

The E4B figure (1,299 tok/s at batch 128) is measured on one GH200. The 12B figure (433 tok/s) is extrapolated by FLOP ratio and is the weakest number on this page — but since the whole line item is ~4 % of the job, even a 2× error changes nothing.

2. Generating the speech

batching strategysubset A GPU-hsubset B GPU-htotal GPU-hcore-h
naive (no bucketing)198,08399,041297,12421,392,916
word-count bucketing143,77071,885215,65415,527,117
length-controlled + bucketing122,47061,235183,70613,226,803

3. GPU-hours by condition block

Speech generation only, all 6,000 voices, both subsets. Cost is proportional to sample count, so it follows the block sizes directly — the VoiceNet block is the expensive one simply because it is the biggest.

blockwhat it isgroupssamples (6,000 voices)GPU-hshare
Emotions40 emotions x {intense, moderate} x {free, contained} x {EN, DE}32092.2 M82,94438.5 %
VoiceNet dimensions57 dimensions x 4 levels x {EN, DE}456131.3 M118,19554.8 %
Edge casesscreams, groans, shivers, laughter, whimpering, crying288.1 M7,2583.4 %
Character clusters12 character LoRAs x {EN, DE}246.9 M6,2212.9 %
Sports commentatorx {EN, DE}20.6 M5180.2 %
Explicitnessx {EN, DE}, behind an age gate20.6 M5180.2 %
total240 M215,654100 %

4. GPU-hours by scale

The same thing per number of reference voices. These are GPU-hours; JUPITER bills whole nodes, so multiply by 72 for core-hours (288 core-h per node-hour, 4 GPUs per node).

voicessamplesaudiotext GPU-hspeech GPU-htotal GPU-hcore-hwall-clock @64 nodes
1004.0 M9k h1883,5943,783272,3460.6 days
50020.0 M44k h94217,97118,9131,361,7322.9 days
1,00039.9 M87k h1,88335,94237,8262,723,4645.9 days
6,000239.6 M523k h11,301215,654226,95516,340,78135.1 days

5. Against the budgets

scenario (speech + 12B text)core-h% of REFORMO left% of LAIONIZE left% of both
naive (no bucketing)22,206,58132.3 %59.1 %20.9 %
word-count bucketing16,340,78123.8 %43.5 %15.4 %
length-controlled + bucketing14,040,46820.4 %37.4 %13.2 %

Planning on the middle row — word-count bucketing, which is measured rather than estimated — the whole corpus costs 16,340,781 core-h, about 15 % of the two budgets combined. REFORMO expires 2026-12-31 and LAIONIZE 2027-04-30, so the split between them is a scheduling question, not a feasibility one.

nodes dedicatedwall-clock
32 nodes (×4 GPUs)70 days
64 nodes (×4 GPUs)35 days
128 nodes (×4 GPUs)18 days
256 nodes (×4 GPUs)9 days

6. Starting smaller — what a partial build costs

Nothing forces all 6,000 voices at once. The matrix is per-voice, so the corpus can be built in voice batches and stopped at any point with a complete, usable result. Every column below is the full 832-condition matrix and both subsets, priced with word-count bucketing and the 12B text model.

reference voicessamplesaudiotext GPU-hspeech GPU-htotal core-h% of both budgetsstoragewall-clock @64 nodes
1004.0 M9k h1883,594272,3460.26 %0.6 TB0.6 days
50020.0 M44k h94217,9711,361,7321.28 %3.1 TB2.9 days
1,00039.9 M87k h1,88335,9422,723,4642.56 %6.3 TB5.9 days
6,000239.6 M523k h11,301215,65416,340,78115.38 %37.7 TB35.1 days
The sensible first step is 100 voices. It costs 272,346 core-h — about 0.26 % of the budget, roughly 0.6 days on 64 nodes — and produces 4.0 M samples, already a substantial corpus. It also surfaces the things that only appear at scale: adapter-cache thrashing, length-bucket drift, and whether the hard-domain texts actually come out usable. Scaling from there is linear and carries no new risk.

7. Storage

At 160 kbps and 48 kHz — the quality the corpus is now written at — 240 M samples of 7.86 s come to about 38 TB of audio, plus metadata. Dropping to 128 kbps saves roughly a fifth. It is stored as WebDataset tar shards plus parquet, never as loose files: at this scale loose files would mean a quarter of a billion objects on a shared filesystem.

Why 48 kHz matters here. Until this week the pipeline resampled every take to 16 kHz before storing it, band-limiting the whole corpus to 8 kHz, and the demo pages then re-encoded that to 40 kbps. The model generates at 48 kHz; the corpus now keeps it. Committing 16,340,781 core-h to a corpus and then throwing away two thirds of its bandwidth on the way to disk would have been the single most expensive mistake available.

What you have at the end

Decisions already settled by measurement

Open risks


Throughput measured on GH200: 1,500 generations/GPU-h at batch 64 (6,016 candidates in 4.01 GPU-h); node billing 72 core-h per GPU-hour. Budgets as of 2026-08-10.