| --- |
| license: other |
| license_name: fish-audio-research-license |
| license_link: https://github.com/maxmelichov/fish-speech/blob/main/LICENSE |
| language: |
| - he |
| - en |
| tags: |
| - text-to-speech |
| - tts |
| - hebrew |
| - fish-speech |
| - s2-pro |
| - lora |
| - voice-cloning |
| base_model: fishaudio/s2-pro |
| library_name: fish-speech |
| pipeline_tag: text-to-speech |
| --- |
| |
| <img src="fih.png" alt="Fish Audio S2-Pro Hebrew" width="420"> |
|
|
| Built with Fish Audio. |
|
|
| # Fish Audio S2-Pro โ Hebrew (LoRA + atomic IPA tokens) |
|
|
| A Hebrew adapter for [`fishaudio/s2-pro`](https://huggingface.co/fishaudio/s2-pro). |
| It keeps the base model's multilingual ability and voice cloning intact, and adds |
| native Hebrew synthesis driven by **IPA** rather than nikud. |
|
|
| This repo contains **only the adapter** (~67M parameters) plus the extended |
| tokenizer. You still need the S2-Pro base weights and codec. |
|
|
| ## What's here |
|
|
| | File | What it is | |
| |---|---| |
| | `hebrew_lora_step2200.safetensors` | LoRA deltas + the trained `ipa_embeddings` table (67M params, bf16) | |
| | `hebrew_lora_step2200.ckpt` | Same weights as a Lightning checkpoint, with optimizer state โ use this to resume training | |
| | `config.json` | S2-Pro config extended with `num_ipa_tokens: 26`, `ipa_token_start: 155774` | |
| | `ipa_token_map.json` | IPA symbol โ atomic token (e.g. `ส` โ `<ipa_u0283>`) | |
| | `ipa_embeddings.pt` | Initial IPA embedding table (mean of the symbol's BPE pieces); the trained one lives in the adapter | |
| | `tokenizer/` | S2-Pro tokenizer extended 155,774 โ 155,800 tokens | |
| | `samples/` | Generated audio (see below) | |
|
|
| ## Quick start |
|
|
| Code lives in the fork the adapter was trained with: |
|
|
| ```bash |
| git clone https://github.com/maxmelichov/fish-speech |
| cd fish-speech && uv sync --python 3.12 --extra cu129 |
| pip install renikud-plus # Hebrew grapheme-to-phoneme |
| |
| bash tools/hebrew/setup_hebrew.sh # base weights + this adapter + IPA checkpoint |
| |
| python tools/hebrew/infer_hebrew.py \ |
| --text "ืฉืืื, ืื ืฉืืืื ืืืื?" \ |
| --lora-checkpoint checkpoints/hebrew/hebrew_lora_step2200.safetensors \ |
| --output out.wav |
| ``` |
|
|
| Add `--ref-audio my_voice.wav --ref-text "..."` to clone a voice. |
|
|
| `infer_hebrew.py` runs plain unvocalized Hebrew through |
| [RenikudPlus](https://github.com/maxmelichov/RenikudPlus) G2P, maps the |
| IPA to the atomic tokens, and chunks long inputs on sentence boundaries. |
| `--lora-scale` scales the delta (0.0 = pure base model) if you want to dial the |
| adaptation down. |
|
|
| **Fine-tuning on your own Hebrew data** is one command โ a directory per speaker |
| of `*.wav` plus sibling `.lab` transcripts: |
|
|
| ```bash |
| AUDIO_ROOT=my_audio tools/hebrew/run_hebrew_pipeline.sh |
| ``` |
|
|
| See [`tools/hebrew/README.md`](https://github.com/maxmelichov/fish-speech/blob/main/tools/hebrew/README.md) |
| for the full guide. |
|
|
| ## How it works |
|
|
| **Atomic IPA tokens.** S2-Pro's BPE splits IPA into pieces that collide with |
| English orthography โ Hebrew `ื` phonemized as `j` was read as the English letter |
| *jay*. So each of the 26 Hebrew IPA symbols gets a dedicated input-only token |
| (`<ipa_j>`, `<ipa_u0283>`, โฆ) in a separate trainable `nn.Embedding`, initialized |
| to the mean of the symbol's original BPE pieces. The output vocabulary is |
| untouched โ these tokens are never predicted, only read. |
|
|
| **What trains.** LoRA r=32, ฮฑ=16 on `attention` + `mlp`, plus the IPA embedding |
| table โ 66.9M parameters total: 60.2M in the slow transformer, 6.7M in the fast |
| transformer, 0.03M IPA embeddings. Frozen are the direct interfaces to codebook |
| space โ `fast_embeddings`, `fast_output`, and the tied slow embeddings/output โ |
| which is what keeps timbre close to the base model. Note ฮฑ/r = 0.5, not the usual |
| 2.0; see *Caveats*. The residual-codebook loss is down-weighted to 0.3 |
| (Qwen3-TTS's sub-talker coefficient) so the gradient stays on the textโsemantic |
| mapping. |
|
|
| **Training.** 279,476 Hebrew utterances (~10 speakers, WER โค 0.1), reference- |
| conditioned on a same-speaker utterance 80% of the time so training prompts match |
| the exact `generate_long()` inference format. bf16, lr 5e-5 constant with 100-step |
| warmup, effective batch 12, 2200 optimizer steps. |
|
|
| ## Upstream bug fixed along the way |
|
|
| S2-Pro sets `scale_codebook_embeddings=True`. At inference, `forward_generate()` |
| divides semantic-position embeddings by `sqrt(num_codebooks + 1)` = 3.317; the |
| training path in `embed()` did **not**. Every fine-tune therefore learned against |
| embeddings 3.3ร larger than the ones it would see at generation time. Teacher- |
| forced CE looked fine while free-running generation collapsed after the first |
| word โ the classic symptom in fishaudio/fish-speech issues |
| [#1136](https://github.com/fishaudio/fish-speech/issues/1136) (Japanese gibberish), |
| [#682](https://github.com/fishaudio/fish-speech/issues/682) (Hindi noise) and |
| [#814](https://github.com/fishaudio/fish-speech/issues/814). |
|
|
| Five Hebrew runs collapsed the same way before this was found. After the fix |
| (train and inference embeddings verified bit-identical): |
|
|
| | | sample RMS | energy decay over the utterance | |
| |---|---|---| |
| | before | 0.008 โ 0.022 | 0.07ร | |
| | after | 0.171 โ 0.205 | 1.02ร | |
| | base model reference | 0.181 | โ | |
|
|
| The fix is in `fish_speech/models/text2semantic/llama.py` in the fork above and |
| applies to any S2-Pro fine-tune, Hebrew or not. |
|
|
| ## Samples |
|
|
| - `00_base_out_of_the_box.wav` โ **stock `fishaudio/s2-pro`, no adapter, no |
| G2P** โ plain Hebrew script straight in. S2-Pro is multilingual and does |
| produce Hebrew-*shaped* speech, but it isn't accurate: this sample of |
| "ืฉืืื, ืื ืฉืืืื ืืืื?" ("hello, how are you today?") comes out as "ืกืืื |
| ืืืฉืืื ืืืื" โ ืฉืืื โ ืกืืื, ืฉืืืื garbled into ืืฉืืื. Not a cherry-pick: an |
| 8-seed sweep on a different sentence in this fork's eval found the same |
| failure every time, and a stock-model WER of 0.383 across 11 sentences, |
| worse than real human speech scores on the same metric. This is the gap |
| the adapter below closes. |
|
|
| The rest of `samples/` is generated **with this adapter**: |
|
|
| - `01_podcast_2hosts_63s.wav` โ 63s two-host Hebrew conversation, cloned voices |
| - `03_longform_15s.wav` โ multi-sentence long-form |
| - `04_yod_BASE.wav` / `05_yod_LORA.wav` โ the `ื` โ English *jay* failure, before and after atomic IPA tokens |
| - `07_clone_LORA_ranlevi.wav` โ voice clone from a real Hebrew speaker reference |
|
|
| ## Caveats โ this checkpoint is early, not final |
|
|
| - **Undertrained, and stopped by hand.** 2,200 optimizer steps โ 53k utterances |
| seen, under 20% of one epoch over the 279k-row set. Train loss was still falling |
| (3.66 โ 2.70 base CE) and val loss was still improving monotonically at every |
| checkpoint (2.934 โ 2.844 โ 2.820 โ 2.807). Nothing had plateaued; the run was |
| simply halted. |
| - **ฮฑ/r = 0.5 is a workaround for a bug that no longer exists.** The unusual |
| scaling was chosen empirically because at ฮฑ=64 the delta destroyed free-running |
| generation โ which we now know was the embedding-scale bug above, not the LoRA |
| strength. That rationale is obsolete post-fix, and the standard ฮฑ = 2r was never |
| re-tried. It may well be better. |
| - The pitch homogenization noted below is the symptom you would expect from |
| putting LoRA on the fast transformer at all. Freezing `fast_layers` entirely is |
| the obvious next experiment. |
|
|
| ## Known limitations |
|
|
| - **Emotion tags (`[whisper]`, `[excited]`, โฆ) do not work** โ and this is not a |
| regression from the LoRA. Measured on the *base* model in *English*: plain / |
| whisper / shouting produced RMS 0.0655 / 0.0652 / 0.0689, i.e. no response at |
| all. The released S2-Pro weights simply lack the tag alignment. |
| - **Pitch is not cloned.** Timbre transfers well (4/4 by ear), but neither base nor |
| LoRA reproduces the reference's F0 (base mean |err| 23 Hz, LoRA 20 Hz). The LoRA |
| homogenizes pitch somewhat: spread across speakers drops from 66 Hz to 26 Hz. |
| - Trained on read/broadcast-style Hebrew; conversational and heavily accented |
| speech are out of distribution. |
| - Hebrew input must go through G2P. Feeding nikud or bare Hebrew script directly |
| to the model is out of distribution โ use `infer_hebrew.py`, which handles it. |
|
|
| ## License |
|
|
| S2-Pro (the base model this adapter is trained on) is released under the |
| **[Fish Audio Research License](https://github.com/maxmelichov/fish-speech/blob/main/LICENSE)** |
| โ *not* CC BY-NC-SA 4.0, correcting an earlier version of this card. This |
| adapter is a Derivative Work under that license and inherits its terms: |
| research and non-commercial use only (personal use, evaluation, academic |
| work); any commercial use requires a separate license directly from Fish |
| Audio (business@fish.audio). See `NOTICE` and `LICENSE` in this repo for the |
| full text and the required attribution. |
|
|
| Do not use it to clone a voice you do not have permission to clone. |
|
|