“Pourquoi payer Opus 4.6 ou Codex ? Ollama + un modèle open source et c’est réglé.” On lit ça partout. Alors j’ai voulu vérifier.
Pas avec un hello world ou un script Python de 20 lignes, mais sur un vrai projet Symfony 8 : créer des entités Doctrine, des controllers avec routes, des formulaires avec validation, un CRUD complet. Le tout validé automatiquement par les outils du framework (lint:container, doctrine:mapping:info, PHPUnit…).
Le modèle testé : Gemma 4 E4B de Google (4.5B paramètres, 9.6 GB), le dernier modèle open-weights en date. L’outil : OpenCode, alternative open source à Claude Code. L’inférence : 100% locale via Ollama.
Le verdict : Gemma 4 E4B génère du code Symfony fonctionnel sur des tâches simples, mais s’effondre dès que la complexité augmente. Voici les chiffres.
La stack testée
Le modèle : Gemma 4 E4B
Gemma 4 est la dernière famille de modèles open-weights de Google (avril 2026). Elle se décline en 4 variantes :
| Variante | Params totaux | Params actifs | Architecture | Contexte | Taille disque |
|---|---|---|---|---|---|
| E2B | 5.1B | 2.3B effectifs | Dense (PLE) | 128K | 7.2 GB |
| E4B | 8B | 4.5B effectifs | Dense (PLE) | 128K | 9.6 GB |
| 26B | 25.2B | 3.8B actifs | MoE (8/128 experts) | 256K | 17 GB |
| 31B | 30.7B | 30.7B | Dense | 256K | 20 GB |
Le “E” signifie “Effective parameters” : grâce à une architecture Per-Layer Embeddings (PLE), ces modèles sont optimisés pour tourner sur des appareils avec peu de mémoire. Le E4B est le modèle par défaut de ollama pull gemma4. Il supporte le texte, l’image et l’audio, et le tool calling natif.
Pour comparaison, Claude Opus 4.6 aurait environ 100B paramètres actifs (estimations communautaires, Anthropic ne communique pas ce chiffre). L’intérêt de Gemma 4 E4B est qu’il tourne en local sur un laptop, gratuitement.
Pourquoi pas le 26B ? Malgré ses 25.2B paramètres totaux, le 26B n’a que 3.8B paramètres actifs (moins que le E4B !) grâce à son architecture MoE. Mais il pèse 17 GB sur disque. J’ai essayé : OpenCode a besoin d’au moins 64K tokens de contexte pour transmettre les outils au modèle (voir la section num_ctx). Avec ce contexte, le 26B grimpe à 25 GB en RAM, ce qui dépasse les 24 GB de ma machine. En réduisant à 32K (21 GB), le modèle charge mais le swap rend l’inférence inutilisable : plus de 15 minutes sans produire le moindre résultat. Sur un MacBook 24 GB, le E4B est le seul modèle Gemma 4 réellement exploitable avec OpenCode. Le 26B nécessiterait au minimum 32 GB de RAM.
L’outil : OpenCode
OpenCode est un agent de code en terminal, open source, comparable à Claude Code. Il donne au modèle des outils (write, bash, read, edit…) pour interagir avec le filesystem. Le mode opencode run permet de l’utiliser en non-interactif, parfait pour un benchmark automatisé.
Le projet : Symfony 8
Un projet Symfony 8.0.8 frais (symfony new --webapp), avec PHP 8.5.3. Tous les composants classiques sont disponibles : Doctrine ORM, Form, Validator, Twig, PHPUnit 13.
Le protocole expérimental
La machine de test
| Composant | Détail |
|---|---|
| Machine | MacBook Pro M4 Pro |
| CPU | 14 cœurs (10P + 4E) |
| RAM | 24 GB |
| OS | macOS 15.7.3 |
| Ollama | 0.20.0-rc1 |
| OpenCode | 1.2.15 |
| PHP | 8.5.3 |
| Symfony | 8.0.8 |
Les 5 scénarios
Chaque scénario est un prompt unique envoyé à OpenCode. Le modèle doit utiliser les outils (write) pour créer les fichiers, pas juste afficher du code en texte.
| ID | Description | Fichiers attendus | Complexité |
|---|---|---|---|
| S1 | Entity Doctrine + Repository | 2 | Basse |
| S2 | Controller + Routes + Templates Twig | 3 | Moyenne |
| S3 | Service avec DI + Test unitaire PHPUnit | 2 | Moyenne |
| S4 | Form + Validation DTO + Controller | 4 | Haute |
| S5 | CRUD complet (Entity → Tests) | 9+ | Très haute |
La validation automatisée
Pour chaque exécution, un pipeline vérifie que le code est fonctionnel (pas juste syntaxiquement correct) :
php -l: syntaxe PHP valide sur chaque fichierphp bin/console lint:container: le conteneur de services Symfony compilephp bin/console lint:twig: les templates Twig sont validesphp bin/console doctrine:mapping:info: Doctrine reconnaît les entitésphp bin/console debug:router: les routes sont enregistréesphp bin/phpunit: les tests unitaires passent
Le barème de scoring
Un score de 0 à 100 est calculé à partir de ces vérifications :
| Vérification | Points | Méthode |
|---|---|---|
| Fichiers créés | 20 | Tous les fichiers attendus existent |
| Syntaxe PHP valide | 20 | php -l passe sur chaque fichier |
| Conteneur DI compile | 20 | php bin/console lint:container |
| Templates Twig valides | 10 | php bin/console lint:twig |
| Mapping Doctrine OK | 10 | php bin/console doctrine:mapping:info |
| Routes enregistrées | 10 | php bin/console debug:router (proportionnel) |
| Tests PHPUnit passent | 10 | php bin/phpunit (exit code 0) |
| Total | 100 |
Les vérifications non-applicables à un scénario (ex: pas de routes pour S1) sont automatiquement créditées.
Reproductibilité
Chaque scénario est exécuté 3 fois. Entre chaque exécution :
git checkout -- .+git clean -fdpour revenir à l’état initial- Suppression du cache Symfony
- Nouvelle session OpenCode (pas de mémoire entre les runs)
Le piège du num_ctx : quand le modèle “oublie” ses outils
Avant de parler des résultats, un point technique crucial que j’ai découvert pendant la mise en place.
Le problème
Avec la configuration par défaut, Gemma 4 refusait d’utiliser les outils d’OpenCode. Au lieu de créer les fichiers avec write, il affichait le code en markdown dans sa réponse, exactement comme un chatbot classique.
Le diagnostic
En interceptant les requêtes entre OpenCode et Ollama, j’ai vérifié que :
- OpenCode envoie bien les 11 outils dans la requête (write, bash, read, edit, glob, grep…)
- Gemma 4 sait faire du tool calling : un test direct via l’API Ollama fonctionne parfaitement
- Le problème est le contexte : le prompt système d’OpenCode + les 11 définitions de tools font ~15-20K tokens
Le num_ctx par défaut d’Ollama est souvent trop petit. Le modèle ne “voit” littéralement pas les outils dans son contexte.
La solution
Créer un modèle Ollama avec un num_ctx de 64K (minimum recommandé par la doc Ollama pour OpenCode) :
# Créer un Modelfile
cat > Modelfile << 'EOF'
FROM gemma4:latest
PARAMETER num_ctx 65536
EOF
# Créer le modèle dérivé
ollama create gemma4-opencode -f Modelfile
Et dans opencode.json :
{
"$schema": "https://opencode.ai/config.json",
"provider": {
"ollama": {
"npm": "@ai-sdk/openai-compatible",
"name": "Ollama (local)",
"options": {
"baseURL": "http://localhost:11434/v1"
},
"models": {
"gemma4-opencode": {
"name": "Gemma 4 E4B (64k ctx)"
}
}
}
}
}
⚠️ Attention : avec 64K de contexte, Gemma 4 consomme significativement plus de RAM. Sur un MacBook avec 24 GB, c’est viable mais l’inférence est plus lente (~2 min par scénario).
Après ce changement, le modèle utilise correctement les outils et crée les fichiers sur le disque. Place aux résultats.
Les résultats
Vue d’ensemble
| Scénario | Score moyen | Écart-type | Succès | Temps moyen | Tokens (sortie) |
|---|---|---|---|---|---|
| S1 Entity + Repo | 86.7/100 | ±18.9 | 67% | 119s | 1 257 |
| S2 Controller + Twig | 53.3/100 | ±4.7 | 0% | 102s | 1 401 |
| S3 Service + Test | 63.3/100 | ±4.7 | 0% | 130s | 2 745 |
| S4 Form + DTO | 50.0/100 | ±0.0 | 0% | 124s | 1 206 |
| S5 CRUD complet | 30.0/100 | ±14.1 | 0% | 121s | 2 307 |
“Succès” = tous les fichiers créés + syntaxe valide + conteneur DI compile. Score ≠ succès car des points sont accordés pour les vérifications partielles (conteneur OK, Twig OK même si les fichiers ne sont pas tous créés).
Détail par scénario
S1. Entity Doctrine + Repository
Prompt : Créer une entité Article avec 5 propriétés et son ArticleRepository.
| Run | Score | Fichiers créés | Container | Doctrine | Temps |
|---|---|---|---|---|---|
| 1 | 60 | Entity seulement (repo manquant) | ✅ | ✅ | 119s |
| 2 | 100 | Entity + Repository | ✅ | ✅ | 119s |
| 3 | 100 | Entity + Repository | ✅ | ✅ | 119s |
Verdict : Sur la tâche la plus simple, Gemma 4 s’en sort très bien. Le code utilise correctement les attributs PHP 8, les types sont bons, le repository étend ServiceEntityRepository. Le run 1 a simplement oublié de créer le repository, un problème de reproductibilité, pas de compétence.
S2. Controller + Routes + Twig
Prompt : Créer un ArticleController avec 2 actions, routes PHP 8, et 2 templates Twig.
| Run | Score | Fichiers créés | Routes | Twig | Temps |
|---|---|---|---|---|---|
| 1 | 50 | Template Twig seulement | ❌ | ✅ | 68s |
| 2 | 50 | Controller seulement | ❌ | ✅ | 119s |
| 3 | 60 | Controller seulement | ✅ | ✅ | 121s |
Verdict : Le modèle ne crée jamais tous les fichiers en une passe. Soit il fait le controller, soit les templates, jamais les deux ensemble. C’est la limite du multi-fichier : avec 4.5B paramètres, le modèle perd le fil quand il doit coordonner plusieurs write consécutifs.
S3. Service + DI + Test unitaire
Prompt : Créer un SlugGenerator avec injection de SluggerInterface et un test PHPUnit avec 4 cas.
| Run | Score | Fichiers créés | Tests PHPUnit | Temps |
|---|---|---|---|---|
| 1 | 70 | Service + Test | ❌ (syntax error) | 292s |
| 2 | 60 | Aucun | — | 50s |
| 3 | 60 | Aucun | — | 49s |
Verdict : Résultat mitigé. Le run 1 a créé les deux fichiers mais le test PHPUnit contenait une erreur de syntaxe. Les runs 2 et 3 n’ont même pas créé de fichier : le modèle a répondu en texte au lieu d’utiliser les outils. La variance est très élevée (292s vs 50s), signe d’instabilité.
S4. Form + DTO + Validation + Controller
Prompt : Créer un formulaire de contact complet avec DTO, validations, controller et template.
| Run | Score | Fichiers créés | Route /contact | Temps |
|---|---|---|---|---|
| 1 | 50 | Aucun | ❌ | 76s |
| 2 | 50 | DTO + Form (2/4) | ❌ | 231s |
| 3 | 50 | Aucun | ❌ | 63s |
Verdict : Le scénario multi-fichier (4 fichiers PHP + 1 template) est trop complexe. Un seul run a créé des fichiers, et seulement 2 sur 4. Le controller et le template n’ont jamais été générés. Score stable à 50 (conteneur + twig + yaml toujours OK car aucun fichier cassé n’est ajouté).
S5. CRUD complet
Prompt : Créer un CRUD Product complet : entity, repository, form, controller (7 routes), templates, et test fonctionnel.
| Run | Score | Fichiers créés | Routes | Doctrine | Temps |
|---|---|---|---|---|---|
| 1 | 40 | Aucun | 0/5 | ❌ | 63s |
| 2 | 40 | Aucun | 0/5 | ❌ | 59s |
| 3 | 10 | Entity + Form + Repo (3/9) | 0/5 | ❌ | 241s |
Verdict : Le CRUD complet (9+ fichiers) est hors de portée. Le modèle est complètement dépassé : il ne crée quasiment aucun fichier, et quand il en crée (run 3), l’ensemble casse le conteneur DI. Le score de 10 au run 3 montre que la tentative partielle fait plus de mal que de bien.
Analyse
Ce qui fonctionne
- Tâches simples (1-2 fichiers) : Gemma 4 génère du code Symfony parfaitement fonctionnel. Les entités Doctrine sont correctes, les attributs PHP 8 bien utilisés, les types cohérents.
- Qualité du code quand il est créé : Quand le modèle réussit à écrire un fichier, le code est propre. Pas de code deprecated, pas d’annotations XML, pas de pratiques obsolètes.
- Tool calling : Avec le
num_ctxà 64K, le modèle utilise correctement les outilswriteetbashd’OpenCode. C’est une vraie avancée par rapport aux versions précédentes. - Coût : Zéro. Tout tourne en local. Sur 15 runs, pas un centime dépensé.
Ce qui ne fonctionne pas (ou mal)
- Multi-fichier : Dès qu’il faut créer plus de 2 fichiers, le modèle décroche. Il en oublie, ou passe en mode “texte” au lieu d’utiliser les outils.
- Reproductibilité : Le même prompt donne des résultats très différents d’un run à l’autre (S1 : 60 puis 100 puis 100). Un modèle 4.5B manque de déterminisme.
- Complexité : Les scénarios S4 et S5 sont hors de portée. Le modèle ne peut pas maintenir la cohérence entre entity, form, controller et templates.
- Lenteur : ~2 minutes par scénario avec 64K de contexte. C’est 4-10x plus lent qu’un modèle cloud.
- Erreurs silencieuses : Parfois le modèle ne crée aucun fichier sans erreur visible : il répond simplement en texte au lieu d’utiliser les outils.
Comparaison avec les modèles cloud
Pour mettre en perspective, voici les données publiques sur les modèles cloud en 2026 :
- Claude Opus 4.6 obtient 87.5% de succès au premier essai sur des tâches de refactoring multi-fichier, et 98.8% sur un benchmark TypeScript custom
- Les modèles open-weights atteignent 94.8% sur ce même benchmark, soit 4 points derrière Opus, mais avec des modèles bien plus gros que 4.5B
- Côté prix, l’API Claude coûte $5/M tokens en entrée et $25/M en sortie pour Opus 4.6
| Critère | Gemma 4 E4B (local, 4.5B) | Claude Opus 4.6 (cloud, ~100B actifs) |
|---|---|---|
| Coût | Gratuit | $5/M input, $25/M output |
| Latence | ~2 min/scénario | ~10-30s/scénario |
| Tool calling | Fiable après config num_ctx | Natif, stable |
| Tâches simples (1-2 fichiers) | 87/100 | ~98-100/100 |
| Tâches complexes (multi-fichier) | 30-50/100 | ~87-98/100 |
| Reproductibilité | Variable (±19 pts) | Très stable |
| Vie privée | 100% local | Données envoyées au cloud |
Reproduire ce benchmark
🔗 github.com/yoanbernabeu/gemma4-symfony-benchmark : scripts, configuration et données brutes des 15 runs.
En 3 commandes
git clone https://github.com/yoanbernabeu/gemma4-symfony-benchmark.git
cd gemma4-symfony-benchmark
./setup.sh # Crée le projet Symfony + modèle Ollama 64k
./benchmark.sh # Lance les 5 scénarios × 3 runs (~45 min)
Prérequis
| Outil | Version min. | Installation |
|---|---|---|
| Ollama | >= 0.20.0 | ollama.com |
| OpenCode | >= 1.2.0 | curl -fsSL https://opencode.ai/install | bash |
| Symfony CLI | >= 5.0 | symfony.com/download |
| PHP | >= 8.4 | Via votre gestionnaire de packages |
| Python 3 | >= 3.9 | Pour l’agrégation des résultats |
Le dépôt contient
| Fichier | Rôle |
|---|---|
setup.sh | Crée le projet Symfony, le modèle Ollama 64k, la baseline Git |
benchmark.sh | Exécute les 5 scénarios × 3 runs avec validation automatisée |
opencode.json | Configuration OpenCode → Ollama |
Modelfile | Modelfile Ollama avec num_ctx 65536 |
results/ | Données brutes des 15 runs (logs JSON, validations) |
Test rapide sans le benchmark complet
# Créer le modèle avec 64k de contexte
echo -e "FROM gemma4:latest\nPARAMETER num_ctx 65536" | ollama create gemma4-opencode -f -
# Dans un projet Symfony existant avec opencode.json configuré :
opencode run "Create a Doctrine entity App\Entity\Article in src/Entity/Article.php with id (integer auto PK), title (string 255), content (text), createdAt (datetime_immutable), isPublished (boolean default false). Use PHP 8 attributes." \
-m ollama/gemma4-opencode --dir .
# Vérifier
php -l src/Entity/Article.php
php bin/console lint:container
php bin/console doctrine:mapping:info
Conclusion
Gemma 4 E4B n’est pas un remplacement pour Claude Code ou Cursor avec un modèle cloud. C’est un fait, et les chiffres le montrent clairement : 87/100 sur une entité simple, 30/100 sur un CRUD complet.
Mais ce n’est pas la bonne question. La bonne question c’est : un modèle de 4.5B paramètres qui tourne sur un laptop peut-il être utile pour du développement Symfony ?
La réponse est oui, pour des tâches ciblées :
- Générer une entité Doctrine avec ses getters/setters → ✅
- Créer rapidement un controller simple → ✅ (avec un peu de chance)
- Créer un CRUD complet en une passe → ❌
Le vrai apport de ce benchmark, c’est d’avoir identifié le piège du num_ctx. Sans 64K de contexte, Gemma 4 ne voit pas les outils d’OpenCode et se comporte comme un chatbot classique. C’est le genre de détail que personne ne documente et qui fait perdre des heures.
Le combo Ollama + OpenCode + un modèle local est prometteur. Aujourd’hui avec 4.5B paramètres, c’est limité. Mais les modèles locaux progressent vite. La même expérience avec un modèle 26B ou 70B donnerait probablement des résultats très différents. Ce benchmark fournit le protocole pour le vérifier.
Benchmark réalisé le 2 avril 2026 sur MacBook Pro M4 Pro (24 GB). Tous les résultats sont reproductibles avec le script fourni.
Les scripts et les données brutes des 15 runs (logs JSON, validations, code généré) sont disponibles dans le dépôt GitHub.
Loading comments...