Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
196 changes: 196 additions & 0 deletions KURULUM.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,196 @@
# Kurulum ve Hızlı Başlangıç

Bu depo (Coqui TTS 0.22.0) artık bakımda değil ve `requirements.txt` bugünkü paket
sürümleriyle çözüldüğünde **import edilebilen ama çalışmayan** bir ortam üretiyor. Aşağıdaki
adımlar, denenmiş ve testleri geçen sürüm kümesini kurar.

Referans donanım: NVIDIA RTX 4050 Laptop (6 GB), sürücü 550.163 (CUDA 12.4), Ubuntu, Python 3.10.

---

## 1. Kurulum

`setup.py` Python **3.9–3.11** arası ister; 3.12 ve üzeri reddedilir.

```bash
cd /path/to/TTS
uv venv --python 3.10
uv pip install -e '.[all,dev,notebooks]'
```

Bu ilk komut tek başına yeterli değil. Ardından üç düzeltme gerekiyor:

### 1a. Sürücünüze uygun torch

Varsayılan çözümleme `cu130` tekerleklerini kurar; bunlar 580+ sürücü ister. CUDA 12.4
sürücüsüyle `torch.cuda.is_available()` sessizce `False` döner ve her şey CPU'da çalışır.

```bash
uv pip install "torch==2.5.1+cu124" "torchaudio==2.5.1+cu124" \
--extra-index-url https://download.pytorch.org/whl/cu124 \
--index-strategy unsafe-best-match
```

> `--index-url` (extra olmayan) kullanmayın: `nvidia-*` bağımlılıkları çözülemez ve kurulum
> "unsatisfiable" hatasıyla düşer.

Farklı bir sürücünüz varsa `nvidia-smi` çıktısındaki **CUDA Version** alanına bakıp uygun
`cuXXX` derlemesini seçin.

### 1b. Çalışan paket sürümleri

```bash
uv pip install "librosa==0.10.2.post1" "transformers==4.40.2" \
"numpy==1.26.4" "scipy==1.11.4"
```

Neden bu sürümler:

| Paket | Sorun |
|---|---|
| `librosa` 0.10.0 | `pkg_resources` import ediyor; setuptools 81+ bunu kaldırdı, `import TTS` patlıyor |
| `transformers` 5.x | `SampleOutput` ve `LogitsWarper` kaldırıldı; `stream_generator.py` ve `tortoise/arch_utils.py` bunları kullanıyor |
| `numpy` 2.x | Derlenmiş `monotonic_align` cython uzantısı numpy 1.x ABI'sine göre |

### 1c. espeak ve nltk verisi

Fonemleştirme `espeak` adında bir çalıştırılabilir arıyor. Sistemde çoğunlukla yalnızca
`espeak-ng` bulunur; kodun kendisi bunun symlink olmasını zaten bekliyor, dolayısıyla sudo'ya
gerek yok:

```bash
ln -sf /usr/bin/espeak-ng .venv/bin/espeak
```

`espeak-ng` yoksa: `sudo apt install espeak-ng`.

Korece dışındaki diller için gerekmez, ama testler için nltk verisi:

```bash
.venv/bin/python -c "import nltk; [nltk.download(p, quiet=True) for p in ['averaged_perceptron_tagger','averaged_perceptron_tagger_eng','punkt','cmudict']]"
```

---

## 2. Komutları çalıştırma

Bu makinede kabuk profili ROS Humble'ı source ediyor ve `PYTHONPATH` venv'in içine sızıyor.
Ayrıca depo testleri çıplak `python` çağırıyor; sistemde yalnızca `python3` var. İkisini de
çözen kalıp:

```bash
env PYTHONPATH= PATH="$PWD/.venv/bin:$PATH" python ...
```

ROS kullanmıyorsanız `PYTHONPATH=` kısmı zararsızdır, bırakabilirsiniz.

### Kurulumu doğrulama

```bash
env PYTHONPATH= .venv/bin/python -c "
import torch, TTS
from TTS.api import TTS as T
print('torch', torch.__version__, '| cuda', torch.cuda.is_available())
print('gpu ', torch.cuda.get_device_name(0) if torch.cuda.is_available() else 'YOK - CPU modunda')
"
```

`cuda True` ve GPU adı görmelisiniz. `False` görüyorsanız adım 1a atlanmış demektir.

---

## 3. Hızlı test

