Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Validació i qualitat

Introducció

Al capítol anterior vam aprendre com preparar i desplegar models de machine learning utilitzant Docker, FastAPI, i estratègies de predicció online i batch. Ara que sabem com desplegar, és moment d’aprendre com fer-ho bé.

Desplegar un model sense validació adequada és com publicar un llibre sense revisar-lo: pot funcionar, però els errors poden ser costosos. En aquest capítol explorarem:

  • Criteris objectius per decidir si un model està llest per producció
  • Testing específic per a sistemes de machine learning
  • Automatització de validació amb git hooks i CI/CD
  • Optimització de models per a producció

Aquestes pràctiques garanteixen que només models validats i de qualitat arribin als teus usuaris.

Validació de models

Abans de posar un model en producció, necessitem establir criteris clars que ens diguin si el model és “prou bo”. Sense aquests criteris, podem acabar desplegant models que no funcionen adequadament o, al contrari, rebutjant models perfectament vàlids.

El problema de la validació subjectiva

Sense criteris clars:

  • “Aquest F1 de 0.78 és bo?” → Depèn del context
  • “Podem desplegar?” → No ho sabem
  • “El nou model és millor?” → Difícil de comparar

Necessitem llindars objectius definits abans d’entrenar.

Tipus de criteris de desplegament

1. Mètriques tècniques

Rendiment mesurable del model en el validation set:

MètricaDescripcióExemple de llindar
AccuracyPercentatge de prediccions correctes> 0.85
F1 ScoreMitjana harmònica de precision i recall> 0.75
Precision% de positius predits que són correctes> 0.80
Recall% de positius reals que detectem> 0.70
ROC-AUCCapacitat de discriminar entre classes> 0.85
MAE/RMSEError en regressió< 5000 (depèn de l’escala)

2. Mètriques de negoci

Impacte real en el negoci o usuaris:

# Exemple: cost de classificació mèdica cost_false_negative = 10_000 # € - malaltia no detectada cost_false_positive = 500 # € - test innecessari # Criteri: cost total < baseline def business_cost(y_true, y_pred): fn = ((y_true == 1) & (y_pred == 0)).sum() fp = ((y_true == 0) & (y_pred == 1)).sum() return fn * cost_false_negative + fp * cost_false_positive

Criteris de negoci típics:

  • Cost de falsos positius vs falsos negatius
  • Impacte en conversió / vendes
  • Experiència d’usuari (latència, qualitat)
  • ROI (retorn d’inversió)

3. Mètriques operacionals

Requisits d’infraestructura i rendiment:

MètricaDescripcióExemple de llindar
Latència p50Temps de resposta mitjà< 50ms
Latència p95Temps de resposta del 95% de peticions< 100ms
Latència p99Temps de resposta del 99% de peticions< 200ms
Mida del modelEspai en disc / memòria< 500 MB
ThroughputPrediccions per segon> 100 req/s

Definir llindars: el procés

Pas 1: Establir la baseline

Abans de desplegar el primer model, definir què és “acceptable”. Opcions per la baseline:

  • Predicció constant: Sempre predir la classe més freqüent
  • Regla heurística: Lògica simple basada en dominis (if-then)
  • Model anterior: Si n’hi ha un en producció
  • Requisit de negoci: Cost màxim acceptable

Exemple:

# Baseline: predicció constant (classe majoritària) from sklearn.dummy import DummyClassifier baseline = DummyClassifier(strategy='most_frequent') baseline.fit(X_train, y_train) baseline_f1 = f1_score(y_val, baseline.predict(X_val)) # baseline_f1 = 0.45 # Criteris: millor que baseline + marge MIN_F1 = baseline_f1 + 0.10 # = 0.55 MIN_RECALL = 0.70 # Requisit de negoci

Pas 2: Consensuar amb stakeholders

Els criteris no són només tècnics. Cal involucrar:

  • Product owners (impacte de negoci)
  • Usuaris finals (experiència)
  • Operacions (infraestructura)
  • Legal/ètica (biaixos, fairness)

