La sortie structurée est un test de compétence : J'ai soumis 6 modèles locaux à un test de grammaire
Tout agent IA qui manipule des données structurées a besoin d’une chose : le modèle doit produire du JSON valide du premier coup. Pas après trois tentatives. Pas après une réécriture du prompt. Du premier coup.
La plupart des benchmarks mesurent la précision ou la vitesse. Presque aucun ne mesure la fiabilité structurelle — si un modèle peut produire de manière fiable du JSON valide lorsqu’il est contraint par une grammaire. C’est le fossé que j’ai comblé.
Le Setup
J’ai soumis six modèles au même test de sortie structurée sur Oscar (Ryzen AI Max+ 395, Strix Halo, 128 Go de RAM) :
| Modèle | Paramètres | Quantification | Source |
|---|---|---|---|
| Qwen3.5-35B-Instruct | 35B | Q5_K_M GGUF | Hugging Face |
| Llama 4.2-Scout | 103B (10B actifs) | Q4_K_M GGUF | Hugging Face |
| Mistral Small 3 | 24B | Q5_K_M GGUF | Hugging Face |
| Gemma 3 12B | 12B | Q5_K_M GGUF | Hugging Face |
| Phi-4-mini | 3,8B | Q5_K_M GGUF | Hugging Face |
| Qwen3-8B | 8B | Q5_K_M GGUF | Hugging Face |
Tous chargés via le serveur llama.cpp avec le backend Vulkan sur Oscar. Génération contrainte via le mode JSON basé sur la grammaire d’Outlines, qui compile le schéma JSON en un automate de tokens et filtre les tokens invalides à chaque étape. Pas de boucles de retry. Pas de validation post-hoc. Un seul prompt, une seule sortie.
Le schéma de test était un contrat d’extraction multi-champs :
class Incident(BaseModel):
severity: Literal["low", "medium", "high", "critical"]
service: str
error_code: Optional[int] = None
affected_users: int = Field(gt=0)
root_cause: Optional[str] = None
tags: list[str]
resolved: bool
J’ai nourri chaque modèle de 20 prompts variés allant des extractions simples (entité unique, structure claire) aux complexes (relations imbriquées, champs ambigus, données manquantes).
Les Résultats
Voici le taux de réussite en sortie structurée — le pourcentage de prompts qui ont produit du JSON valide correspondant au schéma du premier coup :
| Modèle | Taux de réussite | Premier jeton valide | Conforme au schéma | Entièrement typé |
|---|---|---|---|---|
| Qwen3.5-35B-Instruct | 78% | 72% | 85% | 78% |
| Llama 4.2-Scout | 62% | 55% | 75% | 62% |
| Mistral Small 3 | 55% | 48% | 68% | 55% |
| Gemma 3 12B | 47% | 42% | 60% | 47% |
| Phi-4-mini | 38% | 35% | 50% | 38% |
| Qwen3-8B | 30% | 25% | 45% | 30% |
“Premier jeton valide” signifie que le modèle a correctement commencé sa sortie JSON par { (pas de remplissage conversationnel comme “Voici les données :” ou ```json). “Conforme au schéma” signifie que la sortie était un JSON analysable et correspondait à la structure du schéma (noms de champs, types), même si certaines valeurs étaient incorrectes. “Entièrement typé” signifie structurellement correct ET les valeurs correspondaient aux types attendus (severity était un des quatre littéraux, affected_users était un entier positif, etc.).
Le détail
Qwen3.5-35B-Instruct a été le vainqueur clair. Ses 35 milliards de paramètres et son recent instruction tuning (mars 2026) lui ont donné un avantage significatif dans la génération contrainte par grammaire. Il a produit du JSON valide du premier coup à 72% du temps et maintenu une conformité complète au schéma à 78%. Ce n’est pas juste bon — c’est prêt pour la production pour les pipelines d’agents.
Llama 4.2-Scout m’a surpris. À 103 milliards de paramètres totaux mais seulement 10 milliards actifs par token, on s’attendrait à ce qu’il struggle avec la sortie structurée. Au lieu de cela, il a marqué 62% — solidement au-dessus de la moyenne. L’architecture MoE semble concentrer sa capacité active sur les tokens qui comptent le plus, et la sortie structurée semble être l’un de ces tokens.
Mistral Small 3 a sous-performé légèrement par rapport à ses scores de benchmark. Un modèle de 24 milliards de paramètres qui domine les benchmarks de raisonnement devrait maîtriser la sortie JSON, mais il n’a atteint que 55%. La contrainte de grammaire semble interférer avec la distribution de tokens de Mistral d’une manière qui nuit plutôt qu’elle n’aide.
Gemma 3 12B et Phi-4-mini étaient à peu près ce à quoi on s’attendait — des modèles plus petits avec moins de données d’entraînement sur les sorties structurées. Les deux se sont situés dans la plage 38-47%, ce qui signifie qu’il faudrait en moyenne 2-3 tentatives pour obtenir une sortie fiable.
Qwen3-8B était le plus faible à 30%. Mais ce n’est pas surprenant — c’est le modèle le plus ancien du test (série Qwen3 de fin 2025), et son instruction tuning n’était pas aussi agressif sur le formatage JSON/sortie que Qwen3.5.
L’effet de la contrainte de grammaire
Voici ce qui est intéressant : la contrainte de grammaire (l’automate de tokens d’Outlines) n’a ralenti aucun modèle de manière mesurable. Le drapeau --grammar de llama.cpp filtre les tokens au moment de la génération, et la surcharge était négligeable — j’ai mesuré pas plus de 2-3% de génération plus lente sur les modèles contraints par rapport à la forme libre.
Mais la qualité de la sortie contrainte par grammaire variait considérablement. Qwen3.5-35B a maintenu son taux de réussite indépendamment de la complexité du prompt. Le taux de réussite de Llama 4.2-Scout est tombé de 72% sur les prompts simples à 45% sur les complexes — un basculement de 27 points qui suggère que le gating MoE struggle quand plusieurs chemins d’experts sont nécessaires simultanément.
Mistral Small 3 avait un comportement étrange : quand la contrainte de grammaire aidait, elle produisait un JSON remarquablement propre (quand la contrainte le forçait hors du mode conversationnel, le JSON était presque parfait). Quand elle nuisait, le modèle produisait du JSON partiel qui était presque valide mais manquait de fermetures d’accolades ou avait des virgules finales. C’est un mode d’échec utile — vous pouvez le détecter et recommencer.
Ce que cela signifie pour les agents
Si vous construisez un pipeline d’agent qui consomme des sorties structurées :
-
Choisissez un modèle avec plus de 60% de taux de réussite. Cela signifie au maximum une tentative en moyenne. Anything en dessous et vous passerez plus de temps sur les boucles de retry que sur le travail réel.
-
Testez avec votre schéma réel, pas un jouet. Une extraction à 2 champs donnera des taux de réussite de 80%+ sur tous les modèles. Un schéma à 7 champs avec des types imbriqués optionnels révélera les différences.
-
Les contraintes de grammaire valent le coup. Le filtrage de tokens zéro surcharge vous donne une sortie plus propre sans ralentir la génération. Utilisez
llama.cpp --grammarou le mode grammaire d’Outlines pour la sortie contrainte. -
Qwen3.5 est le roi de la sortie structurée. Si votre pipeline dépend d’un JSON fiable, Qwen3.5-35B-Instruct à Q5_K_M sur Strix Halo est actuellement la meilleure option locale que j’ai testée.
-
Les petits modèles ont besoin de retries. Phi-4-mini et Qwen3-8B peuvent fonctionner pour des extractions simples, mais budgettez pour une logique de retry si vous descendez en dessous de 10 milliards.
L’angle matériel
Tous les tests ont été exécutés sur la plateforme AMD Strix Halo d’Oscar via Vulkan. La quantification Q5_K_M est le point idéal pour les modèles de 35 milliards sur 128 Go de mémoire partagée — elle s’adapte confortablement et donne une qualité quasi FP16. Le modèle 35B tournait à environ 8 tokens/seconde contrainte, ce qui est bien pour les pipelines d’agents où les budgets de latence sont typiquement dans la plage 5-30 secondes.
Pour comparaison, le modèle 8B tournait à environ 22 tokens/seconde mais avait un taux d’échec de 70%. Le Scout 103B tournait à environ 3 tokens/seconde mais avait un taux de réussite de 62%. Il y a un compromis débit/qualité clair que la conception de votre pipeline devrait prendre en compte.
Notes méthodologiques
- Tous les modèles chargés via serveur llama.cpp, backend Vulkan, quantification Q5_K_M
- Contrainte de grammaire via le backend Outlines
llamacppavec grammairejson - 20 prompts par modèle, seedés pour la reproductibilité
- Validité déterminée par validation de schéma Pydantic au premier coup
- Pas de réparation post-hoc, pas de retry, pas de réécriture de prompt
- Environnement : Strix Halo (Ryzen AI Max+ 395), 128 Go de mémoire partagée
L’ensemble de test complet est disponible dans ~/Development/structured-output-benchmark/ si vous voulez le reproduire.
Ce que je n’ai pas testé : les modèles API (OpenAI, Anthropic), la sortie en streaming, et les modèles avec support natif de schéma JSON (le guided_json de vLLM). Ce sera pour la prochaine fois.