guest@6admin.io:~$

opencode-watt: combien me coûte mon LLM local, en watts et en euros?

Tous les dashboards d’usage LLM local montrent des tokens par seconde et personne ne montre la facture d’électricité. Les gars de l’API ont un coût par requête; le homelab a “mon GPU existe quelque part sur la courbe de puissance”. opencode-watt (TypeScript, v0.2.0) est mon plugin pour OpenCode qui répond à la question que tout propriétaire de homelab d’inférence finit par poser à voix haute: combien ce modèle me coûte-t-il, en watts et en euros?

Le problème, ce sont les compteurs, pas les maths

Aucune gateway LLM n’expose la puissance par son API: llama.cpp, Ollama et vLLM rapportent des tokens, pas des joules. Les seules sources fiables sont les compteurs de puissance au niveau de l’hôte, donc le plugin les lit directement: nvidia-smi et rocm-smi pour les watts par GPU, les compteurs d’énergie RAPL de /sys/class/powercap pour le package CPU (avec depuis v0.2.0 la sous-zone dram d’Intel en compteur séparé: sur les longs contextes, c’est le cache KV qui bouffe le plus de joules), les power*_input génériques de hwmon pour ce qui en rapporte (amdgpu, nvme) et des requêtes instant-vector Prometheus /VictoriaMetrics pour les machines qui exportent des métriques (une requête DCGM_FI_DEV_GPU_POWER est le même compteur, portant une autre veste). Une option meters filtre par préfixe de source, pour les hôtes où deux compteurs rapportent le même device (AMD: rocm-smi et le hwmon amdgpu): on en garde un.

Chaque sonde dégrade honnêtement: un binaire absent rapporte absent, RAPL réservé au root rapporte denied, un hôte SSH injoignable backoff et retente (jusqu’à 60 s d’intervalle quand aucun compteur ne répond). Et un compteur qui plante ne sacrifie plus le tick entier: sa sonde rapporte error pour ce tick pendant que les autres continuent d’accumuler de l’énergie. La pire chose qu’un moniteur d’énergie puisse faire, c’est inventer une lecture.

La comptabilité: intégrer, soustraire, attribuer

La mécanique est un compteur d’énergie avec une conscience:

  • Intégrer pendant l’activité: échantillonnage toutes les interval_ms (1 s par défaut) pendant qu’une session génère, plus une courte queue après l’idle. Les intervalles sont mesurés entre les lectures elles-mêmes, pas entre les débuts de tick: un nvidia-smi lent ne rétrécit plus l’intervalle intégré.
  • Soustraire le plancher d’idle: une deuxième boucle d’échantillonnage (idle_interval_ms, 10 s) mesure ce que la machine tire en ne faisant rien et ce plancher est soustrait; le plancher est désormais la médiane de la fenêtre d’idle, plus la moyenne, pour qu’un tick pollué par un autre processus (un compile, un lancement d’app) ne gonfle plus le sous-sol. Un GPU à 25 W au repos n’est pas le coût de votre agent; facturer le plancher d’idle ferait de chaque session trois euros de culpabilité. Le plancher est aussi seedé au premier taux dérivable: une session qui démarre sur une machine déjà occupée ne se fait plus facturer toute la machine en attendant le premier tick d’idle.
  • Attribuer quand c’est concurrent: plusieurs sessions qui génèrent en même temps se partagent la puissance du device à parts égales et l’énergie attribuée à aucune session tombe quand même dans les totaux du jour. Les sessions supprimées sortent du busy set (session.deleted), pour qu’une session abandonnée n’absorbe plus l’énergie ni ne dilue la part des autres. Personne ne paie double, rien ne disparaît.

La tarification: là où est le matériel, pas là où est le chat

Le côté coût lit une table tarifaire par pays intégrée (moyennes de détail approximatives, début 2026), avec une surcharge optionnelle et des fenêtres heures pleines/creuses optionnelles: la config d’exemple tarife 22:00-06:00 au tarif HC et le coût est accumulé par tick au tarif résolu, pour qu’une session traversant une frontière de fenêtre somme correctement. Les cibles distantes déclarent leur propre country: l’énergie est tarifiée là où le matériel la consomme, y compris par SSH (ssh: [{host: "inference-box", country: "FR"}]); les sondes SSH passent -n -o BatchMode=yes (un prompt de passphrase ne peut plus bloquer un tick) et réutilisent une connexion via ControlMaster (ssh_control, vrai par défaut, control_master: false par cible pour couper).

Les surfaces: un tool power_cost (scope=session|today|all) montrant les watts par compteur, les kWh bruts et attribués, le coût, le tarif actif et les statuts des sondes, avec un format: "json" pour la donnée structurée (compteurs, tarif, baseline, session/jour/totaux, statuts, budget); un toast TUI en fin de session quand la session a brûlé plus de 1 Wh, parce que le feedback doit arriver au moment de la dépense; et un daily_budget_kwh qui prévient une fois par jour en toast quand l’énergie attribuée franchit le seuil. Un wall_factor (défaut 1, borné [1, 2]) prix le tirage mural au-dessus de l’énergie mesurée (les pertes du PSU et compagnie): le kWh reste mesuré, seul l’argent est scalé. L’état est un state.json écrit atomiquement, sessions purgées après 30 jours. Sous un hôte OpenCode 2.x, il n’existe pas de canal toast du serveur vers la TUI dans le contexte plugin, donc ces notifications tombent en console.info; le paquet sert v1 et v2 depuis une seule entrée.

Les non-goals sont la spécification

La section non-goals du README est la partie la plus honnête: puissance des NPU (pas d’interface Linux standard), sondes Windows/macOS, récupération des prix spot depuis le réseau, attribution par processus, intensité carbone. Chacune est un endroit où la mesure deviendrait silencieusement une estimation. Ce plugin mesure ce que le kernel et les drivers GPU rapportent vraiment; dès que je fabrique un chiffre, tout le plugin devient une vibe.

En watts et en euros

Au bout du compte, la leçon tient en deux lignes. Les gateways exposent des tokens, seul l’hôte expose des joules, la pile de compteurs doit vivre en dessous de la pile d’inférence. Le plancher d’idle se soustrait ou le chiffre devient de la propagande, la tarification est de la géographie, le kWh se price là où est le matériel. Et un watt fabriqué est un bug qui porte une unité, un statut honnête vaut mieux qu’une invention plausible, le kWh reste mesuré et l’argent seul se scale.