nanye commited on
Commit
5533911
·
1 Parent(s): 0eff3a9

changed to gradio

Browse files
Files changed (4) hide show
  1. README.md +23 -5
  2. app.py +85 -0
  3. generate.py +6 -3
  4. requirements.txt +1 -1
README.md CHANGED
@@ -3,14 +3,15 @@ title: Diffusion
3
  emoji: 🌍
4
  colorFrom: indigo
5
  colorTo: purple
6
- sdk: docker
7
- app_port: 8000
 
8
  pinned: false
9
  ---
10
 
11
  # Diffusion Lab
12
 
13
- The reverse process runs the pretrained `google/ddpm-celebahq-256` model through Hugging Face Diffusers. This is an unconditional CelebA-HQ face generator: reverse sampling starts with random Gaussian noise and does not denoise the uploaded image. The upload is used only by the browser-side forward-noising demonstration.
14
 
15
  ## Run
16
 
@@ -20,10 +21,10 @@ Use Python 3.10 or newer. Install a PyTorch build appropriate for your machine f
20
  python -m venv .venv
21
  source .venv/bin/activate
22
  python -m pip install -r requirements.txt
23
- python server.py
24
  ```
25
 
26
- Open <http://127.0.0.1:8000>. The first generation downloads the model weights into `models/`; later runs use that cache. The backend prefers CUDA, then Apple Silicon MPS, then CPU. Model inference uses all 1,000 DDPM steps and sends the selected intermediate images to the browser as they are produced.
27
 
28
  To generate and display one image without starting the server, run:
29
 
@@ -38,3 +39,20 @@ To download a particular zero-based row from the public CelebA-HQ dataset:
38
  python download_celeba_hq.py 1374
39
  python download_celeba_hq.py 42 --split train --output face-42.png
40
  ```
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3
  emoji: 🌍
4
  colorFrom: indigo
5
  colorTo: purple
6
+ sdk: gradio
7
+ app_file: app.py
8
+ python_version: "3.10"
9
  pinned: false
10
  ---
11
 
12
  # Diffusion Lab
13
 
14
+ The Gradio app supports ZeroGPU and streams checkpoint images. The reverse process runs the pretrained `google/ddpm-celebahq-256` model through Hugging Face Diffusers. This is an unconditional CelebA-HQ face generator: reverse sampling normally starts with random Gaussian noise. You can also select the final forward-noise image as its input; this is a demonstration, not a guaranteed reconstruction of the uploaded image.
15
 
16
  ## Run
17
 
 
21
  python -m venv .venv
22
  source .venv/bin/activate
23
  python -m pip install -r requirements.txt
24
+ python app.py
25
  ```
26
 
27
+ Open <http://127.0.0.1:7860>. Startup downloads the model weights into `models/`; later runs use that cache. The backend prefers CUDA, then Apple Silicon MPS, then CPU. Model inference uses all 1,000 DDPM steps and sends the selected intermediate images to the browser as they are produced.
28
 
29
  To generate and display one image without starting the server, run:
30
 
 
39
  python download_celeba_hq.py 1374
40
  python download_celeba_hq.py 42 --split train --output face-42.png
41
  ```