Pas 3: Documentar els criteris

# config/deployment_criteria.yaml model_criteria: technical: f1_score_min: 0.75 recall_min: 0.70 precision_min: 0.65 roc_auc_min: 0.80 business: cost_per_prediction_max: 0.05 # euros false_negative_rate_max: 0.30 operational: latency_p95_max_ms: 100 latency_p99_max_ms: 200 model_size_max_mb: 500 baseline: baseline_f1: 0.45 required_improvement: 0.10

Implementar la validació

# src/validate_model.py import yaml import joblib from sklearn.metrics import f1_score, recall_score, precision_score def load_criteria(criteria_path: str) -> dict: """Carrega criteris de desplegament.""" with open(criteria_path, 'r') as f: return yaml.safe_load(f) def is_deployment_ready(model, X_val, y_val, criteria: dict) -> tuple[bool, list]: """ Comprova si el model compleix tots els criteris. Returns: (approved, reasons): Bool indicant si aprovat, llista de raons """ y_pred = model.predict(X_val) # Calcular mètriques metrics = { 'f1': f1_score(y_val, y_pred, average='weighted'), 'recall': recall_score(y_val, y_pred, average='weighted'), 'precision': precision_score(y_val, y_pred, average='weighted') } # Validar contra criteris issues = [] tech_criteria = criteria['model_criteria']['technical'] if metrics['f1'] < tech_criteria['f1_score_min']: issues.append(f"F1={metrics['f1']:.3f} < {tech_criteria['f1_score_min']}") if metrics['recall'] < tech_criteria['recall_min']: issues.append(f"Recall={metrics['recall']:.3f} < {tech_criteria['recall_min']}") if metrics['precision'] < tech_criteria['precision_min']: issues.append(f"Precision={metrics['precision']:.3f} < {tech_criteria['precision_min']}") approved = len(issues) == 0 return approved, issues # Ús criteria = load_criteria('config/deployment_criteria.yaml') model = joblib.load('models/candidate_model.pkl') approved, issues = is_deployment_ready(model, X_val, y_val, criteria) if approved: print("✓ Model aprovat per desplegament") # Desplegar else: print("✗ Model NO aprovat:") for issue in issues: print(f" - {issue}") # No desplegar, iterar

Comparar amb el model actual

Si ja tenim un model en producció, el nou model ha de ser al menys tan bo o millor:

def compare_with_current(new_metrics: dict, current_metrics: dict, tolerance: float = 0.02) -> bool: """ Comprova que el nou model no és pitjor que l'actual. tolerance: Degradació acceptable (e.g., 0.02 = 2%) """ for metric_name in ['f1', 'recall']: new_val = new_metrics[metric_name] current_val = current_metrics[metric_name] # Nou model no pot ser pitjor (amb tolerància) if new_val < current_val - tolerance: print(f"⚠️ {metric_name}: {new_val:.3f} < {current_val:.3f} (degradació > {tolerance})") return False return True

Exemple complet: Pipeline de validació

# scripts/validate_and_deploy.py def main(): print("=== Validació del model candidat ===\n") # 1. Carregar model i dades candidate = joblib.load('models/candidate_v2.0.0.pkl') X_val = pd.read_parquet('data/validation_set.parquet').drop('target', axis=1) y_val = pd.read_parquet('data/validation_set.parquet')['target'] # 2. Validar criteris absoluts criteria = load_criteria('config/deployment_criteria.yaml') approved, issues = is_deployment_ready(candidate, X_val, y_val, criteria) if not approved: print("✗ Model rebutjat (no compleix criteris):") for issue in issues: print(f" {issue}") return False print("✓ Model compleix criteris mínims") # 3. Comparar amb model actual (si existeix) if os.path.exists('models/current_model_metrics.json'): with open('models/current_model_metrics.json') as f: current_metrics = json.load(f) new_metrics = { 'f1': f1_score(y_val, candidate.predict(X_val)), 'recall': recall_score(y_val, candidate.predict(X_val)) } if not compare_with_current(new_metrics, current_metrics): print("✗ Model pitjor que l'actual") return False print("✓ Model millor o igual que l'actual") # 4. Aprovar per desplegament print("\n✓✓✓ Model APROVAT per desplegament ✓✓✓") return True if __name__ == '__main__': success = main() exit(0 if success else 1)

