03dadc93da
Cambio mecánico, sin efecto en el comportamiento: la suite pasa igual antes y después. Va en un commit propio para no tapar los cambios con sentido. Se agregan además dos flujos de verificación que corren en cada carga al repositorio: - YAML: yamllint para sintaxis y estilo, más la carga de cada config contra su esquema de pydantic. Son cosas distintas — un YAML puede ser sintácticamente perfecto y estar roto igual, con 'run_nombre' en vez de 'run_name'. Ese paso no instala torch: se verificó que la capa de configuración no lo importa, así que corre en segundos en vez de descargar dos gigas y medio de CUDA. - Python: ruff check, ruff format --check y la suite completa con torch de CPU. Las rutas ignoradas de .yamllint.yml van ancladas con barra inicial. Sin anclar, 'data/' y 'runs/' excluían configs/data/ y configs/runs/ — siete archivos, justo los que más importa revisar — y el linter pasaba en verde sin haber mirado nada. Es el mismo defecto que ya había aparecido en .gitignore.
77 lines
2.4 KiB
Python
Executable File
77 lines
2.4 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
"""Valida que todas las configs del repositorio carguen y pasen su esquema.
|
|
|
|
Un YAML sintácticamente correcto puede seguir estando roto: `n_layers` en vez de
|
|
`n_layer`, un schedule que no entra en `max_steps`, float16 sin GradScaler. Eso
|
|
no lo ve un linter de sintaxis — lo ven los validadores de pydantic, que ya
|
|
existen. Esto los ejecuta sobre cada config del repositorio.
|
|
|
|
No importa torch a propósito: así el CI valida configs en segundos en vez de
|
|
descargar los dos gigas y medio de CUDA.
|
|
|
|
python scripts/validar_configs.py
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
RAIZ = Path(__file__).resolve().parent.parent
|
|
sys.path.insert(0, str(RAIZ))
|
|
|
|
from enlace.config.load import ( # noqa: E402
|
|
ConfigError,
|
|
load_agent_config,
|
|
load_config,
|
|
load_distill_config,
|
|
)
|
|
|
|
|
|
def validar() -> int:
|
|
fallas: list[tuple[Path, str]] = []
|
|
verificadas = 0
|
|
|
|
# Las configs de corrida componen las cuatro capas, así que validarlas
|
|
# cubre transitivamente hardware, modelo, entrenamiento y datos.
|
|
for path in sorted((RAIZ / "configs" / "runs").glob("*.yaml")):
|
|
try:
|
|
cfg = load_config(path)
|
|
verificadas += 1
|
|
print(
|
|
f" OK {path.relative_to(RAIZ)} "
|
|
f"({cfg.hardware.name} / {cfg.model.name} / {cfg.train.run_name})"
|
|
)
|
|
except ConfigError as exc:
|
|
fallas.append((path, str(exc)))
|
|
print(f" FALLA {path.relative_to(RAIZ)}")
|
|
|
|
# Los árboles que no son de entrenamiento se cargan aparte.
|
|
for etiqueta, cargador, path in (
|
|
("agente", load_agent_config, RAIZ / "configs" / "agent" / "tools.yaml"),
|
|
("destilación", load_distill_config, RAIZ / "configs" / "data" / "distill.yaml"),
|
|
):
|
|
try:
|
|
cargador(path)
|
|
verificadas += 1
|
|
print(f" OK {path.relative_to(RAIZ)} ({etiqueta})")
|
|
except ConfigError as exc:
|
|
fallas.append((path, str(exc)))
|
|
print(f" FALLA {path.relative_to(RAIZ)}")
|
|
|
|
print()
|
|
if fallas:
|
|
for path, motivo in fallas:
|
|
print(f"--- {path.relative_to(RAIZ)} ---")
|
|
print(motivo)
|
|
print()
|
|
print(f"{len(fallas)} config(s) inválida(s) de {verificadas + len(fallas)}.")
|
|
return 1
|
|
|
|
print(f"{verificadas} configs válidas.")
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
raise SystemExit(validar())
|