42
+
43
+ ## ZeroGPU deployment
44
+
45
+ Push this repository to a Gradio Space and select ZeroGPU in the Space hardware settings. The Dockerfile and legacy server are not used by the Gradio deployment. Account eligibility and daily GPU quotas apply.
46
+
47
+ The app imports `spaces` before PyTorch, loads the model at startup, and places it on CUDA when `SPACES_ZERO_GPU` is enabled. GPU sampling runs inside a decorated Gradio handler. `GPU_DURATION_SECONDS` defaults to 120; benchmark a full 1,000-step run and adjust this Space variable if necessary. A timeout is reported by the Gradio queue; selecting fewer checkpoints does not reduce the sampling steps.
48
+
49
+ For persistent caching, attach a writable storage bucket and set the Space variable `MODEL_CACHE_DIR` to its mount path (for example `/data/models`). The old Docker path `/app/models` is also usable if explicitly configured with that variable.
50
+
51
+ For local use, install `gradio` and `spaces` alongside `requirements.txt` (these packages are provided by the Gradio Spaces runtime):
52
+
53
+ ```sh
54
+ python -m pip install gradio spaces
55
+ python app.py
56
+ ```
57
+
58
+ The original browser interface remains available with `python server.py` for local or dedicated-GPU use.
app.py ADDED
@@ -0,0 +1,85 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Gradio entry point for local use and Hugging Face ZeroGPU."""
2
+ import spaces # Must initialize ZeroGPU before importing torch/diffusers.
3
+
4
+ import os
5
+ import secrets
6
+
7
+ import gradio as gr
8
+ import numpy as np
9
+ from PIL import Image
10
+
11
+ from generate import CHECKPOINTS, ROOT, generate_images, load_model
12
+
13
+ ZERO_GPU = os.environ.get("SPACES_ZERO_GPU", "").lower() in {"1", "true", "yes"}
14
+ load_model(device="cuda" if ZERO_GPU else None)
15
+
16
+
17
+ def parse_settings(seed, checkpoint_text):
18
+ try:
19
+ seed = int(str(seed).strip())
20
+ checkpoints = sorted(set(int(part.strip()) for part in checkpoint_text.split(",")))
21
+ except (ValueError, TypeError):
22
+ raise gr.Error("Enter an integer seed and comma-separated integer checkpoints.")
23
+ if not 0 <= seed <= 2147483647:
24
+ raise gr.Error("Seed must be between 0 and 2147483647.")
25
+ if not 2 <= len(checkpoints) <= 12 or checkpoints[0] != 0 or checkpoints[-1] != 1000:
26
+ raise gr.Error("Use 2–12 checkpoints between 0 and 1000, including both endpoints.")
27
+ return seed, checkpoints
28
+
29
+
30
+ def diffuse(image, seed, checkpoint_text):
31
+ seed, checkpoints = parse_settings(seed, checkpoint_text)
32
+ if image is None:
33
+ raise gr.Error("Upload an image first.")
34
+ pixels = np.asarray(image.convert("RGB").resize((256, 256)), dtype=np.float32) / 127.5 - 1
35
+ noise = np.random.default_rng(seed).standard_normal(pixels.shape)
36
+ alpha_bars = np.cumprod(1 - np.linspace(0.0001, 0.02, 1000))
37
+ frames = []
38
+ final_noise = None
39
+ for step in checkpoints:
40
+ alpha = 1 if step == 0 else alpha_bars[step - 1]
41
+ sample = np.sqrt(alpha) * pixels + np.sqrt(1 - alpha) * noise
42
+ frame = Image.fromarray(np.round((sample.clip(-1, 1) + 1) * 127.5).astype(np.uint8))
43
+ frames.append((frame, f"t = {step}"))
44
+ final_noise = frame
45
+ return frames, final_noise
46
+
47
+
48
+ @spaces.GPU(duration=int(os.environ.get("GPU_DURATION_SECONDS", "120")))
49
+ def denoise(seed, checkpoint_text, input_mode, forward_noise):
50
+ seed, checkpoints = parse_settings(seed, checkpoint_text)
51
+ initial_image = forward_noise if input_mode == "Forward noise" else None
52
+ if input_mode == "Forward noise" and initial_image is None:
53
+ raise gr.Error("Run Diffuse first to create forward noise.")
54
+ frames = []
55
+ for step, frame in generate_images(seed, checkpoints, initial_image):
56
+ if frame is not None:
57
+ frames.append((frame, f"t = {step}"))
58
+ if frame is not None or step % 25 == 0:
59
+ yield list(frames), f"Denoising: {1000 - step}/1000 steps · seed {seed}"
60
+
61
+
62
+ with gr.Blocks(title="Diffusion Lab") as demo:
63
+ gr.Markdown("# Diffusion Lab\nExplore forward noising and the 1,000-step DDPM reverse process.")
64
+ with gr.Row():
65
+ seed = gr.Textbox(value="42", label="Seed")
66
+ randomize = gr.Button("Randomize seed")
67
+ checkpoints = gr.Textbox(value=", ".join(map(str, CHECKPOINTS)), label="Checkpoint timesteps")
68
+ randomize.click(lambda: str(secrets.randbelow(2**31)), outputs=seed, queue=False)
69
+ default_image = ROOT / "assets/celeba_hq_validation_91_256.png"
70
+ source = gr.Image(value=str(default_image) if default_image.exists() else None,
71
+ type="pil", label="Source image")
72
+ diffuse_button = gr.Button("Diffuse")
73
+ forward_gallery = gr.Gallery(label="Forward process: image to noise", columns=4)
74
+ forward_noise = gr.State(None)
75
+ diffuse_button.click(diffuse, [source, seed, checkpoints], [forward_gallery, forward_noise])
76
+ input_mode = gr.Radio(["Gaussian noise", "Forward noise"], value="Gaussian noise", label="Reverse input")
77
+ gr.Markdown("The face model is unconditional. Forward noise is an experiment, not a guaranteed reconstruction.")
78
+ denoise_button = gr.Button("Denoise", variant="primary")
79
+ reverse_gallery = gr.Gallery(label="Reverse process: noise to face", columns=4)
80
+ status = gr.Textbox(label="Progress", interactive=False)
81
+ denoise_button.click(denoise, [seed, checkpoints, input_mode, forward_noise],
82
+ [reverse_gallery, status], concurrency_limit=1)
83
+
84
+ if __name__ == "__main__":
85
+ demo.queue().launch()
generate.py CHANGED
@@ -1,4 +1,5 @@
1
  import argparse
 
2
  import secrets
3
  import threading
4
  from pathlib import Path
@@ -16,7 +17,7 @@ _TORCH = None
16
  _DEVICE = None
17
 
18
 
19
- def load_model():
20
  """Load and cache the diffusion pipeline, returning it and its device."""
21
  global _PIPELINE, _TORCH, _DEVICE
22
 
@@ -31,7 +32,9 @@ def load_model():
31
  f"Import detail: {error}"
32
  ) from error
33
 
34
- if torch.cuda.is_available():
 
 
35
  device = "cuda"
36
  elif hasattr(torch.backends, "mps") and torch.backends.mps.is_available():
37
  device = "mps"
@@ -39,7 +42,7 @@ def load_model():
39
  device = "cpu"
40
 
41
  print(f"Using device: {device}")
42
- pipeline = DDPMPipeline.from_pretrained(MODEL_ID, cache_dir=ROOT / "models")
43
  pipeline.to(device)
44
  _TORCH = torch
45
  _DEVICE = device
 
1
  import argparse
2
+ import os
3
  import secrets
4
  import threading
5
  from pathlib import Path
 
17
  _DEVICE = None
18
 
19
 
20
+ def load_model(device=None):
21
  """Load and cache the diffusion pipeline, returning it and its device."""
22
  global _PIPELINE, _TORCH, _DEVICE
23
 
 
32
  f"Import detail: {error}"
33
  ) from error
34
 
35
+ if device is not None:
36
+ pass
37
+ elif torch.cuda.is_available():
38
  device = "cuda"
39
  elif hasattr(torch.backends, "mps") and torch.backends.mps.is_available():
40
  device = "mps"
 
42
  device = "cpu"
43
 
44
  print(f"Using device: {device}")
45
+ pipeline = DDPMPipeline.from_pretrained(MODEL_ID, cache_dir=Path(os.environ.get("MODEL_CACHE_DIR", ROOT / "models")))
46
  pipeline.to(device)
47
  _TORCH = torch
48
  _DEVICE = device
requirements.txt CHANGED
@@ -2,4 +2,4 @@ diffusers>=0.30.0
2
  matplotlib>=3.8.0
3
  Pillow>=10.0.0
4
  safetensors>=0.4.0
5
- torch>=2.1.0
 
2
  matplotlib>=3.8.0
3
  Pillow>=10.0.0
4
  safetensors>=0.4.0
5
+ torch>=2.8.0