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 # Mètriques tècniques min_accuracy: 0.80 min_f1: 0.75 min_recall: 0.70 # Mètriques operacionals (opcional) max_latency_ms: 100 max_model_size_mb: 500

Implementar la validació

La validació automàtica consisteix en:

  1. Carregar els criteris des del fitxer YAML
  2. Calcular les mètriques del model candidat (F1, recall, precision)
  3. Comparar cada mètrica contra el llindar mínim
  4. Si ja hi ha un model en producció, verificar que el nou no és pitjor (tolerant una petita degradació, p.ex. 2%)
  5. Aprovar o rebutjar automàticament

L’script de validació (validate_model.py) pot retornar exit code 0 (aprovat) o 1 (rebutjat), integrant-se directament amb CI/CD i git hooks.

📖 Implementació pràctica: El patró deployment_ready al capítol de patrons d’implementació explica com guardar el resultat d’aquesta validació en un flag dins del metadata del model, i com el CI/CD l’utilitza per bloquejar desplegaments de models no validats.

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.

Testing vs. Validació: una distinció fonamental

En sistemes de machine learning, cal distingir clarament entre dos conceptes que sovint es confonen:

Testing: Components deterministes

Què és: Verificar que el codi funciona correctament amb comportament predictible i reproduïble.

Què testem:

  • Funcions de validació de dades
  • Preprocessament de features
  • Càrrega de models
  • Endpoints de l’API
  • Lògica de negoci
  • Gestió d’errors

Eina: pytest (tests automatitzats, execució ràpida)

Exemple:

def test_normalize_value(): """Test determinista: sempre ha de retornar el mateix resultat.""" assert normalize(50, 0, 100) == 0.5 assert normalize(0, 0, 100) == 0.0

Validació: Comportament del model

Què és: Avaluar el rendiment estadístic del model amb comportament estocàstic i variable.

Què validem:

  • Accuracy, precision, recall, F1
  • ROC-AUC, precisió mitjana
  • Mètriques de negoci
  • Distribució de prediccions
  • Calibratge del model

Eines: Validation set durant entrenament, MLflow per tracking, anàlisi exploratòria

Exemple:

# Això NO és un test pytest, és validació durant l'entrenament def evaluate_model(model, X_val, y_val): """Validació: mètriques poden variar entre execucions.""" y_pred = model.predict(X_val) f1 = f1_score(y_val, y_pred) print(f"F1 score: {f1:.3f}") # Pot ser 0.823, després 0.819... return f1

Per què aquesta distinció és crítica?

Problema: Si barregem testing i validació, obtenim tests inestables (flaky tests):

# ❌ MAL - Test inestable! def test_model_accuracy(): model = train_model(X_train, y_train) accuracy = model.score(X_test, y_test) assert accuracy > 0.95 # Pot fallar aleatòriament!

Aquest test pot fallar per raons legítimes:

  • Randomització diferent a l’entrenament
  • Canvis en hiperparàmetres que milloren el model però canvien predictions
  • Variabilitat normal en models probabilístics

Solució correcta:

# ✅ BÉ - Testejar infraestructura def test_model_loads(): """Verificar que el model es pot carregar.""" model = load_model('models/model.pkl') assert model is not None assert hasattr(model, 'predict') def test_model_predicts_valid_format(): """Verificar que les prediccions tenen el format correcte.""" model = load_model('models/model.pkl') X = pd.DataFrame({'age': [30], 'income': [50000]}) predictions = model.predict(X) assert len(predictions) == 1 assert isinstance(predictions[0], (int, np.integer, float, np.floating))

Validació durant entrenament (NO en pytest):

# Executar durant train.py, no com a test def main(): model = train_model(X_train, y_train) metrics = evaluate_model(model, X_val, y_val) # Criteris de desplegament if metrics['f1'] >= 0.75 and metrics['recall'] >= 0.70: save_model(model, 'models/model_v2.pkl') print("✓ Model validat i guardat") else: print("✗ Model no compleix criteris")

Taula resum

AspecteTestingValidació
ComportamentDeterministaEstocàstic
Què verificaCodi funciona correctamentModel és prou bo
Eina principalpytestValidation set, MLflow
ExecucióCada commit (CI/CD)Durant/després entrenament
FallidaBug en codi → cal arreglarRendiment insuficient → iterar model
EstabilitatHa de ser 100% establePot variar lleugerament

Regla d’or: Si el resultat pot canviar legítimament entre execucions → és validació, no testing.

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 @pytest.fixture def model(): return joblib.load('models/model.pkl') # tests/test_model.py def test_model_predicts(model): # Fixture injectada automàticament prediction = model.predict([[30, 50000, 5]]) 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.

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). Veurem la implementació al capítol d’aprenentatge continu.

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: automació per self-hosted