```bash
env PYTHONPATH= PATH="$PWD/.venv/bin:$PATH" COQUI_TOS_AGREED=1 python deneme.py
```

`COQUI_TOS_AGREED=1`, XTTS'in lisans onayı sorusunu atlar (etkileşimsiz çalıştırmada gerekir).

`deneme.py` temel bir duman testidir: model indirir, ses klonlar, `output.wav` üretir. Tek
eksiği, GPU'ya düşülüp düşülmediğini söylememesi — CUDA çalışmıyorsa sessizce CPU'ya
geçer, sadece çok yavaşlar. Bunu görmek için başına şunu ekleyin:

```python
print("device:", device)
```

---

## 4. Hızlı çıkarım (XTTS)

Bu çatalda XTTS için iki hızlandırma var. Ayrıntılar: `docs/source/models/xtts.md`.

Doğrudan model API'siyle:

```python
model.load_checkpoint(config, checkpoint_dir="/path/to/xtts/", eval=True, half=True)
out = model.inference(text, "tr", gpt_cond_latent, speaker_embedding,
enable_text_splitting=True, batch_size=4)
```

`TTS.api` üzerinden — dikkat: API metni modele ulaşmadan cümlelere böldüğü için batch ancak
bölmeyi modele bırakınca devreye girer:

```python
from TTS.api import TTS
tts = TTS("tts_models/multilingual/multi-dataset/xtts_v2").to("cuda")
tts.synthesizer.tts_model.use_half_precision()

tts.tts_to_file(
text=uzun_metin,
speaker_wav="Recording.wav",
language="tr",
file_path="output.wav",
split_sentences=False, # metnin tamamını XTTS'e ver
enable_text_splitting=True,
batch_size=4,
)
```

RTX 4050 Laptop'ta paragraf uzunluğunda metin: RTF 0.259 → 0.178 (fp16) → **0.092**
(fp16 + batch=4). VRAM 2.21 → 1.49 GB.

Ölçmek için:

```bash
env PYTHONPATH= PATH="$PWD/.venv/bin:$PATH" COQUI_TOS_AGREED=1 python scripts/bench_xtts_paragraph.py
```

---

## 5. Testler

```bash
# hızlı olanlar (~1 dk)
env PYTHONPATH= PATH="$PWD/.venv/bin:$PATH" python -m pytest tests/text_tests tests/data_tests -q

# ses işleme ve çıkarım (~3 dk)
env PYTHONPATH= PATH="$PWD/.venv/bin:$PATH" python -m pytest tests/aux_tests tests/inference_tests -q

# model ve eğitim testleri (~20 dk)
env PYTHONPATH= PATH="$PWD/.venv/bin:$PATH" python -m pytest tests/tts_tests tests/tts_tests2 tests/vocoder_tests \
--continue-on-collection-errors -q
```

`pytest` ayrıca kurulmalıdır: `uv pip install pytest`.

### Bilinen ve kod hatası olmayan düşen testler

