Validació i qualitat
- Introducció
- Validació de models
- Testing de codi
- Desplegament automàtic
- Optimització en producció
- Resum
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ètrica | Descripció | Exemple de llindar |
|---|---|---|
| Accuracy | Percentatge de prediccions correctes | > 0.85 |
| F1 Score | Mitjana 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-AUC | Capacitat de discriminar entre classes | > 0.85 |
| MAE/RMSE | Error 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ètrica | Descripció | Exemple de llindar |
|---|---|---|
| Latència p50 | Temps de resposta mitjà | < 50ms |
| Latència p95 | Temps de resposta del 95% de peticions | < 100ms |
| Latència p99 | Temps de resposta del 99% de peticions | < 200ms |
| Mida del model | Espai en disc / memòria | < 500 MB |
| Throughput | Prediccions 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:
- Carregar els criteris des del fitxer YAML
- Calcular les mètriques del model candidat (F1, recall, precision)
- Comparar cada mètrica contra el llindar mínim
- Si ja hi ha un model en producció, verificar que el nou no és pitjor (tolerant una petita degradació, p.ex. 2%)
- 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_readyal 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
| Aspecte | Testing | Validació |
|---|---|---|
| Comportament | Determinista | Estocàstic |
| Què verifica | Codi funciona correctament | Model és prou bo |
| Eina principal | pytest | Validation set, MLflow |
| Execució | Cada commit (CI/CD) | Durant/després entrenament |
| Fallida | Bug en codi → cal arreglar | Rendiment insuficient → iterar model |
| Estabilitat | Ha de ser 100% estable | Pot 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í:
- Crea fitxers que comencin per
test_dins d’un directoritests/ - Escriu funcions que comencin per
test_ - Usa
assertper 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:
mypydetecta 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
Per què CI/CD és important?
Sense CI/CD, el flux de treball típic és:
- Desenvolupador escriu codi durant dies/setmanes
- Intenta fer merge → Conflictes amb codi d’altres
- Es descobreixen bugs que interaccionen amb altres canvis
- Desplegament manual → Errors humans, passos oblidats
Amb CI/CD:
- Desenvolupador fa push de canvis petits freqüentment
- Cada push es valida automàticament en minuts
- Errors detectats immediatament, fàcils de corregir
- Desplegament automatitzat, consistent i reproduïble
| Sense CI/CD | Amb CI/CD |
|---|---|
| Integració dolorosa (“merge hell”) | Integració contínua sense conflictes |
| Bugs descoberts tard | Bugs detectats en minuts |
| Desplegament manual, arriscat | Desplegament automàtic, segur |
| Releases grans i poc freqüents | Releases 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.pyque 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:
| Fase | Software tradicional | ML afegeix |
|---|---|---|
| Validació d’entrada | Linting, format | Validació d’schema de dades |
| Build | Compilar, empaquetar | Verificar càrrega del model |
| Tests | Unit, integració | Smoke tests del model |
| Quality gates | Code coverage | Criteris de desplegament (F1, latència, etc.) |
| Artifacts | Docker image | Docker + registre de models |
| Deploy | Blue-green, canary | Shadow 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
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ègia | Descripció | Quan usar |
|---|---|---|
| Rolling update | Substituir instàncies gradualment | Models estables, canvis menors |
| Blue-green | Dos entorns, switch instantani | Necessitat de rollback ràpid |
| Canary | X% de tràfic al nou model | Validar rendiment real abans de rollout |
| Shadow mode | Nou model processa tràfic però no respon | Primer desplegament, zero risc |
| Champion-challenger | A/B test entre models | Comparar 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:
- Fase 1: Validació manual (executar tests i
validate_model.pyabans de desplegar) - Fase 2: Git hooks locals per feedback ràpid
- Fase 3: CI bàsic (tests + build Docker)
- Fase 4: CI complet (data validation + model gates + model registry)
- 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 commitpre-push: S’executa abans de fer push
Server-side hooks (al servidor git):
pre-receive: S’executa abans d’acceptar un pushpost-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 pushal 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:
- Checkout: Actualitza el codi en el directori de treball del servidor
- Test: Executa els tests en un entorn aïllat (per exemple, amb
docker-compose.validate.yml) - Validació del model: Verifica que el model compleix criteris mínims de qualitat
- Build i desplegament: Construeix i aixeca els contenidors de producció
- 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:
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
- Idempotència: Els scripts han de poder executar-se múltiples vegades sense problemes
- Logs clars: Missatges descriptius de què està passant
- Exit codes:
exit 0si èxit,exit 1si error - Timeouts: Evitar que hooks pengin indefinidament
- 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 push | Detecta errors abans del desplegament |
| Configurat per cada desenvolupador | Configurat 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 qualitatfloat32 → 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.
- Inclou:
-
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)
- Exemples:
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)
- Exemples coneguts:
-
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 model | quantització, poda, destil·lació, low-rank |
| Optimitzar el model | fusió d’operacions, optimització del gràfic, compilació |
| Escollir arquitectura | models lleugers, menys features |
| Millorar el serving | batching, paral·lelització, caching |
| Aprofitar hardware | GPU, AVX, dispositius especialitzats |
| Desplegament | imatges 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).