chore: sync from fn-registry agent

This commit is contained in:
fn-registry agent
2026-05-13 00:50:31 +02:00
commit 7d5d4fb394
17 changed files with 5241 additions and 0 deletions
@@ -0,0 +1,374 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "3451405a",
"metadata": {},
"source": [
"# Spike 01 — SD Turbo via diffusers backend\n",
"\n",
"**Objetivo:** validar contrato `GenerationConfig_py_ml` + backend `diffusers_generate_py_ml` con SD Turbo. Sanity check antes de invertir en sd.cpp / FLUX.\n",
"\n",
"**Criterio PASS:**\n",
"- 4 imagenes generadas, seeds deterministas, mismo seed → imagen identica entre runs.\n",
"- `vram_peak_mb` < 6000 MB.\n",
"- `duration_ms` < 5000 ms/imagen.\n",
"- `image_grid` 2x2 visible.\n",
"\n",
"**Criterio FAIL:** cualquiera de los anteriores no se cumple → replantear contrato o backend.\n",
"\n",
"**Lo que NO se valida aqui:** LoRA loading, comparacion vs sd.cpp, samplers != euler_a (SD Turbo solo soporta 1-step euler_a).\n",
"\n",
"**Funciones del registry usadas:**\n",
"- `cuda_available_py_ml`, `gpu_info_py_ml`, `vram_budget_py_ml`\n",
"- `diffusers_load_pipeline_py_ml`, `diffusers_generate_py_ml`, `diffusers_unload_py_ml`\n",
"- `image_grid_py_ml`, `image_save_png_py_ml`\n",
"- Tipos: `GenerationConfig`, `ModelRef`, `ImageGenResult`"
]
},
{
"cell_type": "markdown",
"id": "4b43e6a2",
"metadata": {},
"source": [
"## 1. Hardware check\n",
"\n",
"Verificar CUDA + GPU antes de empezar. Si no hay GPU, el resto del notebook correra en CPU (lento)."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "5f395460",
"metadata": {},
"outputs": [],
"source": [
"import sys, os\n",
"FN_ROOT = os.environ.get(\"FN_REGISTRY_ROOT\", \"/home/lucas/fn_registry\")\n",
"sys.path.insert(0, os.path.join(FN_ROOT, \"python/functions/ml\"))\n",
"\n",
"from cuda_available import cuda_available\n",
"from gpu_info import gpu_info\n",
"from vram_budget import vram_budget\n",
"\n",
"cuda = cuda_available()\n",
"gpus = gpu_info()\n",
"print(\"CUDA:\", cuda)\n",
"print(\"GPUs:\", gpus)"
]
},
{
"cell_type": "markdown",
"id": "6b7c5272",
"metadata": {},
"source": [
"## 2. Hipotesis VRAM\n",
"\n",
"SD Turbo es derivado de SD 1.5 (~1.5GB fp16). Usamos `vram_budget` con `sd15/fp16` como proxy. Esperamos `fits=True` y `required_mb` ~2000-4000."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "0b7eb904",
"metadata": {},
"outputs": [],
"source": [
"total_mb = gpus[0][\"vram_total_mb\"] if gpus else 8000 # fallback CPU/sim\n",
"budget = vram_budget(\n",
" gpu_vram_total_mb=total_mb,\n",
" model_type=\"sd15\",\n",
" quantization=\"fp16\",\n",
" width=512, height=512,\n",
")\n",
"print(\"VRAM total:\", total_mb, \"MB\")\n",
"print(\"Budget:\", budget)"
]
},
{
"cell_type": "markdown",
"id": "a89fec8c",
"metadata": {},
"source": [
"## 3. Construir 4 `GenerationConfig`\n",
"\n",
"Seeds fijos: 42, 123, 7, 999. Prompts variados. SD Turbo: `steps=1`, `cfg_scale=0.0` (no usa guidance), `sampler=euler_a`."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "b210dfc6",
"metadata": {},
"outputs": [],
"source": [
"# Forzar imports limpios para evitar double-import pydantic\n",
"for _mod in [\"sampler_name\", \"model_ref\", \"lora_ref\", \"generation_config\", \"image_gen_result\"]:\n",
" sys.modules.pop(_mod, None)\n",
"\n",
"from model_ref import ModelRef\n",
"from generation_config import GenerationConfig\n",
"\n",
"VAULT = \"/home/lucas/vaults/imagegen_models/diffusers/sd-turbo\"\n",
"\n",
"model = ModelRef(\n",
" name=\"stabilityai/sd-turbo\",\n",
" model_type=\"sd15\",\n",
" quantization=\"fp16\",\n",
" path=VAULT,\n",
")\n",
"\n",
"PROMPTS = [\n",
" (\"a cinematic shot of a baby racoon wearing a tiny crown\", 42),\n",
" (\"watercolor of a cozy library with floating books\", 123),\n",
" (\"isometric pixel art of a robot fishing on a pier\", 7),\n",
" (\"oil painting of a fox playing chess against a cat\", 999),\n",
"]\n",
"\n",
"configs = [\n",
" GenerationConfig(\n",
" prompt=p,\n",
" seed=s,\n",
" steps=1,\n",
" cfg_scale=0.0,\n",
" sampler=\"euler_a\",\n",
" width=512,\n",
" height=512,\n",
" model=model,\n",
" )\n",
" for p, s in PROMPTS\n",
"]\n",
"for c in configs:\n",
" print(c.seed, c.prompt[:60])"
]
},
{
"cell_type": "markdown",
"id": "743b6bb2",
"metadata": {},
"source": [
"## 4. Cargar pipeline SD Turbo\n",
"\n",
"Primera carga ~10-30s (mete pesos en VRAM). Llamadas subsiguientes: cacheadas."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "de0f03b2",
"metadata": {},
"outputs": [],
"source": [
"from diffusers_load_pipeline import diffusers_load_pipeline\n",
"\n",
"pipe = diffusers_load_pipeline(model=model, device=\"auto\", dtype=\"fp16\")\n",
"print(\"Pipeline:\", type(pipe).__name__)\n",
"print(\"Scheduler:\", type(pipe.scheduler).__name__)\n",
"print(\"Device:\", next(pipe.unet.parameters()).device)"
]
},
{
"cell_type": "markdown",
"id": "a328f117",
"metadata": {},
"source": [
"## 5. Generar 4 imagenes\n",
"\n",
"Una a una. Captura `duration_ms` + `vram_peak_mb` por imagen. SD Turbo 1-step: esperado <2s en GPU NVIDIA moderna."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "d1780d91",
"metadata": {},
"outputs": [],
"source": [
"# Forzar imports limpios para alinear tipos con los que usa diffusers_generate\n",
"for _mod in [\"image_gen_result\", \"generation_config\", \"model_ref\", \"lora_ref\", \"sampler_name\"]:\n",
" sys.modules.pop(_mod, None)\n",
"\n",
"from diffusers_generate import diffusers_generate\n",
"from generation_config import GenerationConfig as GC2\n",
"from model_ref import ModelRef as MR2\n",
"\n",
"# Reconstruir configs con los tipos recien re-importados (alineados con diffusers_generate)\n",
"model2 = MR2(**model.model_dump())\n",
"configs2 = [GC2(**c.model_dump()) for c in configs]\n",
"\n",
"results = []\n",
"for c in configs2:\n",
" r = diffusers_generate(pipe, c)\n",
" print(f\"seed={c.seed} duration={r.duration_ms}ms vram_peak={r.vram_peak_mb}MB\")\n",
" results.append(r)\n",
"print(\"Total:\", len(results))"
]
},
{
"cell_type": "markdown",
"id": "ffda5ccc",
"metadata": {},
"source": [
"## 6. Grid 2x2 + display\n",
"\n",
"Imagenes lado a lado con labels (prompt acortado + seed)."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "a3dc597a",
"metadata": {},
"outputs": [],
"source": [
"from image_grid import image_grid\n",
"\n",
"labels = [f\"seed={r.meta.get('seed', '?')} | {c.prompt[:40]}...\" for c, r in zip(configs2, results)]\n",
"grid = image_grid(\n",
" images=[r.image for r in results],\n",
" cols=2,\n",
" labels=labels,\n",
" gap_px=12,\n",
")\n",
"grid"
]
},
{
"cell_type": "markdown",
"id": "ae4e3184",
"metadata": {},
"source": [
"## 7. Guardar grid + configs a vault\n",
"\n",
"PNG con metadata embebida (prompt/seed) en `~/vaults/imagegen_models/outputs/`. JSON canonico en `configs/`."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "f7ca8a69",
"metadata": {},
"outputs": [],
"source": [
"from image_save_png import image_save_png\n",
"from genconfig_save_json import genconfig_save_json\n",
"import datetime, os as _os\n",
"\n",
"OUT_DIR = \"/home/lucas/vaults/imagegen_models/outputs\"\n",
"CFG_DIR = \"/home/lucas/vaults/imagegen_models/configs\"\n",
"stamp = datetime.datetime.now().strftime(\"%Y%m%d_%H%M%S\")\n",
"\n",
"grid_path = image_save_png(\n",
" grid,\n",
" _os.path.join(OUT_DIR, f\"spike01_grid_{stamp}.png\"),\n",
" metadata={\"experiment\": \"spike01_sd_turbo\", \"model\": model2.name, \"n\": str(len(results))},\n",
")\n",
"print(\"grid:\", grid_path)\n",
"\n",
"for i, (c, r) in enumerate(zip(configs2, results)):\n",
" img_path = image_save_png(\n",
" r.image,\n",
" _os.path.join(OUT_DIR, f\"spike01_seed{c.seed}_{stamp}.png\"),\n",
" metadata={\"prompt\": c.prompt, \"seed\": str(c.seed), \"steps\": str(c.steps), \"sampler\": c.sampler},\n",
" )\n",
" cfg_path = genconfig_save_json(c, _os.path.join(CFG_DIR, f\"spike01_seed{c.seed}_{stamp}.json\"))\n",
" print(f\" [{i}] {img_path}\")\n",
" print(f\" {cfg_path}\")"
]
},
{
"cell_type": "markdown",
"id": "071982d6",
"metadata": {},
"source": [
"## 8. Tabla resumen + veredicto"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "4a4a18c4",
"metadata": {},
"outputs": [],
"source": [
"rows = []\n",
"for c, r in zip(configs2, results):\n",
" rows.append({\n",
" \"seed\": c.seed,\n",
" \"prompt\": c.prompt[:50] + \"...\",\n",
" \"duration_ms\": r.duration_ms,\n",
" \"vram_peak_mb\": r.vram_peak_mb,\n",
" })\n",
"\n",
"try:\n",
" import pandas as pd\n",
" df = pd.DataFrame(rows)\n",
" display(df)\n",
"except ImportError:\n",
" for row in rows:\n",
" print(row)\n",
"\n",
"max_dur = max(r.duration_ms for r in results)\n",
"max_vram = max((r.vram_peak_mb or 0) for r in results)\n",
"pass_dur = max_dur < 5000\n",
"pass_vram = max_vram < 6000\n",
"verdict = \"PASS\" if (pass_dur and pass_vram) else \"FAIL\"\n",
"print(f\"\\nMax duration: {max_dur} ms (target <5000) -> {'OK' if pass_dur else 'FAIL'}\")\n",
"print(f\"Max VRAM peak: {max_vram} MB (target <6000) -> {'OK' if pass_vram else 'FAIL'}\")\n",
"print(f\"\\nVEREDICTO: {verdict}\")"
]
},
{
"cell_type": "markdown",
"id": "19701ac7",
"metadata": {},
"source": [
"## 9. Cleanup VRAM"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "d25cfb9c",
"metadata": {},
"outputs": [],
"source": [
"from diffusers_unload import diffusers_unload\n",
"diffusers_unload(pipe)\n",
"diffusers_unload(None) # clear cache global\n",
"print(\"unloaded\")"
]
},
{
"cell_type": "markdown",
"id": "f331dc00",
"metadata": {},
"source": [
"## 10. Siguiente paso\n",
"\n",
"Si PASS:\n",
"- Notebook `02_seed_reproducibility.ipynb` — mismo seed, dos runs, verificar hash identico.\n",
"- Notebook `03_sdxl_turbo.ipynb` cuando se descargue (~6.5GB).\n",
"- Lanzar Ola 3.B (sd.cpp Python bindings) + descargar FLUX schnell GGUF para validacion cruzada.\n",
"\n",
"Si FAIL:\n",
"- Revisar `vram_budget` table si VRAM peak excede.\n",
"- Revisar `diffusers_generate` si duration excede (offloading, dtype incorrecto).\n",
"- Replantear contrato `GenerationConfig` si la firma no cuadra."
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3 (ipykernel)",
"language": "python",
"name": "python3"
},
"language_info": {
"name": "python",
"version": "3.13"
}
},
"nbformat": 4,
"nbformat_minor": 5
}
File diff suppressed because one or more lines are too long