from __future__ import annotations import asyncio.base_events import gc import os import random import threading import time import traceback import warnings from dataclasses import dataclass from pathlib import Path _ORIGINAL_EVENT_LOOP_DEL = asyncio.base_events.BaseEventLoop.__del__ def _quiet_invalid_fd_event_loop_del(self, _warn=warnings.warn): try: _ORIGINAL_EVENT_LOOP_DEL(self, _warn) except ValueError as exc: if "Invalid file descriptor" not in str(exc): raise asyncio.base_events.BaseEventLoop.__del__ = _quiet_invalid_fd_event_loop_del warnings.filterwarnings( "ignore", message=r".*HTTP_422_UNPROCESSABLE_ENTITY.*", category=Warning, module=r"gradio\.routes", ) import gradio as gr import spaces import torch from sefi import SEFIInferencePipeline os.environ.setdefault("PYTORCH_CUDA_ALLOC_CONF", "expandable_segments:True") os.environ.setdefault("HF_HUB_ENABLE_HF_TRANSFER", "1") CACHE_DIR = os.getenv( "SEFI_CACHE_DIR", "/data/sefi-cache" if os.path.isdir("/data") else "/tmp/sefi-cache", ) APP_DIR = Path(__file__).resolve().parent EXAMPLE_ASSETS_DIR = APP_DIR / "assets" / "examples" gr.set_static_paths([EXAMPLE_ASSETS_DIR]) @dataclass(frozen=True) class ModelPreset: label: str repo_id: str family: str steps: int guidance: float MODEL_PRESETS: dict[str, ModelPreset] = { "1b-base": ModelPreset( label="SeFi-Image 1B Base", repo_id="SeFi-Image/SeFi-Image-1B-Base", family="base", steps=50, guidance=4.0, ), "2b-base": ModelPreset( label="SeFi-Image 2B Base", repo_id="SeFi-Image/SeFi-Image-2B-Base", family="base", steps=50, guidance=4.0, ), "5b-base": ModelPreset( label="SeFi-Image 5B Base", repo_id="SeFi-Image/SeFi-Image-5B-Base", family="base", steps=50, guidance=4.0, ), "5b-rl": ModelPreset( label="SeFi-Image 5B RL", repo_id="SeFi-Image/SeFi-Image-5B-RL", family="rl", steps=50, guidance=4.0, ), "1b-turbo": ModelPreset( label="SeFi-Image 1B Turbo", repo_id="SeFi-Image/SeFi-Image-1B-turbo", family="turbo", steps=4, guidance=1.0, ), "2b-turbo": ModelPreset( label="SeFi-Image 2B Turbo", repo_id="SeFi-Image/SeFi-Image-2B-turbo", family="turbo", steps=4, guidance=1.0, ), "5b-turbo": ModelPreset( label="SeFi-Image 5B Turbo", repo_id="SeFi-Image/SeFi-Image-5B-turbo", family="turbo", steps=4, guidance=1.0, ), } DEFAULT_MODEL = "1b-turbo" APP_TITLE = "SeFi-Image" MODEL_PUBLISHER_URL = "https://huggingface.co/SeFi-Image" PROJECT_PAGE_URL = "https://jmliu206.github.io/sefi-web/" IMAGE_SIZE = 1024 TURBO_STEPS = {4, 8, 10} GPU_DURATION_BY_MODEL = { "1b-turbo": 25, "2b-turbo": 35, "5b-turbo": 65, "1b-base": 40, "2b-base": 60, "5b-base": 110, "5b-rl": 110, } EXTRA_STEP_SECONDS_BY_MODEL = { "1b-turbo": 2, "2b-turbo": 3, "5b-turbo": 5, "1b-base": 1, "2b-base": 1, "5b-base": 2, "5b-rl": 2, } EXAMPLES = [ { "label": "Anime cinematic portrait", "model_key": "5b-rl", "prompt": ( "Anime-realistic cinematic portrait of a young adult woman in a " "rain-lit Tokyo alley, expressive eyes, subtle natural skin texture, " "wind-touched dark hair, layered streetwear jacket, neon reflections, " "shallow depth of field, 85mm lens look, elegant color grading, " "highly detailed face, painterly anime realism, dramatic rim light, " "clean composition, masterpiece quality." ), "steps": 50, "guidance": 4.0, "seed": 7401, "image": str(EXAMPLE_ASSETS_DIR / "anime_cinematic_portrait_1024.png"), }, { "label": "Realistic editorial portrait", "model_key": "5b-rl", "prompt": ( "Realistic editorial portrait of a confident young adult man in a " "black turtleneck and tailored coat, soft window light, textured " "gray studio backdrop, cinematic shadows, detailed eyes, natural " "skin, medium-format fashion photography, restrained luxury mood, " "precise facial anatomy, sharp focus, subtle film grain, balanced " "composition." ), "steps": 50, "guidance": 4.0, "seed": 7402, "image": str(EXAMPLE_ASSETS_DIR / "realistic_editorial_portrait_1024.png"), }, { "label": "Mythic alpine landscape", "model_key": "5b-rl", "prompt": ( "Incredible mythic alpine landscape at sunrise, enormous " "snow-covered mountains above a crystal lake, floating mist, " "wildflowers in the foreground, tiny stone observatory on a ridge, " "golden light breaking through storm clouds, epic depth, sweeping " "cinematic composition, ultra detailed environment, atmospheric " "perspective, realistic fantasy concept art, awe inspiring scale." ), "steps": 50, "guidance": 4.0, "seed": 7403, "image": str(EXAMPLE_ASSETS_DIR / "mythic_alpine_landscape_1024.png"), }, { "label": "Cyrillic museum poster", "model_key": "5b-rl", "prompt": ( 'A complex museum poster for a northern light exhibition, strong ' 'editorial typography, large readable Cyrillic headline "СЕВЕРНЫЙ ' 'СВЕТ", smaller Cyrillic subheading "выставка света и льда", ' "asymmetric Swiss grid composition, layered translucent paper, icy " "blue and black ink, precise margins, high-end graphic design, " "photographed as a printed poster on a gallery wall." ), "steps": 50, "guidance": 4.0, "seed": 7301, "image": str(EXAMPLE_ASSETS_DIR / "cyrillic_museum_poster_1024.png"), }, { "label": "Korean night jazz poster", "model_key": "5b-rl", "prompt": ( 'A sophisticated Korean night jazz festival poster, large Hangul ' 'title "서울의 밤", smaller Hangul text "재즈 페스티벌", vertical ' "composition with a saxophone silhouette, neon reflections on rain, " "black paper, magenta and cyan spot colors, tight typographic " "hierarchy, centered moon circle, elegant modern Seoul design." ), "steps": 50, "guidance": 4.0, "seed": 7302, "image": str(EXAMPLE_ASSETS_DIR / "korean_night_jazz_poster_1024.png"), }, { "label": "Chinese tea packaging", "model_key": "5b-rl", "prompt": ( 'Premium Chinese tea packaging poster, large brush-style Chinese ' 'characters "春风茶馆", small vertical Chinese seal text, ceramic ' "tea cup and folded paper wrapper, balanced negative space, deep " "jade green and warm ivory, gold foil accents, product photography " "mixed with graphic layout, calm luxury composition." ), "steps": 50, "guidance": 4.0, "seed": 7303, "image": str(EXAMPLE_ASSETS_DIR / "chinese_tea_packaging_1024.png"), }, { "label": "Multiscript design festival", "model_key": "5b-rl", "prompt": ( 'International design festival poster with three writing systems in ' 'one composition: Cyrillic "ТИХИЙ ГОРОД", Korean "고요한 도시", ' 'Chinese "静城". Use the texts as bold typographic blocks, modular ' "grid, architectural isometric city fragments, layered risograph " "texture, red black and pale gray palette, disciplined composition, " "poster photographed flat on a studio table." ), "steps": 50, "guidance": 4.0, "seed": 7304, "image": str(EXAMPLE_ASSETS_DIR / "multiscript_design_festival_1024.png"), }, ] _MODEL_LOCK = threading.Lock() _LOADED_MODEL_KEY: str | None = None _LOADED_PIPE: SEFIInferencePipeline | None = None def _model_choices() -> list[tuple[str, str]]: return [(preset.label, key) for key, preset in MODEL_PRESETS.items()] def _example_samples() -> list[list[str]]: samples = [] for example in EXAMPLES: preset = MODEL_PRESETS[str(example["model_key"])] settings = ( f"{IMAGE_SIZE}x{IMAGE_SIZE}, {example['steps']} steps, " f"guidance {example['guidance']}, seed {example['seed']}" ) samples.append( [ str(example["image"]), str(example["label"]), preset.label, str(example["prompt"]), settings, ] ) return samples def _torch_cleanup() -> None: gc.collect() if torch.cuda.is_available(): torch.cuda.empty_cache() torch.cuda.ipc_collect() def _clear_loaded_model() -> None: global _LOADED_MODEL_KEY, _LOADED_PIPE _LOADED_PIPE = None _LOADED_MODEL_KEY = None _torch_cleanup() def _load_pipe(model_key: str) -> SEFIInferencePipeline: global _LOADED_MODEL_KEY, _LOADED_PIPE preset = MODEL_PRESETS[model_key] with _MODEL_LOCK: if _LOADED_PIPE is not None and _LOADED_MODEL_KEY == model_key: return _LOADED_PIPE _clear_loaded_model() pipe = SEFIInferencePipeline.from_pretrained( preset.repo_id, cache_dir=CACHE_DIR, device="cuda", dtype="bf16", ) _LOADED_MODEL_KEY = model_key _LOADED_PIPE = pipe return pipe def _friendly_error(exc: BaseException, repo_id: str | None = None) -> str: text = str(exc) lowered = text.lower() gated = ( "requires approval" in lowered or "gated" in lowered or "401" in lowered or "403" in lowered ) if gated: repo_hint = f" for `{repo_id}`" if repo_id else "" return ( f"Model access is not approved{repo_hint}. Open the model page while " "logged in as the Space owner, accept the SeFi non-commercial gate, " "and retry. The Space already has `HF_TOKEN` configured as a secret." ) return f"{type(exc).__name__}: {text}" def _format_seconds(seconds: float) -> str: seconds = max(0, int(round(seconds))) minutes, secs = divmod(seconds, 60) if minutes: return f"{minutes}m {secs:02d}s" return f"{secs}s" def _format_timing(seconds: float) -> str: seconds = max(0.0, float(seconds)) if seconds < 10: return f"{seconds:.1f}s" return _format_seconds(seconds) def _format_generation_summary( *, preset: ModelPreset, width: int, height: int, steps: int, guidance_scale: float, seed: int, total_seconds: float, model_load_seconds: float, model_was_loaded: bool, setup_seconds: float, denoise_seconds: float, decode_seconds: float, ) -> str: if denoise_seconds > 0 and steps > 0: speed = f"{steps / denoise_seconds:.2f} steps/s ({denoise_seconds / steps:.2f}s/step)" else: speed = "n/a" load_note = "already in memory" if model_was_loaded else "download/load/switch" return ( "### Generation summary\n\n" f"`{preset.repo_id}` · {width}x{height} · {steps} steps · " f"guidance {guidance_scale} · seed {seed}\n\n" "| Phase | Time |\n" "| --- | ---: |\n" f"| Total backend time | {_format_timing(total_seconds)} |\n" f"| Model {load_note} | {_format_timing(model_load_seconds)} |\n" f"| Prompt + latent setup | {_format_timing(setup_seconds)} |\n" f"| Raw denoising | {_format_timing(denoise_seconds)} |\n" f"| Decode + output | {_format_timing(decode_seconds)} |\n" f"| Raw denoising speed | {speed} |\n" ) def model_defaults(model_key: str): preset = MODEL_PRESETS[model_key] return ( gr.update(value=preset.steps), gr.update(value=preset.guidance), ( f"Selected `{preset.repo_id}`. Defaults: " f"{preset.steps} steps, guidance {preset.guidance}." ), ) def load_example(index: int): example = EXAMPLES[int(index)] preset = MODEL_PRESETS[str(example["model_key"])] status = ( f'Loaded example "{example["label"]}" generated with `{preset.repo_id}` ' f"at {IMAGE_SIZE}x{IMAGE_SIZE}, {example['steps']} steps, " f'guidance {example["guidance"]}, seed {example["seed"]}.' ) return ( example["model_key"], example["prompt"], example["steps"], example["guidance"], example["seed"], True, example["image"], status, ) def estimate_duration( model_key: str, prompt: str, steps: int, guidance_scale: float, seed: int, randomize_seed: bool, *_args, **_kwargs, ) -> int: del prompt, guidance_scale, seed, randomize_seed preset = MODEL_PRESETS.get(model_key) duration = GPU_DURATION_BY_MODEL.get(model_key, 60) if preset is not None: extra_steps = max(0, int(steps) - preset.steps) duration += extra_steps * EXTRA_STEP_SECONDS_BY_MODEL.get(model_key, 2) return duration @spaces.GPU(duration=estimate_duration) def generate( model_key: str, prompt: str, steps: int, guidance_scale: float, seed: int, randomize_seed: bool, progress=gr.Progress(track_tqdm=False), ): request_started_at = time.monotonic() prompt = prompt.strip() if not prompt: return None, "Enter a prompt.", seed preset = MODEL_PRESETS[model_key] steps = int(steps) guidance_scale = float(guidance_scale) width = IMAGE_SIZE height = IMAGE_SIZE if preset.family == "turbo": if steps not in TURBO_STEPS: return ( None, "Turbo checkpoints currently support 4, 8, or 10 denoising steps.", seed, ) if guidance_scale != 1.0: return None, "Turbo checkpoints should use guidance 1.0.", seed if randomize_seed: seed = random.randint(0, 2**31 - 1) try: if torch.cuda.is_available(): torch.backends.cuda.matmul.allow_tf32 = True progress(0, desc=f"Loading {preset.label}") model_was_loaded = _LOADED_PIPE is not None and _LOADED_MODEL_KEY == model_key load_started_at = time.monotonic() pipe = _load_pipe(model_key) load_finished_at = time.monotonic() pipe_started_at = load_finished_at denoise_started_at: float | None = None last_denoise_step_at: float | None = None progress(0, desc="Preparing prompt and latents") def report_step(step: int, total: int) -> None: nonlocal denoise_started_at, last_denoise_step_at total = max(1, int(total)) step = min(max(0, int(step)), total) now = time.monotonic() if step == 0: denoise_started_at = now if denoise_started_at is None: denoise_started_at = now if step > 0: last_denoise_step_at = now elapsed = now - denoise_started_at remaining = 0.0 if step > 0: remaining = (elapsed / step) * (total - step) desc = ( f"Denoising {step}/{total} steps | " f"elapsed {_format_seconds(elapsed)} | " f"ETA {_format_seconds(remaining)}" ) progress(step / total, desc=desc) images = pipe( prompt, num_inference_steps=steps, guidance_scale=guidance_scale, height=height, width=width, seed=int(seed), progress_callback=report_step, ) pipe_finished_at = time.monotonic() progress(1, desc="Finalizing image") except Exception as exc: traceback.print_exc() return None, _friendly_error(exc, preset.repo_id), seed if not images: return None, "Generation finished without an image.", seed request_finished_at = time.monotonic() if denoise_started_at is None: denoise_started_at = pipe_started_at if last_denoise_step_at is None: last_denoise_step_at = denoise_started_at return ( images[0], _format_generation_summary( preset=preset, width=width, height=height, steps=steps, guidance_scale=guidance_scale, seed=seed, total_seconds=request_finished_at - request_started_at, model_load_seconds=load_finished_at - load_started_at, model_was_loaded=model_was_loaded, setup_seconds=denoise_started_at - pipe_started_at, denoise_seconds=last_denoise_step_at - denoise_started_at, decode_seconds=pipe_finished_at - last_denoise_step_at, ), seed, ) APP_CSS = """ #examples_table { width: 100%; } #examples_table .table-wrap, #examples_table .table-wrap.fixed-height { height: auto !important; max-height: none !important; } #examples_table table { width: 100%; } #examples_table th, #examples_table td { vertical-align: middle; } #examples_table img { width: 64px !important; height: 64px !important; object-fit: cover; border-radius: 6px; } #examples_table td { white-space: normal; line-height: 1.35; } #generation_status table { width: 100%; margin-top: 0.5rem; } #generation_status th, #generation_status td { padding: 6px 8px; } #generation_status th:last-child, #generation_status td:last-child { text-align: right; white-space: nowrap; } """ with gr.Blocks(title=APP_TITLE) as demo: gr.Markdown( f""" # {APP_TITLE} SeFi-Image text-to-image checkpoints in Base, Turbo, and 5B RL variants. Base models use the 50-step guidance-4.0 setting, Turbo models use the 4-step guidance-1.0 setting, and 5B RL is the reinforced-learning checkpoint. [Models on Hugging Face]({MODEL_PUBLISHER_URL}) | [Project page]({PROJECT_PAGE_URL}) """ ) with gr.Row(): with gr.Column(scale=1, min_width=320): model = gr.Dropdown( label="Model", choices=_model_choices(), value=DEFAULT_MODEL, interactive=True, ) prompt = gr.Textbox( label="Prompt", value="A blue ceramic mug on a white desk.", lines=4, max_lines=8, ) with gr.Row(): steps = gr.Slider( minimum=1, maximum=60, step=1, value=MODEL_PRESETS[DEFAULT_MODEL].steps, label="Steps", ) guidance = gr.Slider( minimum=1.0, maximum=8.0, step=0.1, value=MODEL_PRESETS[DEFAULT_MODEL].guidance, label="Guidance", ) with gr.Row(): seed = gr.Number( label="Seed", value=42, precision=0, minimum=0, maximum=2**31 - 1, ) randomize_seed = gr.Checkbox(label="Randomize", value=True) with gr.Row(): run = gr.Button("Generate", variant="primary") with gr.Column(scale=1, min_width=360): image = gr.Image(label="Image", type="pil", format="png") status = gr.Markdown( ( f"Selected `{MODEL_PRESETS[DEFAULT_MODEL].repo_id}`. Defaults: " f"{MODEL_PRESETS[DEFAULT_MODEL].steps} steps, " f"guidance {MODEL_PRESETS[DEFAULT_MODEL].guidance}." ), elem_id="generation_status", ) examples = gr.Dataset( samples=_example_samples(), components=[ gr.Image(label="Image", type="filepath", height=64, width=64, render=False), gr.Textbox(label="Example", render=False), gr.Textbox(label="Model", render=False), gr.Textbox(label="Prompt", render=False), gr.Textbox(label="Settings", render=False), ], headers=["Image", "Example", "Model", "Prompt", "Settings"], type="index", layout="table", label="Examples", samples_per_page=10, elem_id="examples_table", ) model.change(model_defaults, inputs=model, outputs=[steps, guidance, status]) examples.click( load_example, inputs=examples, outputs=[ model, prompt, steps, guidance, seed, randomize_seed, image, status, ], api_name="load_example", api_visibility="undocumented", ) run.click( generate, inputs=[ model, prompt, steps, guidance, seed, randomize_seed, ], outputs=[image, status, seed], api_name="generate", concurrency_limit=1, ) demo.queue(default_concurrency_limit=1) if __name__ == "__main__": demo.launch( allowed_paths=[str(EXAMPLE_ASSETS_DIR)], css=APP_CSS, ssr_mode=False, )