# Parameters
BATCH_MODE = "true"Multi-Model TTS Gateway - Synthese Vocale Multi-Modèles
Module : 02-Audio-Advanced
Niveau : Avance
Technologies : Kokoro TTS, TADA 3B ML, Qwen3 TTS
Duree estimee : 45 minutes
VRAM : ~12 GB total (2+6+4)
Objectifs d’Apprentissage
Prerequis
- Gateway TTS multi-modèle deploye (port 8196)
- Notebooks 01-5 (Kokoro) et 02-1 (Chatterbox) recommandes
- Comprehension de l’API OpenAI TTS
Navigation : Index | << Précédent
import os
# Parametres Papermill - JAMAIS modifier ce commentaire
# Configuration notebook
notebook_mode = "interactive" # "interactive" ou "batch"
skip_widgets = False # True pour mode batch MCP
debug_level = "INFO"
# Parametres TTS
# DEUX services existent (diagnostic #13110) :
# - TTS_GATEWAY_URL (port 8196) : gateway MULTI-modeles kokoro/tada/qwen -- c'est
# lui que ce notebook compare ;
# - TTS_API_URL (port 8191) : service Kokoro SEUL -- les routes /tada/* et /qwen/*
# n'y existent pas (404) et /health n'y a pas de cle "models".
gateway_url = os.getenv("TTS_GATEWAY_URL", "http://localhost:8196")
default_voice = "alloy" # Voix par defaut (OpenAI-compatible)
compare_all_models = True # Comparer les 3 modeles
benchmark_latency = True # Benchmarks de latence
save_results = True # Sauvegarder les resultats# Setup environnement et imports
import os
import sys
import json
import time
import requests
from pathlib import Path
from datetime import datetime
from typing import Dict, List, Any, Optional
import logging
import numpy as np
import soundfile as sf
from IPython.display import Audio, display, HTML
# Import helpers GenAI
GENAI_ROOT = Path.cwd()
while GENAI_ROOT.name != 'GenAI' and len(GENAI_ROOT.parts) > 1:
GENAI_ROOT = GENAI_ROOT.parent
HELPERS_PATH = GENAI_ROOT / 'shared' / 'helpers'
if HELPERS_PATH.exists():
sys.path.insert(0, str(HELPERS_PATH.parent))
try:
from helpers.audio_helpers import (
play_audio, save_audio, display_audio_array, display_audio_bundle
)
print("Helpers audio importes")
except ImportError:
print("Helpers audio non disponibles - mode autonome")
# Repertoires
OUTPUT_DIR = GENAI_ROOT / 'outputs' / 'audio' / 'multi-tts'
OUTPUT_DIR.mkdir(parents=True, exist_ok=True)
# Configuration logging
logging.basicConfig(level=getattr(logging, debug_level))
logger = logging.getLogger('multi_tts')
print(f"Multi-Model TTS Gateway - Synthese Vocale")
print(f"Date : {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
print(f"Gateway : {gateway_url}")
print(f"Sortie : {OUTPUT_DIR.relative_to(GENAI_ROOT)}")Helpers audio importes
Multi-Model TTS Gateway - Synthese Vocale
Date : 2026-08-28 18:05:24
Gateway : http://localhost:8196
Sortie : outputs\audio\multi-tts
L’environnement Python est configuré. La cellule suivante charge le fichier .env et résout l’URL du gateway : TTS_GATEWAY_URL (gateway multi-modèles, port 8196) est la cible de ce notebook ; TTS_API_URL (service Kokoro seul, port 8191) n’est qu’un fallback explicite — interroger ce dernier sur les routes /tada/* ou /qwen/* répond 404, car ces routes n’y existent pas (diagnostic #13110).
# Chargement de la configuration .env
from dotenv import load_dotenv
# Remonter jusqu'au dossier GenAI
current_path = Path.cwd()
genai_path = None
while len(current_path.parts) > 1:
if current_path.name == "GenAI":
genai_path = current_path
break
current_path = current_path.parent
if genai_path:
env_path = genai_path / ".env"
if env_path.exists():
load_dotenv(env_path)
print(f".env charge depuis: {env_path.name}")
else:
print("WARNING: .env non trouve dans GenAI")
else:
print("WARNING: Dossier GenAI non trouve")
# Routage explicite (#13110) : le gateway multi-modeles est designe par
# TTS_GATEWAY_URL + TTS_GATEWAY_API_KEY. TTS_API_URL designe le service Kokoro
# seul : sur ce service, /tada/* et /qwen/* repondent 404 (routes inexistantes)
# et /health n'expose pas la cle "models".
_gw_url = os.getenv('TTS_GATEWAY_URL')
if _gw_url:
tts_url = _gw_url
tts_api_key = os.getenv('TTS_GATEWAY_API_KEY', '')
tts_url_source = 'TTS_GATEWAY_URL (gateway multi-modeles)'
else:
tts_url = os.getenv('TTS_API_URL', gateway_url)
tts_api_key = os.getenv('TTS_API_KEY', '')
tts_url_source = 'TTS_API_URL (fallback -- service possiblement Kokoro seul)'
print("WARNING: TTS_GATEWAY_URL absente du .env -- fallback sur TTS_API_URL.")
print(" Si cette URL designe le service Kokoro seul (port 8191), les")
print(" routes /tada et /qwen n'existent pas : la comparaison restera")
print(" limitee a Kokoro avec des verdicts RECOVERABLE pour les autres.")
tts_headers = {"Authorization": f"Bearer {tts_api_key}"} if tts_api_key else {}
print(f"TTS Gateway URL: {tts_url} (source: {tts_url_source})")
print(f"Auth: {'cle configuree (TTS_GATEWAY_API_KEY)' if tts_api_key else 'ABSENTE -- le gateway requiert une cle'}")WARNING: .env non trouve dans GenAI
TTS Gateway URL: http://localhost:8196 (source: TTS_GATEWAY_URL (gateway multi-modeles))
Auth: cle configuree (TTS_GATEWAY_API_KEY)
Section 1 : Architecture Multi-Model TTS
Le gateway TTS multi-modèle expose 3 modèles via des paths différents :
Gateway (Port 8196)
├── /v1/audio/speech → Kokoro TTS (default)
├── /tada/v1/audio/speech → TADA 3B ML
└── /qwen/v1/audio/speech → Qwen3 TTS
Modèles disponibles
| Modèle | VRAM | Vitesse | Qualite | Voix | Cas d’usage |
|---|---|---|---|---|---|
| Kokoro | ~2GB | Très rapide | Bonne | 6 | Prototypage, gros volume |
| TADA 3B ML | ~6GB | Rapide | Très bonne | 1 | Qualite professionnelle |
| Qwen3 TTS | ~4GB | Moyen | Excellente | 6+ | Voice cloning, custom |
Avantages de l’architecture
- Backward compatible :
/v1/audio/speechutilise Kokoro par defaut - Path-based routing : Pas de paramètre
modelnecessaire - Load balancing : Chaque modèle peut etre deploye sur des GPUs différents
- Lazy loading : Les modèles se chargent a la demande
- OpenAI-compatible : Même API que OpenAI TTS
Le diagramme ci-dessous rend le routage de la gateway TTS sous forme de graphe : un point d’entree unique dispatche vers trois moteurs selon le prefixe d’URL.
flowchart LR
GW(["Gateway<br/>(Port 8196)"]) --> K["/v1/audio/speech<br/>Kokoro TTS (default)"]
GW --> T["/tada/v1/audio/speech<br/>TADA 3B ML"]
GW --> Q["/qwen/v1/audio/speech<br/>Qwen3 TTS"]
classDef gw fill:#d1e7dd,stroke:#0f5132,color:#0a3622
classDef eng fill:#fff3cd,stroke:#b8860b,color:#5c4400
class GW gw
class K,T,Q eng
Lecture. La gateway expose une API unique (port 8196) et route chaque requête vers le moteur TTS approprie selon le prefixe de chemin :
/v1/...vers Kokoro (defaut),/tada/...vers TADA 3B ML,/qwen/...vers Qwen3 TTS. Ce patron de reverse proxy applicatif permet d’offrir une interface OpenAI-compatible homogene tout en multiplexant plusieurs backends specialises derriere une seule adresse.
Verification de l’etat du gateway
Avant de tester la generation TTS, il est essentiel de verifier que le gateway multi-modèle est operationnel et que tous les modèles sont correctement enregistres.
Procedure de verification : 1. Health check : Interroger l’endpoint /health pour confirmer que le gateway tourne 2. Liste des modèles : Recuperer la liste des modèles enregistres via /v1/models 3. Liste des voix : Verifier les voix disponibles pour chaque modèle via /v1/voices
Endpoints testes : - GET {gateway_url}/health : Status du gateway - GET {gateway_url}/v1/models : Modèles disponibles avec leurs paths - GET {gateway_url}/v1/voices : Voix enregistrees et compatibilites
Cette verification permet de s’assurer que l’infrastructure est prete avant de lancer les tests de generation, ce qui evite de perdre du temps a debugger des erreurs reseau ou de configuration.
# Verification du gateway et des modeles
print("VERIFICATION DU GATEWAY")
print("=" * 50)
try:
# Health check -- deux formats possibles (#13110) :
# gateway multi-modeles : {"status": ..., "models": ["kokoro","tada","qwen"]}
# service Kokoro seul : {"status": ..., "model": "kokoro", ...}
response = requests.get(f"{tts_url}/health", timeout=10)
health = response.json()
print(f"Status: {health.get('status', 'inconnu')}")
if 'models' in health:
print(f"Modeles declares par le gateway: {', '.join(health['models'])}")
elif 'model' in health:
print(f"Service MONO-modele detecte (cle 'model'): {health['model']}")
print(" -> un /health sans cle 'models' designe un service seul, pas le")
print(" gateway multi-modeles : verifier TTS_GATEWAY_URL (port 8196).")
else:
print("Format de /health inconnu (ni 'models' ni 'model').")
# Lister les modeles (routes reelles)
try:
response = requests.get(f"{tts_url}/v1/models", headers=tts_headers, timeout=10)
models_info = response.json()
print(f"\nModeles enregistres (routes reelles):")
for model in models_info.get('data', []):
print(f" - {model['id']}: {model['name']}")
print(f" Path: {model['path']}")
print(f" Description: {model['description']}")
except Exception as e:
print(f"\n(listing /v1/models indisponible: {str(e)[:80]})")
# Lister les voix
try:
response = requests.get(f"{tts_url}/v1/voices", headers=tts_headers, timeout=10)
voices_info = response.json()
print(f"\nVoix disponibles:")
for voice in voices_info.get('voices', []):
models = ', '.join(voice.get('models', []))
print(f" - {voice['id']}: {voice['name']} (modeles: {models})")
except Exception as e:
print(f"\n(listing /v1/voices indisponible: {str(e)[:80]})")
print("\nGateway operationnel!")
except requests.exceptions.ConnectionError:
print("ERREUR: Gateway non accessible")
print(f"Verifiez que le service tourne sur {tts_url}")
except Exception as e:
print(f"ERREUR: {str(e)[:100]}")VERIFICATION DU GATEWAY
==================================================
Status: healthy
Modeles declares par le gateway: kokoro, tada, qwen
Modeles enregistres (routes reelles):
- kokoro: Kokoro TTS
Path: /v1/audio/speech
Description: Fast, lightweight TTS model (default)
- tada: TADA 3B ML
Path: /tada/v1/audio/speech
Description: HumeAI's advanced TTS model
- qwen3: Qwen3 TTS
Path: /qwen/v1/audio/speech
Description: Qwen's high-quality TTS with custom voice
Voix disponibles:
- alloy: Alloy (modeles: kokoro, tada, qwen)
- echo: Echo (modeles: kokoro, tada, qwen)
- fable: Fable (modeles: kokoro, tada, qwen)
- onyx: Onyx (modeles: kokoro, tada, qwen)
- nova: Nova (modeles: kokoro, tada, qwen)
- shimmer: Shimmer (modeles: kokoro, tada, qwen)
Gateway operationnel!
Interpretation du health check
La cellule ci-dessus interroge le service et imprime ce qu’elle trouve – cette interpretation decrit comment lire sa sortie, elle ne recopie aucun chiffre.
Deux formats de /health existent (source du diagnostic #13110) :
| Format detecte | Signification | Consequence pour la comparaison |
|---|---|---|
cle 'models' (liste) |
Gateway multi-modeles (kokoro/tada/qwen, port 8196) | Comparaison possible sur les 3 routes |
cle 'model' (scalaire) |
Service mono-modele (Kokoro seul, port 8191) | Routes /tada/* et /qwen/* inexistantes -> 404, comparaison limitee a Kokoro |
Ce que révèle chaque cas :
- Si la cellule affiche la liste des modeles et leurs paths (
/v1/audio/speech,/tada/v1/audio/speech,/qwen/v1/audio/speech), le gateway multi-modeles est bien atteint : la comparaison de la Section 2 pourra mesurer les trois moteurs. - Si elle affiche
Service MONO-modele detecte, la variable d’environnementTTS_GATEWAY_URLest absente et le fallback a pris le service Kokoro seul : la comparaison rendra des verdictsRECOVERABLEpour TADA et Qwen – c’est le comportement honnete, pas un echec du notebook.
Note technique : un
404sur/tada/*ou/qwen/*signifie que la route n’existe pas sur le service interroge (routage vers le mauvais service) ; un401signifie que la route existe mais que la cle d’API manque. Ces deux codes racontent des problemes differents – le tableau de la Section 2 les distingue.
Exercice 1 : Monitoring du Gateway avec Health Check Automatise
Duree estimee : 10 minutes
Dans un environnement de production, il est crucial de surveiller la disponibilite des modèles TTS. Dans cet exercice, vous allez implementer une fonction qui interroge le health check du gateway a intervalles reguliers et genere un rapport de disponibilite.
Objectif : Créer check_gateway_availability() qui teste l’endpoint /health du gateway, enregistre le statut de chaque modèle (disponible/indisponible), et retourne un resume.
Indices : - # Étape 1 : Envoyer une requête GET sur {gateway_url}/health avec un timeout de 10 secondes - # Étape 2 : Parser la reponse JSON pour extraire le statut global et la liste des modèles - # Étape 3 : Construire un dictionnaire {"status": "healthy/unhealthy", "models": {"kokoro": True, "tada": False, ...}} - # Indice : Utiliser requests.get(url, timeout=10) et gerer les exceptions ConnectionError et Timeout
def check_gateway_availability(gateway_url):
"""
Verifie la disponibilite du gateway TTS et de ses modeles.
Args:
gateway_url (str): URL de base du gateway
Returns:
dict: {"status": "healthy"/"unreachable"/"error",
"models": {"kokoro": bool, "tada": bool, "qwen": bool},
"response_time_s": float}
"""
# TODO etudiant : implementer le health check
# Etape 1 : Envoyer une requete GET sur /health avec timeout
# Etape 2 : Parser la reponse JSON pour le statut et les modeles
# Etape 3 : Gerer les exceptions (ConnectionError, Timeout)
result = {"status": "unchecked", "models": {}, "response_time_s": 0.0}
return result # TODO etudiant : retourner le rapport
# TODO etudiant : tester le health check
# health_report = check_gateway_availability(tts_url)
# print(f"Status: {health_report['status']}")
# print(f"Response time: {health_report['response_time_s']:.2f}s")
# for model, available in health_report['models'].items():
# print(f" {model}: {'OK' if available else 'INDISPONIBLE'}")
print("Exercice a completer")Exercice a completer
Section 2 : Génération et comparaison des moteurs déployés
Comparaison directe des modèles réellement déployés sur le gateway, sur un texte commun, avec essais répétés — le bilan ne retient que ce qui a tourné.
Approche de comparaison
Le code suivant mesure les moteurs réellement déployés sur le gateway, sur un texte commun et des paramètres comparables, avec essais répétés.
Methodologie de test : 1. Texte commun : le même texte est envoyé aux trois modèles (mêmes mots, même voix) — pas de variante courte par défaut 2. Essais répétés : N_TRIALS = 3 appels par modèle ; le tableau final rapporte la médiane et la dispersion — robustes à l’essai de chauffe (premier appel = chargement du modèle en VRAM) 3. Routage par path : chaque modèle est contacté sur son endpoint spécifique (pas de paramètre model) 4. Gestion d’erreur : timeout spécifique par modèle (runtime machine-dep, incluant le chargement à froid : 120s Kokoro, 240s TADA, 300s Qwen3) ; un essai échoué n’interrompt ni les essais suivants ni les modèles suivants 5. Bilan honnête : le tableau est généré depuis results — statut HTTP, latence, durée audio, RTF, taille — et le compte final ne retient que les moteurs réellement testés avec succès ; un moteur absent reçoit un verdict RECOVERABLE-*
Paramètres de configuration : - sample_text : texte commun (~220 caractères) pour les trois modèles - default_voice : voix “alloy” (compatible OpenAI) - timeout : adapté à la vitesse de chargement de chaque modèle
Résultats attendus : - Tableau comparatif auto-généré avec médianes et dispersions - Fichiers audio sauvegardés dans outputs/audio/multi-tts/ - Lecteurs audio embarqués (aperçus ~3s) pour écoute directe
# Comparaison des modeles TTS reellement deployes -- texte commun, essais repetes
import io
print("COMPARAISON DES MODELES TTS")
print("=" * 50)
# Texte COMMUN aux trois modeles : memes mots, memes parametres (comparabilite).
sample_text = (
"L'intelligence artificielle transforme la facon dont nous "
"interagissons avec la technologie. La synthese vocale est "
"devenue si naturelle qu'il est parfois difficile de distinguer "
"une voix humaine d'une voix artificielle."
)
# Essais repetes : la mediane est robuste a l'essai de chauffe (le premier appel
# d'un moteur peut payer son chargement en VRAM), la dispersion le montre.
# timeout genereux : chargement a froid possible selon l'activite GPU de la machine.
N_TRIALS = 3
models_config = {
"kokoro": {
"url": f"{tts_url}/v1/audio/speech",
"name": "Kokoro",
"description": "Modele leger et rapide (propriete documentee)",
"timeout": 120,
},
"tada": {
"url": f"{tts_url}/tada/v1/audio/speech",
"name": "TADA 3B ML",
"description": "Modele avance de HumeAI (propriete documentee)",
"timeout": 240,
},
"qwen": {
"url": f"{tts_url}/qwen/v1/audio/speech",
"name": "Qwen3 TTS",
"description": "Modele haute qualite de Qwen (propriete documentee)",
"timeout": 300,
},
}
results = {}
for model_key, config in models_config.items():
print(f"\n--- {config['name']} ---")
print(f"Description (documentee) : {config['description']}")
trials = []
kept = None
for trial in range(1, N_TRIALS + 1):
try:
start_time = time.time()
# Pas de parametre "model" : le routage se fait par le path
response = requests.post(
config['url'],
json={"input": sample_text, "voice": default_voice},
headers=tts_headers,
timeout=config['timeout'],
)
elapsed = time.time() - start_time
content_type = response.headers.get('content-type', '')
if response.status_code == 200 and 'json' not in content_type:
audio_bytes = response.content
audio_data, sample_rate = sf.read(io.BytesIO(audio_bytes))
duration = len(audio_data) / sample_rate
trials.append({"status": 200, "latency_s": elapsed,
"duration_s": duration,
"size_kb": len(audio_bytes) / 1024})
if kept is None:
kept = (audio_bytes, audio_data, sample_rate)
print(f" essai {trial}/{N_TRIALS} : OK latence={elapsed:.2f}s "
f"audio={duration:.1f}s ({len(audio_bytes)/1024:.1f} KB)")
elif response.status_code == 200:
trials.append({"status": -1, "latency_s": elapsed})
print(f" essai {trial}/{N_TRIALS} : reponse JSON inattendue -- "
f"{str(response.json())[:120]}")
else:
trials.append({"status": response.status_code, "latency_s": elapsed})
print(f" essai {trial}/{N_TRIALS} : ECHEC HTTP {response.status_code} -- "
f"{response.text[:120]}")
except requests.exceptions.Timeout:
trials.append({"status": "timeout", "latency_s": config['timeout']})
print(f" essai {trial}/{N_TRIALS} : TIMEOUT (> {config['timeout']}s)")
except Exception as e:
print(f" essai {trial}/{N_TRIALS} : ERREUR {str(e)[:100]}")
ok_trials = [t for t in trials if t.get("status") == 200]
results[model_key] = {
"trials": trials,
"n_ok": len(ok_trials),
"n_trials": N_TRIALS,
"text_len": len(sample_text),
}
if kept is not None:
audio_bytes, audio_data, sample_rate = kept
results[model_key].update({
"audio": audio_bytes,
"duration": ok_trials[0]["duration_s"],
"sample_rate": sample_rate,
})
# ~3s apercu (convention audio-roll) ; audio complet sauvegarde sur disque
try:
_preview = audio_data[:int(sample_rate * 3)]
_buf = io.BytesIO()
sf.write(_buf, _preview, sample_rate, format="WAV")
display_audio_bundle(_buf.getvalue())
except NameError:
print(" (helpers audio indisponibles : pas d'apercu inline)")
if save_results:
filepath = OUTPUT_DIR / f"{model_key}_comparison.wav"
with open(filepath, 'wb') as f:
f.write(audio_bytes)
print(f" Sauvegarde : {filepath.name}")COMPARAISON DES MODELES TTS
==================================================
--- Kokoro ---
Description (documentee) : Modele leger et rapide (propriete documentee)
essai 1/3 : OK latence=13.83s audio=13.9s (652.8 KB)
essai 2/3 : OK latence=2.89s audio=13.9s (652.8 KB)
essai 3/3 : OK latence=2.99s audio=13.9s (652.8 KB)
Sauvegarde : kokoro_comparison.wav
--- TADA 3B ML ---
Description (documentee) : Modele avance de HumeAI (propriete documentee)
essai 1/3 : OK latence=9.51s audio=12.3s (574.7 KB)
essai 2/3 : OK latence=8.90s audio=16.0s (749.1 KB)
essai 3/3 : OK latence=9.74s audio=13.9s (649.7 KB)
Sauvegarde : tada_comparison.wav
--- Qwen3 TTS ---
Description (documentee) : Modele haute qualite de Qwen (propriete documentee)
essai 1/3 : OK latence=40.18s audio=14.1s (660.0 KB)
essai 2/3 : OK latence=42.86s audio=15.9s (746.3 KB)
essai 3/3 : OK latence=43.02s audio=16.0s (750.0 KB)
Sauvegarde : qwen_comparison.wav
Lecture des resultats de generation
Les chiffres ci-dessus sont les mesures du run courant. Cette section explique comment les lire – elle ne recopie aucun statut ni aucun chiffre : le tableau comparatif suivant est genere automatiquement depuis results.
Documente vs mesure :
- Les descriptions des modeles (« leger et rapide », « avance », « haute qualite ») sont des proprietes documentees – des fiches constructeur, pas des observations de ce run.
- Les colonnes du tableau (statut HTTP, latence, duree audio, ratio temps reel, taille) sont des observations mesurees dans ce notebook, sur le texte commun, avec essais repetes.
Si un moteur echoue (404, 401, timeout), le tableau affiche le code d’echec et un verdict RECOVERABLE-MACHINE, et le bilan final compte uniquement les moteurs reellement testes avec succes. Un 404 sur une route prefixee signifie que la route n’existe pas sur le service interroge (cf. routage TTS_GATEWAY_URL vs TTS_API_URL en tete de notebook) ; un 401 signale une cle d’API manquante.
Robustesse du code de comparaison :
requests.exceptions.Timeoutcapture les depassements de delai (timeout par modele)status_code != 200capture les erreurs HTTP sans interrompre les modeles suivants- la verification
content-typeevite de lire du JSON comme de l’audio - chaque moteur garde ses essais separes : un essai echoue ne fausse pas la mediane
Note pedagogique : un modele en panne ne doit pas faire echouer tout le systeme – le pattern « collecter l’echec, continuer, reverdiger le bilan sur ce qui a tourne » est exactement ce qu’on attend d’une application de production.
La cellule suivante construit le tableau comparatif entièrement depuis results : statut HTTP par essai, latence médiane et dispersion (essais répétés), durée audio médiane, ratio temps réel, taille — et le nombre de moteurs réellement testés avec succès. Aucune ligne de ce tableau n’est écrite à la main.
# Tableau comparatif -- GENERE AUTOMATIQUEMENT depuis results (aucun chiffre recopie)
import statistics
print("\n" + "=" * 95)
print("TABLEAU COMPARATIF (mesures du run courant, genere depuis results)")
print("=" * 95)
print(f"{'Modele':<12} {'Essais OK':<10} {'Latence med':<12} {'Dispersion':<16} "
f"{'Audio med':<11} {'RTF':<7} {'KB med':<9}")
print("-" * 95)
n_ok_models = 0
for model_key, data in results.items():
ok = [t for t in data['trials'] if t.get('status') == 200]
name = models_config[model_key]['name']
if ok:
n_ok_models += 1
lats = [t['latency_s'] for t in ok]
durs = [t['duration_s'] for t in ok]
sizes = [t['size_kb'] for t in ok]
med_l, med_d = statistics.median(lats), statistics.median(durs)
if len(lats) > 1:
disp = f"+{(max(lats) - med_l):.2f}/-{(med_l - min(lats)):.2f}s"
else:
disp = "n/a (1 essai)"
rtf = med_d / med_l if med_l > 0 else 0.0
print(f"{name:<12} {len(ok)}/{data['n_trials']:<7} {med_l:<12.2f} {disp:<16} "
f"{med_d:<11.1f} {rtf:<7.1f} {statistics.median(sizes):<9.1f}")
else:
codes = sorted({str(t.get('status')) for t in data['trials']})
verdict = ("RECOVERABLE-MACHINE (route inexistante : mauvais service interroge)"
if any(c == '404' for c in codes)
else "RECOVERABLE-MACHINE (route joignable mais echec/timeout)")
print(f"{name:<12} 0/{data['n_trials']:<7} -- code(s) {','.join(codes)} : {verdict}")
print("-" * 95)
print(f"Modeles reellement testes avec succes : {n_ok_models}/{len(results)}")
if n_ok_models > 1:
lats = {models_config[k]['name']: statistics.median(
[t['latency_s'] for t in results[k]['trials'] if t.get('status') == 200])
for k in results if results[k]['n_ok'] > 0}
fastest = min(lats, key=lats.get)
print(f"Latence mediane la plus basse : {fastest} ({lats[fastest]:.2f}s)")
print("\nNote : aucun classement de qualite/naturalite ici -- cela exigerait un")
print("protocole d'ecoute (MOS) ou une metrique definie, hors scope de ce notebook.")
===============================================================================================
TABLEAU COMPARATIF (mesures du run courant, genere depuis results)
===============================================================================================
Modele Essais OK Latence med Dispersion Audio med RTF KB med
-----------------------------------------------------------------------------------------------
Kokoro 3/3 2.99 +10.84/-0.11s 13.9 4.7 652.8
TADA 3B ML 3/3 9.51 +0.23/-0.61s 13.9 1.5 649.7
Qwen3 TTS 3/3 42.86 +0.16/-2.68s 15.9 0.4 746.3
-----------------------------------------------------------------------------------------------
Modeles reellement testes avec succes : 3/3
Latence mediane la plus basse : Kokoro (2.99s)
Note : aucun classement de qualite/naturalite ici -- cela exigerait un
protocole d'ecoute (MOS) ou une metrique definie, hors scope de ce notebook.
Interpretation de la comparaison
La lecture se fait sur les colonnes mesurées du tableau ci-dessus — cette interprétation décrit les colonnes, elle ne récite pas leurs valeurs (elles sont celles du run courant, imprimées par la cellule précédente).
- Essais OK : nombre d’essais réussis sur le total tenté. Un moteur à 0 essai reçoit un verdict
RECOVERABLE-MACHINEet reste hors du bilan comparatif — on ne compare pas un moteur qu’on n’a pas mesuré. - Latence (médiane ± dispersion) : temps de génération sur les essais réussis. La médiane est robuste à l’essai de chauffe — le premier appel d’un moteur peut payer son chargement en VRAM, la dispersion le révèle (une forte dispersion asymétrique signale un essai froid).
- Ratio temps réel (RTF) : durée audio produite ÷ latence. RTF > 1 : le moteur produit l’audio plus vite que sa durée d’écoute.
- KB méd : taille de l’audio produit — utile pour estimer un débit de stockage.
Propriétés documentées ≠ observations : « leger/rapide » (Kokoro), « avancé » (TADA), « haute qualité » (Qwen3) sont des fiches de modèle. Seules les colonnes du tableau sont mesurées ici. Aucun classement de qualité/naturalité n’est émis : cela exigerait un protocole d’écoute (type MOS) ou une métrique objective définie, hors scope de ce notebook.
Choix de modèle en production : le critère mesuré pertinent dépend du cas d’usage — latence (interaction vocale) vs RTF (génération en masse). La Section 3 donne l’arbre de décision documenté, à croiser avec ces mesures sur votre matériel.
Exemple guide 1 : Comparaison personnalisee avec paramètres
L’exemple ci-dessus compare les modèles déployés avec le texte commun et des paramètres par défaut. Votre mission : testez les modèles avec des textes plus longs et des paramètres différents pour comprendre l’impact sur la qualite et le temps de generation.
Indices : - Utilisez un texte de 100+ mots (extrait de Wikipedia ou autre) - Testez avec speed différents (0.5, 1.0, 1.5, 2.0) pour Kokoro - Mesurez le runtime machine-dep : temps de generation pour chaque configuration - Observez comment la longueur du texte affecte le ratio real-time
# --- Exercice 1 : Comparaison personnalisee ---
def compare_tts_long_text(text, models=None, speed_values=None):
"""
Compare les modeles TTS avec un texte long et des vitesses differentes.
Retourne un DataFrame avec les resultats.
"""
# Exercice: Pour chaque modele, generer l'audio avec le texte long
# Exercice: Pour Kokoro, tester differentes valeurs de speed
# Exercice: Mesurer le temps de generation et la duree audio
# Exercice: Calculer le ratio real-time pour chaque configuration
pass
# Test
# long_text = "Votre texte de 100+ mots ici..."
# df = compare_tts_long_text(long_text)
# print(df.to_string())Section 3 : Guide de Sélection de Modèle
Cette section est un guide documenté (fiches de modèles, arbres de décision) — à croiser avec les observations mesurées de la Section 2 sur votre matériel.
Arbre de decision
Besoin TTS
│
├─ Prototypage / Tests → Kokoro (rapide, gratuit)
├─ Production standard → TADA 3B ML (qualite, vitesse)
├─ Haute qualite → Qwen3 TTS (meilleure naturalite)
├─ Gros volume → Kokoro (pas de cout per-token)
└─ Voice cloning → Qwen3 TTS (custom voices)
Cas d’usage par modèle
| Cas d’usage | Modèle recommande | Pourquoi |
|---|---|---|
| Prototypage rapide | Kokoro | Instantane, pas d’attente |
| Audiobooks | TADA 3B ML | Voix claire, professionnelle |
| Chatbots vocaux | Kokoro | Reponse rapide, faible latence |
| Voice cloning | Qwen3 TTS | Support voix customisees |
| Applications mobiles | Kokoro | Modèle leger, fonctionne sur CPU |
| Contenu marketing | Qwen3 TTS | Meilleure qualite perceptuelle |
Transition vers l’integration
Maintenant que nous avons compare les 3 modèles et compris leurs forces/faiblesses, passons a l’integration pratique. La section suivante montre comment encapsuler la logique de routing dans un client Python reutilisable, puis comment automatiser la sélection du modèle avec un routeur intelligent.
Points abordes : - Client Python multi-modèle avec gestion d’erreurs - Architecture orientee objet pour faciliter l’integration - Routeur intelligent qui selectionne le meilleur modèle automatiquement
Section 4 : Integration dans une Application
Exemple de code Python
import os
import requests
class MultiModelTTSClient:
"""Client TTS multi-modèle."""
def __init__(self, gateway_url="http://localhost:8196"):
self.gateway_url = gateway_url
self.api_key = os.getenv("TTS_GATEWAY_API_KEY", "")
self.models = {
"kokoro": "/v1/audio/speech",
"tada": "/tada/v1/audio/speech",
"qwen": "/qwen/v1/audio/speech"
}
def generate(self, text: str, model: str = "kokoro", voice: str = "alloy"):
"""Generer de la parole avec le modèle specifie."""
url = self.gateway_url + self.models[model]
response = requests.post(
url,
json={"input": text, "voice": voice},
headers={"Authorization": f"Bearer {self.api_key}"},
timeout=60
)
if response.status_code == 200:
return response.content
else:
raise Exception(f"TTS error: {response.status_code}")
# Utilisation
client = MultiModelTTSClient()
audio = client.generate("Bonjour!", model="tada", voice="alloy")Note : l’URL par défaut est le gateway multi-modèles local (port 8196, variable
TTS_GATEWAY_URL) ; la clé vient deTTS_GATEWAY_API_KEY.
# Statistiques de session
print("STATISTIQUES DE SESSION")
print("=" * 50)
print(f"Date : {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
print(f"Gateway : {tts_url}")
print(f"Modeles testes avec succes : "
f"{sum(1 for d in results.values() if d['n_ok'] > 0)}/{len(results)} "
f"(essais OK : {sum(d['n_ok'] for d in results.values())}/"
f"{sum(d['n_trials'] for d in results.values())})")
if save_results:
saved = list(OUTPUT_DIR.glob('*'))
total_size = sum(f.stat().st_size for f in saved) / (1024**2)
print(f"Fichiers sauvegardes : {len(saved)} ({total_size:.1f} MB)")
print(f"\nPROCHAINES ETAPES")
print(f"1. Integrer le TTS dans une application FastAPI (03-1)")
print(f"2. Construire un pipeline vocal complet (03-2)")
print(f"3. Explorer le voice cloning avec XTTS (02-2)")
print(f"4. Comparer tous les modeles TTS et STT (03-1)")
print(f"\nNotebook Multi-Model TTS termine - {datetime.now().strftime('%H:%M:%S')}")STATISTIQUES DE SESSION
==================================================
Date : 2026-08-28 18:08:25
Gateway : http://localhost:8196
Modeles testes avec succes : 3/3 (essais OK : 9/9)
Fichiers sauvegardes : 3 (1.8 MB)
PROCHAINES ETAPES
1. Integrer le TTS dans une application FastAPI (03-1)
2. Construire un pipeline vocal complet (03-2)
3. Explorer le voice cloning avec XTTS (02-2)
4. Comparer tous les modeles TTS et STT (03-1)
Notebook Multi-Model TTS termine - 18:08:25
Conclusion du Notebook
Ce notebook a presente l’architecture multi-modèle TTS avec un gateway centralise qui permet de router les requêtes vers le modèle le plus adapte au cas d’usage.
Points cles retenus
| Concept | Description |
|---|---|
| Path-based routing | Chaque modèle a son propre endpoint (/v1/audio/speech, /tada/v1/audio/speech, /qwen/v1/audio/speech) |
| Routage d’environnement | TTS_GATEWAY_URL (8196) = gateway multi-modèles ; TTS_API_URL (8191) = service Kokoro seul — les routes /tada/*, /qwen/* y répondent 404 |
| Bilan généré, pas recopié | Le tableau comparatif est construit depuis results : statut HTTP, latence médiane ± dispersion, RTF, nombre de moteurs réellement testés |
| Documenté ≠ mesuré | Les fiches des modèles sont des propriétés documentées ; seules les colonnes du tableau sont des observations de ce run |
| Integration simple | Client Python avec méthode generate(text, model, voice) |
Tableau decisionnel (documenté)
| Scénario | Modèle | Rationale |
|---|---|---|
| Prototypage rapide | Kokoro | Generation instantanee, faible latence |
| Production standard | TADA 3B ML | Equilibre qualite/vitesse |
| Haute qualite | Qwen3 TTS | Meilleure naturalite, support voix custom |
| Gros volume | Kokoro | Pas de cout per-token, leger |
| Applications mobiles | Kokoro | Compatible CPU, faible footprint |
À croiser avec les mesures de ce run (Section 2) sur votre matériel.
Prochaines étapes
- Integration API : Construire un endpoint FastAPI qui expose le gateway TTS (notebook 03-1)
- Pipeline vocal complet : Combiner STT (Whisper/Qwen ASR) + TTS pour une application de dialogue (notebook 03-2)
- Voice cloning : Explorer XTTS pour créer des voix personnalisees (notebook 02-2)
- Optimisation : Implementer le cache audio pour reduire la latence sur les textes repetitifs
Note technique : L’authentification par API key (header
Authorization) est necessaire pour les appels au gateway. Configurez la variable d’environnementTTS_GATEWAY_API_KEY(etTTS_GATEWAY_URL) dans le fichier.envdu dossier GenAI.
Exercice 2 : Routage intelligent du gateway TTS
Un gateway TTS doit choisir le meilleur modèle selon les caractéristiques de la demande (langue, qualite, latence). Implementez une fonction de routage.
Indice : Définir des critères de sélection (langue supportee, qualite requise) et retourner le nom du modèle.
def route_tts_request(text, target_lang, quality="standard", max_latency_ms=500):
# Etape 1 : Lister les modeles disponibles et leurs capacites
# Etape 2 : Filtrer par langue supportee
# Etape 3 : Trier par qualite et latence
pass
result = None # TODO etudiant
print("Exercice a completer")Exercice a completer
Exercice 3 : Benchmark de latence avec retry
Mesurer la latence reelle de chaque endpoint TTS avec un mécanisme de retry en cas d’echec.
Indice : Utiliser time.time() pour mesurer chaque appel et implementer un compteur de retry.
def benchmark_tts_latency(endpoints, text, n_trials=3, max_retries=2):
# Etape 1 : Pour chaque endpoint, faire n_trials appels
# Etape 2 : Implementer le retry sur echec
# Etape 3 : Calculer les metriques de latence
pass
result = None # TODO etudiant
print("Exercice a completer")Exercice a completer
Exemple guide 2 : Routeur TTS intelligent
La Section 3 presente un arbre de decision pour choisir le modèle adapte. Votre mission : implementez une fonction smart_tts_router() qui selectionne automatiquement le meilleur modèle et genere l’audio.
Indices : - Critères de sélection : longueur du texte, langue, besoin de clonage vocal, contrainte de temps - Texte court (< 50 mots) + rapidite -> Kokoro - Texte long + qualite -> Qwen3 TTS - Clonage vocal -> Qwen3 TTS (paramètre voice) - Ajoutez un fallback si un modèle est indisponible
# --- Exercice 2 : Routeur TTS intelligent ---
def smart_tts_router(text, voice_clone=None, max_wait_seconds=30):
"""
Selectionne automatiquement le meilleur modele TTS et genere l'audio.
Criteres : longueur, clonage vocal, contrainte de temps.
Retourne (audio_bytes, model_used, generation_time).
"""
# Exercice: Verifier la disponibilite de chaque modele
# Exercice: Estimer le temps de generation selon la longueur du texte
# Exercice: Si voice_clone demande -> Qwen3 TTS
# Exercice: Si texte court + rapide -> Kokoro
# Exercice: Si texte long + qualite -> Qwen3 ou TADA
# Exercice: Implementer un fallback si le modele choisi echoue
pass
# Test
# audio, model, t = smart_tts_router("Bonjour le monde")
# print(f"Modele: {model}, Temps: {t:.1f}s")Pistes de solution pour le routeur intelligent :
Pour implementer le routeur TTS intelligent, voici les étapes cles :
1. Verification de disponibilite
def check_model_health(model_url):
try:
response = requests.get(f"{model_url}/health", timeout=5)
return response.status_code == 200
except:
return False2. Estimation du temps de generation - Kokoro : runtime machine-dep : ~0.1s par mot (ratio real-time ~10x) - TADA 3B ML : runtime machine-dep : ~0.2s par mot (ratio real-time ~5x) - Qwen3 TTS : runtime machine-dep : ~1s par mot (ratio real-time ~1x)
3. Logique de sélection
word_count = len(text.split())
has_voice_clone = voice_clone is not None
urgent = max_wait_seconds < (word_count * 0.5) # Seuil arbitraire
if has_voice_clone:
return "qwen" # Seul Qwen supporte le voice cloning
elif urgent or word_count < 50:
return "kokoro" # Plus rapide
elif word_count > 200:
return "qwen" # Meilleure qualite pour texte long
else:
return "tada" # Compromis4. Fallback - Si le modèle choisi echoue (timeout, erreur), retenter avec Kokoro - Kokoro est le plus stable et le plus rapide, donc bon fallback
Note technique : Ajoutez un système de cache pour les textes repetitifs. Utilisez un hash du texte comme cle de cache pour eviter de regenerer le même audio.