Resum: checklist de desplegament

Abans de desplegar un model, verifica:

  • Criteris tècnics: F1, recall, precision compleixen llindars
  • Criteris de negoci: Cost, ROI, impacte acceptable
  • Criteris operacionals: Latència, mida, throughput acceptables
  • Comparació: Nou model ≥ model actual (si n’hi ha)
  • Baseline: Millor que predicció naive
  • Documentat: Criteris escrits i versionats
  • Consensuat: Stakeholders d’acord amb els criteris

Amb criteris clars, podem desplegar models amb confiança i justificar les decisions tècniques!

Testing de codi

Un cop hem desenvolupat el nostre servei de prediccions, necessitem assegurar-nos que funciona correctament abans de desplegar-lo. Però testejar sistemes de machine learning és diferent de testejar software tradicional.

Per què el testing ML és diferent?

En software tradicional, testem que el codi fa exactament el que esperem: suma(2, 3) ha de retornar 5, sempre. Però en ML, no podem testejar prediccions concretes perquè:

  • Els models són estocàstics: L’entrenament té randomització, el model pot canviar
  • L’accuracy fluctua: Un model millor podria donar diferents prediccions
  • El que importa és el rendiment global, no prediccions individuals

Principi clau: Testem la infraestructura (el codi al voltant del model), no el model en si.

Què SÍ hem de testejar

1. Validació de dades

Les funcions que validen les dades d’entrada són deterministes i crítiques:

# src/validation.py def validate_age(age: float) -> bool: """Comprova que l'edat és vàlida.""" return 0 <= age <= 120 def validate_income(income: float) -> bool: """Comprova que l'ingrés és vàlid.""" return income >= 0
# tests/test_validation.py def test_validate_age_valid(): assert validate_age(30) == True assert validate_age(0) == True assert validate_age(120) == True def test_validate_age_invalid(): assert validate_age(-5) == False assert validate_age(150) == False assert validate_age(float('nan')) == False

Per què: Bugs en la validació poden permetre dades invàlides que trenquen el model silenciosament.

2. Funcions de preprocessament

Les transformacions de dades han de produir resultats correctes:

# src/preprocessing.py def normalize_value(value: float, min_val: float, max_val: float) -> float: """Normalitza un valor entre 0 i 1.""" return (value - min_val) / (max_val - min_val)
# tests/test_preprocessing.py def test_normalize_value(): assert normalize_value(0, 0, 100) == 0.0 assert normalize_value(100, 0, 100) == 1.0 assert normalize_value(50, 0, 100) == 0.5

3. Càrrega i funcionament bàsic del model

No testem accuracy, però sí que el model es pot carregar i usar:

# tests/test_model.py import joblib def test_model_loads(): """Verifica que el model es pot carregar.""" model = joblib.load('models/model.pkl') assert model is not None assert hasattr(model, 'predict') def test_model_predicts_correct_shape(): """Verifica que les prediccions tenen la forma correcta.""" model = joblib.load('models/model.pkl') X = [[30, 50000, 5]] # Exemple amb 3 features predictions = model.predict(X) assert len(predictions) == 1 assert isinstance(predictions[0], (int, float))

4. Endpoints de l’API

Els tests d’integració verifiquen que l’API funciona correctament:

