Monter une IA locale complète
J’suis un nerd, je joue (jouais…) à mes heures perdues. J’ai donc un PC de type “gamer”, avec une bonne carte graphique, un CPU puissant et beaucoup de RAM. C’est un build que j’ai commencé à faire pendant le covid, avant que les prix (déjà à l’époque c’était parti en vrille) ne s’envolent.
Une rtx 3080, et j’étais pas mal pour jouer (Watch Dogs, Satisfactory, et autres merveilles).
Puis j’ai changé de CG, pour faire un peu de ML dessus. Ajouter de la RAM aussi. Et voilà que mon PC Gamer se transforme petit à petit en workstation ML. Cool.
Puis on est entré dans l’ère des agents IA, des LLMs locaux (merci la Chine), des projets OSS pour gérer des agents IA etc (hello OpenClaw, Hermes, etc), et j’ai voulu jouer à tout cela chez moi. La workstation n’attendait que ça. Timing parfait.
Premiers essais avec des IAs locales et on voit déjà les limites de la rtx 3090, malgré ses 24 GB de VRAM.
En bon “tryharder”, je décide de trouver une seconde CG, exactement la même, pour une bouchée de pain (ouais bon, 650 euros quand même). Et puis je dois changer la CM pour pouvoir tirer parti de la puissance des deux CG (nombre de PCI LANE, etc, bref des trucs de bon gros nerds). On trouve une CM sur leboncoin, la CG aussi. On monte tout. La seconde CG surchauffe (seule ou à deux, tests croisés, double check). Je me suis fait berner par un daron de 60 piges qui, je cite, ne jouait qu’à Flight Simulator avec (et avait laissé le plastique sur la plaque de cooling !). J’aurais dû faire attention.
Tryhard toujours, je décide d’ouvrir la carte : pâte thermique SÈCHE.

Je change tout ça (je n’ai qu’une photo malheureusement). On remonte le tout.. ça bombarde et c’est stable en température.
Le build complet:

Let’s go. Step by step.
La machine est prête, j’installe une distribution linux dessus et je fais mes premiers tests concluants, notamment avec gemma 4.
C’est bien cool tout ça, mais je veux pas gérer ma workstation en standalone, avec tout ce que ça peut impliquer.
Solution simple, c’était devant moi tout ce temps ! Intégrer à mon homelab la workstation. Comme avec les rpis, ma workstation se retrouve dans mon cluster en une commande.
Maintenant, que faire ?
Première étape: avoir son propre LLM servi proprement sur ce node.
À ce moment-là, gemma-4 est dans tous les threads llm OW, donc je teste de le servir dessus.
Pour cela, on va utiliser vllm, le configurer pour qu’il charge le bon gemma-4 qui fit dans les 2 CG (au total 48GB). De mémoire, on utilise un modèle quantifié + pré-allocation de 200k de context pour que ça devienne intéressant.
Bluffé des résultats. Ça commence à être sérieux niveau LLM local.
Je laisse ça tourner en local, sans trop l’utiliser.. un peu de opencode sur un “faux” projet pour voir comment il se débrouille. Sans plus.
Puis… arrive le dernier modèle de Qwen: qwen3.6. Les benchmarks annoncent des dingueries, surtout en agentic Coding.
Vous connaissez le mec maintenant : la FOMO me fait passer de Gemma à QWEN. Mais cette fois on va plus loin: je veux en faire un agent perso, dispo sur mon tel partout, tout le temps. Avec de la voix. Le tout privé, en local, aucune donnée ne sort.
Pour réaliser cela, on va partir sur une stack complète qui fit dans la workstation:
cyankiwi/Qwen3.6-27B-AWQ-BF16-INT4Le tout accessible uniquement via mon network privé tailscale.
Chaque brique AI est servie via une API openai compliant, pour une interconnectivité accrue.
Un petit aperçu de open-webui:

