Spaces:
Running on Zero
Running on Zero
| import os | |
| os.environ.setdefault("PYTHONUNBUFFERED", "1") | |
| os.environ.setdefault("GRADIO_SSR_MODE", "0") | |
| os.environ.setdefault("PYTORCH_CUDA_ALLOC_CONF", "expandable_segments:True") | |
| os.environ.setdefault("TORCHDYNAMO_DISABLE", "1") | |
| # spaces MUST be imported before torch / diffusers so ZeroGPU can | |
| # monkey-patch CUDA. This is how the working Ideogram 4 Spaces boot. | |
| import spaces | |
| import math | |
| import sys | |
| import torch | |
| import gradio as gr | |
| from diffusers import Ideogram4Pipeline | |
| from diffusers.quantizers.bitsandbytes.bnb_quantizer import BnB4BitDiffusersQuantizer | |
| from PIL import Image | |
| # cu130-era bitsandbytes returns Params4bit.shape as a plain tuple. | |
| # diffusers' check_quantized_param_shape calls .numel() on it and crashes | |
| # with: AttributeError: 'tuple' object has no attribute 'numel' | |
| # Same shim as linoyts/ideogram-4-turbotime. | |
| def _check_quantized_param_shape(self, param_name, current_param, loaded_param): | |
| current_shape = getattr(current_param, "shape", current_param) | |
| loaded_shape = getattr(loaded_param, "shape", loaded_param) | |
| n = math.prod(tuple(current_shape)) | |
| inferred_shape = (n,) if "bias" in param_name else ((n + 1) // 2, 1) | |
| if tuple(loaded_shape) != tuple(inferred_shape): | |
| raise ValueError( | |
| f"Expected flattened shape of {param_name} to be {inferred_shape}, " | |
| f"got {tuple(loaded_shape)}." | |
| ) | |
| return True | |
| BnB4BitDiffusersQuantizer.check_quantized_param_shape = _check_quantized_param_shape | |
| MODEL_ID = "ideogram-ai/ideogram-4-nf4" | |
| LORA_REPO = "ostris/ideogram_4_turbotime_lora" | |
| LORA_FILE = "ideogram_4_turbotime_v1.safetensors" | |
| HF_TOKEN = os.environ.get("HF_TOKEN") or os.environ.get("HUGGING_FACE_HUB_TOKEN") | |
| def log(msg: str) -> None: | |
| print(msg, file=sys.stderr, flush=True) | |
| if not HF_TOKEN: | |
| raise RuntimeError( | |
| "Set an HF_TOKEN secret. Accept the gate on " | |
| "https://huggingface.co/ideogram-ai/ideogram-4-nf4" | |
| ) | |
| log("startup: loading diffusers Ideogram4Pipeline nf4") | |
| pipe = Ideogram4Pipeline.from_pretrained( | |
| MODEL_ID, | |
| torch_dtype=torch.bfloat16, | |
| token=HF_TOKEN, | |
| ) | |
| # Working ZeroGPU Spaces dequantize nf4 -> bf16 then move to emulated cuda | |
| # at import. ZeroGPU packs those weights and streams them on first GPU call. | |
| if hasattr(pipe, "transformer") and hasattr(pipe.transformer, "dequantize"): | |
| pipe.transformer.dequantize() | |
| if hasattr(pipe, "unconditional_transformer") and hasattr(pipe.unconditional_transformer, "dequantize"): | |
| pipe.unconditional_transformer.dequantize() | |
| pipe.to("cuda") | |
| try: | |
| pipe.transformer.load_lora_adapter( | |
| LORA_REPO, | |
| weight_name=LORA_FILE, | |
| prefix="diffusion_model", | |
| token=HF_TOKEN, | |
| ) | |
| log("startup: TurboTime LoRA loaded (2-8 steps, guidance_scale=1)") | |
| except Exception as exc: | |
| log(f"startup: LoRA load failed, will run base CFG path: {exc!r}") | |
| log("startup: pipeline on cuda (ZeroGPU-emulated until Generate)") | |
| def get_duration(prompt, width, height, steps, progress=None): | |
| # Must accept the same args as generate(), including Gradio's progress. | |
| tokens = (int(width) // 16) * (int(height) // 16) | |
| seconds = 25 + int(steps) * max(0.35, 0.00035 * tokens) | |
| return int(min(max(seconds, 60), 180)) | |
| def generate(prompt, width, height, steps, progress=gr.Progress(track_tqdm=True)): | |
| log( | |
| f"generate() cuda={torch.cuda.is_available()} " | |
| f"{int(width)}x{int(height)} steps={int(steps)}" | |
| ) | |
| if not (prompt or "").strip(): | |
| raise gr.Error("Prompt is empty.") | |
| if not torch.cuda.is_available(): | |
| raise gr.Error("ZeroGPU did not attach CUDA to this call.") | |
| # Magic prompt OFF: feed the textbox verbatim (same as --no-magic-prompt). | |
| # This diffusers build defaults guidance_schedule to the 48-step CFG | |
| # curve. Passing guidance_scale at the same time raises: | |
| # "Only one of guidance_scale and guidance_schedule may be set." | |
| # TurboTime is tuned for a constant scale of 1.0 (no CFG). | |
| kwargs = dict( | |
| prompt=prompt, | |
| height=int(height), | |
| width=int(width), | |
| num_inference_steps=int(steps), | |
| guidance_scale=1.0, | |
| guidance_schedule=None, | |
| generator=torch.Generator(device="cuda").manual_seed(0), | |
| ) | |
| # Some diffusers builds use prompt_upsampling=; keep it off if present. | |
| try: | |
| images = pipe(**kwargs, prompt_upsampling=False).images | |
| except TypeError: | |
| images = pipe(**kwargs).images | |
| image = images[0] | |
| if not isinstance(image, Image.Image): | |
| image = Image.fromarray(image) | |
| log(f"generate() ok {image.size}") | |
| return image | |
| with gr.Blocks(title="Ideogram 4 (ZeroGPU)") as demo: | |
| gr.Markdown( | |
| "# Ideogram 4 on ZeroGPU\n" | |
| "This is the stack the working Spaces use: **diffusers + nf4 + " | |
| "TurboTime LoRA + `size='xlarge'`**. Magic prompt is **off** — " | |
| "your prompt is used as-is.\n\n" | |
| "Needs an `HF_TOKEN` whose account accepted " | |
| "[ideogram-ai/ideogram-4-nf4](https://huggingface.co/ideogram-ai/ideogram-4-nf4). " | |
| "First Space boot can take a long time while weights download." | |
| ) | |
| with gr.Row(): | |
| with gr.Column(): | |
| prompt = gr.Textbox( | |
| label="Prompt", | |
| lines=3, | |
| placeholder="A photo of a cat holding a sign that says hello world", | |
| ) | |
| with gr.Row(): | |
| width = gr.Slider(256, 2048, value=1024, step=16, label="Width") | |
| height = gr.Slider(256, 2048, value=1024, step=16, label="Height") | |
| steps = gr.Slider(2, 12, value=8, step=1, label="Steps (TurboTime LoRA)") | |
| run_btn = gr.Button("Generate", variant="primary") | |
| with gr.Column(): | |
| output = gr.Image(label="Result") | |
| run_btn.click( | |
| fn=generate, | |
| inputs=[prompt, width, height, steps], | |
| outputs=output, | |
| api_name="predict", | |
| ) | |
| demo.queue().launch(ssr_mode=False, show_error=True) |