Silencio-2 is a tiny English TTS (18M generation parameters) with inline emotion and sound tags. It was trained on too little tagged data, so most emotion tags were unreliable. Reading the weights showed why, and a rank-1 edit per layer fixed most of it, with zero gradient steps.
<whisper> takes that actually whisperAll clips come from the held-out test set: lines, seeds and two of the three voices were never used while building the edit. Same text, same voice, same seed, only the weights differ. Clips are loudness-normalized the same way generate() does by default. Every take, failures included, is in the explorer.
Not every whisper works. The worst of the 24 test takes, voice 260 / "You kept the letter all these years." / seed 11, comes out as barely audible mumbling. Hear it in the explorer.
<sad> from "sad", <whisper> from "whisper") and still has 60–80% cosine similarity to it. Making the rows longer would not help: a LayerNorm sits before attention, so only the direction counts.<happy> 100–3000× more than to an average word. For the other tags it is only 1–10×.<whisper> directly in the activations (×30, all heads) produced a whisper in 62% of takes with clean words. ×100 broke speech. Boosting only the three heads that read tags the most did almost nothing.Each of the 8 layers gets one rank-1 term added to its key projection:
<happy> and the sound tags are left untouched.That changes 524k weights (3.4% of the transformer, all inside in_proj), but the edit has only about 5k free parameters. Every variant was tried on a dev split and kept only if it improved. The final one was checked once on the test split.
| Held-out test, 168 emotion takes | Release | Edit |
|---|---|---|
<whisper> actually whispers | 5/24 | 22/24 |
| …and the words are clean | 5/24 | 10/24 |
| Emotion separability, 7 classes | 0.446 | 0.714 |
| Consistency: sad / angry / fear / surprised / whisper | .08 / .05 / .07 / .06 / .09 | .13 / .10 / .15 / .19 / .27 |
| WER, untagged speech | 0.006 | 0.006 |
| WER, emotion takes (mostly whisper) | 0.002 | 0.064 |
| Sound tags: WER around them / sound length | 0.007 / 0.81 s | 0.014 / 0.84 s |
How these were measured. WER comes from Whisper base. A take counts as a whisper when less than 40% of its loud frames are voiced (pyin). Separability is a nearest-centroid classifier on Whisper-base encoder features, after subtracting the mean of each (voice, line, seed) group, holding out whole groups. Consistency is how much the tag moves takes in the same direction (|mean change|² / mean |change|²). The dev split shows the same picture: whisper 0/24 → 24/24, separability 0.35 → 0.61.
generate() raises quiet takes to speech level by default, so keep normalize=True.<surprised> went up about 4 semitones and can sound over the top.3 voices × 4 lines × 2 seeds × 7 classes. "No tag" is the same line through the edited model, to check that plain speech did not change.
pip install -r requirements.txt # from the Hugging Face repo
from silencio2 import Silencio2TTS
tts = Silencio2TTS.from_pretrained("BlueRoseLabs/Silencio-2", revision="weightedit")
tts.save("out.wav", "<whisper> Keep your voice down.", voice="260")