Spaces:
Sleeping
Sleeping
File size: 6,301 Bytes
45769da 5533911 45769da 785e85b 45769da d4ec601 45769da d4ec601 45769da 785e85b 45769da 785e85b d4ec601 45769da d4ec601 785e85b d4ec601 785e85b e41c23f d4ec601 785e85b 45769da d4ec601 45769da 785e85b 45769da 785e85b 45769da d4ec601 45769da d4ec601 45769da d4ec601 45769da 785e85b 45769da 785e85b 45769da 785e85b 45769da 785e85b 45769da 785e85b 45769da 785e85b 45769da | 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 159 160 161 162 163 164 165 166 167 168 169 | import argparse
import os
import secrets
import threading
from pathlib import Path
from PIL import Image
ROOT = Path(__file__).resolve().parent
MODEL_ID = "google/ddpm-celebahq-256"
MODEL_SIZES = {MODEL_ID: 256, "google/ddpm-cifar10-32": 32}
CHECKPOINTS = (0, 50, 100, 200, 350, 500, 700, 1000)
# Gradio may resume a generator on a different worker thread between yields.
# A primitive Lock can be released there; RLock requires the acquiring thread.
_MODEL_LOCK = threading.Lock()
_CACHE_LOCK = threading.Lock()
_PIPELINES = {}
_PIPELINE = None
_TORCH = None
_DEVICE = None
def load_model(device=None, model_id=MODEL_ID):
"""Load and cache the diffusion pipeline, returning it and its device."""
global _PIPELINE, _TORCH, _DEVICE
if model_id not in MODEL_SIZES:
raise ValueError(f"Unsupported model: {model_id}")
with _CACHE_LOCK:
try:
import torch
from diffusers import DDPMPipeline
except ImportError as error:
raise RuntimeError(
"Diffusers dependencies are missing from this Python environment. "
"Install them with `python -m pip install -r requirements.txt`. "
f"Import detail: {error}"
) from error
if device is None:
if torch.cuda.is_available():
device = "cuda"
elif hasattr(torch.backends, "mps") and torch.backends.mps.is_available():
device = "mps"
else:
device = "cpu"
device = str(device)
key = (model_id, device)
if key not in _PIPELINES:
print(f"Loading {model_id} on device: {device}")
pipeline = DDPMPipeline.from_pretrained(
model_id, cache_dir=Path(os.environ.get("MODEL_CACHE_DIR", ROOT / "models")))
if device == "cuda" and os.environ.get("SPACES_ZERO_GPU", "").lower() in {"1", "true", "yes"}:
# ZeroGPU packs CUDA aliases at startup. Give it owned CPU
# storage rather than tensors backed by mapped checkpoint files.
for component in pipeline.components.values():
if isinstance(component, torch.nn.Module):
component._apply(lambda tensor: tensor.clone())
pipeline.to(device)
_PIPELINES[key] = pipeline
_PIPELINE = _PIPELINES[key]
_TORCH = torch
_DEVICE = device
return _PIPELINE, _TORCH, _DEVICE
def is_model_loaded():
return _PIPELINE is not None
def sample_to_image(sample):
"""Convert a model sample tensor into a PIL image."""
image = sample[0].detach().float().cpu().clamp(-1, 1)
image = ((image + 1) * 127.5).round().byte().permute(1, 2, 0).numpy()
return Image.fromarray(image, mode="RGB")
def generate_images(seed, checkpoints=CHECKPOINTS, initial_image=None, device=None, model_id=MODEL_ID):
"""Yield every remaining-step count and an image at requested checkpoints."""
with _MODEL_LOCK:
pipeline, torch, device = load_model(device=device, model_id=model_id)
scheduler = pipeline.scheduler
training_steps = scheduler.config.num_train_timesteps
scheduler.set_timesteps(training_steps, device=device)
# MPS has no torch.Generator implementation. Keep its random stream
# on CPU; Diffusers also accepts this generator for scheduler noise.
random_device = "cpu" if device == "mps" else device
generator = torch.Generator(device=random_device).manual_seed(seed)
unet = pipeline.unet
sample_size = unet.config.sample_size
if isinstance(sample_size, (tuple, list)):
height, width = sample_size
else:
height = width = sample_size
if initial_image is None:
sample = torch.randn(
(1, unet.config.in_channels, height, width),
generator=generator,
device=random_device,
dtype=unet.dtype,
).to(device) * scheduler.init_noise_sigma
else:
import numpy as np
pixels = np.asarray(initial_image.convert("RGB").resize((width, height))).copy()
sample = torch.from_numpy(pixels).permute(2, 0, 1).unsqueeze(0)
sample = sample.to(device=device, dtype=unet.dtype) / 127.5 - 1
yield (
training_steps,
sample_to_image(sample) if training_steps in checkpoints else None,
)
with torch.inference_mode():
for index, timestep in enumerate(scheduler.timesteps):
model_output = unet(sample, timestep).sample
sample = scheduler.step(
model_output,
timestep,
sample,
generator=generator,
).prev_sample
remaining_steps = training_steps - index - 1
image = sample_to_image(sample) if remaining_steps in checkpoints else None
yield remaining_steps, image
def generate_image(seed, model_id=MODEL_ID):
"""Generate and return the final image for a seed."""
final_image = None
for _, image in generate_images(seed, checkpoints=(0,), model_id=model_id):
if image is not None:
final_image = image
if final_image is None:
raise RuntimeError("The diffusion pipeline did not produce an image.")
return final_image
def main():
parser = argparse.ArgumentParser(description="Generate an unconditional DDPM image.")
parser.add_argument(
"--seed",
type=int,
default=None,
help="Random seed (defaults to a randomly generated seed)",
)
parser.add_argument("--model", choices=list(MODEL_SIZES), default=MODEL_ID)
args = parser.parse_args()
seed = args.seed if args.seed is not None else secrets.randbelow(2**31)
if seed < 0 or seed >= 2**63:
parser.error("seed must be between 0 and 2^63 - 1")
print(f"Generating with seed: {seed}")
image = generate_image(seed, model_id=args.model)
import matplotlib.pyplot as plt
plt.imshow(image)
plt.title(f"{args.model} — seed {seed}")
plt.axis("off")
plt.tight_layout()
plt.show()
if __name__ == "__main__":
main()
|