File size: 4,110 Bytes
b83d18f
1fc8d06
b83d18f
 
184daa2
d0e494f
 
 
 
 
 
 
 
 
 
 
 
 
 
 
b83d18f
 
19c2aa1
b83d18f
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
d0e494f
6a497cb
b83d18f
6a497cb
b83d18f
 
 
d0e494f
b83d18f
 
 
 
 
 
 
 
 
 
 
 
 
19c2aa1
b83d18f
 
 
 
 
 
 
 
 
 
6a497cb
d0e494f
b83d18f
 
6a497cb
b83d18f
 
 
 
6a497cb
b83d18f
 
 
 
 
 
 
 
 
 
 
 
6a497cb
 
d0e494f
a90b002
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
import gc, random
import gradio as gr
import torch, spaces
from diffusers import StableDiffusionPipeline, DPMSolverMultistepScheduler

# --- gradio_client bool-schema hotfix (safe no-op if not needed) ---
try:
    import gradio_client.utils as _gcu
    _orig_get_type = _gcu.get_type
    def _get_type_safe(schema):
        # Handle JSON Schema booleans (e.g., additionalProperties: True/False)
        if isinstance(schema, bool):
            return "any"
        return _orig_get_type(schema)
    _gcu.get_type = _get_type_safe
except Exception:
    pass
# -------------------------------------------------------------------

# sanity: show CPU at startup (no GPU yet)
zero = torch.tensor([0.0])
print("startup device:", zero.device)  # should print: cpu

# ====== SD 1.5 minimal loader (lazy) ======
MODEL_ID = "runwayml/stable-diffusion-v1-5"
DTYPE = torch.float16
_PIPE = None

def get_pipe():
    """Create the pipeline on first use (safe for ZeroGPU)."""
    global _PIPE
    if _PIPE is None:
        pipe = StableDiffusionPipeline.from_pretrained(
            MODEL_ID,
            torch_dtype=DTYPE,
            safety_checker=None,
            use_safetensors=True,
            low_cpu_mem_usage=True,
        )
        # fast/stable scheduler
        pipe.scheduler = DPMSolverMultistepScheduler.from_config(
            pipe.scheduler.config, use_karras_sigmas=True, algorithm_type="dpmsolver++"
        )
        # memory savers
        pipe.enable_attention_slicing()
        pipe.enable_vae_slicing()
        pipe.enable_model_cpu_offload()
        _PIPE = pipe
    return _PIPE

def snap8(x: int) -> int:
    x = max(256, min(1024, int(x)))
    return x - (x % 8)

@spaces.GPU  # tiny demo proving CUDA is present inside GPU block
def greet(n: float):
    print("inside greet, cuda available:", torch.cuda.is_available())
    device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
    t = zero.to(device) + torch.tensor([float(n)], device=device)
    return f"Hello {t.item():.3f} Tensor (device: {t.device})"

# SD 1.5 text -> image
@spaces.GPU(duration=120)
def generate(prompt: str, negative: str, steps: int, cfg: float, width: int, height: int, seed: int):
    pipe = get_pipe()
    w, h = snap8(width), snap8(height)

    # seed
    if int(seed) < 0:
        seed = random.randint(0, 2**31 - 1)
    gen = torch.Generator(device="cuda").manual_seed(int(seed))

    if torch.cuda.is_available():
        torch.cuda.empty_cache()
    gc.collect()

    with torch.autocast(device_type="cuda", dtype=DTYPE):
        out = pipe(
            prompt=str(prompt),
            negative_prompt=str(negative or ""),
            num_inference_steps=int(steps),
            guidance_scale=float(cfg),
            width=w, height=h,
            generator=gen,
        )
    return out.images[0]

# ====== UI ======
with gr.Blocks() as demo:
    gr.Markdown("# ZeroGPU demo + Stable Diffusion 1.5 (minimal)")

    with gr.Tab("ZeroGPU sanity"):
        n = gr.Number(label="Add to zero", value=1.0)
        hello = gr.Textbox(label="Result")
        gr.Button("Greet").click(greet, n, hello)

    with gr.Tab("SD 1.5: Text → Image"):
        prompt  = gr.Textbox(label="Prompt", value="a cozy reading nook, warm sunlight, cinematic lighting, highly detailed")
        negative = gr.Textbox(label="Negative (optional)", value="lowres, blurry, watermark, text")
        steps   = gr.Slider(8, 40, value=28, step=1, label="Steps")
        cfg     = gr.Slider(1.0, 12.0, value=7.0, step=0.5, label="CFG")
        width   = gr.Slider(256, 1024, value=640, step=16, label="Width")
        height  = gr.Slider(256, 1024, value=640, step=16, label="Height")
        seed    = gr.Number(value=-1, precision=0, label="Seed (-1 random)")
        out_img = gr.Image(label="Image", interactive=False)
        gr.Button("Generate").click(
            generate, [prompt, negative, steps, cfg, width, height, seed], out_img
        )

if __name__ == "__main__":
    # On Spaces, expose a shareable URL & bind to 0.0.0.0
    demo.queue(max_size=12).launch(share=False, show_error=True, mcp_server=True)