Spaces:
Running on Zero
Running on Zero
File size: 6,023 Bytes
d9a4123 93bd5a0 23df166 d9a4123 23df166 d9a4123 23df166 1cfdf94 883e411 d9a4123 1cfdf94 23df166 533ee5a 955087d 23df166 6822ae0 23df166 955087d 23df166 955087d 23df166 d9a4123 23df166 d9a4123 93bd5a0 d9a4123 93bd5a0 883e411 23df166 d9a4123 23df166 d9a4123 23df166 93bd5a0 b894812 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 | 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))
@spaces.GPU(duration=get_duration, size="xlarge")
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) |