# tests/test_api.py from fastapi.testclient import TestClient from api import app client = TestClient(app) def test_health_endpoint(): """Verifica que el health check funciona.""" response = client.get("/health") assert response.status_code == 200 def test_predict_endpoint_valid(): """Verifica predicció amb dades vàlides.""" response = client.post("/predict", json={ "age": 30, "income": 50000, "score": 5 }) assert response.status_code == 200 assert "prediction" in response.json() def test_predict_endpoint_invalid(): """Verifica que dades invàlides són rebutjades.""" response = client.post("/predict", json={ "age": -5, # Invàlid! "income": 50000 }) assert response.status_code == 422 # Validation error

Què NO hem de testejar

No testejar accuracy o mètriques del model:

# ❌ MAL - Tests inestables! def test_model_accuracy(): assert model.score(X_test, y_test) > 0.95 # Això fallarà impredictiblement

Per què no: L’accuracy varia amb l’entrenament. Això és validació, no testing. Usa el validation set durant l’entrenament, no tests.

No testejar prediccions específiques:

# ❌ MAL - Model pot canviar legítimament def test_specific_prediction(): X = [[30, 50000, 5]] assert model.predict(X)[0] == 1 # Massa específic!

Per què no: Si millorem el model, aquest test podria fallar tot i que el nou model és millor.

No testejar el pipeline d’entrenament complet:

# ❌ MAL - Massa lent i complex def test_entire_training(): data = load_data() # 5 minuts model = train(data) # 20 minuts assert model.score(X_test, y_test) > 0.8 # Massa temps!

Per què no: Els tests han de córrer ràpid (segons, no minuts). Testa components individuals.

Pytest: els fonaments

pytest és l’eina estàndard per testejar en Python. Funciona així:

  1. Crea fitxers que comencin per test_ dins d’un directori tests/
  2. Escriu funcions que comencin per test_
  3. Usa assert per comprovar condicions
# Estructura recomanada project/ ├── src/ │ ├── api.py │ ├── validation.py │ └── preprocessing.py ├── tests/ │ ├── test_api.py │ ├── test_validation.py │ ├── test_preprocessing.py │ └── test_model.py └── models/ └── model.pkl

Executar tests:

# Instal·lar pytest pip install pytest # Executar tots els tests pytest tests/ # Executar amb més detall pytest tests/ -v # Executar només un fitxer pytest tests/test_api.py

Fixtures: reutilitzar configuració

Si necessitem configurar coses abans dels tests (carregar un model, crear dades), usem fixtures:

# tests/conftest.py import pytest import joblib @pytest.fixture def model(): """Carrega el model una vegada per tots els tests.""" return joblib.load('models/model.pkl') @pytest.fixture def sample_data(): """Dades d'exemple per tests.""" return { "age": 30, "income": 50000, "score": 5 }
# tests/test_model.py def test_model_predicts(model, sample_data): """Usa les fixtures model i sample_data.""" X = [[sample_data['age'], sample_data['income'], sample_data['score']]] prediction = model.predict(X) assert prediction is not None

Type hints: validació estàtica

A més de tests, podem detectar errors abans d’executar el codi amb type hints i eines com mypy. Els type hints són anotacions de tipus que indiquen què espera cada funció.

Per què són útils en ML:

  • Detecció d’errors: mypy detecta tipus incorrectes abans d’executar
  • Documentació viva: Els tipus expliquen què espera cada funció
  • IDE autocomplete: Millora la productivitat

Quan usar-los:

  • Sempre en funcions públiques (APIs, endpoints)
  • Sempre en funcions de validació i preprocessament
  • ⚠️ Opcional en scripts d’experimentació o notebooks

Exemple:

# ✅ Amb type hints def predict(features: list[float]) -> dict[str, float]: ... def preprocess(data: pd.DataFrame, columns: list[str]) -> pd.DataFrame: ...

Validació amb mypy:

pip install mypy mypy src/ # Exemple d'error que detecta: # error: Argument 1 to "predict" has incompatible type "str"; expected "list[float]"

Configuració recomanada a pyproject.toml:

[tool.mypy] python_version = "3.11" ignore_missing_imports = true # Necessari per sklearn, pandas, etc.

