GPT-SoVITS adaptado para português do Brasil — modelo base

Estudo de caso sobre como adicionar um idioma novo ao GPT-SoVITS. O upstream suporta chinês, cantonês, inglês, japonês e coreano — não português. Este repo guarda os checkpoints de uma adaptação para pt-BR, versão v2Pro.

⚠️ Trabalho em andamento e experimental. Os checkpoints aqui são de um treino em curso, publicados conforme saem. Não espere qualidade de produção. A intenção é que isto vire uma base pt-BR, sobre a qual se faz fine-tune de uma voz específica depois.

O problema central: a tabela de símbolos é indexada por posição

No GPT-SoVITS o ID de cada fonema é a posição dele na lista de símbolos ({s: i for i, s in enumerate(symbols)}). A lista da v2 tem 732 entradas e é construída com sorted(). Então inserir um símbolo novo no meio desloca todos os IDs seguintes e invalida o embedding de texto pré-treinado — sem lançar erro nenhum: o modelo simplesmente passa a falar embolado.

O upstream já tinha resolvido isso ao adicionar coreano e cantonês: os símbolos novos são anexados depois do sorted(), nunca intercalados. Esta adaptação segue a mesma regra.

O que foi feito

Item Escolha
G2P espeak-ng, voz pt-br, chamado por subprocesso
Símbolos pt-BR 88, anexados nos IDs 732–819
Prefixo P em todo símbolo pt
Embedding de texto estendido de 732 → 820 linhas
Corpus TTS-Portuguese Corpus (CC BY 4.0)
Versão v2Pro (GPT s1v3 + SoVITS s2Gv2Pro)

Por que espeak-ng e não gruut

O espeak tem voz pt-br de verdade (a do gruut é lusitana) e resolve palavra fora de dicionário por regra — o que funciona bem porque a ortografia do português é regular.

Por que os ditongos são separados

O espeak emite ditongos fundidos (ˈɐ̃ʊ̃). Aqui eles são quebrados em núcleo + glide (ˈɐ̃ + ʊ̃). Isso quase divide o inventário pela metade e faz cada símbolo receber muito mais ocorrências no treino — o que decide o resultado quando os dados são poucos.

Por que todo símbolo leva prefixo P

Sem prefixo, 22 dos fonemas pt (a, b, k, t, …) colidiriam com as iniciais de pinyin e o romaji japonês que já estão na tabela. Como o ID vem de enumerate(), a chave duplicada passaria a apontar para a última posição e roubaria silenciosamente o ID de um símbolo já treinado. O cantonês do upstream prefixou tudo com Y exatamente por isso.

Por que fonema estrangeiro é mapeado, não adicionado

Em palavra estrangeira o espeak troca de idioma e emite fonemas de fora do português: θ (Python), h (Hitler), ɑ (Darwin), c (Isaac). Esses não entram no inventário — virariam linhas de embedding que nenhuma palavra portuguesa treina, e sairiam como ruído na inferência. Cada um é mapeado para o vizinho português, que por acaso costuma ser como um brasileiro lê mesmo (Hitler → x, o "r" forte).

Dados

TTS-Portuguese Corpus (Casanova et al.), CC BY 4.0 — um falante, 10,48 h brutas medidas.

Preparo: corte de 2–20 s, reamostragem para 32 kHz mono. Sobram 8,38 h em 3192 clipes, com 0 UNK e 0 falhas de G2P.

Três áudios foram descartados por aparecerem duas vezes nos metadados do corpus com transcrições diferentes — o mesmo áudio com dois textos ensina um mapeamento contraditório.

Plano de treino

Fase 1 (esta) — só o TTS-Portuguese. GPT treinado pesado, SoVITS de leve.

O GPT (s1) mapeia fonemas → tokens semânticos: é onde mora pronúncia, ritmo e entonação, e é o que de fato precisa aprender o português. O SoVITS (s2) converte tokens semânticos + referência → forma de onda: é onde mora o timbre. Treinar o SoVITS pesado num corpus de um falante só assaria aquele timbre e o ruído do corpus no modelo acústico — justo o que precisa ficar neutro para o fine-tune posterior funcionar com outra voz.

Fase 2 (planejada) — somar um corpus multi-falante (CML-TTS português) para a base ficar neutra em timbre e gênero.

