Chapitre 22 / 39
IA on-prem
Le Framework Desktop dans la salle serveur fait tourner les modèles d’IA en local. Quand Rani, Paul ou Jack demandent au système de faire quelque chose — rédiger une lettre, consulter un client, générer un brief — l’IA tourne à Gennevilliers, pas chez un fournisseur cloud. Tout est sur le matériel que Sodimo possède déjà.
Statut : Bloqué — les modèles sont sélectionnés et testés dans sodimo/harness (le dépôt harness de l’OS). Le Framework Desktop est le prérequis pour passer en production.
Trois paliers locaux
Le système choisit le bon palier pour chaque requête. L’utilisateur n’a pas à choisir.
Palier rapide — le palier par défaut pour la plupart des requêtes. Assez rapide pour qu’une réponse arrive en moins de 30 secondes.
Utilisé par :
- Les briefs du matin (Rani, Paul, Jack)
- Les briefs d’appel
- Les alertes churn
- La liste de prospection quotidienne
- Les requêtes courantes
Palier qualité — pour les synthèses où la précision est critique et où un temps de réponse plus long est acceptable.
Utilisé par :
- Le rapport mensuel pour Michel
- L’analyse du portefeuille de contrats
- La synthèse des debriefs de visite sur une semaine d’appels
Palier ensemble — pour les analyses à plus hauts enjeux. Trois modèles génèrent des évaluations indépendantes, chacune est mise en débat par un avocat du diable, puis les résultats sont synthétisés en un seul. C’est le palier le plus long.
Utilisé par :
- L’analyse d’opportunité sur une affaire ou un renouvellement
- La revue d’un contrat à enjeux élevés avant signature
La sélection des modèles au sein de chaque palier est gérée dans sodimo/harness et testée sur des charges de travail Sodimo réelles avant déploiement. Le palier est le contrat avec l’utilisateur ; le modèle spécifique qui le sous-tend peut être remplacé sans changer la façon dont quiconque travaille.
Le runtime sous-jacent
Les modèles locaux tournent sous llama.cpp, routés via llama-swap pour qu’un seul processus puisse héberger plusieurs modèles et permuter entre eux à la demande. Il y a un seul runtime sur la machine, testé sur le GPU AMD exact, épinglé à une version connue-bonne. Les versions upstream ne s’auto-mettent pas à jour ; un changement de version est une modification délibérée de sodimo/dotfiles.
Image · ghcr.io/mostlygeek/llama-swap:vulkan — posture Vulkan uniquement. L’image n’embarque pas de binaire ramalama ; llama-swap exécute /app/llama-server directement. Les vestiges de compatibilité ROCm (/dev/kfd, HSA_OVERRIDE_GFX_VERSION=11.0.0) restent dans le conteneur pour une réactivation future et sont suivis dans sodimo/dotfiles#14.
Inventaire des modèles (post 2026-04-22) :
| Alias | Modèle | Rôle |
|---|---|---|
local-task | qwen3-4b (Vulkan GGUF) | chat par défaut, usage d’outils |
local-heavy | gpt-oss-120b unsloth UD-Q8_K_XL (GGUF 2 shards, 65k ctx, reasoning_effort=high) | raisonnement lourd ; différé jusqu’à l’atterrissage du kernel cmdline harness#11 + téléchargement des 100 Go de GGUF |
local-coder | qwen3-coder-30b (TTL=600s, groupe heavy) | tâches de code |
local-embeddings | qwen3-embedding-8b (TTL=0) | embeddings RAG (voir OpenWebUI) |
Posture runtime Strix Halo
Chaque bloc cmd: de modèle dans llama-swap.yaml porte l’ensemble de flags Strix-Halo obligatoires de kyuz0. Justification par flag :
| Flag | Pourquoi |
|---|---|
--no-mmap | L’iGPU Strix Halo veut de la VRAM contiguë ; mmap la fragmente |
-fa on | flash attention |
-ngl 999 | offloader chaque couche sur le GPU |
--batch-size 4096 --ubatch-size 512 | throughput réglé pour Strix Halo |
--cache-type-k q8_0 --cache-type-v q8_0 | cache KV Q8 — ~2x de contexte effectif |
--jinja | format de chat Jinja-templatisé |
--direct-io --cache-prompt --cache-reuse 256 | cache de prompt |
--threads 12 | Strix Halo a 16 cœurs ; 12 pour l’inférence laisse 4 pour l’hôte |
La fiche opérationnelle (ports, env, volumes, digest d’image exact) vit dans Référence quadlets → llama-swap. Ce chapitre possède le pourquoi ; le 38 possède le câblage.
Règle du self-proxy. Dans llama-swap.yaml, le proxy: de chaque modèle doit cibler http://127.0.0.1:NNNN, jamais http://llama-swap:NNNN. llama-swap et le llama-server spawné partagent un netns — utiliser le hostname du conteneur force un aller-retour DNS netavark inutile qui produit des 502 sous l’DNAT adguard on-box.
Compromis de driver Vulkan. L’image mostlygeek embarque RADV. kyuz0 recommande AMDVLK pour les charges de travail intensives en prompt / long contexte. Chemin de montée en version = image gateway personnalisée avec amdvlk-2025.Q2.1.rpm en couche. Différé.
Référence kyuz0. Toolbox Strix-Halo épinglée au commit 1421e870… ; notes d’interpolation complètes dans sodimo/dotfiles/docs/kyuz0-toolbox.md, procédure de resync dans docs/resync-runbook.md.
Smoke benchmark — 2026-04-22
De bout en bout via OpenWebUI → LiteLLM → llama-swap → llama-server sur le Framework Desktop de dev (même silicium que le harness cible) :
local-task(qwen3-4b) : 53 tok/s sur RADV Vulkan, 11,5 s à froid, 0,08–0,20 s à chaud.local-heavy(gpt-oss-120b) : différé — téléchargement de 100 Go de GGUF + verrou kernel cmdline (harness#11).
Référence croisée : OpenWebUI.
Politique d’escalade et compteur d’utilisation
Routage par défaut
Chaque invocation IA route d’abord vers la stack on-prem. Le mapping alias-vers-palier est fixe :
| Alias | Palier | Usage par défaut |
|---|---|---|
local-task | Rapide | Chat, usage d’outils, courtes rédactions |
local-heavy | Qualité | Raisonnement, synthèse, rapports pour le conseil |
local-coder | Qualité | Génération et revue de code |
cloud-heavy | Cloud (Claude Opus) | Escalade opt-in uniquement |
cloud-heavy n’est pas un repli que le système atteint seul ; c’est un opt-in délibéré par invocation. L’appelant doit passer escalate: "cloud-heavy" dans l’invocation d’outil. Rien n’escalade silencieusement.
Déclencheurs d’escalade
Quatre conditions justifient une escalade explicite vers cloud-heavy :
- Flag explicite — l’invocation de skill, d’agent ou d’outil porte
escalate: "cloud-heavy". C’est le déclencheur principal ; les autres sont de la documentation, pas de l’automatisation. - Débordement de fenêtre de contexte — le prompt assemblé dépasse la fenêtre de contexte du modèle local (65 k tokens pour
local-heavy; moins pour les autres paliers). L’appelant est responsable de détecter ce cas avant le dispatch. - Régression qualitative sur une classe de tâches — une classe de tâches spécifique (ex. revue de contrat juridique) a été caractérisée comme nécessitant une sortie de qualité cloud et est marquée dans le manifeste du skill. Le skill définit
escalate: "cloud-heavy"inconditionnellement pour cette classe. - Exigence réglementaire — livrables d’audit ou avis juridiques où l’opérateur a besoin d’une version de modèle documentée et émise par un fournisseur cloud pour des raisons de chaîne de custody.
Aucun autre déclencheur n’est reconnu. « C’était lent » ou « la réponse locale semblait courte » ne sont pas des déclencheurs d’escalade ; ce sont des retours pour le processus de sélection de modèle dans sodimo/harness.
Compteur d’utilisation
Chaque requête qui route via llama-swap émet une ligne dans run_ledger via l’outil Worker ledger_write (voir ch42 Ce à quoi l’IA peut accéder). Le schéma de ligne correspond exactement au Principe 2 :
| Champ | Valeur pour les exécutions locales |
|---|---|
model | alias (local-task, local-heavy, local-coder) |
provider | local |
tokens_in | nombre de tokens du prompt depuis llama-server |
tokens_out | nombre de tokens de complétion |
latency_ms | durée horloge murale du dispatch au premier token |
cost_eur | 0 — les exécutions on-prem ne portent aucun coût par token |
cost_eur_if_cloud | contrefactuel : ce qu’aurait coûté le même prompt+complétion au tarif Claude Opus |
La colonne cost_eur_if_cloud est le chiffre des économies. Cumulée sur toutes les exécutions locales, SUM(cost_eur_if_cloud) - SUM(cost_eur) donne le titre. Une seule requête SQL contre run_ledger.
Les lignes d’escalade cloud enregistrent le coût réel dans cost_eur ; cost_eur_if_cloud égale cost_eur pour ces lignes (pas d’économie contrefactuelle sur une exécution cloud).
Surface
Le compteur cumulatif — total des exécutions, total des tokens de prompt, total des tokens de complétion, coût cloud implicite économisé — est affiché sur la tuile launchpad (ch61 Launchpad) et sur un graphique des 7 derniers jours sur une page adjacente aux avancements. La surface exacte est un choix ouvert de l’opérateur ; suivi comme point de suite.
L’export Grafana / Prometheus / K3s-style est différé. Le registre contient déjà les données ; un dashboard Grafana nécessiterait un bridge D1-vers-Prometheus sans valeur analytique supplémentaire à la v1. Réévaluation suivie comme Pivot 5a.
Pointer. → Principe 2, ch15 Principes de conception pour la logique de routage ; ch42 Ce à quoi l’IA peut accéder pour le schéma ledger_write ; ch55 D-188 (routage local-first), D-189 (compteur d’utilisation).
Quand le système escalade vers le cloud
Deux déclencheurs envoient une requête unique à Claude (Anthropic) plutôt qu’à la stack locale :
- L’encours du client dépasse 5 000 € — enjeux plus élevés, exigence plus haute pour le brouillon.
- Le modèle local signale un faible niveau de confiance dans sa propre réponse.
Dans les deux cas, l’escalade est automatique, journalisée et visible sur le dashboard des coûts. L’escalade vers le cloud utilise uniquement Claude, par conception. Aucun autre fournisseur cloud n’est branché.
Où tourne l’IA — et pourquoi
Faire tourner l’IA sur le Framework Desktop signifie que les données ne quittent jamais Gennevilliers. Les informations clients, les soldes AR, les détails des contrats et le contenu des emails restent dans le bâtiment. Le matériel est un coût unique que Sodimo a déjà absorbé ; l’usage quotidien de l’IA n’entraîne aucune facturation à la requête.
Objectif : 70 % des requêtes IA traitées en local. Le dashboard suit cela en temps réel.