Skip to content

English · Español

Lab 00 — Roundtrip del registry (MLflow + DVC + SHA canónico)

Objetivo: demostrar la estabilidad del SHA canónico del registry y el walk del linaje sobre artefactos reales del tutor de gramática.

Tiempo estimado: 3–5 horas.

Prerequisito: existen los artefactos de la Fase 18 + Fase 26 + Fase 28 (Mini-GPT base, variante INT8, tutor de gramática LoRA); el tracking server de MLflow está levantado (mlflow ui --backend-store-uri ./mlruns); DVC inicializado con un remote local (.dvc/config apunta a data/dvc-remote/).


Lo que produces

experiments/38-registry-roundtrip/ conteniendo:

  • register.py — tu script driver.
  • results.json — SHAs canónicos registrados, semvers, árboles de linaje, run IDs de MLflow.
  • lineage.mmd — render mermaid del DAG de linaje.
  • manifest.json{seed, versions, config, hardware} per CLAUDE.md §1.
  • README.md — qué registraste, qué mostró el test de estabilidad del SHA.

El escenario

Tres artefactos a registrar:

  1. El modelo Mini-GPT base entrenado FP32 de la Fase 18 (sin LoRA todavía).
  2. La variante cuantizada INT8 del checkpoint de la Fase 18, de la Fase 26.
  3. El adaptador LoRA tutor-de-gramática de la Fase 28 sobre la base de la Fase 18.

El trabajo de la Fase 38 es registrar los tres bajo el esquema de SHA canónico, con el linaje retrocediendo hasta el hash DVC del corpus de verbos de la Fase 12.

TODOs

Bloque A — registrar los tres artefactos

  • Escribe un register.py que:
  • Jale el corpus de verbos de la Fase 12 vía DVC: dvc pull data/processed/train.jsonl.dvc. Captura el dvc_hash.
  • Cargue la run MLflow de la Fase 18 (mlflow_run_id_fp32). Llama a registry.register_pending(run_id=...) desde scripts/mlops/registry.py. Imprime el SHA canónico.
  • Llame a registry.promote(canonical_sha=<fp32_sha>, semver="v0.1.0") (esto es una corrida en modo dev; en producción este paso está gateado por CI per theory/05).
  • Cargue la run MLflow INT8 de la Fase 26. El train_manifest.json de la run debe incluir parent_sha = <fp32_sha> (configura esto en el script de entrenamiento de la Fase 26 — si no está allí, este lab falla hasta que la Fase 26 sea revisada).
  • Llame a register_pending + promote para el bundle INT8 con semver v0.2.0.
  • Lo mismo para el bundle LoRA de la Fase 28: parent_sha = <fp32_sha>, semver v0.3.0.
  • Confirma inspeccionando .lynx-registry/: tags.json mapea tres semvers; index.jsonl tiene tres líneas.
  • Confirma que la UI de MLflow muestra tres modelos registrados (además de tu capa de SHA canónico).

Bloque B — test de estabilidad del SHA canónico

  • Dos corridas, mismo SHA. Desde un shell fresco, registra el bundle de la Fase 18 de nuevo. El register_pending() del registry debería detectar que el SHA canónico ya existe y devolver el handle existente. Confirma que index.jsonl no creció.
  • Re-exporta el bundle MLflow, registra de nuevo. Este es el test estricto: re-guarda el mismo modelo lógico a través de la API de MLflow (que embebe timestamps nuevos en los ficheros de metadata). Nuestro cómputo de SHA canónico debe ignorar esos y producir el mismo SHA. Si no lo hace, la canonicalización está mal.
  • Documenta el no-determinismo que encontraste. Casi seguro el primer intento produce un SHA diferente en la segunda corrida porque algo en el bundle no era canónico (típicamente mtime en headers de tarball, ficheros de metadata de run de MLflow incluidos en el hash por error, o claves JSON sin ordenar). Identifica la fuente, arregla la canonicalización de scripts/mlops/registry.py, vuelve a correr.

Bloque C — walk del linaje

  • Llama a lineage(<int8_sha>). Verifica que devuelve: INT8 → FP32 → corpus_dvc_hash. Imprime la cadena.
  • Llama a lineage(<lora_sha>). Verifica que devuelve: LoRA → FP32 → corpus_dvc_hash.
  • Confirma que dvc pull sobre el corpus_dvc_hash devuelto reconstruye el corpus de entrenamiento exacto.
  • Renderiza el DAG de linaje como un diagrama mermaid (lineage.mmd). Tres nodos de modelo (FP32, INT8, LoRA), un nodo de corpus, aristas: FP32 → corpus, INT8 → FP32, LoRA → FP32.

