"""Linarix-v2 — Hugging Face Space demo (ZeroGPU / RTX Pro 6000 Blackwell + Gradio).""" from __future__ import annotations import os from pathlib import Path # ZeroGPU Spaces: ~/.cache is often read-only — use /tmp for HF + diffusers caches. _hf_root = Path(os.environ.get("HF_HOME", "/tmp/huggingface")) for _sub in ("hub", "modules", "transformers", "diffusers"): (_hf_root / _sub).mkdir(parents=True, exist_ok=True) os.environ.setdefault("HF_HOME", str(_hf_root)) os.environ.setdefault("HUGGINGFACE_HUB_CACHE", str(_hf_root / "hub")) os.environ.setdefault("HF_MODULES_CACHE", str(_hf_root / "modules")) os.environ.setdefault("TRANSFORMERS_CACHE", str(_hf_root / "transformers")) os.environ.setdefault("DIFFUSERS_CACHE", str(_hf_root / "diffusers")) import gc import secrets import sys import time import warnings # FLA / hub noise at import on Spaces builder (Triton probe, py3.10 compile stub, deprecated kwargs). warnings.filterwarnings("ignore", message="Triton is not supported on current platform") warnings.filterwarnings("ignore", message="torch.compile is not available in Python 3.10") warnings.filterwarnings("ignore", message="The `local_dir_use_symlinks` argument is deprecated") import gradio as gr import spaces import torch from huggingface_hub import snapshot_download # Environment banner. The Spaces base image ships torch pre-installed, so the build # log only says "Requirement already satisfied" and never names a version -- but the # exact torch/CUDA/Python triple is what any compiled wheel (causal-conv1d, FLA) has # to be built against, so print it where it is actually visible. print( f"env: python {sys.version_info.major}.{sys.version_info.minor} | " f"torch {torch.__version__} | cuda {torch.version.cuda} | " f"gradio {gr.__version__}", flush=True, ) MODEL_ID = os.environ.get("BOOMER_MODEL_ID", "Akrao9/Linarix-v2") DEFAULT_PROMPT = ( "a lighthouse on a rocky cliff above crashing waves at golden hour, " "dramatic stormy sky, cinematic photography" ) # Drawn from boomer/showcase_prompts.py (the set numbered under the model card's # sample grid), expanded with the descriptive phrasing this model responds best to. EXAMPLE_PROMPTS: list[tuple[str, str]] = [ ( "Mountain Village", "a snow-covered mountain village at blue hour, warm light glowing from every window, smoke rising from chimneys, deep alpine dusk.", ), ( "Desert Canyon", "a desert canyon at sunset, towering layered red rock walls carved by wind, long shadows across the canyon floor, warm directional light.", ), ( "Night Motorcycle", "a vintage motorcycle parked on a rain-slicked city street at night, neon signs reflecting in the wet asphalt, chrome catching the light, cinematic.", ), ( "Venetian Canal", "a narrow canal in Venice in late afternoon, weathered pastel facades leaning over still green water, laundry strung between shutters, golden side light.", ), ] def _hf_token() -> str | None: """Return HF token from Space secrets / env if set; None otherwise. Linarix-v2 itself is gated, so Spaces must set an ``HF_TOKEN`` secret whose owner can read ``Akrao9/Linarix-v2``. Qwen3.5 / DC-AE are public. """ for key in ("HF_TOKEN", "HUGGING_FACE_HUB_TOKEN", "HUGGING_HUB_TOKEN"): value = os.environ.get(key, "").strip() if value: return value return None _hf = _hf_token() if not _hf: raise RuntimeError( f"{MODEL_ID} is gated. Add an HF_TOKEN Space secret " "(Settings → Secrets) with read access to that repo, then restart." ) # Make the token available to every huggingface_hub / transformers / diffusers call. os.environ.setdefault("HF_TOKEN", _hf) os.environ.setdefault("HUGGING_FACE_HUB_TOKEN", _hf) try: from huggingface_hub import login as _hf_login _hf_login(token=_hf, add_to_git_credential=False) except Exception as exc: # noqa: BLE001 — non-fatal; explicit token= still used below print(f"Warning: huggingface_hub.login failed ({exc!r}); continuing with token= kwarg.", flush=True) print(f"Loading Linarix pipeline from {MODEL_ID} ...", flush=True) _model_dir = Path( snapshot_download( MODEL_ID, token=_hf, ignore_patterns=["*.png", "*.jpg", "*.jpeg"], ) ) if str(_model_dir) not in sys.path: sys.path.insert(0, str(_model_dir)) from pipeline_boomer import BoomerPipeline # noqa: E402 pipe = BoomerPipeline.from_pretrained(str(_model_dir), torch_dtype=torch.bfloat16, token=_hf) pipe.to("cuda") pipe._hf_token = _hf print("Pre-loading VAE on cuda ...", flush=True) pipe._ensure_vae() print(f"Pre-loading text encoder ({pipe._te_repo}) on cuda ...", flush=True) pipe._ensure_text_encoder() print("Model ready.", flush=True) MODEL_RESOLUTION = int(pipe.transformer.config.latent_size) * 32 MODEL_PARAMS_M = sum(parameter.numel() for parameter in pipe.transformer.parameters()) / 1e6 MODEL_SAMPLER = str(pipe.default_sampler).upper() DEFAULT_STEPS = int(pipe.default_steps) DEFAULT_CFG = float(pipe.default_cfg_scale) DEFAULT_CFG_RESCALE = float(pipe.default_cfg_rescale) def _resolve_seed(seed: float | int | None) -> int: if seed is None or int(seed) < 0: # OS entropy, NOT torch.randint: ZeroGPU forks a worker per @spaces.GPU call # with an identical torch RNG snapshot (and the pipeline re-seeds the global # generator via torch.manual_seed), so torch draws here repeat the same value. return secrets.randbelow(2**32) return int(seed) def _module_vram_gb(module: object | None) -> float: """CUDA memory for module parameters and buffers (weights only, not activations).""" if module is None or not torch.cuda.is_available() or not hasattr(module, "parameters"): return 0.0 bytes_used = 0 for tensor in (*module.parameters(), *module.buffers()): if tensor.is_cuda: bytes_used += tensor.numel() * tensor.element_size() return bytes_used / (1024**3) def _cuda_peak_gb() -> float: if not torch.cuda.is_available(): return 0.0 return torch.cuda.max_memory_allocated() / (1024**3) def _format_gen_stats( *, step_count: int, elapsed_s: float, peak_vram_gb: float, dit_weights_gb: float, denoise_vram_gb: float | None = None, decode_vram_gb: float | None = None, ) -> str: it_per_s = step_count / elapsed_s if elapsed_s > 0 else 0.0 parts = [ f"Generation Speed: {it_per_s:.2f} it/s", f"Peak VRAM: {peak_vram_gb:.2f} GB", ] if denoise_vram_gb is not None: parts.append(f"Denoise VRAM: {denoise_vram_gb:.2f} GB") if decode_vram_gb is not None: parts.append(f"Decode VRAM: {decode_vram_gb:.2f} GB") parts.append(f"DiT weights: {dit_weights_gb:.2f} GB") return " | ".join(parts) # ZeroGPU "large" = half NVIDIA RTX Pro 6000 Blackwell MIG slice (48 GB VRAM). @spaces.GPU(size="large", duration=150) def generate_image( prompt: str, seed: float, steps: float, cfg_scale: float, ) -> tuple[object, str]: prompt = (prompt or "").strip() if not prompt: raise gr.Error("Please enter a prompt before generating.") resolved_seed = _resolve_seed(seed) step_count = max(1, int(steps)) cfg = float(cfg_scale) dit_weights_gb = _module_vram_gb(pipe.transformer) if torch.cuda.is_available(): torch.cuda.empty_cache() torch.cuda.reset_peak_memory_stats() started = time.perf_counter() result = pipe( prompt, steps=step_count, seed=resolved_seed, cfg_scale=cfg, cfg_rescale=DEFAULT_CFG_RESCALE, offload_text_encoder=True, ) elapsed_s = time.perf_counter() - started image = result[0] peak_vram_gb = _cuda_peak_gb() run_stats = getattr(pipe, "last_run_stats", None) denoise_vram_gb = getattr(run_stats, "peak_denoise_gb", None) if run_stats else None decode_vram_gb = getattr(run_stats, "peak_decode_gb", None) if run_stats else None stats_line = _format_gen_stats( step_count=step_count, elapsed_s=elapsed_s, peak_vram_gb=peak_vram_gb, dit_weights_gb=dit_weights_gb, denoise_vram_gb=denoise_vram_gb, decode_vram_gb=decode_vram_gb, ) print(stats_line, flush=True) gc.collect() if torch.cuda.is_available(): torch.cuda.empty_cache() status = ( f"Done — seed {resolved_seed}, steps {step_count}, CFG {cfg:.1f}. " f"{MODEL_RESOLUTION}×{MODEL_RESOLUTION}px via {MODEL_SAMPLER}.\n{stats_line}" ) return image, status def reset_form() -> tuple[str, None, str, float]: return DEFAULT_PROMPT, None, "Reset.", -1.0 def build_ui() -> gr.Blocks: example_rows = [[text, -1, DEFAULT_STEPS, DEFAULT_CFG] for _, text in EXAMPLE_PROMPTS] with gr.Blocks(title="Linarix-v2") as demo: gr.Markdown( """ # Linarix-v2 — Text to Image """ f"""Generate **{MODEL_RESOLUTION}×{MODEL_RESOLUTION}** images from text with a **~{MODEL_PARAMS_M:.0f}M-parameter** Hybrid GatedDeltaNet-2/DDT model.use descriptive prompts; **CFG {DEFAULT_CFG:.1f}** is the default. Runs on NVIDIA RTX Pro 6000 Blackwell. First run may take a minute while a GPU is allocated. """ ) with gr.Row(equal_height=False): with gr.Column(scale=3): prompt = gr.Textbox( label="Prompt", value=DEFAULT_PROMPT, lines=10, ) with gr.Accordion("Advanced settings", open=False): seed = gr.Number( label="Seed (-1 = random)", value=-1, precision=0, ) steps = gr.Slider( label="Denoising steps", minimum=16, maximum=64, step=1, value=DEFAULT_STEPS, ) cfg_scale = gr.Slider( label="CFG scale", minimum=1.0, maximum=8.0, step=0.1, value=DEFAULT_CFG, ) with gr.Row(): generate_btn = gr.Button("Generate", variant="primary") reset_btn = gr.Button("Reset") output = gr.Image(label="Generated image", type="pil", height=640) status = gr.Textbox(label="Status", interactive=False) with gr.Column(scale=2): gr.Markdown("### Example prompts") gr.Markdown("Click an example to **generate immediately**.") gr.Examples( examples=example_rows, inputs=[prompt, seed, steps, cfg_scale], outputs=[output, status], fn=generate_image, run_on_click=True, cache_examples=False, label="", examples_per_page=4, ) generate_btn.click( fn=generate_image, inputs=[prompt, seed, steps, cfg_scale], outputs=[output, status], ) reset_btn.click( fn=reset_form, outputs=[prompt, output, status, seed], ) prompt.submit( fn=generate_image, inputs=[prompt, seed, steps, cfg_scale], outputs=[output, status], ) return demo demo = build_ui() if __name__ == "__main__": demo.launch()