La question « build vs buy » pour l'infrastructure LLM divise les équipes tech depuis 2 ans. Côté cloud : zéro friction, scale-to-zero, mais coût qui croît linéairement avec l'usage et qui finit par exploser à partir d'un certain volume. Côté self-hosted : capex up-front, opex faible, mais compétences sysadmin requises.
Cet article documente notre choix : nous avons construit une stack LLM 100 % self-hosted, opérée par une seule personne (moi), dimensionnée pour servir 400-1 100 requêtes/heure 24/7 avec une disponibilité > 99 %. Le tout pour 8 k€ HT capex initial et 250 €/mois opex. À notre volume actuel, ça remplace l'équivalent de ~12 k€/mois de Bedrock Claude Sonnet 4.6, soit un payback en moins d'un mois.
Le but n'est pas de convaincre tout le monde de quitter le cloud. C'est de partager une architecture qui marche, en expliquant les choix matériels, les routing rules, et la mécanique économique. Si vous gérez plus de 200 k€/an de facture LLM cloud, vous devriez lire jusqu'à la fin.
Topologie : 5 nœuds, 1 routeur
Vue d'ensemble en ASCII pour donner le mental model :
[ Internet / agents apps ]
|
v
+-------------------------+
| LiteLLM Router :4000 | (sur Mac mini)
| - Auth (API keys) |
| - Routing rules |
| - Fallback / retry |
| - Prometheus /metrics |
+------------+------------+
|
+---------------+-----------+----------+--------------+
| | | | |
v v v v v
+---------+ +-----------+ +--------+ +---------+ +----------+
| DGX | | Octominer | | Xeon | | Mac mini| | Bedrock |
| Spark | | (5 GPU) | | (stora | (agents)| | (fallback|
| GB10 | +-----------+ | + qdr) | +---------+ | only) |
+---------+ | 4060 Ti | +--------+ +----------+
| Qwen3.6 | | (vLLM) |
| 35B-A3B | | Qwen3-14B|
| always- | | AWQ |
| on long | | tool-call|
| reviewer| +-----------+
+---------+ | 6700 XT |
| llama- | | Qwen3-TTS|
| swap | +-----------+
| (Llama70| | RX 570 ×3 |
| Mistral| | Whisper |
| DeepCdr| | farm |
| ...) | +-----------+
+---------+ | RX 570 ×1 |
| VAAPI |
| (vidéo) |
+-----------+
[ Tailscale mesh : tous nœuds ]Tout est connecté par Tailscale (WireGuard chiffré end-to-end). Le LiteLLM router est l'unique point d'entrée pour les applications. Les nœuds backend ne sont jamais exposés publiquement. Bedrock est branché en fallback de dernière ligne (rarement appelé, voir routing rules).
Nœud 1 : DGX Spark GB10 (long-reviewer)
- Hardware : NVIDIA DGX Spark, GB10 Grace Blackwell, 128 GB UMA, 4 TB SSD NVMe Samsung 990 Pro
- OS : Ubuntu 24.04 Server LTS, kernel 6.8 NVIDIA custom, driver 570.x
- Modèle always-on : Qwen3.6-35B-A3B PrismaQuant 4.75bit (22.9 GB VRAM)
- Modèles on-demand (via llama-swap TTL 600s) : Llama 3.3 70B Q4, Mistral Small 24B Q5, DeepSeek-Coder 33B Q4
- Stack : vLLM 0.20.x (long-reviewer), llama.cpp HEAD (modèles GGUF on-demand), llama-swap 1.x devant
- Capex : 4 200 € HT (Spark 128 GB) + 180 € (SSD 4 TB) + 90 € (UPS APC 600 VA) = 4 470 € HT
- Power : 240 W idle, 400 W charge mesurée
- Rôle : tier « long-reviewer » — modèles > 20B avec raisonnement profond, contexte 32 K
Détails opérationnels et leçons apprises sur 90 jours dans le billet 1 de la série. Performance technique de PrismaQuant dans le billet 2.
Nœud 2 : Octominer (5 GPU spécialisés)
C'est le nœud le plus créatif du setup. Un chassis Octominer X12-Ultra (initialement vendu pour le minage Ethereum, racheté à 350 € d'occasion en 2024) avec 12 slots PCIe 1x. Architecture i5 entry-level, 16 GB RAM, alim 1600 W titanium. On y a installé 5 GPU avec 5 rôles distincts :
GPU 1 : NVIDIA RTX 4060 Ti 16 GB — tool-caller
- Modèle : Qwen3-14B-AWQ INT4 (8.2 GB VRAM)
- Stack : vLLM 0.20 (Marlin kernels)
- Latence : 518 ms TTFT warm, 130 tok/s decode @ 4K
- Rôle : tier « tool-caller » — appels d'outils structurés, routing decisions, function calling pour agents
- Capex : 600 € (Newegg, occasion-comme-neuf)
- Volume : 60 % du trafic agents — c'est le workhorse
GPU 2 : AMD RX 6700 XT 12 GB — TTS
- Modèle : Qwen3-TTS 1.7B BF16 (3.4 GB VRAM)
- Stack : qwen-tts 0.1.1 sur PyTorch ROCm 2.9.1,
HSA_OVERRIDE_GFX_VERSION=10.3.0 - Performance : 17x realtime FR voix échantillon (mesuré sur le pipeline Signal IA podcast)
- Capex : 290 € HT (occasion 2024)
- Rôle : génération TTS pour podcast Signal IA (1 épisode/semaine, ~3h gen pour 17 min audio) + voice agents ad-hoc
GPU 3 + 4 + 5 : 3× AMD RX 570 8 GB — Whisper farm
- Modèle : Whisper Large v3 sur 3 GPU en parallel, via faster-whisper ROCm
- Performance : 2x realtime mesuré sur batch d'ingestion YouTube
- Stack : faster-whisper 1.x compilé ROCm, queue Redis pour distribution batch
- Capex : 35 € × 3 = 105 € HT (occasion 2024 — c'est ridicule)
- Rôle : transcription massive de contenus audio (ingest podcast, formations vidéo, support tickets vocaux)
- Pourquoi 3 RX 570 et pas 1 RTX 3090 ? Coût équivalent ~700 € pour le 3090 vs 105 € pour les 3 RX 570. Whisper est embarrassingly parallel : 3 GPU faibles > 1 GPU rapide pour ce workload.
GPU 6 : RX 570 8 GB additionnelle — VAAPI media
- Rôle : encodage vidéo VAAPI (Plex media server, transcoding podcast vers MP4)
- Économie : 24× moins de CPU vs encodage logiciel x264, libère le i5 pour les autres workloads
- Capex : 35 € HT
Total Octominer : 350 € chassis + 600 € + 290 € + 105 € + 35 € = 1 380 € HT pour 5 GPU spécialisés servant 4 workloads distincts en parallèle. C'est le meilleur investissement de tout le setup en termes de versatility / euro.
Nœud 3 : Xeon (stockage + orchestration)
- Hardware : Dell PowerEdge R430 d'occasion (acheté 900 € en 2023), 2× Xeon E5-2680v4, 128 GB DDR4 ECC, 8× SAS 600 GB en RAID 6
- OS : Proxmox VE 8 (virtualisation pour isoler workloads)
- Services : Qdrant (vector DB pour RAG), n8n (workflow automation), Prometheus + Grafana (monitoring), backup rsync vers USB ext4
- Power : 95 W idle, 180 W charge
- Capex : 900 € HT (occasion 2023, amorti sur 4 ans)
- Rôle : tout ce qui n'a pas besoin de GPU mais qui doit être 24/7
Nœud 4 : Mac mini M2 Pro (orchestrateur agents)
- Hardware : Mac mini M2 Pro, 32 GB RAM unifiée, 512 GB SSD
- OS : macOS 15 Sequoia
- Services : launchd agents pour le brain (interactive Claude Code session), pipeline Signal IA podcast, git-mirror local, Tailscale
- Power : 8 W idle, 25 W charge — ridicule pour ce qu'il fait
- Capex : 1 250 € HT (neuf 2024)
- Rôle : orchestration humain-in-the-loop (interactive sessions), agents autonomes 24/7 via launchd, pipeline podcast (oralize Claude CLI + scp vers Octominer pour TTS)
Nœud 5 : LiteLLM router (entry point)
LiteLLM tourne en container Docker sur le Mac mini (low overhead, ~80 MB RAM, ~2 % CPU en charge typique). C'est l'unique point d'entrée pour toutes les applications consumer. Configuration minimaliste mais fonctionnelle :
# /etc/litellm/config.yaml
model_list:
# Tier 1 : long-reviewer (DGX, always-on)
- model_name: long-reviewer
litellm_params:
model: openai/qwen3-long-reviewer
api_base: http://dgx.tailnet:8001/v1
api_key: dummy # internal mesh, no key needed
timeout: 90
model_info:
mode: chat
max_input_tokens: 32768
max_output_tokens: 8192
# Tier 2 : tool-caller (Octominer 4060 Ti, always-on)
- model_name: tool-caller
litellm_params:
model: openai/qwen3-14b-awq
api_base: http://octominer.tailnet:8002/v1
api_key: dummy
timeout: 30
# Tier 3 : chat-fast (Octominer 4060 Ti, on-demand via llama-swap)
- model_name: chat-fast
litellm_params:
model: openai/qwen3-7b-awq
api_base: http://octominer.tailnet:8002/v1
api_key: dummy
timeout: 15
# Tier 4 : Whisper batch
- model_name: whisper
litellm_params:
model: openai/whisper-large-v3
api_base: http://octominer.tailnet:9000/v1
api_key: dummy
timeout: 600
# Tier 5 : TTS
- model_name: tts
litellm_params:
model: openai/qwen3-tts
api_base: http://octominer.tailnet:9100/v1
api_key: dummy
timeout: 300
# Fallback : Bedrock Claude Sonnet (only if all local fails)
- model_name: bedrock-fallback
litellm_params:
model: bedrock/anthropic.claude-sonnet-4-6-v1:0
aws_region_name: eu-west-1
timeout: 60
router_settings:
routing_strategy: simple-shuffle # round-robin si plusieurs deployments
fallbacks:
- long-reviewer: ["bedrock-fallback"]
- tool-caller: ["chat-fast", "bedrock-fallback"]
num_retries: 2
timeout: 30
retry_after: 3
general_settings:
master_key: env_var/LITELLM_MASTER_KEY
database_url: env_var/DATABASE_URL # PostgreSQL on Xeon for budget tracking
alerting:
- slack
budget_duration: 1mo
max_budget: 50 # USD/month hard cap on Bedrock fallbackNote : la routing strategy est volontairement simple. Pas de load-balancing pondéré, pas d'A/B testing intégré. Ces features de LiteLLM marchent, mais on n'en a pas besoin à notre échelle. KISS.
Routing rules : qui sert quoi
Les applications appellent le LiteLLM router avec un model abstrait (long-reviewer, tool-caller, chat-fast). Le router décide ensuite où l'envoyer. Règles effectives :
| Type de requête | Modèle abstrait | Backend physique | % trafic |
|---|---|---|---|
| Code review profonde, analyse de specs | long-reviewer | DGX Qwen3.6-35B | 20 % |
| Function calling, tool decisions | tool-caller | Octominer 4060 Qwen3-14B | 50 % |
| Chat rapide, classification | chat-fast | Octominer 4060 Qwen3-7B | 20 % |
| Transcription audio batch | whisper | Octominer 3× RX 570 | 5 % |
| Synthèse vocale (podcast) | tts | Octominer RX 6700 XT | 3 % |
| Fallback dégradation | bedrock-fallback | AWS Bedrock Claude Sonnet | 2 % |
Les 2 % Bedrock sont concentrés sur 2 cas : (1) tier long-reviewer indisponible (DGX en boot ou crashé) et la requête est urgente, (2) user opt-in « use Claude Sonnet pour cette question » pour cas particuliers où on veut comparer la qualité. Le budget cap LiteLLM (50 $/mois) est largement sous-utilisé en pratique (~12 $/mois moyen sur 3 mois).
Mesh Tailscale : ACLs + DNS
Tailscale crée un VPN WireGuard mesh entre tous les nœuds. Chaque nœud a un nom DNS automatique {nom}.tailnet.{tailnet-id}.ts.net et on peut résoudre par {nom}.tailnet via MagicDNS.
// ACL Tailscale (versionnable, comité écrit en JSON5)
{
"tagOwners": {
"tag:llm-server": ["thomas@gmail.com"],
"tag:orchestrator": ["thomas@gmail.com"],
"tag:storage": ["thomas@gmail.com"]
},
"acls": [
// LiteLLM router peut parler à tous les LLM servers
{
"action": "accept",
"src": ["tag:orchestrator"],
"dst": ["tag:llm-server:8001-9100"]
},
// LLM servers peuvent atteindre le storage (Qdrant, S3-like)
{
"action": "accept",
"src": ["tag:llm-server"],
"dst": ["tag:storage:6333,9000"]
},
// Mon Mac perso peut tout faire (admin)
{
"action": "accept",
"src": ["thomas@gmail.com"],
"dst": ["*:*"]
}
],
"ssh": [
// Je peux SSH dans tous les nœuds depuis n'importe où
{
"action": "accept",
"src": ["thomas@gmail.com"],
"dst": ["tag:llm-server", "tag:storage"],
"users": ["talki", "root"]
}
]
}Coût Tailscale : 0 € (Personal Pro plan, 100 devices gratuit). Pour un usage entreprise, compter 5 $/user/mois.
Économie : capex vs opex Bedrock
Capex total
- DGX Spark : 4 470 €
- Octominer chassis + 5 GPU : 1 380 €
- Xeon Dell R430 (occasion) : 900 €
- Mac mini M2 Pro : 1 250 €
- UPS, câbles, switches Mikrotik 10G : 320 €
- Total : 8 320 € HT
Opex mensuel
- Électricité (mesurée Linky) : 190 €/mois
- Internet (fibre 1Gbps amortie) : 30 €/mois
- Domaine + Tailscale Pro : 0 €/mois (gratuit Personal)
- Bedrock fallback : ~12 €/mois
- Maintenance hardware (provision) : 20 €/mois
- Total : 252 €/mois
Équivalent cloud
Notre volume actuel (412 k requêtes/mois, output médian 800 tokens, input médian 2 200 tokens) calculé en Bedrock Claude Sonnet 4.6 (3 $/1M input + 15 $/1M output) :
- Input : 412k × 2 200 / 1M × 3 $ = 2 720 $
- Output : 412k × 800 / 1M × 15 $ = 4 944 $
- Total Bedrock équivalent : ~7 664 $ / 7 100 € HT/mois
Payback : 8 320 € capex / (7 100 - 252) € économisés par mois = 1,2 mois. Au-delà, c'est 7 100 € d'économie pure par mois (~85 200 €/an).
Sur 36 mois (durée de vie matérielle estimée) : le setup self-hosted nous économise ~250 000 € HT cumulés à volume constant. Si le volume double, l'économie double aussi parce que le hardware sature avant le coût (on a actuellement ~40 % d'utilisation moyenne).
Monitoring + alertes
- Prometheus : scrape
/metricsde LiteLLM (requests/s par model, latency p50/p95/p99, fallback rate), vLLM (KV cache usage, throughput tok/s), node_exporter (CPU/RAM/disk/network par nœud) - Grafana : 4 dashboards principaux : Fleet Overview, Per-Model Latency, Cost Tracking (synthèse Bedrock $/jour), GPU Health (memory, temp, power)
- Alertes : Tailscale push notif sur mon iPhone si (a) un nœud down > 60s, (b) DGX GPU memory > 110 GB > 60s (early warn OOM), (c) fallback rate > 5 % sur 1h, (d) Bedrock spend > 1 $ sur 1h
- Logs : Loki sur Xeon, retention 30 jours. Suffit pour debug post-mortem.
Modes de défaillance + plan B
Tout système distribué casse. Voici les modes observés sur 6 mois et comment on les gère :
- DGX Spark down (3 incidents) : LiteLLM bascule automatiquement long-reviewer → bedrock-fallback. SLA dégradé (latence x3 pour les requêtes longues), mais service maintenu. Mean Time To Recovery : 18 min.
- Octominer GPU thermique throttle (2 incidents été 2025) : ventilateurs chassis encrassés, GPU à 92 °C, throughput divisé par 2. Détecté par Grafana alert « tok/s < baseline -50 % ». Fix : nettoyage poussière + remplacement ventilateurs (15 €/u). MTTR 45 min.
- Tailscale outage (1 incident, mai 2025) : Tailscale control plane down ~30 min. Les connexions existantes continuent (peer-to-peer DERP fallback) mais nouvelles connexions impossibles. Pas d'impact agents en cours, agents nouveaux sont en queue jusqu'à recovery.
- llama-swap eviction race (cf billet 1) : 6 incidents au total avant fix. Maintenant zéro depuis 2 mois grâce au
flockautour des starts vLLM. - SSD plein sur Xeon (1 incident) : Loki logs ont explosé après un bug de retention. Disk full → Qdrant crash → cascading failure des agents RAG. Fix : retention pol Loki réduite à 14 jours, monitoring alerte à 80 % fill.
Si je devais refaire le setup aujourd'hui
3 changements après 6 mois d'opération :
- Acheter le DGX Spark plus tôt. J'ai attendu 2 mois après l'annonce pour pré-commander. Pendant ces 2 mois, j'ai payé ~14 k€ de Bedrock qui auraient été économisés. Le payback du Spark est tellement rapide à notre volume qu'hésiter était une erreur.
- Mettre 2× RTX 4060 Ti dans l'Octominer. Une seule 4060 Ti commence à saturer le tier tool-caller en pic (50 % du trafic, ~250 req/min observé). Une deuxième en load-balancing aurait été 600 € bien dépensés.
- Choisir un Mac Studio M4 Max au lieu du Mac mini M2 Pro pour l'orchestrateur. Le mini M2 commence à souffrir avec brain Claude Code + 6 launchd agents en parallèle. Le Studio M4 Max (~3 200 €) aurait apporté 8x plus de RAM unifiée et la possibilité de faire tourner un modèle de fallback local (Llama 3.3 8B Q4) directement sur l'orchestrateur.
Questions fréquentes
Pourquoi ne pas tout faire tourner sur le DGX Spark seul ?
Pour 4 raisons. (1) Spécialisation matérielle : un Whisper batch tourne 8x plus vite sur 3× RX 570 que sur GB10 parce que les RX 570 ont un débit FP16 médiocre mais un cache L2 énorme et coûtent 35€ d'occasion pièce. (2) Fault tolerance : si le Spark plante, les agents tool-caller continuent sur l'Octominer. (3) Coût marginal : ajouter un Octominer 4060 Ti à 600€ pour offloader 60% des requêtes 'simples' libère le Spark pour les requêtes 'lourdes'. (4) Latence locale : la TTS Qwen3-TTS sur RX 6700 XT colocalisée avec les microphones (mac-2) évite un round-trip réseau pour les workflows audio.
Qu'est-ce que LiteLLM et pourquoi pas Ollama ou OpenAI proxy direct ?
LiteLLM est un proxy unifié OpenAI-compatible qui traduit vers 100+ backends (vLLM, llama.cpp, Anthropic, OpenAI, Bedrock, Vertex). Il gère routing, fallback, retry, rate limiting, observability (Prometheus, Langfuse) et budget tracking par utilisateur. Vs Ollama : Ollama est un binaire monolithique (model loader + serveur), LiteLLM est un router devant N backends. Vs proxy direct OpenAI : LiteLLM permet de switcher de backend (claude-3.5 prod, qwen3-35b dev) en changeant 1 ligne YAML, sans toucher au code applicatif.
Pourquoi Tailscale plutôt qu'un VLAN local classique ?
Tailscale donne 4 choses qu'un VLAN ne donne pas : (1) chiffrement WireGuard end-to-end même intra-LAN (zero-trust), (2) DNS automatique (dgx.tailnet vs ARP-guess en VLAN), (3) accès depuis l'extérieur sans VPN supplémentaire (je peux ssh dans le DGX depuis un café), (4) ACLs déclaratives en JSON versionnable. Tradeoff : ~5% de latence en plus vs LAN pur (mesuré 0.7ms vs 0.3ms entre Octominer et DGX). Pour un workload LLM où le bottleneck est le compute (decode 30 tok/s = 33ms par token), c'est négligeable.
Comment llama-swap décide-t-il quel modèle évincer ?
TTL pur (Time To Live). Chaque modèle a un timestamp de dernière utilisation. Toutes les 30 secondes, llama-swap scanne et SIGTERM les modèles inactifs depuis plus que leur TTL configuré (par défaut 600s). Pas d'algorithme LRU/LFU sophistiqué — c'est volontaire : un modèle qu'on n'a pas appelé depuis 10 minutes a peu de chance d'être réappelé bientôt en workflow agent. La VRAM libérée est immédiatement disponible pour le prochain modèle requesté. Voir notre billet 1 sur le edge case llama-swap + vLLM sur GB10 (assertion error pendant le profiling vLLM).
Quel est le bilan énergétique total des 5 nœuds ?
Mesuré au compteur EDF Linky sur un mois moyen : DGX Spark 240W idle / 400W charge, Octominer 380W idle (5 GPU + carte mère server) / 620W charge totale, Xeon 95W idle / 180W charge, Mac mini orchestrateur 8W idle / 25W charge. Total mesuré : ~1.1 kWh idle (24/7) + ~1.8 kWh en charge (10h/jour estimé). Coût mensuel à 0.21 €/kWh : ~190 €/mois électricité tout inclus. Comparable à un PC gaming RTX 4080 utilisé 4h/jour. Acceptable.
Comment monitorer la santé du mesh ?
Stack standard : Prometheus scrapes /metrics sur LiteLLM, vLLM, llama-swap. Grafana sur le Xeon (low-load) avec dashboards 'agent latency', 'token cost', 'GPU memory headroom', 'TTL eviction rate'. Alertes Tailscale push notif quand : (a) un nœud devient unreachable >60s, (b) GPU memory >110 GB sur GB10 pendant >60s (early warning OOM, voir billet 1), (c) routing fallback rate >5% sur 1h (signe d'un backend dégradé). Coût : 0€, tout en self-hosted sur le mesh existant.
Combien de temps faut-il pour reconstruire ce setup from scratch ?
Pour un sysadmin senior familier avec Linux + Docker + un peu de réseau : compter 4-6 jours pleins. Jour 1 : install OS + drivers NVIDIA/AMD sur tous les nœuds. Jour 2 : Tailscale mesh + ACLs + DNS. Jour 3 : vLLM build custom + llama-swap + premier modèle. Jour 4 : LiteLLM frontale + routing rules + auth. Jour 5 : Whisper farm + TTS + intégration agents. Jour 6 : monitoring + backup + runbooks. Pour un débutant : doublez. Notre code de configuration complet est public sur fossouo/talki-infra.
Conclusion
Le setup self-hosted n'est pas pour tout le monde. Si vous gérez moins de 100 k€/an de facture LLM cloud, le ROI ne justifie pas la complexité. Si vous n'avez pas accès à un sysadmin senior, le risque d'incident vous coûtera plus cher que les économies. Si votre charge est extrêmement variable (pic Black Friday × 50), le cloud reste imbattable pour absorber les pics.
Mais si vous êtes au-dessus de 200 k€/an de Bedrock/OpenAI, avec un workload soutenu, et que vous avez une personne capable de gérer du Linux/CUDA/network, alors la décision build vs buy bascule clairement vers build. Le payback est si rapide que la question devient « pourquoi pas maintenant ? ».
Cet article est le 3e d'une série de 3 sur notre adventure GB10. Lire aussi :
- DGX Spark GB10 : retour d'expérience après 3 mois — leçons opérationnelles UMA + flashinfer + llama-swap
- Quantization PrismaQuant 4.75bit Qwen3.6-35B — la quantization qui rend tout ça économiquement viable
- Optimisation des coûts IA en production 2026 — méthodologie de décision build vs buy
- Agents IA en production 2026 — les charges de travail qui tournent sur ce setup