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/configapunta adata/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:
- El modelo Mini-GPT base entrenado FP32 de la Fase 18 (sin LoRA todavía).
- La variante cuantizada INT8 del checkpoint de la Fase 18, de la Fase 26.
- 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.pyque: - Jale el corpus de verbos de la Fase 12 vía DVC:
dvc pull data/processed/train.jsonl.dvc. Captura eldvc_hash. - Cargue la run MLflow de la Fase 18 (
mlflow_run_id_fp32). Llama aregistry.register_pending(run_id=...)desdescripts/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 mododev; en producción este paso está gateado por CI pertheory/05). - Cargue la run MLflow INT8 de la Fase 26. El
train_manifest.jsonde la run debe incluirparent_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+promotepara el bundle INT8 con semverv0.2.0. - Lo mismo para el bundle LoRA de la Fase 28:
parent_sha = <fp32_sha>, semverv0.3.0. - Confirma inspeccionando
.lynx-registry/:tags.jsonmapea tres semvers;index.jsonltiene 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 queindex.jsonlno 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
mtimeen 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 descripts/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 pullsobre elcorpus_dvc_hashdevuelto 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 quetags.jsonse actualizó atómicamente (write-to-tmp + rename). - Confirma que
index.jsonltiene una entrada registrando la operación retag con el SHA anterior.
Bloque E — manifest + README¶
- Escribe
manifest.jsoncon 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 (perLYNX_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
mtimeen 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). Sinxxhash, sinblake3, sin hashers de terceros. - Solo MLflow para almacenamiento. No uses el
transition_model_version_stagedel model-registry de MLflow para identidad. La identidad vive enscripts/mlops/registry.pyví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_hashestá entrain_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, luegoos.rename. - Sin nuevo
src/<module>/. Todo el código va enscripts/mlops/. - CPU-only. Este lab no toca GPU.
Condiciones de parada¶
Hecho cuando:
- Los tres SHAs canónicos registrados son estables entre re-registros desde un shell fresco.
lineage(<sha>)devuelve una cadena correcta para los tres, terminando en el hash DVC del corpus de la Fase 12.tags.jsonno puede ser silenciosamente corrupto por un registro semver duplicado.manifest.jsonexiste y lista los tres SHAs, los tres run IDs de MLflow y el hash DVC del corpus.- El DAG mermaid se renderiza.
Pitfalls (lee antes de debuggear)¶
- MLflow embebe timestamps en metadata.
mlflow.pyfunc.save_model()escribe un YAMLMLmodelcon timestamps de creación. Incluir este fichero en el cómputo del SHA canónico romperá la estabilidad. El SHA canónico debe ser sobremodel.safetensors+tokenizer.json+config.json+eval_report.json+train_manifest.json+parent_shasolamente. Cualquier cosa que MLflow genere como side-car se ignora. tarno es canónico.tar cf bundle.tar models/mini-gpt-fp32/produce un tarball con headersmtime. Dos invocaciones detarproducen bytes diferentes. El fix es hashear los ficheros del bundle individualmente, no el tarball.json.dumpsno es canónico por defecto. Usajson.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.safetensorses 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 pullextrae adata/processed/train.jsonl. Si re-corres el registro tras editar ese fichero (incluso por accidente — p. ej., una ejecución suelta de notebook), elcorpus_dvc_hashregistrado en el manifest puede no coincidir con lo que hay en disco. Siempre hazdvc statusantes 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.