GreenIT
Master 2 · Polytech UCALab 2D. Lopez Pacheco

Une blague,
173 tokens.

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.

La règle du lab. Toujours limiter max_output_tokens en test, en se souvenant que le thinking compte dans la limite. Et ne jamais mettre une clé d'API dans le code.
7
tokens d'entrée
173
tokens de sortie facturés
91%
de la sortie est du thinking (158)
0,33 à 0,68Wh
énergie estimée par EcoLogits
En 30 secondes

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

Ce qu'on mesure

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 :

ENTRÉE

Tokens du prompt

Connus avant l'envoi : client.models.count_tokens(...). Ici 7.

RÉPONSE

Tokens visibles

usage_metadata.candidates_token_count. Ici 15 : la blague que l'utilisateur lit.

RAISONNEMENT

Tokens de thinking

usage_metadata.thoughts_token_count. Ici 158. Facturés comme de la sortie, jamais affichés.

Facturé par Google

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

Prefill : une passe. Decode : une passe par token.

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.

0 / 0
Prêt
Prompt (prefill)Thinking (decode, invisible)Réponse visible (decode)
ÉNERGIE

Une passe forward par token de sortie

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.

PRIX

Le tarif suit la physique

Entrée 0,50 $ par million de tokens, sortie 3,00 $, soit 6 fois plus. Et le prix de sortie inclut le thinking.

Nuance

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

Ce que coûte une requête

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.

Instrument Coût, énergie et carbone d'une requête Gemini
Coût de la requête -
Entrée (0,50 $/M)-
Sortie (3,00 $/M)-
Part du thinking-
Énergie estimée-
Pour 1 M de requêtes-
CO2eq, 1 M req, France-

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

Estimer l'impact sans le mesurer

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
POURQUOI UN INTERVALLE

min et max, pas une valeur

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.

POURQUOI LE DERNIER CHUNK

Impacts en fin de stream

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.

0,00033kWh
minimum estimé (0,335 Wh)
0,00068kWh
maximum estimé (0,678 Wh)
2x
largeur de l'intervalle : l'incertitude fait partie du résultat

05 · Le code du lab

Le script, annoté

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
Piège de sécurité

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

Les neuf étapes

Q1-Q2

Mettre en place et le piège du thinking

Clé, SDK, streaming limité en tokens.

▸

1 Mise en place

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.

2 Streaming avec limite de tokens

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! »

Piège

Le thinking est compté dans max_output_tokens. Limiter pour économiser le quota gratuit peut donc casser la réponse sans erreur.

Q3-Q4

EcoLogits et énergie

Installation, récupération de min et max.

▸

3 Installation

pip install ecologits[google-genai] : l'extra tire le support du fournisseur Gemini.

4a Où est le texte ?

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.

4b L'énergie

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.

Q5

Prix de la requête

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) ?

PosteCalculCoût
Entrée7 x 0,50 / 1 000 0000,0000035 $
Sortie173 x 3,00 / 1 000 0000,000519 $
Total0,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.

Q6

Énergie des tokens d'entrée et de sortie

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.

Q7

Compter les tokens

Entrée avant envoi, sortie en fin de stream.

▸

a Entrée

tot_tokens = client.models.count_tokens(model="gemini-3-flash-preview", contents=quest)
print(tot_tokens.total_tokens)      # 7

b Sortie

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.

Q8

Profils de tokens

Des applications à entrée courte et sortie longue, et l'inverse.

▸

a Entrée courte, sortie longue

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.

b Entrée longue, sortie courte

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.

c Ce que ça change

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.

Attention

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.

Q9

Envoyer min et max au serveur du lab

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.

Bonus

Du kWh au CO2eq, et à l'échelle

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) ?

1 Énergie totale

0,335 Wh x 1 000 000 = 335 kWh ; 0,678 Wh x 1 000 000 = 678 kWh.

2 Émissions = E x I

PaysMinimumMaximum
France (56 g/kWh)335 x 56 = 18,8 kg678 x 56 = 38,0 kg
Allemagne (381 g/kWh)335 x 381 = 127,6 kg678 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

QCM et cartes mémo

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.

Prêt

Pool de 22 questions. Tire un échantillon au hasard, correction immédiate et explication à chaque réponse.

Cartes mémo

Tokens
Les trois compteurs ?
clique pour la réponse ↻
Tokens
Entrée (count_tokens, avant envoi), candidates_token_count (visible), thoughts_token_count (thinking). Sortie facturée = visible + thinking.
Thinking
Le piège de max_output_tokens ?
clique pour la réponse ↻
Thinking
Le thinking compte dedans. À 100 la réponse est tronquée, à 512 tout tient.
Thinking
Chiffres de la blague ?
clique pour la réponse ↻
Thinking
7 tokens en entrée, 173 en sortie = 15 visibles + 158 de thinking (91 %).
Prefill / decode
Prefill ?
clique pour la réponse ↻
Prefill / decode
Le prompt traité en une passe parallèle.
Prefill / decode
Decode ?
clique pour la réponse ↻
Prefill / decode
La sortie générée un token à la fois, une passe forward complète par token, chacun dépend des précédents.
Prefill / decode
Énergie entrée vs sortie ?
clique pour la réponse ↻
Prefill / decode
Un token de sortie déclenche sa propre passe, N tokens d'entrée en partagent une : la sortie coûte bien plus, en énergie et en prix.
Prix
Tarif gemini-3-flash-preview ?
clique pour la réponse ↻
Prix
Entrée 0,50 $, sortie 3,00 $ par million de tokens (thinking inclus).
Prix
Coût de la blague ?
clique pour la réponse ↻
Prix
0,000522 $, dont 99 % pour la sortie. Un million de requêtes : ~522 $.
EcoLogits
Initialisation ?
clique pour la réponse ↻
EcoLogits
EcoLogits.init(providers=["google_genai"]) avant de créer le client (monkey-patch).
EcoLogits
Où lire l'énergie ?
clique pour la réponse ↻
EcoLogits
Sur le dernier chunk : chunk.impacts.energy.value.min et .max, en kWh.
EcoLogits
Pourquoi min et max ?
clique pour la réponse ↻
EcoLogits
Architecture du modèle non publiée et multimodal : incertitude propagée. Ici 0,335 à 0,678 Wh.
Profils
Entrée courte, sortie longue ?
clique pour la réponse ↻
Profils
Génération : nouvelle, boilerplate de code, expansion d'un plan.
Profils
Entrée longue, sortie courte ?
clique pour la réponse ↻
Profils
Classification, extraction : contrat de 40 pages avec question oui/non, sentiment, résumé d'une phrase.
Carbone
1 M de requêtes en France ?
clique pour la réponse ↻
Carbone
0,335 à 0,678 Wh chacune = 335 à 678 kWh, x 56 g = 18,8 à 38 kg CO2eq. En Allemagne x6,8.
Sécurité
Clé d'API ?
clique pour la réponse ↻
Sécurité
Jamais dans le code : .env gitignoré + --env-file + .env.example. Clé fuitée = la révoquer.