Aller au contenu principal

Optimisation de la latence

La latence : l’ennemi de la conversation naturelle

Dans une conversation humaine, le temps de réponse moyen est d’environ 200 millisecondes. Au-delà de 500 ms, l’interlocuteur commence à percevoir un délai. Pour qu’un assistant vocal soit perçu comme naturel, la latence totale entre la fin de la question et le début de la réponse doit rester sous la seconde.

Voxtral affiche une latence modèle d’environ 90 ms — mais la latence totale perçue par l’utilisateur est bien plus élevée. Comprendre et optimiser chaque composant est essentiel.

Anatomie de la latence totale

La latence totale se décompose en plusieurs segments :

Latence réseau (aller)

Le temps pour envoyer la requête de votre serveur aux serveurs Mistral. Variable selon votre localisation géographique et la qualité de votre connexion. En Europe, comptez 20-80 ms.

Latence modèle (~90 ms)

Le temps de traitement par le modèle Voxtral pour générer le premier chunk audio. C’est la partie que vous ne pouvez pas optimiser — elle dépend de l’infrastructure Mistral.

Latence d’encodage

Le temps pour encoder l’audio dans le format demandé. C’est ici que le choix du format impacte directement la latence :

  • PCM : ~0 ms (pas d’encodage, audio brut)
  • Opus : ~5-10 ms (codec rapide)
  • MP3 : ~200-500 ms (le codec MP3 a besoin d’accumuler des données avant de produire le premier frame)

Latence réseau (retour)

Le temps pour recevoir les chunks audio. En streaming, les chunks sont petits, donc cette latence est faible.

Latence de buffer côté client

Votre application accumule généralement 2-3 chunks avant de commencer la lecture pour éviter les micro-coupures. Ajoutez 50-150 ms.

Streaming vs batch : l’impact chiffré

MétriqueBatch (sans streaming)Streaming PCMStreaming MP3
Premier son1-3 secondes~0,7 seconde~2 secondes
Latence perçueÉlevéeTrès basseMoyenne
Bande passantePic uniqueRépartieRépartie

Le streaming avec PCM divise la latence perçue par 3 à 4 par rapport au batch.

Optimisation côté serveur

Choisir le bon format

Pour une application conversationnelle, le format PCM est le choix optimal :

with client.audio.speech.complete(
    model="voxtral-mini-tts-2603",
    input=texte,
    voice_id=voice_id,
    response_format="pcm",  # Latence minimale
    stream=True,
) as stream:
    for event in stream:
        if event.event == "speech.audio.delta":
            jouer_audio(base64.b64decode(event.data.audio_data))

Utiliser une voix sauvegardée

Le voice_id est plus rapide que ref_audio : l’échantillon n’a pas besoin d’être traité à chaque requête. Gain : 50-100 ms.

Réduire la longueur du texte

Plus le texte est court, plus le premier chunk arrive vite. Pour un assistant vocal, générez phrase par phrase :

def generer_par_phrases(texte_complet, voice_id):
    """Génère l'audio phrase par phrase pour minimiser la latence."""
    phrases = texte_complet.split(". ")

    for phrase in phrases:
        phrase = phrase.strip()
        if not phrase:
            continue
        if not phrase.endswith("."):
            phrase += "."

        with client.audio.speech.complete(
            model="voxtral-mini-tts-2603",
            input=phrase,
            voice_id=voice_id,
            response_format="pcm",
            stream=True,
        ) as stream:
            for event in stream:
                if event.event == "speech.audio.delta":
                    yield base64.b64decode(event.data.audio_data)

Optimisation côté client

Buffer adaptatif

Au lieu d’un buffer fixe, adaptez sa taille en fonction des conditions réseau :

class BufferAdaptatif:
    def __init__(self):
        self.buffer = []
        self.seuil_min = 2   # chunks minimum avant lecture
        self.seuil_max = 5   # ne pas accumuler plus
        self.lecture_commencee = False

    def ajouter_chunk(self, chunk):
        self.buffer.append(chunk)

        if not self.lecture_commencee:
            if len(self.buffer) >= self.seuil_min:
                self.lecture_commencee = True
                return self.vider_buffer()
        else:
            return chunk
        return None

    def vider_buffer(self):
        data = b"".join(self.buffer)
        self.buffer = []
        return data

Pré-chauffage de la connexion

Envoyez une requête de test au démarrage de votre application pour établir la connexion TCP et le handshake TLS. Les requêtes suivantes seront plus rapides grâce au keep-alive HTTP.

Mesurer la latence

Pour diagnostiquer les goulots d’étranglement, mesurez chaque segment :

import time

t0 = time.perf_counter()

with client.audio.speech.complete(
    model="voxtral-mini-tts-2603",
    input="Test de latence.",
    voice_id=voice_id,
    response_format="pcm",
    stream=True,
) as stream:
    for event in stream:
        if event.event == "speech.audio.delta":
            t1 = time.perf_counter()
            print(f"Premier chunk reçu en {(t1 - t0)*1000:.0f} ms")
            break

Points clés à retenir

  • La latence totale comprend le réseau, le modèle (~90 ms), l’encodage et le buffer client
  • Le format PCM offre la latence la plus basse car il n’y a pas d’encodage
  • Le streaming réduit la latence perçue d’un facteur 3 à 4 par rapport au batch
  • Utilisez un voice_id plutôt que ref_audio pour gagner 50-100 ms
  • Générez phrase par phrase pour minimiser le temps avant le premier son