English · Español
Lab 04 — Gate de deploy en CI: bloquear regresiones del tutor de gramática¶
Objetivo: cablear
.github/workflows/deploy-grammar-tutor.yml. Verificar que promueve un candidato limpio; verificar que rechaza un candidato deliberadamente regresado.Tiempo estimado: 3–5 horas.
Prerequisito: labs 00–03 hechos.
eval_baseline.jsoncommiteado en la raíz del repo con precisión por bucket de la entrada activa del registry de producción. Tracking server de MLflow alcanzable desde el runner de GitHub Actions (para el currículo: un MLflow público de solo lectura en el laptop, o MLflow in-CI con los artefactos pusheados como workflow artifact).
Lo que produces¶
experiments/38-ci-gate/ conteniendo:
degraded_model/— una copia de un checkpoint registrado existente con una regresión por bucket deliberada inyectada (p. ej., pesos del head de participio pasado perturbados; o un wrapper que aleatoriza 5% de las salidas de participio pasado).register_degraded.py— sube el modelo degradado a MLflow, captura el run_id.pr_clean.md— narrativa + screenshot de la corrida CI que pasó y promovió un candidato limpio.pr_regressed.md— narrativa + screenshot de la corrida CI que falló sobre el candidato regresado.manifest.json.
Además, fuera del directorio del experimento:
.github/workflows/deploy-grammar-tutor.yml— el workflow CI.scripts/mlops/compare_baseline.py— la lógica de comparación invocada por el workflow.eval_baseline.json— baseline commiteada (puede ya existir tras el lab 00).
TODOs¶
Bloque A — escribe el workflow¶
- Crea
.github/workflows/deploy-grammar-tutor.yml. Trigger: PR etiquetadadeploy-candidatecon un comentario conteniendomlflow_run_id=<id> semver=v0.X.Y. - Seis etapas per
theory/05Parte 1: actions/checkout@v4+astral-sh/setup-uv@v3+uv sync --frozen.dvc pull data/eval/phase-20.jsonl.dvc(y el corpus si es necesario). Afirma quedvc hashcoincide coneval_baseline.json["eval_set_dvc_hash"].mlflow artifacts download --run-id ${{ inputs.run_id }} --dst-path ./candidate/.python -m minieval --bundle ./candidate/ --eval-set data/eval/phase-20.jsonl --output candidate_eval.json.python -m scripts.mlops.compare_baseline --candidate candidate_eval.json --baseline eval_baseline.json --tolerance 0.02. Exit 0 = pass, no-cero = fail.- En pass:
python -m scripts.mlops.registry promote --canonical-sha <derived from candidate> --semver <from PR comment>. Luegogh pr commentcon el SHA promovido + semver. En fail:gh pr commentcon los buckets que fallaron y sal no-cero. - Pinea cada acción a un SHA, no un tag (
@v4→@<sha>). Higiene de cadena de suministro (cross-referencesecurity/supply-chain.md).
Bloque B — escribe la lógica de comparación¶
-
scripts/mlops/compare_baseline.py: - Carga
candidate_eval.jsonyeval_baseline.json. - Para cada bucket en la baseline, chequea
candidate.bucket.accuracy >= baseline.bucket.accuracy - tolerance_pp. - Si algún bucket falla, imprime una tabla formateada en markdown de los buckets que fallaron a stdout y sale con código 2.
- Si todos los buckets pasan, imprime un resumen de una línea y sale 0.
- Test-unitea: JSONs sintéticos de candidato + baseline, ambos casos de pass y fail. Los tests viven en
tests/mlops/test_compare_baseline.py.
Bloque C — dry run con candidato limpio¶
- Abre una PR titulada
chore: deploy v0.3.1 (rerun of LoRA grammar tutor, no model change). Añade la etiquetadeploy-candidate. Comentamlflow_run_id=<existing_lora_run_id> semver=v0.3.1. - Observa la corrida del workflow. Comportamiento esperado: la etapa 5 pasa (sin regresión vs baseline), la etapa 6 promueve. Confirma que el
index.jsonldel registry tiene una nueva línea y quetags.jsontiene el nuevo semver. - Captura la corrida verde de CI + el comentario-promoción del bot.
- Guarda como
pr_clean.mden el directorio del experimento.
Bloque D — inyección de regresión¶
- Toma un modelo registrado existente (el tutor de gramática LoRA es buen candidato). Aplica una perturbación focalizada:
- Opción 1 (más simple): envuelve el modelo en
degraded_model/wrapper.pyque machaca aleatoriamente el 10% de las salidas de participio pasado. Es más fácil que perturbar pesos pero es observable como regresión por el gate de evaluación. - Opción 2 (más profunda): añade ruido gaussiano a la matriz
Bdel adaptador LoRA para el slice del decoder de participio pasado. Más realista pero más lento de inyectar. - Corre la evaluación localmente para confirmar: la precisión agregada puede mantenerse cercana al baseline, pero el bucket de participio pasado cae > 2pp.
- Sube a MLflow como una nueva run; captura el run_id.
Bloque E — dry run con candidato regresado¶
- Abre una PR titulada
chore: deploy v0.3.2 (degraded — should fail CI). Añade la etiquetadeploy-candidate. Comentamlflow_run_id=<degraded_run_id> semver=v0.3.2. - Observa la corrida del workflow. Comportamiento esperado: la etapa 5 falla porque
tense.past_participleregresa más de 2pp. El bot comenta en la PR con el bucket que falló. La etapa 6 no se ejecuta. El registry no cambia. - Captura la corrida roja de CI + el comentario del bucket que falló.
- Guarda como
pr_regressed.mden el directorio del experimento. - Cierra la PR sin mergear — el punto es que el gate funcionó, no que queramos los cambios de esta PR en main.
Bloque F — recetas Justfile + manifest¶
- Añade
just register-model <run-id> <semver>para invocar el path de promoción local (modo dev) con un aviso de que el path de producción es CI. - Añade
just compare-baseline <candidate.json>para dry-runear la comparación localmente. -
manifest.jsonlista: el run_id limpio usado para el Bloque C, el run_id degradado usado para el Bloque E, el SHA deeval_baseline.jsonen el momento de cada PR.
Restricciones¶
- Sin flag
--forceen el code path de producción. Si te encuentras añadiendo uno, para. El dev local tiene modoLYNX_ENV=dev; producción tiene CI. No hay tercer path. - GitHub Actions pineadas. Cada acción usada en el workflow está pineada por commit SHA, no por tag. Regla de cadena de suministro.
- Sin secrets en el workflow. El acceso a MLflow usa secrets de GitHub Actions a nivel repo (configurados en los settings del repo, no commiteados). Documenta los nombres de los secrets en
pr_clean.md. - La baseline está commiteada.
eval_baseline.jsones un fichero tracked. Actualizarlo es su propia PR. - Sin nuevo
src/<module>/.compare_baseline.pyyregistry promoteviven enscripts/mlops/.
Condiciones de parada¶
Hecho cuando:
- El fichero del workflow existe y es sintácticamente válido (
gh workflow listlo muestra). - La PR de candidato limpio cerró con corrida CI exitosa y una nueva entrada en el registry.
- La PR de candidato regresado cerró con corrida CI fallida y sin cambio en el registry.
- Ambas PRs tienen screenshots guardadas en el directorio del experimento.
scripts/mlops/compare_baseline.pytiene tests unitarios, todos pasando.
Pitfalls¶
- CI corre contra
main, no la branch de la PR. GitHub Actions checkea la branch de la PR por defecto para eventospull_requestperomainparaworkflow_dispatch. Usapull_request_targetcon cuidado — puede dar permisos excesivos. Default: trigger enpull_request: labeledy corre conpermissions: { contents: read, pull-requests: write }. - Las descargas MLflow son lentas en cold cache. Cachea la descarga del artefacto con
actions/cachekeyado porrun_id. Ahorra minutos por corrida. - DVC necesita auth. Si el remote DVC es privado (p. ej., S3), el workflow necesita credenciales cloud. Para el currículo, el remote es local; CI solo corre
dvc fetch --remote localcontra una copia bundleada con el workflow artifact. Documenta esto en el README del lab. - La baseline puede romperse silenciosamente. Si el hash DVC del eval set en
eval_baseline.jsonno coincide con el corpus jalado, todas las comparaciones se hacen contra el eval equivocado. El workflow afirma este hash en la etapa 2 — verifica con un mismatch deliberado (commitea una baseline apuntando a un hash de eval obsoleto; confirma que CI falla rápido). - La promoción no es atómica entre el registry + MLflow.
promote()actualizatags.jsony añade un tag MLflow. Si la mitad falla, tienes medio-estado. Implementa: escribe tags.json primero (local, atómico vía tmp+rename), luego añade el tag MLflow; ante fallo de MLflow, rollback del fichero local y reintenta. Documenta el modo-fallo en el README del lab. - Spam del bot de promoción en reintentos. Un workflow fallido re-corrido no debería promover dos veces.
promote()es idempotente sobre (canonical_sha, semver) — verifica con un re-run.
Cuándo consultar solutions/¶
Tras los seis bloques. solutions/04-ci-deploy-gate-ref.md (apertura de fase) revisa tu diseño de workflow, los edge cases de la lógica de comparación, el pineado de acciones y la historia de rollback ante fallo.
Este es el último lab de la Fase 38. Tras completar los cinco (00–04), tienes:
- Un registry con SHAs canónicos estables sobre MLflow + DVC.
- Routing shadow + A/B cableado en el stack de servicio (sin nuevo módulo src).
- Un detector de drift con umbrales calibrados.
- Una tabla CpQU por modelo registrado.
- Un gate de CI que rechaza promover una regresión.
Juntos: la columna vertebral MLOps para el capstone de la Fase 39.
Siguiente: reporte de Fase 38 → capstone de Fase 39.