Els git hooks són una tècnica fonamental de CI/CD, especialment en entorns self-hosted on controlem la infraestructura. Són scripts que s’executen automàticament en resposta a esdeveniments de git.

Tipus de hooks

Client-side hooks (al repositori local del desenvolupador):

  • pre-commit: S’executa abans de crear un commit
  • pre-push: S’executa abans de fer push

Server-side hooks (al servidor git):

  • pre-receive: S’executa abans d’acceptar un push
  • post-receive: S’executa després d’acceptar un push (desplegament!)
  • update: S’executa per cada branch que es pugi

Pre-commit hook: validació local

El pre-commit hook s’executa automàticament abans de crear un commit al repositori local. El seu propòsit és donar feedback immediat al desenvolupador.

Què fa:

  • S’executa automàticament quan fas git commit
  • Executa validacions ràpides (tests, linters, format)
  • Si alguna validació falla, rebutja el commit
  • Si tot va bé, permet crear el commit

Flux conceptual:

git commit -m "fix bug" ↓ [Hook executa tests automàticament] ↓ Tests OK? ──┬─ SÍ → Commit creat ✓ └─ NO → Commit REFUSAT ✗ (el desenvolupador veu l'error immediatament)

Avantatge principal: Feedback en segons, abans que el codi surti del teu ordinador.

Limitació important: El desenvolupador pot saltar-se’l amb git commit --no-verify. Per això, la validació definitiva ha d’estar al servidor, no només al client.

Post-receive hook: desplegament automàtic

El post-receive hook és el cor del CI/CD en entorns self-hosted. S’executa al servidor git després d’acceptar un push.

Què fa:

  • S’executa automàticament quan algú fa git push al servidor
  • Típicament només s’activa per la branch principal (main/master)
  • Executa tot el pipeline de validació i desplegament
  • Si qualsevol pas falla, aborta el desplegament

Pipeline típic:

  1. Checkout: Actualitza el codi en el directori de treball del servidor
  2. Test: Executa els tests en un entorn aïllat (per exemple, amb docker-compose.validate.yml)
  3. Validació del model: Verifica que el model compleix criteris mínims de qualitat
  4. Build i desplegament: Construeix i aixeca els contenidors de producció
  5. Health check: Verifica que l’API respon correctament

Si qualsevol d’aquests passos falla, el desplegament s’atura i el desenvolupador rep l’error.

Flux conceptual:

No

No

No

Developer: git push

Server: post-receive hook

Checkout codi

Executar tests

Tests OK?

❌ Abortar

Validar model

Model OK?

Build & Deploy

Health check

Healthy?

✓ Desplegat

Organització de hooks en projectes grans

En projectes més complexos, és recomanable modularitzar els hooks separant cada fase del pipeline en scripts independents:

Fases típiques:

  • Validació: Executar tests automàtics
  • Pre-desplegament: Validar que el model compleix criteris de qualitat
  • Desplegament: Aixecar els nous contenidors/serveis
  • Post-desplegament: Verificar que tot funciona (health checks)

Avantatge de la modularització:

  • Cada script té una responsabilitat única
  • És més fàcil debugar quan alguna fase falla
  • Es poden reutilitzar scripts en diferents contextos
  • El hook principal només orquestra l’execució seqüencial

El hook principal (per exemple, post-receive) simplement crida cada script en ordre, i si qualsevol falla, atura tot el pipeline.

Quan usar git hooks vs. plataformes CI/CD?

Git hooks són ideals quan:

  • Controlem el servidor (self-hosted)
  • Volem solució simple sense dependències externes
  • Projectes petits-mitjans
  • Desplegaments a un sol servidor
  • Necessitem automatització immediata

Plataformes CI/CD (GitHub Actions, GitLab CI) quan:

  • Repositoris públics o cloud
  • Múltiples entorns (staging, production, diferents clouds)
  • Projectes grans amb múltiples equips
  • Necessitem workflows complexos (matriu de tests, múltiples SO)
  • Volem UI visual del pipeline

Combinació: Poden conviure! Pre-commit local + post-receive al servidor + GitHub Actions per tests addicionals.

Bones pràctiques

  1. Idempotència: Els scripts han de poder executar-se múltiples vegades sense problemes
  2. Logs clars: Missatges descriptius de què està passant
  3. Exit codes: exit 0 si èxit, exit 1 si error
  4. Timeouts: Evitar que hooks pengin indefinidament
  5. Rollback: Estratègia per revertir desplegaments fallits

Resum: Doble capa de protecció

Validació local (pre-commit)Validació remota (post-receive)
Feedback ràpid (segons)Feedback després del push (minuts)
Es pot saltar (–no-verify)Impossible saltar
Detecta errors abans del pushDetecta errors abans del desplegament
Configurat per cada desenvolupadorConfigurat centralment al servidor
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).