Resultado da fase 1

Treinado em 2026-08-13 numa RTX 4070 (12 GB). SoVITS: 6 épocas, ~25 min. GPT: 20 épocas, ~21 min (532 passos/época, batch 6).

Arquivos aqui: gpt/ptbr_base-eN.ckpt (épocas 2 a 20, de 2 em 2) e sovits/ptbr_base_eN_sM.pth (épocas 4 e 6).

As linhas pt-BR aprenderam?

Deslocamento do embedding em relação à base estendida, em % do desvio do próprio tensor (as escalas dos dois modelos são muito diferentes, então o valor absoluto não é comparável):

Modelo época 732 antigas 88 pt razão
SoVITS 6 0,2% 4,1% 17,1x
GPT 2 2,0% 8,3% 4,2x
GPT 20 6,7% 18,2% 2,7x

O SoVITS saiu como planejado: as linhas pt aprenderam e as antigas ficaram praticamente congeladas. Efeito do treino leve com text_low_lr_rate = 1.0, que mira o LR só nos parâmetros de texto.

O GPT reescreve o pré-treino. As 732 linhas antigas andaram 6,7%, porque o s1 não tem equivalente do text_low_lr_rate — o modelo inteiro treina com LR cheio. A razão estabiliza em 2,7x a partir da época 12, ou seja, é deriva que assenta e não esquecimento descontrolado. Ainda assim: este GPT fica pior em zh/en/ja/ko/yue. Para uma base pt-BR o custo é aceitável; para uso multilíngue, não.

Experimento: init normal contra warm

As 88 linhas novas do embedding podem começar de duas formas: sorteadas de uma normal com a média e o desvio do próprio tensor (normal), ou herdando o vetor do fonema ARPAbet mais próximo já treinado (warm).

Comparação controlada — mesmos dados, mesma seed, mesmo batch, 20 épocas, e as 732 linhas antigas bit a bit idênticas nos dois pontos de partida:

época top_3_acc normal warm ganho
0 0,16843 0,21763 +29,2%
1 0,25449 0,27412 +7,7%
2 0,28576 0,29454 +3,1%
19 0,55436 0,56039 +1,1%

Em tempo: o warm adianta +0,42 época, número ao qual a acurácia e a loss chegam de forma independente.

Conclusão: o warm dá uma partida bem melhor, mas a vantagem é absorvida rápido e acaba modesta. Vale como padrão porque é de graça; importaria de verdade em treino curto. Ressalva: uma seed por braço, sem intervalo de confiança.

Os pesos dos dois braços estão aqui: gpt/ptbr_base-* (normal) e gpt/ptbr_warm-* (warm).

O que ainda não foi medido

Não há avaliação perceptual (MOS, WER de ASR reversa) nem comparação entre --init normal e --init warm no embedding. As amostras geradas terminam por EOS dentro do limite de tokens e têm duração compatível com o texto, mas isso é sanidade de pipeline, não qualidade de voz.

Como usar

Os pesos são para o GPT-SoVITS v2Pro, e exigem o frontend pt-BR (o symbols2.py com os 88 símbolos e o text/portuguese.py). Com o symbols2.py original de 732 símbolos eles não funcionam: os IDs ≥732 estouram o embedding.

Selecione o idioma Português na interface de inferência.

Licenças e créditos

  • Código GPT-SoVITS: MIT, © 2024 RVC-Boss.
  • TTS-Portuguese Corpus: CC BY 4.0, Edresson Casanova et al.
  • Estes checkpoints: CC BY 4.0, herdando a exigência de atribuição do corpus.

Se você usar isto, cite o corpus:

@article{casanova2022tts,
  title   = {TTS-Portuguese Corpus: a corpus for speech synthesis in Brazilian Portuguese},
  author  = {Casanova, Edresson and Junior, Arnaldo Candido and Shulby, Christopher
             and de Oliveira, Frederico Santos and Teixeira, João Paulo and
             Ponti, Moacir Antonelli and Aluisio, Sandra},
  journal = {Language Resources and Evaluation},
  year    = {2022}
}
Downloads last month

-

Downloads are not tracked for this model. How to track
Inference Providers NEW
This model isn't deployed by any Inference Provider. 🙋 Ask for provider support