On demande « Tell me a short joke » à Gemini et on mesure : 7 tokens en entrée, 173 en sortie dont 158 de raisonnement invisible, et une énergie estimée par EcoLogits. Le lab montre pourquoi la sortie coûte bien plus que l'entrée.
Un LLM lit le prompt en une passe parallèle (prefill) mais génère la réponse token par token (decode), une passe complète par token. La sortie coûte donc bien plus, en énergie et en prix (3,00 $ contre 0,50 $ par million). Les modèles à raisonnement ajoutent des tokens de thinking facturés comme de la sortie et jamais affichés : sur une blague, 158 sur 173. EcoLogits estime l'énergie par requête sous forme d'un intervalle min/max. Le prix et l'énergie d'une application se déduisent de son profil de tokens.
01 · Le décor
Un token est un morceau de texte (un mot court, une partie de mot, un signe). Les modèles lisent et écrivent en tokens, et les API facturent en tokens. Une requête a trois compteurs :
Connus avant l'envoi : client.models.count_tokens(...). Ici 7.
usage_metadata.candidates_token_count. Ici 15 : la blague que l'utilisateur lit.
usage_metadata.thoughts_token_count. Ici 158. Facturés comme de la sortie, jamais affichés.
Tokens de sortie facturés = visibles + thinking = 15 + 158 = 173. En streaming ces compteurs évoluent avec les chunks : on garde la dernière valeur vue.
02 · Pourquoi la sortie coûte plus
Un LLM est autorégressif : chaque token produit dépend de tous les précédents. Le prompt, lui, est connu d'avance : on peut le traiter tout entier en parallèle. La génération, elle, ne peut pas être parallélisée : elle avance un token à la fois.
Chaque token produit déclenche une passe complète du modèle. Les N tokens du prompt se partagent une passe parallélisable. Générer une longue réponse pèse plus qu'envoyer un long prompt.
Entrée 0,50 $ par million de tokens, sortie 3,00 $, soit 6 fois plus. Et le prix de sortie inclut le thinking.
Le prefill n'est pas gratuit : un prompt de 30 000 tokens coûte de l'énergie (l'attention grandit avec la longueur). La règle « sortie plus chère que l'entrée » vaut token pour token, pas en absolu.
03 · Prix et énergie
Entre les nombres de tokens d'une requête (ou choisis un profil) et lis le coût, l'énergie estimée et le carbone. L'énergie est extrapolée depuis la mesure du lab : 0,335 à 0,678 Wh pour 173 tokens de sortie, soit 1,9 à 3,9 mWh par token de sortie. L'entrée est ignorée dans ce modèle simple.
Les profils « assistant », « nouvelle » et « contrat » sont des ordres de grandeur d'énoncé, pas des mesures. Le carbone utilise 56 g/kWh (France, moyenne annuelle, cf. Lab 3). Modèle d'énergie proportionnel aux seuls tokens de sortie : il sous-estime les très longs prompts.
04 · EcoLogits
EcoLogits est une bibliothèque qui estime l'énergie, le carbone et l'impact d'une requête d'IA générative. Elle s'accroche au client du fournisseur par monkey-patching : une fois initialisée, chaque réponse porte un attribut impacts.
EcoLogits.init(providers=["google_genai"]) # AVANT de créer le client client = genai.Client(api_key=os.environ["GEMINI_API_KEY"]) for chunk in stream: try: answer += chunk.candidates[0].content.parts[0].text # plus chunk.text except (AttributeError, IndexError, TypeError): pass # chunks sans texte if getattr(chunk, "impacts", None) is not None: # dernier chunk energy_min = chunk.impacts.energy.value.min # en kWh energy_max = chunk.impacts.energy.value.max
L'architecture de gemini-3-flash-preview n'est pas publiée et le modèle est multimodal : EcoLogits le signale par deux warnings (« model architecture has not been released », « multimodal, expect lower precision »). Il propage l'incertitude dans l'intervalle.
L'énergie dépend du nombre total de tokens générés, connu seulement à la fin. Les chunks intermédiaires n'ont pas d'attribut impacts, d'où le getattr.
05 · Le code du lab
Version finale de gemini_energy.py avec les compteurs de tokens et l'envoi au serveur du lab. La clé et le token viennent de variables d'environnement, jamais du code.
EcoLogits.init(providers=["google_genai"]) client = genai.Client(api_key=os.environ["GEMINI_API_KEY"]) MODEL = "gemini-3-flash-preview"; quest = "Tell me a short joke." # Q7a : tokens d'entrée, connus avant l'envoi input_tokens = client.models.count_tokens(model=MODEL, contents=quest).total_tokens stream = client.models.generate_content_stream( model=MODEL, contents=quest, config=types.GenerateContentConfig(max_output_tokens=512)) # thinking inclus ! for chunk in stream: ... # texte + impacts, voir plus haut um = getattr(chunk, "usage_metadata", None) if um is not None: # Q7b : garder la dernière valeur candidates_tok = um.candidates_token_count or 0 thoughts_tok = um.thoughts_token_count or 0 output_tokens = candidates_tok + thoughts_tok # ce que Google facture # Q9 : envoi de min et max au serveur du lab requests.post(os.environ["AIENERGY_URL"], json={"min": energy_min, "max": energy_max}, headers={"Authorization": "Bearer " + os.environ["AIENERGY_TOKEN"]}, timeout=10)
docker build -t greenit-lab2 . docker run --rm --env-file .env -v "$PWD":/tp greenit-lab2 python3 gemini_energy.py
Une clé d'API écrite dans un script finit tôt ou tard dans un dépôt, une capture, un partage. Le lab utilise un fichier .env ignoré par git (.gitignore), passé au conteneur par --env-file, avec un .env.example sans secret pour le modèle. Si une clé a été committée : la révoquer et en régénérer une, l'effacer de l'historique ne suffit pas. Sur le serveur du lab, le certificat est auto-signé : désactiver la vérification TLS (verify=False) est tolérable en TP, jamais en production.
06 · Questions du lab corrigées
Clé, SDK, streaming limité en tokens.
Créer une clé sur Google AI Studio, l'exposer dans la variable d'environnement GEMINI_API_KEY (recommandation de la doc : pas dans le code), installer google-genai. Modèle imposé : gemini-3-flash-preview au lieu de gemini-3.5-flash du quickstart.
config=types.GenerateContentConfig(max_output_tokens=512)
Avec une limite trop basse, par exemple 100, le modèle consomme tout son budget en raisonnement et la réponse visible est tronquée (« Why don' »). À 512, thinking et réponse tiennent : « Why don't scientists trust atoms? Because they make up everything! »
Le thinking est compté dans max_output_tokens. Limiter pour économiser le quota gratuit peut donc casser la réponse sans erreur.
Installation, récupération de min et max.
pip install ecologits[google-genai] : l'extra tire le support du fournisseur Gemini.
Avec EcoLogits, la réponse n'est plus à chunk.text mais à chunk.candidates[0].content.parts[0].text. Certains chunks (métadonnées, impacts) n'ont pas de texte : d'où le try/except.
Quand chunk.impacts existe (dernier chunk) : chunk.impacts.energy.value.min et .max, en kWh. Mesuré : min 0,00033474 kWh, max 0,00067834 kWh, soit 0,335 à 0,678 Wh.
Calcul du coût de la blague.
Énoncé. Prix de gemini-3-flash-preview (paid tier) : entrée 0,50 $ par million de tokens, sortie 3,00 $ par million (thinking inclus). Quel est le coût de la blague (7 tokens en entrée, 173 en sortie) ?
| Poste | Calcul | Coût |
|---|---|---|
| Entrée | 7 x 0,50 / 1 000 000 | 0,0000035 $ |
| Sortie | 173 x 3,00 / 1 000 000 | 0,000519 $ |
| Total | 0,000522 $ (0,052 cent) |
La sortie pèse 99 % du coût. Pour un million de requêtes identiques : environ 522 $. Sans le thinking (15 tokens visibles), la sortie ne coûterait que 0,000045 $ : le raisonnement invisible multiplie la facture de sortie par 11.
Expliquer la différence.
Les tokens d'entrée sont traités en une passe parallèle (prefill) : tout le prompt est encodé d'un coup. Les tokens de sortie sont générés un par un (decode) : une passe forward complète par token, chacun dépendant de tous les précédents.
Un token de sortie coûte donc nettement plus qu'un token d'entrée, parce qu'il déclenche sa propre passe d'inférence alors que N tokens d'entrée se partagent une passe parallélisable. C'est pourquoi le prix de sortie (3,00 $) dépasse celui d'entrée (0,50 $), et pourquoi générer de longues réponses pèse plus, en énergie comme en coût, que fournir de longs prompts.
Entrée avant envoi, sortie en fin de stream.
tot_tokens = client.models.count_tokens(model="gemini-3-flash-preview", contents=quest) print(tot_tokens.total_tokens) # 7
Tokens visibles : chunk.usage_metadata.candidates_token_count (15). Tokens de raisonnement : chunk.usage_metadata.thoughts_token_count (158). Le total facturé par Google est la somme : 173. Ces nombres évoluent au fil des chunks : on sauvegarde la dernière valeur.
Le thinking représente 158 / 173 = 91 % de la sortie : sur ce modèle, l'essentiel du coût et de l'énergie d'une réponse courte part dans le raisonnement, pas dans le texte rendu.
Des applications à entrée courte et sortie longue, et l'inverse.
Génération de contenu. « Écris une nouvelle de 2000 mots sur un robot » (prompt d'une dizaine de tokens, réponse d'environ 3000). Autres exemples : boilerplate de code depuis une phrase, expansion d'un plan en texte.
Classification et extraction. « Voici un contrat de 40 pages : est-il résiliable sans préavis ? Oui ou non » (prompt énorme, réponse d'un mot). Autres exemples : analyse de sentiment d'un long document, résumé en une phrase, recherche d'une valeur dans un gros log.
Utilise la calculatrice avec ces deux profils : la nouvelle consomme environ 17 fois plus d'énergie estimée que la blague (3000 tokens de sortie contre 173), alors que le contrat, malgré ses 30 000 tokens d'entrée, coûte surtout par son entrée. Le profil de tokens décide de la facture.
Le modèle d'énergie de la calculatrice ne compte que la sortie : pour le contrat il sous-estime. Le prefill de 30 000 tokens a un coût réel.
POST JSON avec jeton bearer.
requests.post(url,
json={"min": energy_min, "max": energy_max}, # ex. {"min": 0.001, "max": 0.003}
headers={"Authorization": f"Bearer {token}"},
timeout=10)
L'URL et le jeton bearer sont fournis par l'encadrant et lus dans AIENERGY_URL et AIENERGY_TOKEN. Tant que le vrai serveur n'est pas joignable, on valide le chemin contre https://httpbin.org/post avec un jeton factice : httpbin renvoie le corps JSON reçu, ce qui prouve que le format est correct.
Relier ce lab à l'équation du cours.
Énoncé. Une requête consomme entre 0,335 et 0,678 Wh. Combien de CO2eq pour un million de requêtes en France (56 g/kWh), puis en Allemagne (381 g/kWh) ?
0,335 Wh x 1 000 000 = 335 kWh ; 0,678 Wh x 1 000 000 = 678 kWh.
| Pays | Minimum | Maximum |
|---|---|---|
| France (56 g/kWh) | 335 x 56 = 18,8 kg | 678 x 56 = 38,0 kg |
| Allemagne (381 g/kWh) | 335 x 381 = 127,6 kg | 678 x 381 = 258,3 kg |
Même requête, même modèle : un facteur 6,8 entre les deux pays. Et l'incertitude d'EcoLogits (un facteur 2 entre min et max) est du même ordre que l'effet du lieu : on ne peut pas ignorer l'un ou l'autre.
07 · Réviser
Le QCM tire un échantillon au hasard dans le pool ; chaque réponse est corrigée et expliquée.
Entraîneur Pour un QCM qui s'adapte à ton niveau, revient sur tes erreurs et explique chaque réponse avec une fiche illustrée : s'entraîner sur ce chapitre.
Pool de 22 questions. Tire un échantillon au hasard, correction immédiate et explication à chaque réponse.