| Test | Neden |
|---|---|
| `test_phonemizer.py` espeak testleri (4) | espeak-ng 1.50 ile 1.51+ arasındaki fonem farkı (`ᵻ` ↔ `ɪ`) |
| `test_tokenizer.py::...eos_bos_and_blank` | aynı espeak-ng sürüm farkı (`c` ↔ `k`) |
| `test_korean_phonemizer.py` | `mecab` kurulu değil; yalnızca Korece'yi etkiler |
| `test_losses.py::BCELossTest` | `1.4e-45 != 0.0` — torch sürümünden gelen denormal sayı |
| `tests/vc_tests` (GPU'da) | Test CPU tensörü üretip CUDA modeline veriyor; `CUDA_VISIBLE_DEVICES="" ` ile 11/11 geçer |
| `test_xtts_v2-0_gpt_train.py` | XTTS v2 GPT eğitimi 6 GB VRAM'e sığmıyor; `CUDA_VISIBLE_DEVICES=""` ile geçer |

---

## 6. Bu makinede çalışmayanlar

- **DeepSpeed** — `nvcc` yok (inference kernel'lerini JIT derleyemez) ve depo, güncel
DeepSpeed'in kaldırdığı `replace_method` argümanını geçiyor. Hızlandırma için yukarıdaki
fp16 + batch yolunu kullanın.
- **XTTS v2 GPT eğitimi** — 6 GB VRAM yetmiyor.
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,11 @@ Underlined "TTS*" and "Judy*" are **internal** 🐸TTS models that are not relea
You can also help us implement more models.

## Installation

> **Bu çatal için:** aşağıdaki adımlar bugünkü paket sürümleriyle çalışmayan bir ortam
> üretiyor (torch/CUDA uyumsuzluğu, `pkg_resources`, numpy 2, transformers 5). Denenmiş
> kurulum, hızlı test ve hızlı çıkarım ayarları için **[KURULUM.md](KURULUM.md)**.

🐸TTS is tested on Ubuntu 18.04 with **python >= 3.9, < 3.12.**.

If you are only interested in [synthesizing speech](https://tts.readthedocs.io/en/latest/inference.html) with the released 🐸TTS models, installing from PyPI is the easiest option.
Expand Down
109 changes: 102 additions & 7 deletions TTS/tts/layers/xtts/gpt.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
# ported from: https://github.com/neonbjb/tortoise-tts

import functools
import math
import random

Expand All @@ -14,8 +13,27 @@
from TTS.tts.layers.xtts.perceiver_encoder import PerceiverResampler


def null_position_embeddings(range, dim):
return torch.zeros((range.shape[0], range.shape[1], dim), device=range.device)
class NullPositionEmbeddings(nn.Module):
"""Stand-in for an unused position embedding table.

Kept as a module rather than a plain function so that its output dtype follows
``.half()`` / ``.to()`` on the parent model. Returning hardcoded float32 zeros
would promote the activations back to float32 when added to the input
embeddings, which silently defeats half precision inference.
"""

def __init__(self, dim):
super().__init__()
self.dim = dim
# non-persistent so it stays out of the checkpoint's state_dict
self.register_buffer("_dtype_probe", torch.zeros(1), persistent=False)

def forward(self, range): # pylint: disable=redefined-builtin
return torch.zeros(
(range.shape[0], range.shape[1], self.dim),
device=range.device,
dtype=self._dtype_probe.dtype,
)


class LearnedPositionEmbeddings(nn.Module):
Expand Down Expand Up @@ -67,19 +85,19 @@ def build_hf_gpt_transformer(
gpt = GPT2Model(gpt_config)
# Override the built in positional embeddings
del gpt.wpe
gpt.wpe = functools.partial(null_position_embeddings, dim=model_dim)
gpt.wpe = NullPositionEmbeddings(model_dim)
# Built-in token embeddings are unused.
del gpt.wte

mel_pos_emb = (
LearnedPositionEmbeddings(max_mel_seq_len, model_dim)
if max_mel_seq_len != -1
else functools.partial(null_position_embeddings, dim=model_dim)
else NullPositionEmbeddings(model_dim)
)
text_pos_emb = (
LearnedPositionEmbeddings(max_text_seq_len, model_dim)
if max_mel_seq_len != -1
else functools.partial(null_position_embeddings, dim=model_dim)
else NullPositionEmbeddings(model_dim)
)
# gpt = torch.compile(gpt, mode="reduce-overhead", fullgraph=True)
return gpt, mel_pos_emb, text_pos_emb, None, None
Expand Down Expand Up @@ -359,6 +377,9 @@ def get_style_emb(self, cond_input, return_latent=False):
if not return_latent:
if cond_input.ndim == 4:
cond_input = cond_input.squeeze(1)
# the reference mel is always computed in float32; follow the encoder's dtype so
# that the model can be run in half precision
cond_input = cond_input.to(next(self.conditioning_encoder.parameters()).dtype)
conds = self.conditioning_encoder(cond_input) # (b, d, s)
if self.use_perceiver_resampler:
conds = self.conditioning_perceiver(conds.permute(0, 2, 1)).transpose(1, 2) # (b, d, 32)
Expand Down Expand Up @@ -502,6 +523,7 @@ def forward(
# Compute speech conditioning input
if cond_latents is None:
cond_latents = self.get_style_emb(cond_mels).transpose(1, 2)
cond_latents = cond_latents.to(text_emb.dtype)

# Get logits
sub = -5 # don't ask me why 😄
Expand Down Expand Up @@ -566,7 +588,9 @@ def compute_embeddings(
text_inputs = F.pad(text_inputs, (0, 1), value=self.stop_text_token)
text_inputs = F.pad(text_inputs, (1, 0), value=self.start_text_token)
emb = self.text_embedding(text_inputs) + self.text_pos_embedding(text_inputs)
emb = torch.cat([cond_latents, emb], dim=1)
# follow the model's dtype: float32 latents would promote the whole prefix back to
# float32 and silently undo half precision inference
emb = torch.cat([cond_latents.to(emb.dtype), emb], dim=1)
self.gpt_inference.store_prefix_emb(emb)
gpt_inputs = torch.full(
(
Expand Down Expand Up @@ -599,6 +623,77 @@ def generate(
return gen.sequences[:, gpt_inputs.shape[1] :], gen
return gen[:, gpt_inputs.shape[1] :]

def compute_embeddings_batch(self, cond_latents, text_inputs, text_lengths):
"""Batched counterpart of :meth:`compute_embeddings`.

Unlike the single-sequence path, the start/stop text tokens are expected to be
part of ``text_inputs`` already, so that every sentence keeps the same positional
embeddings it would get on its own; the padding sits after the stop token and is
hidden with the returned attention mask.

Args:
cond_latents: conditioning latents, ``(B, cond_len, dim)``.
text_inputs: right-padded ``[start, tokens..., stop]`` sequences, ``(B, T)``.
text_lengths: real length of each row of ``text_inputs``, ``(B,)``.

Returns:
Tuple of the placeholder gpt inputs ``(B, cond_len + T + 1)`` and the matching
attention mask.
"""
emb = self.text_embedding(text_inputs) + self.text_pos_embedding(text_inputs)
emb = torch.cat([cond_latents.to(emb.dtype), emb], dim=1)
self.gpt_inference.store_prefix_emb(emb)

gpt_inputs = torch.full(
(emb.shape[0], emb.shape[1] + 1), # +1 for the start_audio_token
fill_value=1,
dtype=torch.long,
device=text_inputs.device,
)
gpt_inputs[:, -1] = self.start_audio_token

cond_len = cond_latents.shape[1]
positions = torch.arange(text_inputs.shape[1], device=text_inputs.device)
attention_mask = torch.ones_like(gpt_inputs)
attention_mask[:, cond_len : cond_len + text_inputs.shape[1]] = (
positions[None, :] < text_lengths[:, None]
).long()
return gpt_inputs, attention_mask

def generate_batch(
self,
cond_latents,
text_inputs,
text_lengths,
**hf_generate_kwargs,
):
"""Generate mel codes for several sentences at once.

The autoregressive loop is bound by reading the model weights, so decoding a batch
costs barely more than decoding a single sequence. Returns one code sequence per
row, each already trimmed at its own stop token.
"""
gpt_inputs, attention_mask = self.compute_embeddings_batch(cond_latents, text_inputs, text_lengths)
gen = self.gpt_inference.generate(
gpt_inputs,
attention_mask=attention_mask,
bos_token_id=self.start_audio_token,
pad_token_id=self.stop_audio_token,
eos_token_id=self.stop_audio_token,
max_length=self.max_gen_mel_tokens + gpt_inputs.shape[-1],
**hf_generate_kwargs,
)
codes = gen[:, gpt_inputs.shape[1] :]

# Sequences that stopped early are padded with stop tokens up to the longest one;
# cut each back to its own stop token, which is what single-sentence decoding returns.
trimmed = []
for row in codes:
stops = (row == self.stop_audio_token).nonzero()
end = stops[0].item() + 1 if len(stops) > 0 else row.shape[0]
trimmed.append(row[:end].unsqueeze(0))
return trimmed

def get_generator(self, fake_inputs, **hf_generate_kwargs):
return self.gpt_inference.generate_stream(
fake_inputs,
Expand Down
15 changes: 14 additions & 1 deletion TTS/tts/layers/xtts/latent_encoder.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,21 @@


class GroupNorm32(nn.GroupNorm):
"""GroupNorm that always normalizes in float32, whatever dtype the model runs in.

The weights have to be cast along with the input: `super().forward(x.float())` would
otherwise feed float32 activations to half precision parameters.
"""

def forward(self, x):
return super().forward(x.float()).type(x.dtype)
out = F.group_norm(
x.float(),
self.num_groups,
self.weight.float() if self.weight is not None else None,
self.bias.float() if self.bias is not None else None,
self.eps,
)
return out.type(x.dtype)


def conv_nd(dims, *args, **kwargs):
Expand Down
Loading
Loading