Bloque D — invariantes de tags.json

  • Intenta registrar un nuevo SHA canónico bajo v0.1.0. Esperado: RegistryError("semver already mapped to a different SHA").
  • Actualiza el mapeo de semver explícitamente: registry.retag(canonical_sha=<new_fp32_sha>, semver="v0.1.0"). Confirma que tags.json se actualizó atómicamente (write-to-tmp + rename).
  • Confirma que index.jsonl tiene una entrada registrando la operación retag con el SHA anterior.

Bloque E — manifest + README

  • Escribe manifest.json con seed, versiones de librería (Python, MLflow, DVC, NumPy), config (los run IDs de MLflow de los tres bundles y el hash DVC del corpus) y hardware (per LYNX_CORTEX.md §5).
  • Escribe README.md (300–500 palabras) cubriendo:
  • Qué registraste: los tres SHAs canónicos, los tres run IDs de MLflow, el hash DVC del corpus.
  • La fuente de no-determinismo que encontraste en el bundle de MLflow (sé específico: ¿fue mtime en headers de tar, el orden de claves JSON, un fichero de metadata de MLflow que incluiste por accidente, precisión fp, o algo más?).
  • El diagrama de linaje y qué muestra sobre el fan-out FP32 → INT8 / LoRA.
  • Una frase sobre por qué esto importa para el capstone de la Fase 39 y el lab 04 de la Fase 38 (el gate de CI).

Restricciones

  • Solo stdlib para hashing (hashlib.sha256). Sin xxhash, sin blake3, sin hashers de terceros.
  • Solo MLflow para almacenamiento. No uses el transition_model_version_stage del model-registry de MLflow para identidad. La identidad vive en scripts/mlops/registry.py vía el SHA canónico. Los run/version IDs de MLflow son punteros de metadata, no la clave primaria.
  • DVC para el corpus. corpus_dvc_hash está en train_manifest.json; el walk de linaje lo usa. No embebas los bytes del corpus en el registry — para eso está DVC.
  • Escrituras atómicas para mutaciones de tags.json: escribe a *.tmp, luego os.rename.
  • Sin nuevo src/<module>/. Todo el código va en scripts/mlops/.
  • CPU-only. Este lab no toca GPU.

Condiciones de parada

Hecho cuando:

  1. Los tres SHAs canónicos registrados son estables entre re-registros desde un shell fresco.
  2. lineage(<sha>) devuelve una cadena correcta para los tres, terminando en el hash DVC del corpus de la Fase 12.
  3. tags.json no puede ser silenciosamente corrupto por un registro semver duplicado.
  4. manifest.json existe y lista los tres SHAs, los tres run IDs de MLflow y el hash DVC del corpus.
  5. El DAG mermaid se renderiza.

Pitfalls (lee antes de debuggear)

  • MLflow embebe timestamps en metadata. mlflow.pyfunc.save_model() escribe un YAML MLmodel con timestamps de creación. Incluir este fichero en el cómputo del SHA canónico romperá la estabilidad. El SHA canónico debe ser sobre model.safetensors + tokenizer.json + config.json + eval_report.json + train_manifest.json + parent_sha solamente. Cualquier cosa que MLflow genere como side-car se ignora.
  • tar no es canónico. tar cf bundle.tar models/mini-gpt-fp32/ produce un tarball con headers mtime. Dos invocaciones de tar producen bytes diferentes. El fix es hashear los ficheros del bundle individualmente, no el tarball.
  • json.dumps no es canónico por defecto. Usa json.dumps(d, sort_keys=True, separators=(',', ':')). Whitespace al final, orden de claves y representación de punto flotante todos importan. Testea volcando el mismo dict dos veces en dos sesiones de Python diferentes y comparando los bytes.
  • safetensors es canónico. Un fichero safetensors es idéntico byte-a-byte dados los mismos datos tensoriales. Úsalo como formato de pesos; no hagas el tuyo propio.
  • JSON dependiente del locale. Si tu Python está en un locale no-UTF-8, la salida JSON puede variar. Fuerza UTF-8 vía encoding='utf-8'.
  • DVC jala a un working tree. dvc pull extrae a data/processed/train.jsonl. Si re-corres el registro tras editar ese fichero (incluso por accidente — p. ej., una ejecución suelta de notebook), el corpus_dvc_hash registrado en el manifest puede no coincidir con lo que hay en disco. Siempre haz dvc status antes de registrar.

Cuándo consultar solutions/

Tras los cinco bloques. solutions/00-registry-roundtrip-ref.md (escrito en apertura de fase) compara tu elección de canonicalización, la estructura del tipo de retorno de lineage(), la integración MLflow y la estrategia de atomicidad de register().


Siguiente lab: lab/01-shadow-ab.md.