Resum del testing ML

Testa:

  • ✅ Validació de dades
  • ✅ Preprocessament
  • ✅ Càrrega del model
  • ✅ API endpoints
  • ✅ Format de les prediccions

NO testis:

  • ❌ Accuracy del model
  • ❌ Prediccions específiques
  • ❌ Pipeline d’entrenament complet

Recorda: Els tests asseguren que la infraestructura funciona. La qualitat del model es valida durant l’entrenament amb el validation set.

Desplegament automàtic

Ara que sabem què testejar, necessitem automatitzar quan s’executen aquests tests. Aquesta automatització és crítica per garantir que només codi validat arribi a producció.

CI/CD: Integració i desplegament continus

Per garantir que tot el codi passi validació abans d’arribar a producció, necessitem automatització al servidor. Aquí és on entra CI/CD.

Què és CI/CD?

CI/CD (Continuous Integration / Continuous Deployment) és una pràctica estàndard en el desenvolupament de software modern que automatitza la validació i el desplegament del codi.

Continuous Integration (CI) - Integració contínua:

  • Cada cop que un desenvolupador fa push al repositori, s’executen automàticament tests i validacions
  • L’objectiu és detectar errors d’integració aviat, quan són fàcils de corregir
  • Sense CI, els problemes es descobreixen tard (quan s’intenta fer merge o desplegar), quan ja són costosos de resoldre

Continuous Deployment (CD) - Desplegament continu:

  • Un cop el codi passa totes les validacions, es desplega automàticament a producció (o a un entorn de staging)
  • Elimina el procés manual de desplegament, que és propens a errors
  • Permet releases freqüents i incrementals

No

Push

Build

Tests

Passa?

Notificar error

Deploy Staging

Deploy Production

Per què CI/CD és important?

Sense CI/CD, el flux de treball típic és:

  1. Desenvolupador escriu codi durant dies/setmanes
  2. Intenta fer merge → Conflictes amb codi d’altres
  3. Es descobreixen bugs que interaccionen amb altres canvis
  4. Desplegament manual → Errors humans, passos oblidats

Amb CI/CD:

  1. Desenvolupador fa push de canvis petits freqüentment
  2. Cada push es valida automàticament en minuts
  3. Errors detectats immediatament, fàcils de corregir
  4. Desplegament automatitzat, consistent i reproduïble
Sense CI/CDAmb CI/CD
Integració dolorosa (“merge hell”)Integració contínua sense conflictes
Bugs descoberts tardBugs detectats en minuts
Desplegament manual, arriscatDesplegament automàtic, segur
Releases grans i poc freqüentsReleases petites i freqüents

Eines de CI/CD

Les plataformes més comunes són:

  • GitHub Actions: Integrat amb GitHub, configuració amb fitxers YAML
  • GitLab CI/CD: Integrat amb GitLab, molt potent
  • Jenkins: Open-source, autohospedable, molt flexible
  • CircleCI, Travis CI: Alternatives cloud populars

Totes funcionen amb el mateix principi: detectar push → executar tasques definides → bloquejar si fallen.

Alternatives lleugeres: git hooks i scripts

No sempre necessitem plataformes CI/CD completes. Per a projectes més petits o entorns sense accés a serveis externs, podem aconseguir el mateix objectiu amb:

  • Git hooks al servidor: Qualsevol servidor git permet configurar hooks (com post-receive) que executen tests i validen el model quan algú fa push. Si fallen, el push es rebutja.
  • Scripts personalitzats: El script validate_model.py que hem creat s’integra directament en qualsevol d’aquestes solucions.
  • Versionat de models amb git: Guardar models amb noms versionats (model_v1.0.0.pkl), mantenir fitxers JSON amb mètriques, i usar tags de git per marcar versions desplegades. Tot queda traçable sense dependències externes.

Recomanació: Comença amb scripts simples i git hooks. Afegeix complexitat només quan sigui necessari.

