Le déploiement de systèmes RAG (Retrieval-Augmented Generation) en environnement de production nécessite une validation rigoureuse de leur capacité à gérer des pics de trafic. Langchain-Chatchat, bien que robuste pour le traitement de documents privés et le déploiement local, peut présenter des latences critiques sous une forte concurrence. Pour quantifier ces limites et éviter les interruptions de service, l'utilisation d'outils de test de charge automatisés est indispensable.
Locust se distingue dans cet écosystème par son approche orientée code. Contrairement aux solutions traditionnelles basées sur des interfaces graphiques lourdes ou des configurations XML, il permet de modéliser des scénarios d'utilisation complexes en Python pur. S'appuyant sur des coroutines (via gevent ou httpx), Locust génère des milliers de requêtes simultanées avec une empreinte mémoire minimale, ce qui en fait un candidat idéal pour évaluer les infrastructures LLM locales.
Conception du script de charge
Voici une implémentation optimisée ciblant l'endpoint de dialogue de Langchain-Chatchat. Ce script utilise FastHttpUser pour maximiser le débit réseau et intègre une logique stricte de validation métier.
from locust import FastHttpUser, task, between
import random
class RagSystemLoadTester(FastHttpUser):
# Simulation du temps de lecture et de frappe de l'utilisateur
wait_time = between(1.5, 4.0)
query_pool = [
"Détaillez le mécanisme d'indexation des documents PDF.",
"Quels fournisseurs LLM sont supportés nativement ?",
"Comment configurer le partitionnement du texte (chunking) ?",
"Expliquez l'intégration avec les bases vectorielles.",
"Quelle est la procédure de mise à jour des embeddings ?"
]
@task
def submit_rag_query(self):
payload = {
"question": random.choice(self.query_pool),
"session_id": None,
"stream": False,
"context_length": 4096
}
with self.client.post(
"/chat/knowledge_base",
json=payload,
catch_response=True,
name="KB_Inference"
) as response:
if response.status_code != 200:
response.failure(f"Statut inattendu: {response.status_code}")
return
try:
data = response.json()
answer_text = data.get("answer", "")
if not answer_text.strip():
response.failure("Réponse générée vide ou nulle")
except Exception as err:
response.failure(f"Échec de désérialisation JSON: {err}")
Cette implémentation comporte plusieurs choix techniques cruciaux :
- Distribution aléatoire : L'extraction dynamique depuis
query_poolempêche les mécanismes de cache (sémantique ou HTTP) de fausser les métriques de performance réelles. - Validation contextuelle : L'utilisation de
catch_response=Truepermet d'invalider une requête techniquement réussie (HTTP 200) mais fonctionnellement erronée (ex: réponse vide du LLM ou erreur silencieuse du moteur RAG). - Désactivation du streaming : Définir
stream: Falseforce le serveur à maintenir la connexion ouverte jusqu'à la génération complète, permettant de mesurer précisément la latence totale de bout en bout (Time to Last Token).
Exécution des tests de charge
Locust offre deux modes d'exécution adaptés à différentes phases du cycle de développement :
Mode interactif (Web UI)
Optimisé pour l'analyse exploratoire et le réglage des paramètres en temps réel.
locust -f rag_tester.py --host http://target-server:8080
L'interface, accessible par défaut sur le port 8089, permet d'ajuster dynamiquement le nombre d'utilisateurs virtuels et la vitesse de génération (spawn rate), tout en visualisant les percentiles de latence (P50, P95, P99).
Mode Headless (Automatisation)
Conçu pour l'intégration dans les pipelines CI/CD ou les environnements serveurs dépourvus d'interface graphique.
locust -f rag_tester.py \
--host http://target-server:8080 \
--users 150 \
--spawn-rate 10 \
--run-time 15m \
--headless \
--html performance_report.html
Cette configuration simule une montée en charge progressive de 10 utilisateurs par seconde jusqu'à atteindre 150 connexions simultanées, maintenues pendant 15 minutes. Le résultat est consolidé dans un rapport HTML autonome.
Identification et résolution des goulots d'étranglement
La corrélation des métriques Locust avec la télémétrie du serveur cible permet d'isoler les points de friction spécifiques aux architectures RAG :
- Saturation de l'inférence LLM : Caractérisée par une augmentation exponentielle de la latence avec un CPU sous-utilisé et une VRAM à 100%. Les stratégies d'atténuation incluent l'activation du continuous batching (via vLLM ou TensorRT-LLM) ou la transition vers des modèles quantifiés (GGUF/AWQ) de moindre paramétrage.
- Dégradation de la recherche vectorielle : Lorsque le temps de récupération des documents augmente proportionnellement au volume de la base de connaissances. L'adoption d'algorithmes d'indexation avancés comme HNSW (Hierarchical Navigable Small World) avec un paramètre
ef_searchcalibré offre un meilleur compromis précision/vitesse que les index plats. - Épuisement mémoire (OOM) : Les interruptions brutales sous forte charge résultent souvent du chargement simultané de multiples contextes volumineux. La limitation stricte de la taille des fragments de texte (chunks) à 512 tokens et l'implémentation de caches disque pour les embeddings sont requises.
- Blocage de la boucle d'événements FastAPI : Bien que FastAPI soit asynchrone, certaines bibliothèques de traitement de texte ou d'appel LLM opèrent de manière synchrone. L'encapsulation de ces opérations bloquantes via
asyncio.to_threadourun_in_threadpoolest impérative pour préserver la concurrence du serveur.
Stratégies de simulation avancées
Pour garantir la validité des données récoltées, l'architecture de test doit respecter certaines contraintes :
- Isolation matérielle : Le générateur de charge doit être hébergé sur une instance distincte du serveur Langchain-Chatchat pour éviter la contention des ressources (CPU, I/O).
- Montée en charge progressive (Ramp-up) : Un
spawn-ratemodéré permet d'observer le comportement du système à différents paliers de charge et d'identifier le point de rupture exact avant la saturation totale. - Pondération des scénarios : Le trafic réel est hétérogène. L'utilisation des décorateurs
@task(weight)permet de simuler un ratio réaliste, par exemple 80% de requêtes factuelles courtes et 20% de dialogues multi-tours exigeants en contexte.
Établissement d'une baseline de performance
Les données issues de ces tests n'acquièrent de la valeur que si elles sont pérennisées sous forme de baseline. Il est crucial de documenter le débit maximal (Requêtes/Seconde) et la latence P99 pour chaque configuration matérielle et logicielle. Toute modification de l'architecture — qu'il s'agisse du remplacement du modèle d'embedding, de l'ajustement de la stratégie de partitionnement ou de la mise à jour du framework — doit être systématiquement validée par un test de charge comparatif. Cette approche empirique garantit l'absence de régression et assure la stabilité du système RAG face aux exigences de la production.