CI i CD reaccionen a esdeveniments de codi: un push, una PR, un tag. Però al mòdul 4 va quedar una decisió pendent de complir: el scoring batch de churn s'ha d'executar cada dilluns a les 06:00 per alimentar la campanya de retenció de màrqueting, i a dia d'avui continua sent "algú llança score_batch.py els dilluns". Cap commit no dispara això — ho dispara el calendari. I no n'hi ha prou amb programar-ho: cal reintentar-ho si el remote de dades falla a les 06:01, avisar algú si falla de debò, i poder respondre el dimarts la pregunta "va córrer el scoring ahir i amb quin resultat?". Això és orquestració, i aquesta lliçó la resol amb Prefect: entendràs què afegeix un orquestrador sobre cron i sobre dvc repro, compararàs les opcions del mercat, i construiràs el flow real del scoring setmanal de CineClick amb reintents, timeout, alertes i schedule.
Contingut
- Què és un orquestrador i què afegeix sobre cron i
dvc repro - Panorama d'orquestradors i per què Prefect per a CineClick
- Conceptes de Prefect: flow, task, deployment, schedule, work pool
- El flow real:
flows/scoring_setmanal.py - Fallades: retries amb backoff, timeout i alertes
- Programar el dilluns a les 06:00
- Esbós del segon flow: reentrenament
- Orquestrador vs. CI/CD: què va a cada lloc
Què és un orquestrador i què afegeix sobre cron i dvc repro
La temptació immediata és un crontab: 0 6 * * 1 python scripts/score_batch.py. Funciona… fins a la primera matinada en què el remote de DVC triga a respondre, l'script mor, ningú no se n'assabenta, i màrqueting llança la campanya del dilluns amb el parquet de la setmana passada. Cron llança processos; no els opera. La diferència és a tot el que passa quan alguna cosa no va bé:
| Necessitat | cron | dvc repro a mà |
Orquestrador |
|---|---|---|---|
| Planificació (calendari) | Sí | No | Sí |
| Dependències entre passos | No (un script monolític) | Sí (el DAG del dvc.yaml) |
Sí |
| Reintents automàtics amb espera | No | No | Sí, per task i configurable |
| Timeout per pas | No | No | Sí |
| Alertes en fallar | Només si et muntes l'email de cron | No | Sí (Slack, webhook, email) |
| Visibilitat històrica ("va córrer dilluns?") | Grep al syslog, amb sort | No | UI amb cada execució, estat i logs |
| Reexecutar només el que ha fallat | No | Parcial (caceja stages) | Sí |
| Pas de dades entre passos | Fitxers i fe | Fitxers declarats | Valors de retorn de funcions |
Ull a la segona columna: dvc repro ja ens dona un DAG reproduïble (mòdul 2) i no el llençarem. DVC sap quins passos depenen de quines dades; el que no sap és quan executar-se, reintentar si la xarxa parpelleja ni avisar si falla. Orquestrador i DVC són complementaris: l'orquestrador és el despertador, el supervisor i el megàfon; DVC (o les nostres funcions Python) és la feina en si. De fet, l'esbós de reentrenament del final d'aquesta lliçó embolcalla dvc repro dins d'una task de Prefect.
Panorama d'orquestradors i per què Prefect per a CineClick
| Orquestrador | Filosofia | Fort en | Quan triar-lo |
|---|---|---|---|
| Airflow | El veterà: DAGs declarats, scheduler central, ecosistema enorme d'operadors | Equips de dades grans amb centenars de DAGs i necessitat d'integracions (BigQuery, Spark…) | Ja hi ha Airflow a l'empresa, o el volum/varietat de pipelines ho justifica |
| Prefect | Pythònic: un flow és una funció decorada; la infraestructura mínima és un procés | Equips petits que volen orquestrar codi Python existent sense reescriure'l | Pocs pipelines, equip Python, ganes de pagar poca infraestructura |
| Dagster | Orientat a assets de dades: declares quines dades produeixes, no només quines tasques corren | Llinatge i qualitat de dades com a ciutadans de primera; bon tipatge | El catàleg de dades i el seu llinatge són el centre del problema |
| Kubeflow Pipelines | Natiu de Kubernetes: cada pas és un contenidor | ML pesat sobre K8s: GPUs, entrenament distribuït, aïllament per pas | Plataforma ML corporativa sobre Kubernetes amb molts equips |
L'elecció per a CineClick ja va quedar compromesa al mòdul 1: Prefect. La justificació, ara que sabem què cal orquestrar, s'aguanta sola:
- Volum mínim: dos pipelines (scoring setmanal i, més endavant, reentrenament). Aixecar un Airflow amb el seu scheduler, la seva base de dades i els seus workers per a dos flows és matar mosques a canonades.
- És Python del que ja tenim:
score_batch.pyexisteix i funciona; amb Prefect, convertir-lo en flow és afegir decoradors, no reescriure'l en un altre paradigma. - Infraestructura gairebé nul·la: un worker de Prefect és un procés Python; pot córrer en un pod petit del clúster que ja operem. Amb Prefect Cloud (o un servidor Prefect self-hosted lleuger) tenim UI, schedules i alertes sense administrar gairebé res.
- Kubeflow quedaria justificat si entrenéssim distribuït amb GPUs — i la GPU va quedar descartada per escrit a 04-05.
Conceptes de Prefect: flow, task, deployment, schedule, work pool
Prefect té més peces de les que necessitem; aquestes cinc basten per a tot el que farem:
- Task: una funció Python decorada amb
@task. És la unitat de reintent, timeout i logging. Regla pràctica: una task = un pas que voldries reintentar o veure a la UI per separat. - Flow: una funció decorada amb
@flowque crida tasks (i opcionalment altres flows). Defineix l'ordre i les dependències — implícites: si la task B rep el resultat de l'A, B espera l'A. - Deployment: un flow registrat al servidor de Prefect amb la seva configuració: des d'on s'executa, amb quins paràmetres i amb quin schedule. És el que converteix "una funció al meu repo" en "una cosa que el servidor sap llançar".
- Schedule: el calendari del deployment; farem servir una expressió cron.
- Work pool i worker: el work pool és la cua on el servidor deixa la feina pendent; el worker és el procés (en el nostre cas, de tipus
process: un simple procés Python en un pod) que escolta aquesta cua i executa els flows. Aquesta separació permet que el servidor no executi res ell mateix: només coordina.
flowchart LR
S[Servidor Prefect<br/>schedules + estat + UI] -->|"dilluns 06:00: hi ha feina"| WP[(Work pool<br/>cineclick-pool)]
WP -->|la recull| W[Worker 'process'<br/>pod al clúster]
W -->|executa| F[flow scoring_setmanal]
F -->|estats i logs| SEl flow real: flows/scoring_setmanal.py
Traduïm la decisió de 04-01 (scoring batch setmanal per a la campanya de retenció) a un flow amb sis tasks. Cada task reutilitza peces que ja existeixen: el dvc pull del mòdul 2, l'esquema_clients de pandera de 05-01, el champion del registry del mòdul 3 i la predicció vectoritzada de 04-05.
# flows/scoring_setmanal.py
"""Scoring batch setmanal de churn per a la campanya de retenció.
Programat: dilluns 06:00. Sortida: data/prediccions_churn.parquet"""
import hashlib
import subprocess
from datetime import datetime
import mlflow
import pandas as pd
import requests
from prefect import flow, task, get_run_logger
from cineclick_churn.features import construir_features
from cineclick_churn.validacio import esquema_clients
RUTA_DADES = "data/clients_churn.csv"
RUTA_SORTIDA = "data/prediccions_churn.parquet"
URI_CHAMPION = "models:/churn-cineclick@champion"
WEBHOOK_MARKETING = "https://hooks.slack.com/services/XXX/simulat"
@task(retries=3, retry_delay_seconds=[30, 120, 300], timeout_seconds=600)
def descarregar_dades_fresques() -> pd.DataFrame:
"""dvc pull del dataset. Fràgil per xarxa: 3 reintents amb backoff."""
subprocess.run(["dvc", "pull", RUTA_DADES], check=True)
return pd.read_csv(RUTA_DADES)
@task # sense retries: si les dades són invàlides, reintentar no les arregla
def validar_dades(df: pd.DataFrame) -> pd.DataFrame:
"""El mateix esquema pandera de la CI, ara sobre les dades completes."""
return esquema_clients.validate(df, lazy=True)
@task(retries=2, retry_delay_seconds=60, timeout_seconds=300)
def carregar_champion():
"""Carrega models:/churn-cineclick@champion. Fràgil per xarxa (registry)."""
return mlflow.sklearn.load_model(URI_CHAMPION)
@task
def predir_lot(model, df: pd.DataFrame) -> pd.DataFrame:
"""Predicció vectoritzada sobre tot el dataframe de cop
(la lliçó 04-05: res d'iterar files, ~370x més ràpid)."""
X = construir_features(df.drop(columns=["abandonament"], errors="ignore"))
probas = model.predict_proba(X)[:, 1]
return pd.DataFrame({
"id_client": df["id_client"],
"probabilitat_abandonament": probas,
"data_scoring": datetime.now().date().isoformat(),
})
@task
def escriure_prediccions(prediccions: pd.DataFrame) -> str:
prediccions.to_parquet(RUTA_SORTIDA, index=False)
return RUTA_SORTIDA
@task(retries=3, retry_delay_seconds=30)
def notificar_marketing(ruta: str, n_clients: int, n_risc: int) -> None:
"""Webhook (Slack simulat): màrqueting sap que el fitxer està a punt."""
requests.post(WEBHOOK_MARKETING, json={
"text": (f"Scoring setmanal de churn a punt: {ruta} — "
f"{n_clients} clients, {n_risc} en risc (p >= 0.5).")
}, timeout=10).raise_for_status()
@flow(name="scoring-setmanal-churn", log_prints=True)
def scoring_setmanal():
logger = get_run_logger()
df = descarregar_dades_fresques()
df = validar_dades(df)
model = carregar_champion()
prediccions = predir_lot(model, df)
ruta = escriure_prediccions(prediccions)
n_risc = int((prediccions["probabilitat_abandonament"] >= 0.5).sum())
logger.info("Scoring completat: %s files, %s en risc", len(prediccions), n_risc)
notificar_marketing(ruta, len(prediccions), n_risc)
if __name__ == "__main__":
scoring_setmanal() # executable en local tal qual, sense servidorPunts que mereixen aturar-s'hi:
- El flow es llegeix com el procés: descarregar → validar → carregar el champion → predir → escriure → notificar. Les dependències no es declaren en YAML: surten del fet que cada task consumeix el retorn de l'anterior. Si
validar_dadesfalla, res del que ve després no s'executa. - Els reintents no són uniformes, i això és deliberat.
descarregar_dades_fresquesicarregar_championfallen per xarxa — reintentar té sentit.validar_dadesfalla perquè les dades estan malament — reintentar la mateixa validació sobre les mateixes dades és perdre temps i enterrar l'alerta; ha de fallar ràpid i alt. - Reutilització total:
construir_featuresiesquema_clientssón exactament els mateixos objectes que fan servir l'API online, la CI i l'entrenament. El scoring batch no té la seva pròpia lògica de features ni el seu propi contracte de dades — aquella va ser la batalla guanyada al mòdul 3. - Executable en local:
python flows/scoring_setmanal.pycorre el flow complet sense servidor ni worker. Depurar un flow de Prefect és depurar una funció Python — una de les raons de l'elecció.
Fallades: retries amb backoff, timeout i alertes
Anatomia dels reintents
retries=3: fins a 4 intents en total (l'original més 3).retry_delay_seconds=[30, 120, 300]: espera creixent (backoff) — 30 s, després 2 min, després 5 min. Una fallada transitòria de xarxa sol resoldre's sola; martellejar cada segon només afegeix càrrega al servei que ja està patint.timeout_seconds=600: si eldvc pulles queda penjat (pitjor que fallar: no avisa), al cap de 10 minuts Prefect mata l'intent i compta com a fallada — que al seu torn dispara el reintent següent.
I quan falla de debò
Suposa que el dilluns el remote magatzem està caigut una hora sencera. Els 4 intents s'esgoten (06:00, 06:10 amb timeout + esperes…), la task queda en estat Failed, el flow sencer queda Failed, i passen tres coses:
- Estat visible: l'execució apareix en vermell a la UI de Prefect, amb el traceback de cada intent i els seus temps. El dimarts no cal reconstruir què va passar — està escrit.
- Alerta: una automation de Prefect ("quan un flow run de
scoring-setmanal-churnentri en Failed → notifica al canal#alertes-ml") avisa l'equip. La configuració és declarativa al servidor; no cal programar res al flow. Distingeix-la denotificar_marketing: aquella task és part del procés de negoci (avisar que el parquet està a punt); l'automation és operació (avisar que alguna cosa s'ha trencat). - Reexecució manual barata: arreglat el remote, un clic a "Retry" a la UI (o
prefect flow-run retry <id>) rellança l'execució. No cal esperar el dilluns següent ni recordar els arguments.
La conseqüència per a màrqueting també està controlada: com que la notificació només s'envia al final, no reben avís d'un parquet que no existeix. Sense notícia = sense fitxer nou; amb l'alerta interna, l'equip pot avisar màrqueting del retard proactivament.
Programar el dilluns a les 06:00
Amb Prefect 2/3, el deployment es declara des del mateix flow i se serveix amb un worker. Primer, el work pool (una vegada):
Després, registrar el deployment amb el seu schedule — l'expressió cron 0 6 * * 1 es llegeix: minut 0, hora 6, qualsevol dia del mes, qualsevol mes, dia de la setmana 1 (dilluns):
# flows/desplegar_flows.py
from scoring_setmanal import scoring_setmanal
if __name__ == "__main__":
scoring_setmanal.deploy(
name="scoring-setmanal-dilluns",
work_pool_name="cineclick-pool",
cron="0 6 * * 1", # dilluns 06:00
# la zona horària del schedule es fixa a Europe/Madrid al
# servidor; sense ella, "06:00" seria UTC i a l'estiu arribaria a les 08:00
)I un worker escoltant el pool (en producció, un pod petit del clúster amb el repo instal·lat; en desenvolupament, el teu terminal):
Amb això, la decisió presa a 04-01 — "scoring setmanal, dilluns a les 06:00" — per fi es compleix sola: el servidor crea l'execució a l'hora programada, el worker la recull, i si ningú no està mirant… precisament per a això hi ha els estats i les alertes.
Esbós del segon flow: reentrenament
El scoring no és l'únic procés recurrent a l'horitzó: tard o d'hora el model s'haurà de reentrenar amb dades fresques. El flow és estructuralment semblant i reutilitza encara més — embolcalla el pipeline DVC del mòdul 2 sencer:
# flows/reentrenament.py — ESBÓS, es completa al mòdul 6
from prefect import flow, task
import subprocess
@task(retries=2, retry_delay_seconds=60)
def dades_fresques():
subprocess.run(["dvc", "pull"], check=True)
@task(timeout_seconds=3600)
def reproduir_pipeline():
"""dvc repro executa preparar_dades -> construir_features ->
entrenar -> avaluar, amb la seva cache d'stages intacta."""
subprocess.run(["dvc", "repro"], check=True)
@task
def registrar_challenger():
"""Llegeix metrics.json; si supera els llindars de 05-01, registra la
nova versió a MLflow i li posa l'àlies @challenger.
MAI @champion: la promoció segueix el flux humà de 05-02."""
...
@flow(name="reentrenament-churn")
def reentrenament():
dades_fresques()
reproduir_pipeline()
registrar_challenger()Fixa't en el que aquest esbós decideix i en el que no decideix. Decideix el com: DVC fa la feina pesada (amb la seva cache d'stages: si les dades no han canviat, preparar_dades ni s'executa), i el resultat arriba com a màxim a @challenger — la promoció a @champion manté l'aprovació humana de 05-02, també quan l'entrenament és automàtic. El que no decideix és el quan: cada mes? Quan la deriva de dades superi un llindar? Quan el rendiment caigui? Aquesta política és matèria de la lliçó 06-03, quan tinguem el monitoratge que la informa. De moment, el flow existeix i es pot llançar a mà des de la UI — que ja és infinitament millor que "la Laura el llança des del seu portàtil".
Orquestrador vs. CI/CD: què va a cada lloc
Amb tres eines d'automatització en joc (CI, CD, orquestrador) és fàcil posar les coses on no toca. El criteri de repartiment és el disparador:
| Procés | Disparador | Eina | Per què |
|---|---|---|---|
| Tests de codi/dades/model | Push, PR | GitHub Actions (CI) | Esdeveniment de codi; efímer; per commit |
| Build i desplegament de la imatge | Tag v*.*.* |
GitHub Actions (CD) | Esdeveniment de codi; lliurament de software |
| Scoring batch setmanal | Calendari (dilluns 06:00) | Prefect | Procés de dades recurrent; necessita retries, estat i alertes |
| Reentrenament | Calendari o senyal de monitoratge (06-03) | Prefect | Procés de dades llarg, amb dependències i reintents |
Promoció de model a @champion |
Decisió humana | Script + revisió (05-02) | No és ni esdeveniment de codi ni calendari: és una decisió |
La regla mnemotècnica: GitHub Actions respon a "el codi ha canviat"; Prefect respon a "toca processar dades". Els runners d'Actions són efímers i anònims — perfectes per a tests, pèssims per a un procés de negoci l'historial d'execucions del qual vols consultar d'aquí a tres mesos. Podries forçar un schedule: a Actions per al scoring (existeix), però perdries reintents per pas, timeout per pas, reexecució selectiva i la UI d'estats — és a dir, tot el que demanàvem al principi de la lliçó.
Errors Comuns i Consells
- Un flow d'una sola task gegant. Si
scoring_setmanalfos una única task que ho fa tot, una fallada del webhook al final obligaria a repetir també la descàrrega i la predicció. La granularitat de les tasks defineix la granularitat dels reintents: separa el que és fràgil (xarxa) del que és determinista (càlcul). - Retries a tot arreu "per si de cas". Reintentar una validació de dades fallida retarda l'alerta 10 minuts per arribar al mateix error. Reintenta el que és transitori (xarxa, serveis externs); falla ràpid en el que és determinista.
- Oblidar la zona horària del schedule.
0 6 * * 1en UTC són les 07:00 o les 08:00 a Madrid segons l'època de l'any — i màrqueting esperant des de les 06:00. Fixa explícitamentEurope/Madridal schedule del deployment. - Lògica de negoci dins del flow en lloc del paquet. Si
predir_lotreimplementés les features "perquè era més còmode", tindríem la tercera còpia de la lògica que el mòdul 3 va unificar. Les tasks orquestren; el paquetcineclick_churnimplementa. - Confondre la notificació de negoci amb l'alerta de fallada.
notificar_marketingavisa de l'èxit a un consumidor; l'automation de Prefect avisa de la fallada a l'equip. Si barreges totes dues a la mateixa task, una fallada del scoring pot acabar notificant "fitxer a punt" a màrqueting o deixant l'equip sense alerta. - No provar el flow en local abans de desplegar-lo. El flow és una funció:
python flows/scoring_setmanal.pyl'executa sencer. Depurar mirant els logs del worker al servidor és la manera lenta. - Consell: dona al flow un nom estable i descriptiu (
scoring-setmanal-churn) des del primer dia. Les automations, els dashboards i les converses d'incidents pengen d'aquest nom; reanomenar-lo després trenca més del que sembla.
Exercicis
Exercici 1
Màrqueting demana que, a més del parquet, el scoring escrigui un CSV amb només els 500 clients de més probabilitat d'abandonament (data/top_risc.csv), i que si aquest pas extra falla, el flow es consideri igualment exitós (el parquet és el que és crític; el CSV és cortesia). Escriu la task i la seva integració al flow. Pista: investiga return_state=True o la gestió d'excepcions dins del flow.
Exercici 2
El dilluns a les 06:00 el servidor de MLflow està caigut per un manteniment no avisat i torna a les 06:20. Amb el flow tal com està escrit, el scoring de dilluns es completa o falla? Justifica-ho calculant la línia temporal dels intents de carregar_champion (retries=2, retry_delay_seconds=60, timeout_seconds=300) suposant que cada intent contra el servidor caigut falla al cap d'uns ~10 segons i que les tasks prèvies han acabat a les 06:05.
Exercici 3
Un company proposa moure la CI a l'orquestrador: "així ho tenim tot a Prefect, un flow ci que corre ruff i pytest quan algú faci push". Dona tres raons concretes per les quals els tests de la lliçó 05-01 encaixen millor a GitHub Actions que a Prefect.
Solucions
Exercici 1
@task(retries=1, retry_delay_seconds=30)
def escriure_top_risc(prediccions: pd.DataFrame, n: int = 500) -> str:
top = prediccions.nlargest(n, "probabilitat_abandonament")
top.to_csv("data/top_risc.csv", index=False)
return "data/top_risc.csv"I al flow, després d'escriure_prediccions:
estat = escriure_top_risc(prediccions, return_state=True)
if estat.is_failed():
logger.warning("top_risc.csv no generat; el parquet SÍ que està disponible")Amb return_state=True la crida retorna l'estat en lloc de propagar l'excepció: la task apareix en vermell a la UI (visibilitat intacta) però el flow continua i acaba en èxit. És la manera idiomàtica de marcar un pas com a "desitjable però no crític". L'alternativa d'un try/except al voltant funcionaria, però l'estat del flux quedaria menys fidel a la UI.
Exercici 2
Es completa. Línia temporal: tasks prèvies llestes a les 06:05. Intent 1 de carregar_champion ≈ 06:05:00, falla al cap d'uns ~10 s (06:05:10). Espera 60 s → intent 2 ≈ 06:06:10, falla (06:06:20). Espera 60 s → intent 3 (l'últim: retries=2 = 3 intents en total) ≈ 06:07:20, falla (06:07:30). Resultat: els tres intents s'esgoten a les 06:07:30 i el servidor no torna fins a les 06:20 — el flow falla, l'automation alerta, i a les 06:20+ algú (o un clic a Retry) el rellança. Moralitat doble: (a) calcula la finestra total que cobreixen els teus reintents (aquí ~2,5 minuts, insuficient per a un manteniment de 20); per a dependències amb caigudes llargues conegudes, un backoff més llarg ([60, 300, 900]) cobreix ~21 minuts; (b) fins i tot quan els reintents no basten, el sistema ha degradat bé: estat visible, alerta emesa, reexecució d'un clic — compareu-ho amb el cron silenciós de l'inici.
Exercici 3
(1) Disparador: la CI reacciona a push/PR, un esdeveniment que GitHub emet de manera nativa cap a Actions; portar-ho a Prefect exigiria muntar webhooks i perdria la integració amb el flux de revisió. (2) Bloqueig del merge: el valor de la CI és marcar la PR en verd/vermell i bloquejar el merge (branch protection); Prefect no participa en aquest mecanisme — tindries tests que corren però no protegeixen res. (3) Entorn efímer per commit: cada run d'Actions parteix d'un runner net i fa checkout del commit exacte de la PR, que és just el que vols per validar codi; el worker de Prefect és un procés persistent amb una versió del repo instal·lada, pensat per a processos de dades, no per provar N branques concurrents. És la taula de la lliçó en acció: esdeveniment de codi → Actions; procés de dades recurrent → Prefect.
Conclusió
L'últim procés manual de CineClick ja es mou sol: el flow scoring-setmanal-churn (a flows/scoring_setmanal.py) descarrega les dades amb dvc pull, les valida amb el mateix esquema pandera de la CI, carrega models:/churn-cineclick@champion, prediu en lot vectoritzat, escriu data/prediccions_churn.parquet i avisa màrqueting — cada dilluns a les 06:00 (0 6 * * 1, Europe/Madrid) via deployment sobre el work pool cineclick-pool, amb reintents amb backoff on hi ha xarxa, timeout on hi pot haver penjades, i alerta a l'equip quan falla de debò. Vam triar Prefect davant d'Airflow, Dagster i Kubeflow pel que CineClick és: dos pipelines, un equip petit i codi Python que ja funcionava. I ha quedat repartit el mapa de l'automatització: Actions per a esdeveniments de codi, Prefect per a processos de dades, i les decisions — la promoció a @champion, fins i tot des del flow de reentrenament esbossat — per a les persones. El mòdul ja té CI, CD i orquestració; en queda la peça més delicada. Perquè tot el que hem construït desplega el model nou de cop després de staging, i les mètriques offline — per bones que siguin — no garanteixen el que farà amb trànsit real. L'última lliçó del mòdul ensenya a deixar anar un model amb xarxa: shadow, canary i A/B.
Curs de MLOps
Mòdul 1: Fonaments de MLOps
- Què és MLOps i per què els models moren al notebook
- El cicle de vida d'un model de ML en producció
- Nivells de maduresa MLOps i rols de l'equip
- El projecte del curs: del notebook a producció
Mòdul 2: Del notebook al codi reproduïble
- Estructura d'un projecte de ML: del notebook al paquet
- Entorns reproduïbles i gestió de dependències
- Versionat de dades amb DVC
- Pipelines d'entrenament reproduïbles
Mòdul 3: Experiments i registre de models
- Tracking d'experiments amb MLflow
- Model registry: versionar i promocionar models
- Feature stores: quan i per a què
Mòdul 4: Servir models en producció
- Patrons de desplegament: batch, online i streaming
- Un servei de predicció amb FastAPI
- Empaquetatge amb Docker
- Escalat i desplegament: Kubernetes i serverless
- Optimització de la inferència: latència i cost
Mòdul 5: Automatització: CI/CD i orquestració
- CI per a ML: tests de codi, dades i models
- CD: automatitzar el desplegament del model
- Orquestració de pipelines de ML
- Estratègies de release: shadow, canary i A/B
