feat(ml): auto-commit con 14 cambios

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-13 01:22:02 +02:00
parent 88b5b27dc0
commit aec5d82011
14 changed files with 1302 additions and 0 deletions
@@ -0,0 +1,167 @@
"""Tests para el backend sdcpp_python: sdcpp_python_load y sdcpp_python_generate."""
from __future__ import annotations
import sys
import os
import pytest
# Ajustar path para importar desde python/functions/ml/
_ML_PATH = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", ".."))
sys.path.insert(0, _ML_PATH)
# Importacion lazy — salta todos los tests si el package no esta instalado.
pytest.importorskip(
"stable_diffusion_cpp",
reason="stable_diffusion_cpp no instalado — skip tests sdcpp_python backend",
)
# El paquete usa modulos hermanos sin prefijo (model_ref, generation_config...).
# Para evitar el double-import problem, mapeamos los aliases antes de importar
# las funciones bajo test.
import ml.model_ref as _mref_module
import ml.generation_config as _gcfg_module
import ml.image_gen_result as _igr_module
for _alias, _mod in [
("model_ref", _mref_module),
("generation_config", _gcfg_module),
("image_gen_result", _igr_module),
]:
if _alias not in sys.modules:
sys.modules[_alias] = _mod # type: ignore[assignment]
from ml.model_ref import ModelRef
from ml.generation_config import GenerationConfig
from ml.image_gen_result import ImageGenResult
from ml.sdcpp_python_load import sdcpp_python_load, _clear_sd_cache
from ml.sdcpp_python_generate import sdcpp_python_generate
# ---------------------------------------------------------------------------
# Constantes
# ---------------------------------------------------------------------------
SD_TURBO_SAFETENSORS = (
"/home/lucas/vaults/imagegen_models/diffusers/sd-turbo/sd_turbo.safetensors"
)
# ---------------------------------------------------------------------------
# Fixtures
# ---------------------------------------------------------------------------
@pytest.fixture(scope="session")
def sd_turbo_model() -> ModelRef:
"""ModelRef apuntando al safetensors de SD Turbo en local."""
if not os.path.isfile(SD_TURBO_SAFETENSORS):
pytest.skip(
f"Modelo SD Turbo no encontrado en {SD_TURBO_SAFETENSORS}"
)
return ModelRef(
name="sd-turbo",
model_type="sd15",
quantization="fp16",
path=SD_TURBO_SAFETENSORS,
)
@pytest.fixture(scope="session")
def loaded_sd(sd_turbo_model: ModelRef):
"""StableDiffusion cargado una sola vez para toda la sesion de tests."""
_clear_sd_cache()
sd = sdcpp_python_load(sd_turbo_model, n_threads=-1, wtype="default")
yield sd
_clear_sd_cache()
@pytest.fixture(scope="session")
def sd_turbo_cfg(sd_turbo_model: ModelRef) -> GenerationConfig:
"""GenerationConfig para SD Turbo: 512x512, 4 steps, euler_a, seed=42."""
return GenerationConfig(
prompt="a simple red apple on a white table",
negative_prompt=None,
seed=42,
steps=4,
cfg_scale=1.0,
sampler="euler_a",
width=512,
height=512,
model=sd_turbo_model,
loras=[],
)
# ---------------------------------------------------------------------------
# Tests
# ---------------------------------------------------------------------------
def test_load_retorna_objeto(loaded_sd) -> None:
"""load retorna objeto StableDiffusion"""
from stable_diffusion_cpp import StableDiffusion
assert isinstance(loaded_sd, StableDiffusion), (
f"Se esperaba StableDiffusion, se obtuvo {type(loaded_sd)}"
)
def test_load_caches(sd_turbo_model: ModelRef, loaded_sd) -> None:
"""segunda llamada retorna instancia cacheada"""
import time
t0 = time.perf_counter()
sd2 = sdcpp_python_load(sd_turbo_model, n_threads=-1, wtype="default")
elapsed = time.perf_counter() - t0
assert sd2 is loaded_sd, "Segunda llamada debe retornar la misma instancia cacheada"
assert elapsed < 0.5, (
f"Segunda llamada tardo {elapsed:.3f}s — deberia ser inmediata (cache hit)"
)
def test_generate_retorna_image_gen_result(
loaded_sd, sd_turbo_cfg: GenerationConfig
) -> None:
"""generate retorna ImageGenResult valido"""
result = sdcpp_python_generate(loaded_sd, sd_turbo_cfg)
assert isinstance(result, ImageGenResult), (
f"Se esperaba ImageGenResult, se obtuvo {type(result)}"
)
assert result.image is not None, "result.image no debe ser None"
def test_duration_ms_mayor_que_cero(
loaded_sd, sd_turbo_cfg: GenerationConfig
) -> None:
"""duration_ms mayor que cero"""
result = sdcpp_python_generate(loaded_sd, sd_turbo_cfg)
assert result.duration_ms > 0, (
f"duration_ms debe ser > 0, se obtuvo {result.duration_ms}"
)
def test_meta_backend_es_sdcpp_python(
loaded_sd, sd_turbo_cfg: GenerationConfig
) -> None:
"""meta backend es sdcpp_python"""
result = sdcpp_python_generate(loaded_sd, sd_turbo_cfg)
assert result.meta.get("backend") == "sdcpp_python", (
f"meta['backend'] debe ser 'sdcpp_python', se obtuvo {result.meta.get('backend')!r}"
)
assert result.meta.get("model") == sd_turbo_cfg.model.name
assert result.meta.get("sampler") == sd_turbo_cfg.sampler
assert result.meta.get("seed") == sd_turbo_cfg.seed
def test_vram_peak_mb_es_none(
loaded_sd, sd_turbo_cfg: GenerationConfig
) -> None:
"""vram_peak_mb es None — sdcpp no expone medicion de VRAM"""
result = sdcpp_python_generate(loaded_sd, sd_turbo_cfg)
assert result.vram_peak_mb is None, (
f"vram_peak_mb debe ser None, se obtuvo {result.vram_peak_mb}"
)