CI/CD per a projectes ML

El pipeline CI/CD és una pràctica general de software. Ara veurem com els projectes de ML afegeixen passos específics a aquest pipeline estàndard.

Què afegeix ML al pipeline?

Els projectes de ML necessiten passos addicionals que no existeixen en software tradicional:

FaseSoftware tradicionalML afegeix
Validació d’entradaLinting, formatValidació d’schema de dades
BuildCompilar, empaquetarVerificar càrrega del model
TestsUnit, integracióSmoke tests del model
Quality gatesCode coverageCriteris de desplegament (F1, latència, etc.)
ArtifactsDocker imageDocker + registre de models
DeployBlue-green, canaryShadow mode, champion-challenger

Connexió important: Els criteris de desplegament que hem definit a la secció anterior (F1 > 0.75, recall > 0.70, latència < 100ms) es converteixen en portes de qualitat automàtiques dins del pipeline CI/CD.

Pipeline CI/CD complet per ML

No

No

Push

Lint + Type Check

Validació schema dades

Unit Tests

Build Docker

Model Smoke Test

Criteris tècnics OK?

❌ Pipeline FALLA

Millor que model actual?

Registrar model

Deploy Staging

Integration Tests

Deploy Production

Diferència clau amb software tradicional: Els passos de validació de dades, criteris de model i registre de model són específics de ML.

Integrar els criteris de desplegament

El script validate_model.py que hem creat a la secció anterior s’integra directament al pipeline:

# Exemple conceptual (GitHub Actions) jobs: validate: steps: - name: Run tests run: pytest tests/ -v - name: Validate model criteria run: python scripts/validate_model.py # Falla si el model no compleix F1 > 0.75, etc. - name: Build Docker run: docker build -t model:${{ github.sha }} .

Si validate_model.py retorna exit code 1 (model no aprovat), el pipeline s’atura i no es desplega.

Validació de dades en CI/CD

Un problema comú en ML: l’schema de dades canvia sense avisar. Podem validar-lo automàticament:

# ci/validate_data_schema.py import pandera as pa import pandas as pd schema = pa.DataFrameSchema({ "age": pa.Column(float, checks=pa.Check.in_range(0, 120)), "income": pa.Column(float, checks=pa.Check.ge(0)), "target": pa.Column(int, checks=pa.Check.isin([0, 1])) }) # Validar que les dades de test segueixen l'schema esperat test_data = pd.read_parquet("data/test.parquet") schema.validate(test_data) # Falla si l'schema ha canviat

Això detecta quan algú modifica les dades o afegeix columnes inesperades.

Model registry

A més de Docker images, els projectes ML necessiten versionar els models amb les seves mètriques. Un model registry és un repositori centralitzat que guarda:

  • L’artefacte del model (fitxer .pkl, .onnx, etc.)
  • Mètriques d’avaluació (F1, accuracy, latència)
  • Metadades (data d’entrenament, paràmetres, dataset)
  • Estat (staging, production, archived)

Per a projectes petits, podem implementar un registre senzill amb git: models versionats, fitxers JSON amb mètriques, i tags per marcar versions. Per a projectes més grans, existeixen eines especialitzades com MLflow, DVC o Weights & Biases.

Estratègies de desplegament per a models

El CD tradicional (blue-green, canary) s’aplica a ML, però hi ha estratègies específiques:

EstratègiaDescripcióQuan usar
Rolling updateSubstituir instàncies gradualmentModels estables, canvis menors
Blue-greenDos entorns, switch instantaniNecessitat de rollback ràpid
CanaryX% de tràfic al nou modelValidar rendiment real abans de rollout
Shadow modeNou model processa tràfic però no responPrimer desplegament, zero risc
Champion-challengerA/B test entre modelsComparar rendiment en producció

Shadow mode és especialment útil per a ML: el model nou rep les mateixes peticions que el model en producció, però les respostes es descarten (només es loguen per comparar).