Performance:
Engine 000: Avg prompt throughput: 531.9 tokens/s, Avg generation throughput: 120.5 tokens/s, Running: 1 reqs, Waiting: 0 reqs,
Ça fonctionne, l’UI répond bien et c’est assez fluide. Bon c’est lent, mais il doit y avoir un moyen d’optimiser ça. Une bonne v1.
Pour ce test, on va utiliser un projet opensource: opencode.
Pour ceux qui ne savent pas ce que c’est: c’est un peu l’équivalent de claude code pour anthropic, et codex pour openai. Un logiciel permettant de vous assister complètement sur la compréhension, la gestion, et l’évolution d’une codebase.
On le plug à notre llm local via une configuration simple:
{
"$schema": "https://opencode.ai/config.json",
"model": "vllm/qwen3.6-27b",
"small_model": "vllm/qwen3.6-27b",
"provider": {
"vllm": {
"npm": "@ai-sdk/openai-compatible",
"name": "vLLM (local)",
"options": {
"baseURL": "https://my.endpoint.com"
},
"models": {
"qwen3.6-27b": {
"name": "Qwen 3.6 27B"
}
}
}
}
}
L’interface ressemble à ça:

Tout est setup, c’est good. Maintenant, que va-t-on lui faire coder pour tester ses performances ?
Prompt:
Let's build a well known game: tictactoe.
The goal is quite simple: a small React App, Typescript, Tailwind, served with a local dev server. 2 players can play the game locally on the same computer. Rules are the game rules, you know them.
Simple yet beautiful design, keep it simple and UX friendly.
Plan and go !
En one-shot:
Code disponible ici:
https://github.com/ademoverflow/tictactoe-qwen/
Ce que opencode a fait:
Summary: Tic-Tac-Toe Build
1. Project Scaffolding
- Ran npm create vite@latest . -- --template react-ts to scaffold a Vite + React + TypeScript project in the existing empty git repo
- Installed all dependencies with npm install
- Added Tailwind CSS 4 via npm install -D tailwindcss @tailwindcss/vite
2. Configuration
- vite.config.ts — Added @tailwindcss/vite plugin alongside the React plugin
- index.html — Updated <title> from "tictactoe" to "Tic-Tac-Toe"
- Cleaned up default template assets (App.css, hero.png, react.svg, vite.svg, favicon.svg)
3. Styles — src/index.css
- Replaced the Vite template CSS entirely
- Imported Tailwind via @import "tailwindcss"
- Defined 4 custom @keyframes:
- cell-pop — scale + fade animation for marks appearing
- glow — pulsing box-shadow for winning cells
- fade-in — slide-up + fade for result banners
- shimmer — (available for future use)
- Mapped keyframes to utility classes (.cell-pop, .cell-glow, .fade-in)
4. Game Logic — src/App.tsx
- Win detection — checkWinner() iterates 8 possible lines (3 rows, 3 cols, 2 diagonals)
- Draw detection — isDraw() checks if all 9 cells are filled
- AI (minimax with alpha-beta pruning) — getBestMove() evaluates all possible moves recursively, making the computer unbeatable
- Game state — board (9 cells), xIsNext, mode (2p / ai), status (playing / won / draw), winningLine, thinking flag
- AI move trigger — useEffect fires on AI's turn with a 400ms delay for natural feel
- Components — CellComponent renders each cell with SVG X/O, hover states, and winning glow
5. UI Design
- Gradient background (slate-100 → slate-200), centered layout
- Mode toggle buttons (pill-shaped, indigo active state)
- Turn indicator with animated dots during AI thinking
- 3×3 grid with rounded cells, shadow, hover scale effect
- X in indigo, O in rose/red
- Winning cells get a pulsing glow animation
- Result banner with fade-in animation
- "New Game" button after game ends, "Reset" button during play
6. Verification
- npx tsc --noEmit — zero TypeScript errors
- npm run dev — Vite dev server started successfully on http://localhost:5173/
Fonctionnel, aucun souci, pas de bug.
Ce sera l’objet du prochain article.
D’ici là, patience.
À la prochaine,
Adem.