Salta al contenuto principale

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.

Cosa ne pensi di questo articolo?

AI e Carbonio: Misurare e Ridurre il Footprint del ML Training
Green SoftwareFoto:Ruben BoekelooPexels

In Breve

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.

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

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

Discuti questo articolo nella community

Unisciti al gruppo Telegram per discutere con altri sviluppatori, fare domande e condividere le tue esperienze.

Ti è piaciuto questo articolo?

Esplora altri contenuti sul blog o scopri i miei progetti