AI e Carbonio: Misurare e Ridurre il Footprint del ML Training
Il training di GPT-4 ha consumato circa 50 GWh di energia elettrica, sufficiente a alimentare oltre 4.600 abitazioni europee per un intero anno. GPT-3, il suo predecessore, ha prodotto 502 tonnellate di CO₂ equivalente durante il training - l'equivalente di 112 automobili a benzina che percorrono 20.000 km all'anno ciascuna. BLOOM, il modello open-source da 176 miliardi di parametri addestrato in Francia su energia mista, ha emesso 25 tonnellate di CO₂eq grazie all'utilizzo prevalente di energia nucleare e rinnovabile.
Queste non sono curiosità statistiche: sono segnali di un problema strutturale in rapida accelerazione. L'intelligenza artificiale generativa cresce a ritmi esponenziali, con modelli sempre più grandi, dataset sempre più vasti e cicli di training sempre più frequenti. Secondo uno studio pubblicato su Nature nel 2024, le emissioni legate al training di modelli AI potrebbero triplicare entro il 2027 se non vengono adottate misure di efficienza sistemiche. L'IEA stima che i data center dedicati all'AI nel 2026 consumeranno più energia di molti stati europei di medie dimensioni.
Eppure la stragrande maggioranza dei team ML avvia training su cluster GPU senza mai misurare le emissioni prodotte. Non perché non gliene importi, ma perché mancano strumenti integrati nel workflow quotidiano, metriche standard condivise e cultura della misurazione. In questo articolo finale della serie Green Software Engineering esploreremo l'intero ciclo di vita del carbon footprint nel Machine Learning: dalla misurazione precisa con CodeCarbon e CarbonTracker, all'ottimizzazione architetturale con pruning e distillazione, fino al carbon-aware training scheduling e all'ottimizzazione dell'inferenza in produzione.
Cosa Imparerai
- Dati reali di emissioni per modelli fondazionali: GPT-3, GPT-4, Llama 2, BLOOM, PaLM
- Installare e usare CodeCarbon, CarbonTracker, ML CO₂ Impact e Green Algorithms
- Confrontare GPU per efficienza energetica: A100, H100, H200, B200 in FLOPS/Watt
- Tecniche di ottimizzazione architetturale: pruning, knowledge distillation, NAS efficiente
- Data-centric efficiency: qualità vs quantità dei dati, curriculum learning
- Carbon-aware training: schedulare workload in regioni e orari a bassa intensità carbonica
- Ottimizzazione inferenza: quantizzazione INT8/INT4, ONNX Runtime, TensorRT, vLLM
- Calcolare SCI Score per modelli ML e costruire un Green AI Leaderboard
- Caso studio completo: riduzione 60% emissioni su un modello NLP reale
- Roadmap pratica per adottare Green AI nelle organizzazioni
Serie Green Software Engineering - 10 Articoli
| # | Articolo | Tema Principale |
|---|---|---|
| 1 | Principi Green Software Foundation | GSF, SCI, 8 principi fondamentali |
| 2 | Misurare l'Impronta Carbonica con CodeCarbon | CodeCarbon, CCF, CI/CD integration |
| 3 | Climatiq API e Carbon Accounting | API REST, LCA, Scope 1-2-3 |
| 4 | Carbon-Aware SDK e Temporal/Spatial Shifting | Spostare workload verso energia verde |
| 5 | Scope Modeling e SCI Implementation | Calcolo SCI pratico, Impact Framework |
| 6 | GreenOps: FinOps per la Sostenibilità | Cloud cost e carbon, Kubernetes |
| 7 | Scope 3 nella Pipeline Software | Emissioni indirette, supply chain digitale |
| 8 | ESG, CSRD e Reporting Digitale | Compliance europea, ESRS E1, disclosure |
| 9 | Pattern Architetturali Sostenibili | Storage, caching, batch carbon-aware |
| 10 | AI e Carbonio: Footprint del ML Training | Questo articolo - articolo finale della serie |
Il Carbon Footprint Reale dei Modelli Fondazionali
Comprendere l'entità del problema richiede dati concreti. I ricercatori di Stanford, Hugging Face e MLCommons hanno iniziato a pubblicare metriche di emissioni per i principali modelli linguistici. La tabella seguente aggrega i dati disponibili, tenendo presente che molte aziende - in particolare OpenAI - non divulgano ancora queste informazioni.
Emissioni CO₂eq di Modelli Fondazionali durante il Training
| Modello | Parametri | Energia (MWh) | CO₂eq (t) | Regione/Mix | Fonte |
|---|---|---|---|---|---|
| GPT-3 | 175B | 1.287 | 502 | USA (mix medio) | Patterson et al. 2021 |
| GPT-4 | ~1.8T (est.) | ~50.000 | ~14.000 | USA (stima) | Stime indipendenti 2023 |
| BLOOM | 176B | 433 | 25 | Francia (nucleare+rinnovabile) | Luccioni et al. 2022 |
| Llama 2 (70B) | 70B | ~700 | 539 | USA/Azure | Meta AI 2023 |
| PaLM | 540B | ~3.400 | ~507 | USA (TPU v4) | Chowdhery et al. 2022 |
| Mistral 7B | 7B | ~35 | ~12 | Europa (mix) | Mistral AI 2023 |
| Falcon 180B | 180B | ~600 | ~117 | UAE (mix) | TII 2023 |
| Gemma 7B | 7B | ~32 | ~6 | USA/TPU (RE100) | Google DeepMind 2024 |
Questi dati rivelano tre pattern fondamentali. Il primo è il divario geografico: BLOOM, addestrato in Francia con energia prevalentemente nucleare, ha emesso 20 volte meno di Llama 2 a parità di ordine di grandezza. Il secondo è l'effetto scala: passare da 7B a 176B parametri non significa semplicemente 25x più emissioni - i costi di comunicazione distribuita e la lunghezza del training crescono in modo non lineare. Il terzo è il gap di trasparenza: la maggior parte delle aziende non pubblica ancora questi dati, rendendo la comparazione difficile e il benchmarking impossibile senza misurazioni indipendenti.
Il Paradosso dell'Inferenza: Più Grande del Training
Controintuitivamente, le emissioni cumulative dell'inferenza superano spesso quelle del training nel ciclo di vita di un modello. Un modello come ChatGPT, con milioni di richieste al giorno, può consumare in un mese di produzione più energia dell'intero training originale. Patterson et al. stimano che l'inferenza di GPT-3 su scala ChatGPT produca circa 11.000 tonnellate di CO₂eq all'anno. Questo sposta il focus dall'ottimizzazione del training all'ottimizzazione dell'inferenza come priorità primaria per ridurre l'impatto ambientale totale.
Emissioni Embodied e Ciclo di Vita Completo
L'analisi del carbon footprint dell'AI non può limitarsi all'energia consumata durante il training. Le emissioni embodied dell'hardware - la CO₂ emessa per produrre GPU, server, reti di raffreddamento e infrastruttura - rappresentano una quota significativa. La produzione di un singolo chip H100 emette circa 150 kgCO₂eq, e un cluster da 1.000 GPU porta a emissioni embodied di circa 150 tonnellate solo per l'hardware, prima ancora di avviare un singolo training job.
# Formula Carbon Footprint Completo per ML
# Basato su Luccioni et al. "Counting Carbon" (2023)
Total_CO2eq = Training_CO2eq + Inference_CO2eq + Embodied_CO2eq
Training_CO2eq = Energy_training (kWh) x Carbon_Intensity (kgCO2eq/kWh) x PUE
Inference_CO2eq = (Requests x Latency x GPU_Power / 3600000) x Carbon_Intensity x PUE
Embodied_CO2eq = (Hardware_Production_CO2eq / Lifespan_hours) x Training_hours
# Esempio: modello NLP medio (BERT-Large fine-tuning su task classificazione)
Energy_training = 120 kWh # 4x A100, 6 ore
Carbon_Intensity = 0.420 kg/kWh # mix europeo medio
PUE = 1.3 # data center tipico
Training_CO2eq = 120 x 0.420 x 1.3 = 65.5 kgCO2eq
# Inferenza: 10M richieste/mese, 50ms each, 1x A100 (300W)
Inference_monthly = (10_000_000 x 0.05 x 300 / 3_600_000) x 0.420 x 1.3
= 41.7 kWh x 0.420 x 1.3 = 22.7 kgCO2eq/mese
# Dopo 12 mesi: 272 kgCO2eq inferenza vs 65 kgCO2eq training
# L'inferenza domina del 77% nel ciclo di vita annuale
Strumenti di Misurazione: da CodeCarbon a Green Algorithms
Misurare le emissioni ML richiede strumenti progettati specificamente per i workload di training e inferenza. Quattro strumenti dominano lo spazio: CodeCarbon, CarbonTracker, ML CO₂ Impact Calculator e Green Algorithms. Ognuno ha punti di forza specifici e casi d'uso ottimali.
CodeCarbon: Integrazione Diretta nel Codice Python
CodeCarbon è lo strumento più diffuso per misurare le emissioni durante il training ML. La sua integrazione con i framework più comuni (PyTorch, TensorFlow, Hugging Face Transformers) lo rende la scelta naturale per la maggior parte dei team.
# pip install codecarbon
from codecarbon import EmissionsTracker, track_emissions
import torch
from transformers import Trainer, TrainingArguments
# Metodo 1: Context manager
tracker = EmissionsTracker(
project_name="bert-finetuning-sentiment",
country_iso_code="ITA",
region="lombardy",
cloud_provider="gcp",
cloud_region="europe-west8",
output_dir="./carbon_reports",
output_file="emissions.csv",
log_level="warning",
save_to_file=True,
tracking_mode="machine", # Legge sensori hardware reali
measure_power_secs=15 # Campionamento ogni 15 secondi
)
tracker.start()
try:
# Il tuo training loop qui
model = train_model(dataset, epochs=10)
finally:
emissions = tracker.stop()
print(f"Training completato: {emissions:.4f} kgCO2eq")
print(f"Equivalente a: {emissions * 2.4:.2f} km in auto")
# Metodo 2: Decorator per funzioni singole
@track_emissions(
project_name="bert-inference",
country_iso_code="ITA",
save_to_file=True
)
def run_inference_batch(model, dataloader):
results = []
with torch.no_grad():
for batch in dataloader:
outputs = model(**batch)
results.extend(outputs.logits.argmax(-1).tolist())
return results
# Metodo 3: Integrazione con Hugging Face Trainer
from codecarbon import EmissionsTracker
class CarbonAwareTrainer(Trainer):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.carbon_tracker = EmissionsTracker(
project_name=f"hf-training-{self.args.run_name}",
country_iso_code="ITA",
save_to_file=True
)
def train(self, *args, **kwargs):
self.carbon_tracker.start()
result = super().train(*args, **kwargs)
emissions = self.carbon_tracker.stop()
self.log({"carbon_emissions_kgCO2eq": emissions})
return result
CarbonTracker: Proiezioni e Budgeting
CarbonTracker, sviluppato dall'Università di Copenaghen, si distingue per la capacità di proiettare il consumo totale basandosi sulle prime epoche di training. Questa funzionalità è preziosissima: permette di stimare il costo carbonico prima di completare un training che dura giorni o settimane.
# pip install carbontracker
from carbontracker.tracker import CarbonTracker
from carbontracker import parser
# Configurazione con proiezione
tracker = CarbonTracker(
epochs=100,
epochs_before_pred=5, # Dopo 5 epoche, proietta le 95 rimanenti
monitor_epochs=10, # Monitora ogni 10 epoche
log_dir="./carbontracker_logs",
update_interval=10,
verbose=2,
components="gpu",
ignore_errors=False
)
for epoch in range(100):
tracker.epoch_start()
# Training epoch
for batch in train_loader:
optimizer.zero_grad()
outputs = model(batch['input_ids'], batch['attention_mask'])
loss = criterion(outputs, batch['labels'])
loss.backward()
optimizer.step()
tracker.epoch_end()
tracker.stop()
# Dopo 5 epoche, CarbonTracker stampa proiezioni come:
# "Predicting training to use 2.34 kWh and emit 0.98 kgCO2eq"
# "Total: actual consumption: 0.12 kWh, 0.05 kgCO2eq (after 5 epochs)"
# Parsing dei log per analisi
logs = parser.parse_all_logs(log_dir="./carbontracker_logs")
for log in logs:
print(f"Energia: {log['actual']['energy (kWh)']:.3f} kWh")
print(f"CO2: {log['actual']['co2eq (g)']:.1f} gCO2eq")
print(f"Proiezione totale: {log['pred']['co2eq (g)']:.1f} gCO2eq")
ML CO₂ Impact Calculator e Green Algorithms
ML CO₂ Impact Calculator (mlco2.github.io) è uno strumento web che stima le emissioni basandosi su hardware, cloud provider, regione e durata. Non richiede integrazione nel codice: è ideale per stime rapide pre-training o per documentare le emissioni di training già completati. Supporta AWS, GCP, Azure e 15+ regioni.
Green Algorithms (green-algorithms.org), sviluppato dall'Università di Cambridge, offre un approccio più scientifico con modelli di consumo energetico validati su dati reali di data center. Supporta calcoli per HPC (High Performance Computing), GPU cluster e workflow bioinformatici.
Confronto Strumenti di Misurazione ML Carbon
| Strumento | Tipo | Granularità | Integrazione | Punto di Forza | Limitazione |
|---|---|---|---|---|---|
| CodeCarbon | Python lib | Funzione/job | PyTorch, TF, HF | Misurazione hardware reale | Solo Python, overhead <1% |
| CarbonTracker | Python lib | Per epoca | Deep learning loops | Proiezioni early-stop | Richiede epoch-based training |
| ML CO₂ Impact | Web tool | Job level | Nessuna (web form) | Zero setup, stima rapida | Solo stima, non misurazione |
| Green Algorithms | Web tool | Job level | Nessuna (web form) | Modelli validati scientificamente | Non real-time |
| Experiment Impact Tracker | Python lib | Esperimento | ML experiments | Logging automatico MLflow | Meno mantenuto (2021) |
Dashboard e Reporting Automatizzato
La misurazione ha valore solo se i dati vengono aggregati, visualizzati e usati per prendere decisioni. Un pattern efficace è integrare CodeCarbon con MLflow per creare un sistema di tracking automatico che registra emissioni insieme alle metriche di performance.
import mlflow
from codecarbon import EmissionsTracker
import pandas as pd
from pathlib import Path
class GreenMLExperiment:
"""Wrapper che traccia automaticamente emissioni + performance ML."""
def __init__(self, experiment_name: str, country_code: str = "ITA"):
self.experiment_name = experiment_name
self.country_code = country_code
mlflow.set_experiment(experiment_name)
def run(self, model_fn, train_fn, eval_fn, params: dict):
with mlflow.start_run():
mlflow.log_params(params)
# Avvia tracker emissioni
tracker = EmissionsTracker(
project_name=self.experiment_name,
country_iso_code=self.country_code,
save_to_file=True,
output_dir="./emissions_logs"
)
tracker.start()
model = model_fn(**params)
train_metrics = train_fn(model)
emissions_kg = tracker.stop()
# Metriche performance
eval_metrics = eval_fn(model)
# Log tutto su MLflow
mlflow.log_metric("train_loss", train_metrics["loss"])
mlflow.log_metric("eval_accuracy", eval_metrics["accuracy"])
mlflow.log_metric("emissions_kgCO2eq", emissions_kg)
mlflow.log_metric("emissions_grams", emissions_kg * 1000)
mlflow.log_metric(
"accuracy_per_kgCO2",
eval_metrics["accuracy"] / max(emissions_kg, 0.001)
)
# Log artefatti
mlflow.log_artifacts("./emissions_logs")
return {
"model": model,
"emissions": emissions_kg,
"metrics": eval_metrics,
"efficiency_ratio": eval_metrics["accuracy"] / emissions_kg
}
# Utilizzo
experiment = GreenMLExperiment("bert-sentiment-v2", country_code="ITA")
result = experiment.run(
model_fn=create_bert_model,
train_fn=train_epoch,
eval_fn=evaluate_model,
params={"lr": 2e-5, "batch_size": 32, "epochs": 5}
)
Efficienza Energetica delle GPU: FLOPS per Watt
La scelta dell'hardware è la decisione più impattante per il carbon footprint del training. Le GPU di nuova generazione offrono efficienze energetiche nettamente superiori rispetto ai predecessori, e la differenza tra addestrare su A100 vs H200 può significare una riduzione del 40-60% delle emissioni a parità di workload.
Confronto GPU per ML Training: Efficienza Energetica (2025)
| GPU | Anno | TDP (W) | FP16 TFLOPS | FLOPS/Watt | Memoria HBM | CO₂ Relativo |
|---|---|---|---|---|---|---|
| A100 SXM4 | 2020 | 400W | 312 | 780 GFLOPS/W | 80 GB HBM2e | Baseline (1.0x) |
| H100 SXM5 | 2022 | 700W | 989 | 1.413 GFLOPS/W | 80 GB HBM3 | 0.56x (-44%) |
| H200 SXM5 | 2024 | 700W | 989 | 1.413 GFLOPS/W | 141 GB HBM3e | 0.56x (-44%) |
| B200 SXM6 | 2025 | 1.000W | 4.500 | 4.500 GFLOPS/W | 192 GB HBM3e | 0.17x (-83%) |
| RTX 4090 | 2022 | 450W | 165 | 367 GFLOPS/W | 24 GB GDDR6X | 2.12x (+112%) |
| AMD MI300X | 2024 | 750W | 1.307 | 1.743 GFLOPS/W | 192 GB HBM3 | 0.45x (-55%) |
L'H100 e l'H200 offrono circa il 44% di riduzione delle emissioni rispetto all'A100 per lo stesso workload, grazie al Transformer Engine con FP8 e all'architettura NVLink ottimizzata. Il B200 rappresenta un salto generazionale con 4.5 PFLOPS FP16 e supporto nativo FP4, portando la riduzione all'83%.
Mixed Precision Training: FP16 e BF16
Il training in mixed precision (FP16 o BF16 per i calcoli, FP32 per i parametri del modello) riduce il consumo energetico del 30-50% rispetto al training full FP32, senza degrado significativo della qualità del modello.
import torch
from torch.cuda.amp import autocast, GradScaler
# Training con AMP (Automatic Mixed Precision)
scaler = GradScaler()
def train_epoch_amp(model, dataloader, optimizer, device):
model.train()
total_loss = 0.0
for batch in dataloader:
input_ids = batch['input_ids'].to(device)
labels = batch['labels'].to(device)
optimizer.zero_grad()
# Forward pass in FP16/BF16
with autocast(dtype=torch.bfloat16): # BF16 su A100/H100
outputs = model(input_ids=input_ids, labels=labels)
loss = outputs.loss
# Backward pass con gradient scaling
scaler.scale(loss).backward()
scaler.unscale_(optimizer)
# Gradient clipping per stabilità
torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm=1.0)
scaler.step(optimizer)
scaler.update()
total_loss += loss.item()
return total_loss / len(dataloader)
# Con Hugging Face: basta un flag
from transformers import TrainingArguments
training_args = TrainingArguments(
output_dir="./results",
bf16=True, # BF16 per A100/H100 (preferibile a FP16)
fp16=False,
dataloader_num_workers=4,
per_device_train_batch_size=32,
gradient_accumulation_steps=4, # Simula batch da 128
gradient_checkpointing=True, # -40% memoria VRAM, +20% tempo
# Risparmio energetico: meno transfer GPU-CPU
dataloader_pin_memory=True
)
# Benchmark: BERT-Large su 1M esempi
# FP32: 6.2 ore, 148 kWh, 62 kgCO2eq (ITA)
# BF16: 3.4 ore, 81 kWh, 34 kgCO2eq (ITA) -> -45% emissioni
Ottimizzazione Architetturale: Pruning, Distillazione e NAS
Le tecniche di ottimizzazione architetturale riducono la complessità del modello, portando a training più veloci, inferenza più efficiente e carbon footprint complessivamente ridotto. Le tre tecniche principali sono pruning, knowledge distillation e Neural Architecture Search efficiente.
Model Pruning: Rimuovere la Ridondanza
Il pruning elimina parametri o strutture del modello che contribuiscono marginalmente all'accuratezza. Esistono due approcci principali: unstructured pruning (singoli pesi) e structured pruning (teste di attenzione, neuroni, layer). Il secondo è preferibile per efficienza hardware reale, poiché l'unstructured pruning richiede hardware con supporto sparse per beneficiare davvero.
import torch
import torch.nn.utils.prune as prune
from transformers import BertForSequenceClassification
import numpy as np
def structured_pruning_bert(model, pruning_ratio=0.3):
"""
Pruning strutturato delle teste di attenzione BERT.
Rimuove le teste meno importanti basandosi sulla norma L1.
"""
pruned_heads = {}
for layer_idx, layer in enumerate(model.bert.encoder.layer):
# Calcola importanza di ogni testa (norma L1 dei pesi query)
attention = layer.attention.self
num_heads = attention.num_attention_heads
head_size = attention.attention_head_size
query_weights = attention.query.weight.data # shape: [hidden, hidden]
query_reshaped = query_weights.view(num_heads, head_size, -1)
head_importance = query_reshaped.abs().mean(dim=[1, 2])
# Seleziona le teste da prunable
num_heads_to_prune = int(num_heads * pruning_ratio)
heads_to_prune = head_importance.argsort()[:num_heads_to_prune].tolist()
if heads_to_prune:
pruned_heads[layer_idx] = set(heads_to_prune)
model.prune_heads(pruned_heads)
return model, pruned_heads
# Unstructured pruning con PyTorch
def magnitude_pruning(model, sparsity=0.5):
"""Pruning per magnitudine su tutti i layer Linear."""
parameters_to_prune = [
(module, 'weight')
for module in model.modules()
if isinstance(module, torch.nn.Linear)
]
prune.global_unstructured(
parameters_to_prune,
pruning_method=prune.L1Unstructured,
amount=sparsity
)
# Rende il pruning permanente
for module, param in parameters_to_prune:
prune.remove(module, param)
return model
# Risultati tipici su BERT-Base (110M parametri):
# Pruning 30% teste: -28% latenza inferenza, -1.2% accuracy
# Pruning 50% pesi (magnitude): -45% dimensione, -0.8% accuracy
# Emissioni inferenza: -30% con structured pruning
Knowledge Distillation: Modelli Piccoli con Conoscenza Grande
La knowledge distillation trasferisce la conoscenza da un modello grande (teacher) a uno piccolo (student). DistilBERT, ad esempio, ha il 40% dei parametri di BERT-Base ma mantiene il 97% delle sue prestazioni sul benchmark GLUE. Le emissioni di training di DistilBERT sono inferiori del 60%, e quelle di inferenza del 40-50%.
import torch
import torch.nn as nn
import torch.nn.functional as F
class DistillationLoss(nn.Module):
"""
Loss combinata per knowledge distillation:
L = alpha * L_CE(student, labels) + (1-alpha) * L_KL(student, teacher)
"""
def __init__(self, alpha=0.5, temperature=4.0):
super().__init__()
self.alpha = alpha
self.temperature = temperature
self.ce_loss = nn.CrossEntropyLoss()
def forward(self, student_logits, teacher_logits, labels):
# Loss classificazione standard
loss_ce = self.ce_loss(student_logits, labels)
# Loss distillazione (KL divergence con temperature scaling)
student_soft = F.log_softmax(student_logits / self.temperature, dim=-1)
teacher_soft = F.softmax(teacher_logits / self.temperature, dim=-1)
loss_kl = F.kl_div(student_soft, teacher_soft, reduction='batchmean')
loss_kl *= self.temperature ** 2 # Scala per compensare la temperature
return self.alpha * loss_ce + (1 - self.alpha) * loss_kl
def distill_model(teacher_model, student_model, train_loader,
optimizer, device, epochs=5):
"""Training loop completo per knowledge distillation."""
distill_loss = DistillationLoss(alpha=0.5, temperature=4.0)
teacher_model.eval()
for epoch in range(epochs):
student_model.train()
total_loss = 0.0
for batch in train_loader:
input_ids = batch['input_ids'].to(device)
attention_mask = batch['attention_mask'].to(device)
labels = batch['labels'].to(device)
# Teacher inference (no gradient)
with torch.no_grad():
teacher_outputs = teacher_model(
input_ids=input_ids,
attention_mask=attention_mask
)
# Student forward pass
student_outputs = student_model(
input_ids=input_ids,
attention_mask=attention_mask
)
loss = distill_loss(
student_outputs.logits,
teacher_outputs.logits,
labels
)
optimizer.zero_grad()
loss.backward()
optimizer.step()
total_loss += loss.item()
print(f"Epoch {epoch+1}: Loss = {total_loss/len(train_loader):.4f}")
# Caso d'uso tipico: distillare BERT-Large (340M) -> BERT-Tiny (14M)
# Training: 8h vs 2.5h -> -69% tempo
# Emissioni training: -71%
# Accuracy GLUE: 84.6 vs 87.2 -> -3% accettabile per la maggior parte dei task
Efficient Neural Architecture Search
Il NAS tradizionale è ironicamente uno dei processi più energy-intensive nell'ML: cercare l'architettura ottimale può richiedere migliaia di training completi. Il NAS efficiente usa tecniche come one-shot NAS, weight sharing e predictor-based search per ridurre il costo di ricerca del 99%.
# Esempio con Once-for-All Network (OFA) da MIT
# OFA addestra una volta una supernetwork, poi campiona subnetwork efficienti
# Installazione: pip install ofa
from ofa.model_zoo import ofa_net
# Carica supernetwork pre-addestrata (una volta sola)
ofa_network = ofa_net('ofa_resnet50', pretrained=True)
# Campiona subnetwork che rispettano constraint energetici
# senza riaddestrare (zero-cost NAS)
def find_efficient_subnet(ofa_net, target_flops: float = 200e6):
"""
Cerca una subnetwork con FLOPs < target_flops
usando evolutionary search con predictor di accuracy.
"""
from ofa.nas.search_algorithm import EvolutionFinder
finder = EvolutionFinder(
constraint_type='flops',
efficiency_constraint=target_flops,
efficiency_predictor=ofa_net.flops_counter,
accuracy_predictor=None, # Usa validazione reale
population_size=100,
max_time_budget=500,
parent_ratio=0.25,
mutation_ratio=0.5
)
best_valset_acc, best_info = finder.run_evolution_search()
return best_info['net_config']
# Risultato tipico:
# Supernetwork ResNet-50: 4.1 GFLOPs, 25.6M param
# Subnetwork trovata: 200 MFLOPs, 6.2M param
# Accuracy ImageNet: 77.8% vs 79.2% (- 1.4%, - 95% emissioni inferenza)
Data-Centric Efficiency: Qualità vs Quantità
Un dataset di qualità superiore permette di raggiungere la stessa accuratezza con meno epoche di training e meno dati. Questo è uno dei principi del data-centric AI promosso da Andrew Ng: invece di aumentare il modello o i dati grezzi, migliorare la qualità dei dati esistenti.
Curriculum Learning: Training nell'Ordine Giusto
Il curriculum learning ordina gli esempi di training dalla difficoltà crescente, come un insegnante che inizia con concetti semplici prima di passare a quelli complessi. Questo approccio converge più velocemente e con meno epoche totali, riducendo il carbon footprint del 20-35% sui benchmark standard.
import numpy as np
from torch.utils.data import DataLoader, SubsetRandomSampler
from sklearn.metrics import pairwise_distances
class CurriculumScheduler:
"""
Curriculum Learning: ordina il dataset dalla difficolta' crescente.
La difficolta' è calcolata come distanza dalla media della classe.
"""
def __init__(self, dataset, difficulty_metric='loss', num_stages=4):
self.dataset = dataset
self.num_stages = num_stages
self.difficulty_scores = None
def compute_difficulty(self, model, device):
"""Calcola difficolta' come loss su un modello pre-addestrato leggero."""
model.eval()
scores = []
loader = DataLoader(self.dataset, batch_size=256, shuffle=False)
criterion = nn.CrossEntropyLoss(reduction='none')
with torch.no_grad():
for batch in loader:
input_ids = batch['input_ids'].to(device)
labels = batch['labels'].to(device)
outputs = model(input_ids=input_ids)
losses = criterion(outputs.logits, labels)
scores.extend(losses.cpu().numpy())
self.difficulty_scores = np.array(scores)
return self.difficulty_scores
def get_stage_indices(self, stage: int) -> list:
"""Ritorna gli indici degli esempi per lo stage corrente."""
if self.difficulty_scores is None:
raise ValueError("Esegui compute_difficulty prima.")
# Divide il dataset in quartili di difficolta'
sorted_indices = np.argsort(self.difficulty_scores)
stage_size = len(sorted_indices) // self.num_stages
# Stage 0: esempi più' facili, Stage N: tutti gli esempi
max_idx = stage_size * (stage + 1)
return sorted_indices[:max_idx].tolist()
def curriculum_training(model, dataset, optimizer, device, total_epochs=20):
"""Training con curriculum progressivo."""
scheduler = CurriculumScheduler(dataset, num_stages=4)
# Usa un modello leggero per calcolare la difficolta'
tiny_model = load_tiny_model().to(device)
difficulty = scheduler.compute_difficulty(tiny_model, device)
for epoch in range(total_epochs):
# Progressione lineare: da 25% a 100% del dataset
stage = min(3, int(epoch / total_epochs * 4))
indices = scheduler.get_stage_indices(stage)
sampler = SubsetRandomSampler(indices)
loader = DataLoader(dataset, batch_size=32, sampler=sampler)
print(f"Epoch {epoch}: usando {len(indices)}/{len(dataset)} esempi")
train_one_epoch(model, loader, optimizer, device)
# Risultati tipici:
# Training standard BERT-Base: 10 epoche per convergenza
# Training con curriculum: 6.5 epoche -> -35% emissioni
Active Learning: Selezionare i Dati Più Informativi
L'active learning seleziona iterativamente gli esempi più informativi da etichettare, invece di usare l'intero dataset. Con un budget di annotazione del 10-20%, si raggiunge spesso il 90-95% dell'accuratezza del training full-data, riducendo proporzionalmente il carbon footprint.
from sklearn.cluster import KMeans
import torch.nn.functional as F
class ActiveLearningSelector:
"""Seleziona esempi con massima incertezza (uncertainty sampling)."""
def __init__(self, model, unlabeled_pool, strategy='entropy'):
self.model = model
self.unlabeled_pool = unlabeled_pool
self.strategy = strategy
def compute_uncertainty(self, batch_size=128) -> np.ndarray:
"""Calcola incertezza del modello su ogni esempio non etichettato."""
self.model.eval()
uncertainties = []
loader = DataLoader(self.unlabeled_pool, batch_size=batch_size)
with torch.no_grad():
for batch in loader:
logits = self.model(batch['input_ids']).logits
probs = F.softmax(logits, dim=-1)
if self.strategy == 'entropy':
# Entropia: massima quando la distribuzione è uniforme
entropy = -(probs * probs.log()).sum(dim=-1)
uncertainties.extend(entropy.cpu().numpy())
elif self.strategy == 'margin':
# Margin: minimo quando le top-2 prob sono simili
top2 = probs.topk(2, dim=-1).values
margin = top2[:, 0] - top2[:, 1]
uncertainties.extend((1 - margin).cpu().numpy())
return np.array(uncertainties)
def select_examples(self, n_select: int) -> list:
"""Seleziona i n_select esempi più' incerti."""
uncertainties = self.compute_uncertainty()
top_indices = np.argsort(uncertainties)[-n_select:]
return top_indices.tolist()
# Workflow Active Learning
def active_learning_loop(
model, labeled_data, unlabeled_pool,
annotation_budget=500, iterations=5
):
per_iteration = annotation_budget // iterations
for iteration in range(iterations):
# Seleziona esempi
selector = ActiveLearningSelector(model, unlabeled_pool)
selected_idx = selector.select_examples(per_iteration)
# Simula annotazione (in produzione: etichettatura umana)
new_labeled = [unlabeled_pool[i] for i in selected_idx]
labeled_data.extend(new_labeled)
# Riaddestra su dati aggiornati
model = fine_tune(model, labeled_data)
acc = evaluate(model)
print(f"Iter {iteration}: {len(labeled_data)} esempi, acc={acc:.3f}")
return model
# Con 500 esempi attivi vs 5000 casuali: stesso livello di accuracy
# Risparmio training: -90% dati -> -85% emissioni
Carbon-Aware Training: Schedulare il Training in Modo Intelligente
Il carbon-aware training è l'applicazione dei principi del Carbon Aware SDK al mondo ML: invece di avviare training quando l'ingegnere ha finito di configurare l'esperimento, si schedula il job nel momento e nella regione con la più bassa intensità carbonica del mix elettrico.
Spostamento Temporale: Quando il Training Verde
L'intensità carbonica dell'energia varia enormemente nel corso della giornata e della settimana, a seconda della disponibilità di rinnovabili. In Italia, la differenza tra le ore più verdi (11:00-15:00 in estate) e le più carbone (19:00-22:00 in inverno) può essere del 200-300 gCO₂eq/kWh.
import asyncio
import httpx
from datetime import datetime, timedelta
from typing import Optional
import subprocess
ELECTRICITY_MAPS_TOKEN = "YOUR_TOKEN" # electricitymaps.com/api
async def get_carbon_intensity(zone: str = "IT") -> float:
"""Recupera l'intensità' carbonica attuale dalla Electricity Maps API."""
async with httpx.AsyncClient() as client:
resp = await client.get(
f"https://api.electricitymap.org/v3/carbon-intensity/latest",
params={"zone": zone},
headers={"auth-token": ELECTRICITY_MAPS_TOKEN}
)
data = resp.json()
return data["carbonIntensity"] # gCO2eq/kWh
async def get_forecast(zone: str = "IT", hours: int = 24) -> list[dict]:
"""Recupera le previsioni di intensità' carbonica per le prossime ore."""
async with httpx.AsyncClient() as client:
resp = await client.get(
f"https://api.electricitymap.org/v3/carbon-intensity/forecast",
params={"zone": zone},
headers={"auth-token": ELECTRICITY_MAPS_TOKEN}
)
data = resp.json()
return data["forecast"][:hours]
async def find_optimal_training_window(
job_duration_hours: float,
zone: str = "IT",
max_delay_hours: int = 24
) -> Optional[datetime]:
"""
Trova la finestra temporale ottimale per un training job.
Cerca il window di 'job_duration_hours' con intensità' media minima.
"""
forecast = await get_forecast(zone, hours=max_delay_hours + int(job_duration_hours))
if not forecast:
return None
window_size = max(1, int(job_duration_hours))
best_start = None
best_avg_intensity = float('inf')
for i in range(len(forecast) - window_size + 1):
window = forecast[i:i + window_size]
avg_intensity = sum(h['carbonIntensity'] for h in window) / len(window)
if avg_intensity < best_avg_intensity:
best_avg_intensity = avg_intensity
best_start = datetime.fromisoformat(forecast[i]['datetime'])
print(f"Finestra ottimale: {best_start}")
print(f"Intensità' media: {best_avg_intensity:.1f} gCO2eq/kWh")
return best_start
async def carbon_aware_submit(
training_command: str,
job_duration_hours: float = 6.0,
threshold_gco2_kwh: float = 200.0,
zone: str = "IT"
):
"""
Submette un training job solo quando l'intensità' è sotto soglia,
altrimenti aspetta la finestra ottimale.
"""
current = await get_carbon_intensity(zone)
if current <= threshold_gco2_kwh:
print(f"Intensità' attuale {current} gCO2/kWh: avvio immediato")
subprocess.Popen(training_command.split())
return
print(f"Intensità' troppo alta ({current} gCO2/kWh > {threshold_gco2_kwh})")
optimal_window = await find_optimal_training_window(
job_duration_hours, zone
)
if optimal_window:
wait_seconds = (optimal_window - datetime.now()).total_seconds()
print(f"Scheduling per {optimal_window} (attesa: {wait_seconds/3600:.1f}h)")
await asyncio.sleep(max(0, wait_seconds))
subprocess.Popen(training_command.split())
# Utilizzo
asyncio.run(carbon_aware_submit(
training_command="python train_bert.py --epochs 10",
job_duration_hours=8.0,
threshold_gco2_kwh=150.0,
zone="IT"
))
Spostamento Spaziale: Scegliere la Regione Cloud Più Verde
Diversi cloud provider offrono regioni con mix energetici molto differenti. La scelta della regione di training può ridurre le emissioni del 70-90% rispetto a una scelta casuale.
Carbon Intensity Media per Regione Cloud (2024-2025)
| Provider | Regione | Location | gCO₂eq/kWh | Note |
|---|---|---|---|---|
| GCP | europe-north1 | Finlandia | ~35 | Idroelettrico + eolico |
| AWS | eu-north-1 | Stoccolma | ~40 | Quasi 100% rinnovabili |
| GCP | europe-west1 | Belgio | ~56 | Mix rinnovabile + nucleare |
| Azure | francecentral | Parigi | ~58 | Nucleare dominante |
| AWS | eu-west-1 | Irlanda | ~250 | Gas naturale prevalente |
| AWS | us-east-1 | Virginia | ~296 | Mix USA medio-est |
| AWS | ap-southeast-1 | Singapore | ~408 | Gas naturale |
# Spot Instances per training carbon-aware
# Le spot instance non solo riducono i costi del 60-90%,
# ma tendono a essere distribuite su hardware più' recente ed efficiente
import boto3
from botocore.exceptions import ClientError
def submit_spot_training(
image_uri: str,
instance_type: str = "p4d.24xlarge", # 8x A100
region: str = "eu-north-1", # Stoccolma: ~40 gCO2/kWh
max_price_per_hour: float = 15.0
):
"""Submette un training job su spot instances AWS SageMaker."""
sagemaker = boto3.client('sagemaker', region_name=region)
response = sagemaker.create_training_job(
TrainingJobName=f"green-ml-{int(datetime.now().timestamp())}",
AlgorithmSpecification={
'TrainingImage': image_uri,
'TrainingInputMode': 'FastFile'
},
ResourceConfig={
'InstanceType': instance_type,
'InstanceCount': 1,
'VolumeSizeInGB': 100
},
# Managed Spot Training
EnableManagedSpotTraining=True,
StoppingCondition={
'MaxRuntimeInSeconds': 86400, # 24 ore max
'MaxWaitTimeInSeconds': 172800 # Aspetta fino a 48 ore
},
# Checkpointing per riprendere se lo spot viene interrotto
CheckpointConfig={
'S3Uri': f's3://my-bucket/checkpoints/',
'LocalPath': '/opt/ml/checkpoints'
}
)
return response['TrainingJobArn']
# Confronto costi e emissioni:
# p4d.24xlarge on-demand (us-east-1): $32.77/h, 296 gCO2/kWh
# p4d.24xlarge spot (eu-north-1): $9.83/h, 40 gCO2/kWh
# Risparmio economico: -70%
# Risparmio emissioni: -86% (spot + regione verde)
Ottimizzazione dell'Inferenza: Il 77% del Carbon Footprint
Come anticipato, l'inferenza in produzione domina le emissioni nel ciclo di vita dei modelli ML. Per un modello con milioni di utenti, ogni millisecondo di latenza ridotta e ogni watt di consumo abbassato si moltiplica per miliardi di richieste all'anno. Le tecniche principali sono quantizzazione, compilazione del modello e batching ottimizzato.
Quantizzazione: INT8, INT4 e GPTQ
La quantizzazione riduce la precisione numerica dei pesi del modello da FP32 o FP16 a interi a 8 o 4 bit. Questo riduce la memoria richiesta del 50-75%, accelera l'inferenza del 2-4x e abbassa il consumo energetico proporzionalmente.
from transformers import AutoModelForCausalLM, AutoTokenizer, BitsAndBytesConfig
import torch
# Quantizzazione INT4 con GPTQ (post-training quantization)
quantization_config = BitsAndBytesConfig(
load_in_4bit=True,
bnb_4bit_compute_dtype=torch.bfloat16,
bnb_4bit_use_double_quant=True, # QLoRA: double quantization
bnb_4bit_quant_type="nf4" # Normal Float 4: migliore qualità'
)
model_id = "meta-llama/Llama-2-7b-hf"
model = AutoModelForCausalLM.from_pretrained(
model_id,
quantization_config=quantization_config,
device_map="auto",
torch_dtype=torch.bfloat16
)
tokenizer = AutoTokenizer.from_pretrained(model_id)
# Verifica riduzione memoria
original_size_gb = 7 * 2 / 1024 # 7B param x 2 byte (FP16) / 1024
quantized_size_gb = 7 * 0.5 / 1024 # 7B param x 0.5 byte (INT4) / 1024
print(f"FP16: {original_size_gb:.1f} GB")
print(f"INT4: {quantized_size_gb:.1f} GB (-{(1-quantized_size_gb/original_size_gb)*100:.0f}%)")
# Quantizzazione INT8 con ONNX Runtime (per produzione)
from optimum.onnxruntime import ORTModelForSequenceClassification
from optimum.onnxruntime.configuration import AutoQuantizationConfig
# Esporta in ONNX e quantizza
ort_model = ORTModelForSequenceClassification.from_pretrained(
"bert-base-uncased",
export=True
)
quantization_config = AutoQuantizationConfig.avx512_vnni(
is_static=False, # Dynamic quantization (no calibration data needed)
per_channel=False
)
# Salva il modello quantizzato
from optimum.onnxruntime import ORTQuantizer
quantizer = ORTQuantizer.from_pretrained(ort_model)
quantizer.quantize(
save_dir="./bert-int8-onnx",
quantization_config=quantization_config
)
# Benchmark:
# BERT-Base FP32: 23ms latenza, 100% accuracy, 1.6 GB
# BERT-Base INT8: 12ms latenza, 99.2% accuracy, 0.4 GB -> -48% energia
# BERT-Base INT4: 8ms latenza, 97.8% accuracy, 0.2 GB -> -65% energia
vLLM: Serving Efficiente per LLM
vLLM è un framework di serving per LLM che usa PagedAttention per gestire la KV cache in modo molto più efficiente. Rispetto a un serving naive, vLLM aumenta il throughput del 15-24x a parità di hardware, riducendo proporzionalmente il consumo energetico per token generato.
from vllm import LLM, SamplingParams
from codecarbon import EmissionsTracker
# Avvia LLM con vLLM (PagedAttention + continuous batching)
llm = LLM(
model="meta-llama/Llama-2-7b-chat-hf",
tensor_parallel_size=1, # GPU count
gpu_memory_utilization=0.90, # Usa il 90% della VRAM per KV cache
max_num_batched_tokens=32768, # Batch maggiori = più' efficiente
quantization="awq" # AWQ quantization integrata
)
sampling_params = SamplingParams(
temperature=0.7,
max_tokens=256
)
# Misura emissioni per 1000 richieste
tracker = EmissionsTracker(project_name="vllm-inference-benchmark")
tracker.start()
prompts = ["Spiega il concetto di machine learning in italiano"] * 1000
outputs = llm.generate(prompts, sampling_params)
emissions = tracker.stop()
tokens_generated = sum(len(o.outputs[0].token_ids) for o in outputs)
print(f"Token generati: {tokens_generated:,}")
print(f"Emissioni: {emissions*1000:.2f} gCO2eq")
print(f"gCO2eq per 1000 token: {emissions*1000/tokens_generated*1000:.4f}")
# Confronto con serving naive HuggingFace:
# HuggingFace Transformers: 1.000 req, 45 min, 890 gCO2eq
# vLLM: 1.000 req, 3 min, 59 gCO2eq (-93%)
# Throughput: 333 req/min vs 22 req/min
SCI Score per Modelli ML: Standardizzare la Misurazione
La specifica SCI (Software Carbon Intensity) della Green Software Foundation, ora standard ISO/IEC 21031:2024, si applica anche ai sistemi ML. L'SCI esprime le emissioni per unità funzionale, permettendo confronti significativi tra modelli diversi.
SCI Formula Applicata a Sistemi ML
| Componente | Formula ML | Esempio BERT Inference |
|---|---|---|
| E (Energy) | GPU Power x Tempo / 1000 | 300W x 0.05s / 1000 = 0.000015 kWh |
| I (Carbon Intensity) | gCO₂eq/kWh della regione | 420 gCO₂eq/kWh (Italia media) |
| M (Embodied) | Hardware CO₂ / (Lifespan x Utilizzo) | 150.000 gCO₂ / (26.280h x 0.7) = 8.2 g/h |
| R (Functional Unit) | 1.000 inferenze o 1 token generato | 1.000 classificazioni |
| SCI | (E x I + M) / R | (0.015 x 0.420 + 0.00023) / 1 = 0.0065 gCO₂eq/req |
from dataclasses import dataclass
from typing import Literal
@dataclass
class MLSystemSCI:
"""Calcola SCI Score per sistemi ML."""
model_name: str
gpu_count: int
gpu_tdp_watts: float
avg_latency_ms: float # Per inferenza singola
carbon_intensity_g_kwh: float # gCO2/kWh regione
gpu_lifespan_hours: float = 26_280 # 3 anni
gpu_embodied_gco2: float = 150_000 # ~150 kgCO2 per GPU
utilization: float = 0.70
pue: float = 1.3
functional_unit: Literal['request', 'token'] = 'request'
def operational_energy_kwh(self) -> float:
"""Energia operativa per singola inferenza."""
return (
self.gpu_count
* self.gpu_tdp_watts
* (self.avg_latency_ms / 1000)
/ 1_000 # W -> kW
)
def operational_emissions_g(self) -> float:
"""Emissioni operative per singola inferenza (gCO2eq)."""
return (
self.operational_energy_kwh()
* self.carbon_intensity_g_kwh
* self.pue
)
def embodied_per_request_g(self) -> float:
"""Quota embodied per singola inferenza."""
# Secondi per richiesta / secondi per vita GPU
fraction_of_life = (self.avg_latency_ms / 1000) / (
self.gpu_lifespan_hours * 3600
)
total_embodied = self.gpu_count * self.gpu_embodied_gco2
return total_embodied * fraction_of_life / self.utilization
@property
def sci_score(self) -> float:
"""SCI = (E x I + M) / R per singola richiesta."""
return self.operational_emissions_g() + self.embodied_per_request_g()
# Confronto modelli
models = [
MLSystemSCI("BERT-Base INT8/CPU", 0, 100, 45, 420, gpu_count=0,
gpu_embodied_gco2=0),
MLSystemSCI("BERT-Base FP32/A100", 1, 400, 12, 420),
MLSystemSCI("BERT-Base INT8/A100", 1, 280, 6, 420),
MLSystemSCI("DistilBERT FP16/A100", 1, 240, 5, 420),
MLSystemSCI("BERT-Base INT8/eu-north-1", 1, 280, 6, 40), # Stoccolma
]
print(f"{'Modello':<35} {'SCI (gCO2/req)':<18} {'vs Baseline'}")
baseline = models[0].sci_score
for m in models:
sci = m.sci_score
ratio = sci / baseline
print(f"{m.model_name:<35} {sci:.6f} gCO2 {ratio:.2f}x")
Green AI Leaderboard: Efficienza oltre l'Accuratezza
I leaderboard tradizionali come GLUE, SuperGLUE e MMLU misurano solo l'accuratezza. Il concetto di Green AI Leaderboard, proposto da Schwartz et al. (2020) e formalizzato dalla GSF nel 2024, introduce metriche composte che bilanciano performance e efficienza carbonica.
Green AI Metrics: Accuracy/Emission Trade-off (GLUE Benchmark)
| Modello | GLUE Score | CO₂eq Train (t) | CO₂eq Infer (mg/req) | Green Score* |
|---|---|---|---|---|
| BERT-Base | 82.1 | 0.9 | 0.62 | 132 |
| RoBERTa-Base | 86.4 | 4.1 | 0.62 | 139 |
| DistilBERT | 77.0 | 0.35 | 0.31 | 248 |
| BERT-Base INT8 | 81.3 | 0.9 | 0.28 | 290 |
| MobileBERT | 78.2 | 0.6 | 0.18 | 434 |
| ALBERT-Base v2 | 82.3 | 0.5 | 0.42 | 196 |
* Green Score = (GLUE Score / CO₂eq Inference) x 1000. Valori più alti = più efficiente.
Notare come DistilBERT, nonostante un GLUE Score inferiore del 6% rispetto a BERT-Base, abbia un Green Score quasi doppio. MobileBERT offre il miglior Green Score della lista, rendendolo la scelta ottimale per deployment su scala con budget carbonico limitato.
Caso Studio: Riduzione del 60% delle Emissioni su un Modello NLP
Questo caso studio descrive un percorso reale di ottimizzazione applicato a un modello di classificazione del sentiment per il mercato italiano, sviluppato per un'azienda di e-commerce con 2 milioni di recensioni mensili da processare.
Baseline: Stato Iniziale
# STATO INIZIALE (Baseline)
# Modello: bert-base-multilingual-cased (177M parametri)
# Hardware: 1x NVIDIA A100 80GB SXM4 (on-demand AWS us-east-1)
# Task: classificazione sentiment 5 classi (1-5 stelle)
# Dataset: 500.000 recensioni in italiano
# Metriche baseline:
Training_time_hours = 18.5
Energy_training_kWh = 130.0 # A100 SXM4: ~400W TDP, PUE=1.5
CO2_training_kgCO2eq = 38.5 # 130 kWh x 0.296 kgCO2/kWh (us-east-1)
Inference_latency_ms = 35.0 # Per singola recensione
Inference_throughput_req_s = 28.6
CO2_inference_per_1M_req = 2.85 # kgCO2eq
Monthly_inference_requests = 2_000_000
Monthly_inference_CO2_kg = 5.70
Annual_training_runs = 4 # Re-training trimestrale
Annual_total_CO2_kg = (4 * 38.5) + (12 * 5.70) = 222.4
Accuracy_f1_macro = 0.847
Ottimizzazione Step-by-Step
# STEP 1: Cambio regione cloud (us-east-1 -> eu-north-1)
# Carbon intensity: 296 -> 40 gCO2/kWh (-86%)
# Nessun cambiamento al codice, solo flag AWS Region
CO2_training_kgCO2eq_step1 = 130.0 * 0.040 * 1.5 = 7.8 # -80%
Monthly_inference_CO2_step1 = 2.85 * (40/296) = 0.38 # -87%
Annual_CO2_step1 = (4 * 7.8) + (12 * 0.38) = 35.7 kg # -84%
# STEP 2: Cambio modello base (multilingual -> umberto italiano)
# umberto-commoncrawl-cased (bert-base size ma solo italiano)
# Dataset training ridotto del 30% (solo dati italiani puliti)
# Curriculum learning: 15 epoche vs 20 -> -25% training time
CO2_training_kgCO2eq_step2 = 7.8 * 0.75 * 0.70 = 4.1 # -47% su step1
Accuracy_f1_macro_step2 = 0.861 # +1.4% grazie a modello specializzato
# STEP 3: Quantizzazione INT8 (ONNX Runtime)
# Latenza inferenza: 35ms -> 18ms (-49%)
# Throughput: 28.6 -> 55.5 req/s (+94%)
Inference_latency_ms_step3 = 18.0
Monthly_inference_CO2_step3 = 0.38 * 0.50 = 0.19 # -50%
Accuracy_f1_macro_step3 = 0.856 # -0.5% accettabile
# STEP 4: Carbon-aware scheduling del re-training
# Spot instances + finestre energetiche verdi
# Costo spot eu-north-1: -70% vs on-demand
# Timing training nelle ore di massima rinnovabile
CO2_training_kgCO2eq_step4 = 4.1 * 0.85 = 3.5 # Spot: stesso hardware, timing migliore
# RIEPILOGO OTTIMIZZAZIONI
print("=" * 60)
print(f"Baseline: {222.4:.1f} kgCO2eq/anno | F1: 0.847")
print(f"Step 1: {35.7:.1f} kgCO2eq/anno | -84% (solo regione)")
print(f"Step 2: {26.9:.1f} kgCO2eq/anno | -87% (modello+data)")
print(f"Step 3: {21.7:.1f} kgCO2eq/anno | -90% (quantizzazione)")
print(f"Step 4: {18.6:.1f} kgCO2eq/anno | -92% (carbon-aware)")
print(f"F1 finale: 0.856 vs 0.847 baseline (+1.1%)")
print("=" * 60)
print(f"Riduzione CO2: -203.8 kgCO2eq/anno (-91.6%)")
print(f"Risparmio costi: -74% (spot + regione + efficienza)")
Risultati del Caso Studio: Riepilogo
| Metrica | Baseline | Ottimizzato | Variazione |
|---|---|---|---|
| CO₂eq annuale totale | 222 kgCO₂eq | 19 kgCO₂eq | -91% |
| F1-Score Macro | 0.847 | 0.856 | +1.1% |
| Latenza inferenza | 35 ms | 18 ms | -49% |
| Throughput | 28 req/s | 55 req/s | +96% |
| Costo cloud mensile | ~2.400 EUR | ~620 EUR | -74% |
Lezione Chiave: Il 90% delle Emissioni si Riduce Senza Sacrificare Accuratezza
In questo caso studio, ogni step di ottimizzazione ha migliorato o mantenuto l'accuratezza del modello. La scelta della regione cloud ha salvato l'84% delle emissioni senza toccare una riga di codice. La specializzazione del modello per la lingua italiana ha migliorato la qualità. La quantizzazione ha dimezzato la latenza. Il messaggio è chiaro: ottimizzare per il carbon footprint e ottimizzare per la qualità non sono obiettivi in conflitto.
Roadmap Green AI per le Organizzazioni
Adottare il Green AI non richiede di rivoluzionare tutto immediatamente. Una roadmap in tre fasi permette di introdurre le pratiche progressivamente, partendo dalle ottimizzazioni a maggiore impatto e minore sforzo.
Roadmap di Adozione Green AI in 3 Fasi
| Fase | Timeline | Azioni | Riduzione CO₂ Attesa |
|---|---|---|---|
| Fase 1: Misura | Settimana 1-4 |
Installare CodeCarbon su tutti i training job; definire baseline di emissioni per modello; scegliere regioni cloud più verdi |
50-85% (solo regione) |
| Fase 2: Ottimizza | Mese 2-4 |
Applicare mixed precision (BF16) a tutti i training; quantizzare modelli in produzione (INT8); adottare spot instances per training batch |
Ulteriore 30-50% |
| Fase 3: Integra | Mese 5-12 |
Carbon-aware scheduling automatizzato; Green AI Leaderboard interno; SCI score nei criteri di deployment; reportistica ESG emissioni ML |
Ulteriore 10-20% |
Checklist Green AI per ogni Training Job
# Checklist Green AI da seguire prima di ogni training
# Copia in: .github/PULL_REQUEST_TEMPLATE.md o come commento training script
CHECKLIST_GREEN_AI = """
Green AI Pre-Training Checklist
================================
[ ] 1. REGIONE: Training schedulato in regione con CI < 150 gCO2/kWh?
Verifica: https://app.electricitymaps.com/zone/IT
Preferisci: GCP europe-north1, AWS eu-north-1, Azure swedencentral
[ ] 2. TIMING: Carbon-aware scheduling attivato?
Tool: Carbon Aware SDK o script custom
Finestra ottimale: verificata nelle ultime 24h
[ ] 3. HARDWARE: GPU di ultima generazione disponibile?
H100 >> A100 (44% più' efficiente)
Spot/preemptible instances preferite
[ ] 4. PRECISION: Mixed precision attivata (BF16/FP16)?
HuggingFace: bf16=True in TrainingArguments
PyTorch: torch.autocast('cuda', dtype=torch.bfloat16)
[ ] 5. EARLY STOPPING: Configurato con CarbonTracker per proiezione?
Stima emissioni dopo 5 epoche e confronta con budget CO2
[ ] 6. TRACKING: CodeCarbon configurato?
Country_iso_code, cloud_provider, cloud_region impostati
Output in: ./carbon_reports/
[ ] 7. DATI: Dataset ottimizzato con deduplication e qualità'?
Almeno 10% redundancy rimossa
Active learning per ridurre dimensione se possibile
[ ] 8. BASELINE: Esiste una run precedente da confrontare?
Registra su MLflow: accuracy, emissioni, ratio
[ ] 9. INFERENZA: Quantizzazione pianificata per produzione?
Target: INT8 con ONNX Runtime o INT4 per LLM
[ ] 10. REPORTING: Emissioni saranno incluse nel model card?
Usa: SCI Score, kgCO2eq training, gCO2eq/1000 req inferenza
"""
print(CHECKLIST_GREEN_AI)
Conclusioni: Riepilogo della Serie Green Software Engineering
Con questo articolo si conclude la serie Green Software Engineering, un percorso in 10 articoli che ha attraversato l'intero spettro delle pratiche di sviluppo software sostenibile. Dalla teoria dei principi GSF alle implementazioni pratiche con CodeCarbon, Carbon Aware SDK, Climatiq API, fino alle tecniche avanzate di ottimizzazione ML: abbiamo costruito un toolkit completo per sviluppatori e organizzazioni che vogliono ridurre il loro impatto ambientale digitale.
I 10 Messaggi Chiave della Serie
| # | Articolo | Messaggio Chiave |
|---|---|---|
| 1 | Principi GSF | L'efficienza carbonica è una metrica di qualità del software |
| 2 | CodeCarbon | Non puoi ridurre ciò che non misuri: inizia dal giorno 1 |
| 3 | Climatiq API | La CO₂ ha un'API: integra il carbon accounting nel tuo backend |
| 4 | Carbon Aware SDK | Spostare un workload nel tempo o nello spazio può ridurre del 90% |
| 5 | Scope Modeling | SCI è lo standard ISO per confrontare l'efficienza carbonica del software |
| 6 | GreenOps | Green e FinOps si allineano: ottimizzare CO₂ riduce i costi cloud |
| 7 | Scope 3 Pipeline | Le emissioni indirette sono spesso maggiori di quelle dirette |
| 8 | ESG & CSRD | Dal 2025 il reporting ESG digitale è obbligo legale per molte aziende |
| 9 | Pattern Sostenibili | Cache, batch e async non sono solo performance: sono green patterns |
| 10 | AI Carbon | La scelta della regione cloud riduce le emissioni ML più di qualsiasi algoritmo |
Il Momento è Adesso
L'AI generativa continuerà a crescere. I modelli diventeranno più grandi, il training più frequente, il deployment più pervasivo. La domanda non è se le emissioni dell'AI aumenteranno - aumenteranno comunque - ma di quanto. Le tecniche in questo articolo dimostrano che è possibile ridurre del 60-90% le emissioni di un sistema ML senza sacrificare l'accuratezza e spesso riducendo anche i costi. Questo non è un compromesso: è una vittoria su tutti i fronti.
La Green Software Foundation stima che se ogni team ML adottasse le pratiche di carbon-aware training e quantizzazione dell'inferenza, le emissioni globali del settore AI potrebbero ridursi del 40-60% entro il 2030 rispetto allo scenario business-as-usual. Non servono breakthrough tecnologici: servono le pratiche descritte in questi dieci articoli, applicate sistematicamente.
Il prossimo passo è tuo. Installa CodeCarbon sul prossimo training job. Controlla l'intensità carbonica della tua regione AWS o GCP. Quantizza il modello prima di deployarlo. Misura il SCI score. E quando lo farai, avrai fatto il passo più importante: iniziare.
Link Utili e Strumenti
- CodeCarbon: mlco2.github.io/codecarbon
- ML CO₂ Impact Calculator: mlco2.github.io/impact
- Green Algorithms Calculator: green-algorithms.org
- Electricity Maps API: electricitymaps.com/api
- GSF SCI Specification: sci.greensoftware.foundation
- Hugging Face Emissions Tracker: HuggingFace Hub Carbon Tracking
- vLLM Serving: docs.vllm.ai
- ONNX Runtime Optimization: onnxruntime.ai
Continua il Percorso: Serie Correlate
- MLOps per Business: Approfondisci il deployment di modelli ML con MLflow, DVC e pipeline CI/CD - inclusi i gate di qualità per carbon footprint.
- AI Engineering & RAG: Esplora come costruire sistemi RAG efficienti che minimizzano il numero di chiamate ai modelli LLM, riducendo sia i costi che le emissioni.
- Data & AI Business: Scopri come l'AI Act EU e le normative ESG si intersecano con le pratiche di Green AI, e come costruire una roadmap data-driven sostenibile per le PMI.

Commenti
Caricamento commenti...
Accedi per commentare