krzynio commited on
Commit
32ac4ea
·
verified ·
1 Parent(s): 7310e39

Upload folder using huggingface_hub

Browse files
Cangjie5_TC.json CHANGED
The diff for this file is too large to render. See raw diff
 
README.md CHANGED
@@ -72,23 +72,77 @@ Chatterbox is provided in an exported ONNX format, enabling fast and portable in
72
  - Easy voice conversion script using onnxruntime
73
  - [Outperforms ElevenLabs](https://podonos.com/resembleai/chatterbox)
74
 
75
- # Changelog
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
76
 
77
- - **2025-03-30**: Fixed Polish and other diacritics-heavy languages (German, French, etc.):
78
- - **embed_tokens.onnx**: Expanded `text_emb.weight` from `(2352, 1024)` to `(2454, 1024)` to match the full tokenizer vocabulary. The previous export truncated 102 embedding rows, causing out-of-bounds errors for Polish characters (ą, ę, ć, ś, ź, ż, ń, ó).
79
- - **Preprocessing fix**: Added mandatory `text.lower()` + NFKD Unicode normalization before tokenization, matching the original PyTorch `MTLTokenizer.preprocess_text()`. Without NFKD, composed diacritics (e.g. `ą` U+0105) are tokenized as single unknown tokens instead of being decomposed into base character + combining mark, which is how the model was trained.
 
 
 
 
 
 
 
80
 
81
  # Tips
82
  - **General Use (TTS and Voice Agents):**
83
- - The default settings (`exaggeration=0.5`, `cfg=0.5`) work well for most prompts.
 
84
 
85
  - **Expressive or Dramatic Speech:**
86
- - Try increase `exaggeration` to around `0.7` or higher.
87
- - Higher `exaggeration` tends to speed up speech;
88
 
 
 
 
 
89
 
90
  # Usage
91
- [Link to GitHub ONNX Export and Inference script](https://github.com/VladOS95-cyber/onnx_conversion_scripts/tree/main/chatterbox)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
92
 
93
  ```python
94
  # !pip install --upgrade onnxruntime==1.22.1 huggingface_hub==0.34.4 transformers==4.46.3 numpy==2.2.6 tqdm==4.67.1 librosa==0.11.0 soundfile==0.13.1 resemble-perth==1.0.1
@@ -483,8 +537,9 @@ def run_inference(
483
 
484
  if __name__ == "__main__":
485
  run_inference(
486
- text="Bonjour, comment ça va? Ceci est le modèle de synthèse vocale multilingue Chatterbox, il prend en charge 23 langues.",
487
- language_id="fr",
 
488
  exaggeration=0.5,
489
  output_file_name="output.wav",
490
  apply_watermark=False,
@@ -493,10 +548,21 @@ if __name__ == "__main__":
493
  ```
494
 
495
 
 
 
 
 
 
 
 
 
 
 
496
  # Acknowledgements
497
  - [Xenova](https://huggingface.co/Xenova)
498
  - [Vladislav Bronzov](https://github.com/VladOS95-cyber)
499
  - [Resemble AI](https://github.com/resemble-ai/chatterbox)
 
500
 
501
  # Built-in PerTh Watermarking for Responsible AI
502
 
 
72
  - Easy voice conversion script using onnxruntime
73
  - [Outperforms ElevenLabs](https://podonos.com/resembleai/chatterbox)
74
 
75
+ # What's fixed in this repo
76
+
77
+ This is a corrected ONNX export of [ResembleAI/chatterbox](https://huggingface.co/ResembleAI/chatterbox) multilingual TTS. The original community ONNX exports ([onnx-community/chatterbox-multilingual-ONNX](https://huggingface.co/onnx-community/chatterbox-multilingual-ONNX)) had multiple issues that caused **incorrect pronunciation for Polish and other diacritics-heavy languages** (sounding like Russian instead of the target language). This repo fixes all of them. Polish speech generation is now on par with the PyTorch reference model.
78
+
79
+ ### Issues found and fixed
80
+
81
+ **1. Wrong model weights (root cause of "Russian accent")**
82
+
83
+ The original ONNX export used weights from an older checkpoint (`t3_23lang.safetensors`), not the latest `t3_mtl23ls_v2.safetensors` that the PyTorch `ChatterboxMultilingualTTS` uses. This caused 61 weight mismatches across the language model (including a 0.42 max diff in the final layer norm) and 3 mismatched embedding weights. We verified this numerically — every weight now exactly matches the PyTorch model.
84
+
85
+ Additionally, the `language_model.onnx` was re-exported from the correct Llama backbone using `transformers==4.46.3` to ensure the RoPE (rotary position embeddings) cos/sin caches are computed correctly. Different transformers versions produce different RoPE implementations (32-dim vs 64-dim), causing completely divergent attention outputs even with identical weights.
86
+
87
+ **2. Truncated embedding vocabulary**
88
+
89
+ `embed_tokens.onnx` had `text_emb.weight` with shape `(2352, 1024)` but the tokenizer produces IDs up to 2454. The 102 missing rows caused out-of-bounds crashes for Polish diacritics (ą, ę, ć, ś, ź, ż, ń, ó) and other non-ASCII characters. Fixed by extracting the full `(2454, 1024)` embeddings from the correct PyTorch checkpoint.
90
+
91
+ **3. Missing text preprocessing**
92
+
93
+ The ONNX inference code was missing two critical preprocessing steps that the PyTorch `MTLTokenizer.preprocess_text()` always applies:
94
+ - `text.lower()` — the model was trained on lowercase text
95
+ - `unicodedata.normalize("NFKD", text)` — decomposes diacritics into base character + combining mark (e.g., `ą` → `a` + `̨`), which is how the tokenizer was trained
96
+
97
+ Without NFKD normalization, composed Polish characters hit the wrong (or missing) token embeddings.
98
+
99
+ ### Recommended settings
100
 
101
+ | Parameter | Value | Notes |
102
+ |-----------|-------|-------|
103
+ | `temperature` | 0.3 | Low temp prevents language drift without CFG |
104
+ | `repetition_penalty` | 2.0 | Matches PyTorch multilingual default |
105
+ | `exaggeration` | 0.5 | Default emotion intensity |
106
+ | `cfg_weight` | 0.0 | CFG not yet implemented for ONNX inference |
107
+ | `min_p` | 0.05 | Filters low-probability tokens |
108
+ | `max_tokens` | 500 | Enough for most sentences |
109
+
110
+ > **Note:** The PyTorch model uses Classifier-Free Guidance (CFG) with `cfg_weight=0.5` and `temperature=0.8`, which allows higher temperature without language drift. CFG requires running the language model with batch=2 (conditional + unconditional branches). This is not yet implemented in the ONNX inference script, so we compensate with lower temperature.
111
 
112
  # Tips
113
  - **General Use (TTS and Voice Agents):**
114
+ - The default settings (`exaggeration=0.5`, `temperature=0.3`) work well for most prompts.
115
+ - Use `temperature=0` for fully deterministic (greedy) output.
116
 
117
  - **Expressive or Dramatic Speech:**
118
+ - Try increasing `exaggeration` to around `0.7` or higher.
119
+ - Higher `exaggeration` tends to speed up speech.
120
 
121
+ - **If you hear wrong accent/language:**
122
+ - Make sure text preprocessing includes `text.lower()` + NFKD normalization.
123
+ - Keep temperature low (0.0–0.3) until CFG is implemented.
124
+ - Verify you are using the ONNX models from this repo, not the original community exports.
125
 
126
  # Usage
127
+
128
+ Included `test_polish.py` can be used as a standalone inference script:
129
+
130
+ ```bash
131
+ pip install onnxruntime transformers numpy tqdm librosa soundfile
132
+
133
+ # Default settings (Polish, temp=0.3)
134
+ python test_polish.py
135
+
136
+ # Custom text and settings
137
+ python test_polish.py --text "Cześć, jak się masz?" --temperature 0.5 --exaggeration 0.7 -o output.wav
138
+
139
+ # Greedy (deterministic)
140
+ python test_polish.py --temperature 0 -o output_greedy.wav
141
+ ```
142
+
143
+ ### Full inference code
144
+
145
+ [Link to original GitHub ONNX conversion scripts](https://github.com/VladOS95-cyber/onnx_conversion_scripts/tree/main/chatterbox)
146
 
147
  ```python
148
  # !pip install --upgrade onnxruntime==1.22.1 huggingface_hub==0.34.4 transformers==4.46.3 numpy==2.2.6 tqdm==4.67.1 librosa==0.11.0 soundfile==0.13.1 resemble-perth==1.0.1
 
537
 
538
  if __name__ == "__main__":
539
  run_inference(
540
+ text="Dzień dobry, nazywam się Weronika. Dziś jest piękna pogoda i chciałabym zaprosić wszystkich na spacer po parku.",
541
+ language_id="pl",
542
+ target_voice_path="polish-female-common-voice.wav",
543
  exaggeration=0.5,
544
  output_file_name="output.wav",
545
  apply_watermark=False,
 
548
  ```
549
 
550
 
551
+ # Changelog
552
+
553
+ - **2026-03-31**: Full model re-export and Polish language fix
554
+ - Re-exported `language_model.onnx` from the correct checkpoint (`t3_mtl23ls_v2.safetensors`) using `transformers==4.46.3` for correct RoPE computation
555
+ - Patched all 4 embedding weights in `embed_tokens.onnx` from the correct checkpoint
556
+ - Added NFKD normalization + lowercase preprocessing to match PyTorch training pipeline
557
+ - Expanded `text_emb.weight` from `(2352, 1024)` to `(2454, 1024)` to cover full tokenizer vocabulary
558
+ - Added `test_polish.py` with tuned defaults and CLI interface
559
+ - Included Polish reference voice and example output
560
+
561
  # Acknowledgements
562
  - [Xenova](https://huggingface.co/Xenova)
563
  - [Vladislav Bronzov](https://github.com/VladOS95-cyber)
564
  - [Resemble AI](https://github.com/resemble-ai/chatterbox)
565
+ - [Folx](https://huggingface.co/Folx) — Polish language fixes and correct model re-export
566
 
567
  # Built-in PerTh Watermarking for Responsible AI
568
 
example_output_polish.wav ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:812239fdc8d163d71adf4101029c0294dbc12d458a78c732dddf2732f40935db
3
+ size 358124
onnx/embed_tokens.onnx CHANGED
@@ -1,3 +1,3 @@
1
  version https://git-lfs.github.com/spec/v1
2
- oid sha256:aff4b0863dec868c9ccc589d7d7617ea54c6b2377822e138bb023b83acf9f794
3
- size 13288
 
1
  version https://git-lfs.github.com/spec/v1
2
+ oid sha256:cd3449f120c1738c34c1908de07ee4fccbc12f2723e88b89dba5f037b254a6f3
3
+ size 13200
onnx/embed_tokens.onnx_data CHANGED
@@ -1,3 +1,3 @@
1
  version https://git-lfs.github.com/spec/v1
2
- oid sha256:3222d642b7f3ece8d1b12072b20e7f8e6fda53d70641cbe0383ad6f963cc9dfc
3
  size 68808704
 
1
  version https://git-lfs.github.com/spec/v1
2
+ oid sha256:d7c50b7240475f19e94675869460a1204f6d8a72348bf72c1814e5a004b491c9
3
  size 68808704
onnx/language_model.onnx CHANGED
@@ -1,3 +1,3 @@
1
  version https://git-lfs.github.com/spec/v1
2
- oid sha256:861a34585605e8ad671051788afc495dcbeaee833a41523a1b33aded9c3babc7
3
- size 171387
 
1
  version https://git-lfs.github.com/spec/v1
2
+ oid sha256:3a38c047d205122aa36e43c8a6e5ac027b391ce90f522cfa206f41ab39a4bdea
3
+ size 657688
onnx/language_model.onnx_data CHANGED
@@ -1,3 +1,3 @@
1
  version https://git-lfs.github.com/spec/v1
2
- oid sha256:b3556d41085196c122b7197e4d44ec4475b6d7cfe0971a70faa95caa38ad787a
3
- size 2080632832
 
1
  version https://git-lfs.github.com/spec/v1
2
+ oid sha256:87a4e23357d396d42ef959165eaecfb95108963d2c2147b46b01cca130af734d
3
+ size 2047078400
onnx/speech_encoder.onnx CHANGED
@@ -1,3 +1,3 @@
1
  version https://git-lfs.github.com/spec/v1
2
- oid sha256:8f1c8a0f89b77bf9cd5dd8f2e034eb2c79dc00fe70d41196b28c257643b00ccb
3
- size 1184608
 
1
  version https://git-lfs.github.com/spec/v1
2
+ oid sha256:36f42eb070f7f90423b51bea3475c784b4cc88756514f527bcfb26067b59ee01
3
+ size 592665681
onnx_fixed/embed_tokens.onnx ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:aff4b0863dec868c9ccc589d7d7617ea54c6b2377822e138bb023b83acf9f794
3
+ size 13288
onnx_fixed/embed_tokens.onnx_data ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:ae9479b9f126b2ca973ab4057425a3820d36571b2133d453527bab05d6c5e359
3
+ size 68808704
polish-female-common-voice.wav ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:49acc23c4513cb957f9591a38370db089d2121fa77c01d740ffd66ce3ae3bd94
3
+ size 491322
test_polish.py ADDED
@@ -0,0 +1,288 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Test Polish language TTS with chatterbox-ONNX-polish.
2
+ Verifies that Polish diacritics (ą, ę, ć, ś, ź, ż, ł, ń, ó) are handled correctly.
3
+
4
+ All generation parameters match the original PyTorch ChatterboxMultilingualTTS.generate() defaults.
5
+ """
6
+
7
+ import argparse
8
+ import os
9
+ import onnxruntime
10
+ from transformers import AutoTokenizer
11
+ import numpy as np
12
+ from tqdm import tqdm
13
+ import librosa
14
+ import soundfile as sf
15
+ from unicodedata import normalize
16
+
17
+ S3GEN_SR = 24000
18
+ START_SPEECH_TOKEN = 6561
19
+ STOP_SPEECH_TOKEN = 6562
20
+
21
+ # Path to this script's directory (the model repo)
22
+ MODEL_DIR = os.path.dirname(os.path.abspath(__file__))
23
+
24
+
25
+ class RepetitionPenaltyLogitsProcessor:
26
+ def __init__(self, penalty: float):
27
+ self.penalty = penalty
28
+
29
+ def __call__(self, input_ids, scores):
30
+ score = np.take_along_axis(scores, input_ids, axis=1)
31
+ score = np.where(score < 0, score * self.penalty, score / self.penalty)
32
+ scores_processed = scores.copy()
33
+ np.put_along_axis(scores_processed, input_ids, score, axis=1)
34
+ return scores_processed
35
+
36
+
37
+ def top_p_filter(logits, top_p):
38
+ """Nucleus (top-p) filtering: zero out tokens outside the top-p cumulative probability."""
39
+ if top_p >= 1.0:
40
+ return logits
41
+ sorted_indices = np.argsort(-logits, axis=-1)
42
+ sorted_logits = np.take_along_axis(logits, sorted_indices, axis=-1)
43
+ probs = np.exp(sorted_logits - np.max(sorted_logits, axis=-1, keepdims=True))
44
+ probs = probs / probs.sum(axis=-1, keepdims=True)
45
+ cumulative_probs = np.cumsum(probs, axis=-1)
46
+ # Remove tokens with cumulative probability above top_p (keep at least 1 token)
47
+ sorted_mask = cumulative_probs - probs > top_p
48
+ sorted_logits[sorted_mask] = -np.inf
49
+ # Unsort
50
+ result = np.empty_like(logits)
51
+ np.put_along_axis(result, sorted_indices, sorted_logits, axis=-1)
52
+ return result
53
+
54
+
55
+ def min_p_filter(logits, min_p):
56
+ """Min-p filtering: remove tokens with probability below min_p * max_prob."""
57
+ if min_p <= 0.0:
58
+ return logits
59
+ probs = np.exp(logits - np.max(logits, axis=-1, keepdims=True))
60
+ probs = probs / probs.sum(axis=-1, keepdims=True)
61
+ max_prob = probs.max(axis=-1, keepdims=True)
62
+ mask = probs < (min_p * max_prob)
63
+ logits = logits.copy()
64
+ logits[mask] = -np.inf
65
+ return logits
66
+
67
+
68
+ def prepare_language(txt, language_id):
69
+ """Match PyTorch MTLTokenizer.preprocess_text(): lowercase + NFKD normalize."""
70
+ txt = txt.lower()
71
+ txt = normalize("NFKD", txt)
72
+ if language_id:
73
+ txt = f"[{language_id.lower()}]{txt}"
74
+ return txt
75
+
76
+
77
+ def run_inference(
78
+ text,
79
+ language_id="pl",
80
+ target_voice_path=None,
81
+ # Generation parameters — tuned for ONNX multilingual quality
82
+ max_new_tokens=500,
83
+ exaggeration=0.5, # 0.0-1.0: emotion/intensity control (unique to Chatterbox)
84
+ cfg_weight=0.0, # CFG not yet supported in ONNX, keep at 0
85
+ temperature=0.3, # low temp avoids language drift without CFG
86
+ top_p=1.0, # nucleus sampling threshold (1.0 = disabled)
87
+ min_p=0.05, # min probability filter (relative to top token)
88
+ repetition_penalty=2.0, # higher penalty prevents repetition loops
89
+ seed=None, # random seed for reproducibility
90
+ model_dir=MODEL_DIR,
91
+ output_file_name="example_output_polish.wav",
92
+ ):
93
+ if seed is not None:
94
+ np.random.seed(seed)
95
+
96
+ onnx_dir = os.path.join(model_dir, "onnx")
97
+
98
+ if not target_voice_path:
99
+ target_voice_path = os.path.join(model_dir, "default_voice.wav")
100
+
101
+ print(f"Text: {text}")
102
+ print(f"Settings: exaggeration={exaggeration}, cfg_weight={cfg_weight}, "
103
+ f"temperature={temperature}, top_p={top_p}, min_p={min_p}, "
104
+ f"repetition_penalty={repetition_penalty}, seed={seed}")
105
+ print(f"Voice: {os.path.basename(target_voice_path)}")
106
+
107
+ speech_encoder_session = onnxruntime.InferenceSession(os.path.join(onnx_dir, "speech_encoder.onnx"))
108
+ embed_tokens_session = onnxruntime.InferenceSession(os.path.join(onnx_dir, "embed_tokens.onnx"))
109
+ llama_with_past_session = onnxruntime.InferenceSession(os.path.join(onnx_dir, "language_model.onnx"))
110
+ cond_decoder_session = onnxruntime.InferenceSession(os.path.join(onnx_dir, "conditional_decoder.onnx"))
111
+
112
+ audio_values, _ = librosa.load(target_voice_path, sr=S3GEN_SR)
113
+ audio_values = audio_values[np.newaxis, :].astype(np.float32)
114
+
115
+ tokenizer = AutoTokenizer.from_pretrained(model_dir)
116
+ text_prepared = prepare_language(text, language_id)
117
+ input_ids = tokenizer(text_prepared, return_tensors="np")["input_ids"].astype(np.int64)
118
+
119
+ position_ids = np.where(
120
+ input_ids >= START_SPEECH_TOKEN, 0,
121
+ np.arange(input_ids.shape[1])[np.newaxis, :] - 1
122
+ )
123
+
124
+ ort_embed_tokens_inputs = {
125
+ "input_ids": input_ids,
126
+ "position_ids": position_ids.astype(np.int64),
127
+ "exaggeration": np.array([exaggeration], dtype=np.float32)
128
+ }
129
+
130
+ repetition_penalty_processor = RepetitionPenaltyLogitsProcessor(penalty=repetition_penalty)
131
+ num_hidden_layers = 30
132
+ num_key_value_heads = 16
133
+ head_dim = 64
134
+
135
+ use_cfg = cfg_weight > 0.0
136
+ # Batch size: 2 for CFG (conditional + unconditional), 1 without
137
+ B = 2 if use_cfg else 1
138
+
139
+ # Load text position embeddings for CFG uncond branch
140
+ # PyTorch zeros text content but KEEPS position embeddings in the uncond branch
141
+ text_pos_emb_weight = None
142
+ if use_cfg:
143
+ import onnx
144
+ from onnx import numpy_helper
145
+ onnx_model = onnx.load(os.path.join(onnx_dir, "embed_tokens.onnx"), load_external_data=True)
146
+ for init in onnx_model.graph.initializer:
147
+ if init.name == "text_pos_emb.weight":
148
+ text_pos_emb_weight = numpy_helper.to_array(init) # (2050, 1024)
149
+ break
150
+ del onnx_model
151
+
152
+ generate_tokens = np.array([[START_SPEECH_TOKEN]])
153
+
154
+ # --- Initial step: build full context embeddings ---
155
+ inputs_embeds = embed_tokens_session.run(None, ort_embed_tokens_inputs)[0]
156
+ cond_emb, prompt_token, ref_x_vector, prompt_feat = speech_encoder_session.run(
157
+ None, {"audio_values": audio_values}
158
+ )
159
+ cond_inputs = np.concatenate((cond_emb, inputs_embeds), axis=1)
160
+
161
+ if use_cfg:
162
+ # CFG unconditional: replace text embeddings with ONLY position embeddings
163
+ # (matching PyTorch: text_emb[1].zero_() then += text_pos_emb)
164
+ uncond_embeds = inputs_embeds.copy()
165
+ text_mask = input_ids[0] < START_SPEECH_TOKEN # which tokens are text
166
+ for j in range(len(text_mask)):
167
+ if text_mask[j]:
168
+ pos = int(position_ids[0, j])
169
+ uncond_embeds[0, j, :] = text_pos_emb_weight[pos] # position only, no content
170
+ uncond_inputs = np.concatenate((cond_emb, uncond_embeds), axis=1)
171
+ current_embeds = np.concatenate([cond_inputs, uncond_inputs], axis=0) # (2, seq, dim)
172
+ else:
173
+ current_embeds = cond_inputs # (1, seq, dim)
174
+
175
+ _, seq_len, _ = current_embeds.shape
176
+ past_key_values = {
177
+ f"past_key_values.{layer}.{kv}": np.zeros([B, num_key_value_heads, 0, head_dim], dtype=np.float32)
178
+ for layer in range(num_hidden_layers)
179
+ for kv in ("key", "value")
180
+ }
181
+ attention_mask = np.ones((B, seq_len), dtype=np.int64)
182
+
183
+ # --- Generation loop ---
184
+ for i in tqdm(range(max_new_tokens), desc="Generating", dynamic_ncols=True):
185
+ logits, *present_key_values = llama_with_past_session.run(None, dict(
186
+ inputs_embeds=current_embeds,
187
+ attention_mask=attention_mask,
188
+ **past_key_values,
189
+ ))
190
+
191
+ logits = logits[:, -1, :] # (B, vocab)
192
+
193
+ if use_cfg:
194
+ # CFG: logits = cond + cfg_weight * (cond - uncond)
195
+ cond_logits = logits[0:1, :]
196
+ uncond_logits = logits[1:2, :]
197
+ logits = cond_logits + cfg_weight * (cond_logits - uncond_logits)
198
+
199
+ # Apply repetition penalty
200
+ logits = repetition_penalty_processor(generate_tokens, logits)
201
+
202
+ # Sampling or greedy
203
+ if temperature > 0:
204
+ logits = logits / temperature
205
+ logits = min_p_filter(logits, min_p)
206
+ logits = top_p_filter(logits, top_p)
207
+ probs = np.exp(logits - np.max(logits, axis=-1, keepdims=True))
208
+ probs = probs / probs.sum(axis=-1, keepdims=True)
209
+ probs = np.nan_to_num(probs, nan=0.0)
210
+ if probs.sum() == 0:
211
+ probs[0, np.argmax(logits)] = 1.0
212
+ next_token = np.array([[np.random.choice(probs.shape[-1], p=probs[0])]], dtype=np.int64)
213
+ else:
214
+ next_token = np.argmax(logits, axis=-1, keepdims=True).astype(np.int64)
215
+
216
+ generate_tokens = np.concatenate((generate_tokens, next_token), axis=-1)
217
+ if (next_token.flatten() == STOP_SPEECH_TOKEN).all():
218
+ break
219
+
220
+ # Embed next speech token for next step
221
+ next_pos = np.full((1, 1), i + 1, dtype=np.int64)
222
+ next_embeds = embed_tokens_session.run(None, {
223
+ "input_ids": next_token,
224
+ "position_ids": next_pos,
225
+ "exaggeration": np.array([exaggeration], dtype=np.float32),
226
+ })[0] # (1, 1, dim)
227
+
228
+ # For CFG: duplicate token embedding for both branches
229
+ if use_cfg:
230
+ current_embeds = np.concatenate([next_embeds, next_embeds], axis=0)
231
+ else:
232
+ current_embeds = next_embeds
233
+
234
+ attention_mask = np.concatenate([attention_mask, np.ones((B, 1), dtype=np.int64)], axis=1)
235
+ # Map present.X.key/value outputs -> past_key_values.X.key/value inputs
236
+ for layer in range(num_hidden_layers):
237
+ for ki, kv in enumerate(("key", "value")):
238
+ out_idx = layer * 2 + ki
239
+ past_key_values[f"past_key_values.{layer}.{kv}"] = present_key_values[out_idx]
240
+
241
+ speech_tokens = generate_tokens[:, 1:-1]
242
+ speech_tokens = np.concatenate([prompt_token, speech_tokens], axis=1)
243
+
244
+ wav = cond_decoder_session.run(None, {
245
+ "speech_tokens": speech_tokens,
246
+ "speaker_embeddings": ref_x_vector,
247
+ "speaker_features": prompt_feat,
248
+ })[0]
249
+ wav = np.squeeze(wav, axis=0)
250
+ sf.write(output_file_name, wav, S3GEN_SR)
251
+ print(f"\n{output_file_name} saved!")
252
+
253
+
254
+ if __name__ == "__main__":
255
+ parser = argparse.ArgumentParser(description="Chatterbox Polish TTS test")
256
+ parser.add_argument("--text", type=str,
257
+ default="Dzień dobry, nazywam się Weronika. Dziś jest piękna pogoda i chciałabym zaprosić wszystkich na spacer po parku.")
258
+ parser.add_argument("--lang", type=str, default="pl")
259
+ parser.add_argument("--voice", type=str, default=os.path.join(MODEL_DIR, "polish-female-common-voice.wav"))
260
+ parser.add_argument("--output", "-o", type=str, default=os.path.join(MODEL_DIR, "example_output_polish.wav"))
261
+ parser.add_argument("--exaggeration", type=float, default=0.5, help="Emotion intensity 0.0-1.0 (default: 0.5)")
262
+ parser.add_argument("--cfg-weight", type=float, default=0.0, help="Classifier-free guidance weight (default: 0.0, not yet supported)")
263
+ parser.add_argument("--temperature", type=float, default=0.3, help="Sampling temperature, 0=greedy (default: 0.3)")
264
+ parser.add_argument("--top-p", type=float, default=1.0, help="Nucleus sampling threshold (default: 1.0)")
265
+ parser.add_argument("--min-p", type=float, default=0.05, help="Min probability filter (default: 0.05)")
266
+ parser.add_argument("--repetition-penalty", type=float, default=2.0, help="Repetition penalty (default: 2.0)")
267
+ parser.add_argument("--max-tokens", type=int, default=500, help="Max speech tokens to generate (default: 500)")
268
+ parser.add_argument("--seed", type=int, default=None, help="Random seed for reproducibility")
269
+ args = parser.parse_args()
270
+
271
+ print("=" * 60)
272
+ print("POLISH TTS TEST")
273
+ print("=" * 60)
274
+
275
+ run_inference(
276
+ text=args.text,
277
+ language_id=args.lang,
278
+ target_voice_path=args.voice,
279
+ output_file_name=args.output,
280
+ exaggeration=args.exaggeration,
281
+ cfg_weight=args.cfg_weight,
282
+ temperature=args.temperature,
283
+ top_p=args.top_p,
284
+ min_p=args.min_p,
285
+ repetition_penalty=args.repetition_penalty,
286
+ max_new_tokens=args.max_tokens,
287
+ seed=args.seed,
288
+ )