# Concepte de shadow mode async def predict(request): # Champion (model actual) respon al client result = champion_model.predict(request.data) # Challenger (model nou) s'executa en paral·lel, només per logging asyncio.create_task( log_shadow_prediction(challenger_model, request.data) ) return result # Només el champion respon

Implementació progressiva

No cal implementar-ho tot de cop:

  1. Fase 1: Validació manual (executar tests i validate_model.py abans de desplegar)
  2. Fase 2: Git hooks locals per feedback ràpid
  3. Fase 3: CI bàsic (tests + build Docker)
  4. Fase 4: CI complet (data validation + model gates + model registry)
  5. Fase 5: CD automatitzat amb estratègies de desplegament

Git hooks: validació local

A més de CI/CD al servidor, podem afegir una capa de validació local per obtenir feedback immediat. Els git hooks són scripts que s’executen automàticament en resposta a esdeveniments de git.

El més útil és el pre-commit hook: abans d’acceptar un commit, executa els tests automàticament i rebutja el commit si fallen.

git commit -m "fix bug" ↓ [Executa tests automàticament] ↓ Tests OK? ──┬─ SÍ → Commit creat ✓ └─ NO → Commit REFUSAT ✗

Avantatge: Feedback en segons, sense esperar el pipeline de CI/CD.

Limitació: Els hooks són locals - si algú els desactiva (--no-verify), el codi erroni pot arribar al repositori. Per això CI/CD és imprescindible com a última línia de defensa.

Resum: Doble capa de protecció

Validació local (Git hooks)Validació remota (CI/CD)
Feedback ràpid (segons)Feedback més lent (minuts)
Es pot saltarImpossible saltar
Detecta errors abans del pushDetecta errors abans del desplegament
Configurat per cada desenvolupadorConfigurat centralment
Primera línia de defensaÚltima línia de defensa

Junts, garanteixen que el codi erroni no arribi mai a producció!

Optimització en producció

Quan passem d’un notebook a un sistema en producció, un dels reptes principals és la velocitat d’inferència: quant triga el model a respondre. Les tècniques més importants per reduir latència i consum de recursos són:

Reduir la mida i el cost del model

Aquestes tècniques fan el model més lleuger sense alterar gaire la seva qualitat:

  • Quantització: Fer els números més petits.

    • Consisteix a representar els pesos del model amb menys bits.
    • Això redueix memòria, temps de càlcul i sovint té un impacte molt petit en la precisió.
    • Exemples:
      • float32 → float16: la meitat d’espai, gairebé mateixa qualitat
      • float32 → int8: un 75% menys d’espai, rendiment molt millor
    • Útil quan la latència és crítica (mòbils, edge, real-time).
  • Poda (Pruning): Treure neurones que fan poca feina.

    • Elimina parts del model que aporten molt poc.
    • Imaginem una xarxa com un arbre: la poda treu branques inútils.
    • Menys pesos → càlcul més ràpid.
    • L’impacte en precisió sol ser petit si es fa progressivament.
  • Destil·lació (Knowledge Distillation): Un model gran ensenya a un de petit com comportar-se.

    • El model “professor” genera sortides suaus.
    • Un model “estudiant” més petit aprèn a imitar-lo.
    • Permet mantenir gairebé el mateix rendiment amb un model molt més lleuger.
  • Factorització de baix rang (Low-rank): Descompondre matrius grans en peces més petites.

    • Divideix matrius de pesos en dues o tres matrius petites.
    • Mantenim quasi el mateix comportament amb menys càlcul.

Optimitzar el flux intern del model

