---
title: "Gemma 4 peut-il remplacer Claude Code ? Benchmark sur un vrai projet Symfony"
excerpt: "Un modèle local de 9.6 GB peut-il générer du code Symfony fonctionnel ? Benchmark de Gemma 4 E4B : 5 scénarios, 15 runs, validation automatisée."
publishDate: 2026-04-03T00:00:00.000Z
tags: ["ia", "ollama", "gemma4", "google", "opencode", "symfony", "benchmark", "open-source", "php", "local-llm"]
canonical: "https://yoandev.co/gemma4-opencode-benchmark"
---

"*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](https://ai.google.dev/gemma/docs/core/model_card_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](https://news.ycombinator.com/item?id=47319205) (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](#le-piège-du-num_ctx--quand-le-modèle-oublie-ses-outils)). 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](https://opencode.ai) 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) :

1. **`php -l`** : syntaxe PHP valide sur chaque fichier
2. **`php bin/console lint:container`** : le conteneur de services Symfony compile
3. **`php bin/console lint:twig`** : les templates Twig sont valides
4. **`php bin/console doctrine:mapping:info`** : Doctrine reconnaît les entités
5. **`php bin/console debug:router`** : les routes sont enregistrées
6. **`php 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 -fd` pour 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 :

1. **OpenCode envoie bien les 11 outils** dans la requête (write, bash, read, edit, glob, grep...)
2. **Gemma 4 sait faire du tool calling** : un test direct via l'API Ollama fonctionne parfaitement
3. **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](https://docs.ollama.com/integrations/opencode)) :

```bash
# 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` :

```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 outils `write` et `bash` d'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](https://philippdubach.com/posts/claude-opus-4.6-anthropics-new-flagship-ai-model-for-agentic-coding/) sur des tâches de refactoring multi-fichier, et [98.8% sur un benchmark TypeScript](https://www.implicator.ai/open-weights-llms-score-94-8-on-custom-coding-benchmark-4-behind-claude-opus/) custom
- **Les modèles open-weights** atteignent [94.8% sur ce même benchmark](https://www.implicator.ai/open-weights-llms-score-94-8-on-custom-coding-benchmark-4-behind-claude-opus/), 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](https://platform.claude.com/docs/en/about-claude/pricing)

| Critère | Gemma 4 E4B (local, 4.5B) | Claude Opus 4.6 (cloud, ~100B actifs) |
|:--------|:--------------------------|:---------------------------------------|
| Coût | **Gratuit** | [$5/M input, $25/M output](https://platform.claude.com/docs/en/about-claude/pricing) |
| 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](https://github.com/yoanbernabeu/gemma4-symfony-benchmark)** : scripts, configuration et données brutes des 15 runs.

### En 3 commandes

```bash
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](https://ollama.com) |
| OpenCode | >= 1.2.0 | `curl -fsSL https://opencode.ai/install \| bash` |
| Symfony CLI | >= 5.0 | [symfony.com/download](https://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`](https://github.com/yoanbernabeu/gemma4-symfony-benchmark/blob/main/setup.sh) | Crée le projet Symfony, le modèle Ollama 64k, la baseline Git |
| [`benchmark.sh`](https://github.com/yoanbernabeu/gemma4-symfony-benchmark/blob/main/benchmark.sh) | Exécute les 5 scénarios × 3 runs avec validation automatisée |
| [`opencode.json`](https://github.com/yoanbernabeu/gemma4-symfony-benchmark/blob/main/opencode.json) | Configuration OpenCode → Ollama |
| [`Modelfile`](https://github.com/yoanbernabeu/gemma4-symfony-benchmark/blob/main/Modelfile) | Modelfile Ollama avec `num_ctx 65536` |
| [`results/`](https://github.com/yoanbernabeu/gemma4-symfony-benchmark/tree/main/results) | Données brutes des 15 runs (logs JSON, validations) |

### Test rapide sans le benchmark complet

```bash
# 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](https://github.com/yoanbernabeu/gemma4-symfony-benchmark/tree/main/results) (logs JSON, validations, code généré) sont disponibles dans le [dépôt GitHub](https://github.com/yoanbernabeu/gemma4-symfony-benchmark).*