Millorem com el model “fa les coses per dins”:

  • Fusió d’operacions (Operator Fusion): Fer diverses operacions en una sola passa.

    • Evita moviments de memòria i passos innecessaris.
    • Per exemple, combinar Convolució + BatchNorm + ReLU.
    • Runtimes com ONNX Runtime, TensorRT o OpenVINO ho fan automàticament.
  • Optimització del graf (Graph Optimization): Netejar i reordenar les operacions.

    • Inclou:
      • treure nodes inútils
      • simplificar expressions
      • refactoritzar el flux d’operacions
    • S’activa sovint en exportar a ONNX.
  • Compilació específica per hardware: Generar codi optimitzat per la màquina on s’executarà.

    • Exemples:
      • TorchScript (PyTorch)
      • XLA (TensorFlow)
      • TVM (compilador ML open source)
      • TensorRT (NVIDIA, molt ràpid en GPU)

Escollir millors models i millor arquitectura

A vegades la millor optimització és utilitzar un model més eficient:

  • Models dissenyats per ser lleugers: Arquitectures pensades per ser ràpides.

    • Exemples coneguts:
      • MobileNet (visió)
      • EfficientNet-Lite (visió)
      • DistilBERT / TinyBERT (text)
      • Whisper-Tiny (àudio)
  • Selecció de features: Menys característiques = menys feina.

    • El pre-processament pot ser la part més lenta del pipeline.
    • Reduir features també redueix latència.

Estratègies de serving i producció

Maneres de millorar el rendiment al desplegar el model:

  • Batching dinàmic: Agrupar peticions en mini-lots per aprofitar millor el hardware.

    • Permet fer 10 inferències en una sola passa en comptes de 10 passes separades.
    • Eines que ho ofereixen:
      • NVIDIA Triton
      • Ray Serve
      • KServe
      • BentoML
  • Paral·lelització i multithreading: Aprofitar diversos nuclis de CPU.

    • Podeu servir múltiples peticions en paral·lel amb diversos workers o processos.
  • Caching d’inferències: Si una petició es repeteix, no cal recalcular.

    • Desa resultats en memòria (Redis/Memcached).
    • Per a moltes aplicacions, és una de les optimitzacions més potents.

Millorar l’aprofitament del hardware

Per millorar l’aprofitament del hardware:

  • GPU acceleration: Les GPUs estan fetes per multiplicar matrius molt ràpid.

    • Ideal per models grans i procesaments paral·lels.
  • Instruccions vectorials de CPU: Fer més operacions per instrucció.

    • Si el servidor suporta AVX/AVX2/AVX-512, el model anirà força més ràpid.
  • Hardware especialitzat: Dispositius creats només per fer inferència.

    • Google TPU
    • AWS Inferentia
    • Intel Movidius (edge)

Bones pràctiques quan uses Docker en producció

Bones pràctiques quan uses Docker en producció:

  • Utilitzar imatges minimalistes: Menys capa → arrencada més ràpida → menys vulnerabilitats.
  • Precarregar el model en arrencar: Evita la latència de carregar-lo quan arriba la primera petició.
  • Evitar dependències innecessàries: Menys mida, menys riscos, menys temps de build.
  • Monitoritzar sempre: Prometheus + Grafana per controlar:
    • latència
    • throughput
    • càrrega CPU/GPU
    • memòria i ús de model

Resum final

AccióTècniques principals
Reduir el modelquantització, poda, destil·lació, low-rank
Optimitzar el modelfusió d’operacions, optimització del gràfic, compilació
Escollir arquitecturamodels lleugers, menys features
Millorar el servingbatching, paral·lelització, caching
Aprofitar hardwareGPU, AVX, dispositius especialitzats
Desplegamentimatges petites, precarrega, monitorització

Resum

En aquest capítol hem après a garantir la qualitat dels nostres models:

  • Definir criteris objectius (tècnics, de negoci, operacionals) per aprovar desplegaments
  • Testejar la infraestructura ML sense testejar el model mateix
  • Usar git hooks per validació local automàtica abans de commits
  • Implementar CI/CD per validació al servidor abans de desplegaments
  • Aplicar tècniques d’optimització quan sigui necessari

Amb aquestes pràctiques, tenim control complet sobre la qualitat dels models desplegats. Al proper capítol veurem com monitoritzar models en producció i detectar quan les dades canvien (drift).