BiliSakura commited on
Commit
cfec117
·
verified ·
1 Parent(s): b37986c

Add class-conditional BiliSakura diffusers playground UI.

Browse files

Unified model selector, inference config widgets, and ZeroGPU-ready generation for all *-diffusers checkpoints.

Files changed (5) hide show
  1. README.md +15 -2
  2. app.py +405 -0
  3. model_catalog.py +480 -0
  4. model_loader.py +194 -0
  5. requirements.txt +9 -0
README.md CHANGED
@@ -1,6 +1,6 @@
1
  ---
2
  title: Visual Generative Model Playground
3
- emoji: 🏃
4
  colorFrom: blue
5
  colorTo: indigo
6
  sdk: gradio
@@ -8,6 +8,19 @@ sdk_version: 6.15.2
8
  python_version: '3.12'
9
  app_file: app.py
10
  pinned: false
 
11
  ---
12
 
13
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  ---
2
  title: Visual Generative Model Playground
3
+ emoji: 🎨
4
  colorFrom: blue
5
  colorTo: indigo
6
  sdk: gradio
 
8
  python_version: '3.12'
9
  app_file: app.py
10
  pinned: false
11
+ short_description: Class-conditional BiliSakura diffusers playground on ZeroGPU
12
  ---
13
 
14
+ # Visual Generative Model Playground
15
+
16
+ Class-conditional image generation demo for [`BiliSakura/*-diffusers`](https://huggingface.co/BiliSakura) on **ZeroGPU**.
17
+
18
+ Select a model, configure inference args (`class_labels`, `num_inference_steps`, `guidance_scale`, resolution), and generate.
19
+
20
+ ## Hardware
21
+
22
+ Set Space hardware to **ZeroGPU** in Settings.
23
+
24
+ ## Models
25
+
26
+ ADM, DiT, DiT-MoE, EDM2, FiT, iMF, JiT, LightningDiT, NiT, PixelFlow, PixNerd, pMF, Self-Flow, and SiT (47 variants).
app.py ADDED
@@ -0,0 +1,405 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Gradio demo for BiliSakura visual generative foundation models on Hugging Face ZeroGPU."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Any
6
+
7
+ import gradio as gr
8
+ import torch
9
+
10
+ from model_catalog import (
11
+ COLLECTIONS,
12
+ MODEL_LABELS,
13
+ get_profile_by_label,
14
+ parse_model_label,
15
+ )
16
+ from model_loader import PIPELINE_MANAGER, run_inference
17
+
18
+ try:
19
+ import spaces
20
+ except ImportError: # Local development without the Spaces runtime.
21
+ class _SpacesStub:
22
+ @staticmethod
23
+ def GPU(*args, **kwargs):
24
+ def decorator(fn):
25
+ return fn
26
+
27
+ if args and callable(args[0]):
28
+ return args[0]
29
+ return decorator
30
+
31
+ spaces = _SpacesStub() # type: ignore[assignment]
32
+
33
+
34
+ DEFAULT_MODEL = MODEL_LABELS[0]
35
+ DEFAULT_PROFILE = get_profile_by_label(DEFAULT_MODEL)
36
+
37
+ INTERVAL_COLLECTIONS = {"iMF-diffusers", "NiT-diffusers", "PixelFlow-diffusers"}
38
+ PMF_COLLECTION = "pMF-diffusers"
39
+
40
+
41
+ def _model_info_markdown(profile) -> str:
42
+ extras = profile.extra_call_kwargs
43
+ extra_lines = ""
44
+ if extras:
45
+ extra_lines = "\n".join(f"- `{key}`: `{value}`" for key, value in extras.items())
46
+ extra_lines = f"\n\n**Default extra args**\n{extra_lines}"
47
+ return (
48
+ f"**Hub repo:** [`{profile.hub_model_id}`](https://huggingface.co/{profile.hub_model_id})\n\n"
49
+ f"- dtype: `{profile.dtype}`\n"
50
+ f"- default resolution: `{profile.default_height}x{profile.default_width}`\n"
51
+ f"- GPU size: `{profile.gpu_size}`"
52
+ f"{extra_lines}"
53
+ )
54
+
55
+
56
+ def _interval_defaults(profile) -> tuple[float, float]:
57
+ extras = profile.extra_call_kwargs
58
+ if "guidance_interval_start" in extras:
59
+ return float(extras["guidance_interval_start"]), float(extras["guidance_interval_end"])
60
+ interval = extras.get("guidance_interval", (0.0, 0.7))
61
+ return float(interval[0]), float(interval[1])
62
+
63
+
64
+ def _build_extra_kwargs(
65
+ profile,
66
+ guidance_interval_start: float,
67
+ guidance_interval_end: float,
68
+ guidance_interval_min: float,
69
+ guidance_interval_max: float,
70
+ noise_scale: float,
71
+ ) -> dict[str, Any]:
72
+ if profile.collection == "iMF-diffusers":
73
+ return {
74
+ "guidance_interval_start": guidance_interval_start,
75
+ "guidance_interval_end": guidance_interval_end,
76
+ }
77
+ if profile.collection in {"NiT-diffusers", "PixelFlow-diffusers"}:
78
+ return {"guidance_interval": (guidance_interval_start, guidance_interval_end)}
79
+ if profile.collection == PMF_COLLECTION:
80
+ return {
81
+ "guidance_interval_min": guidance_interval_min,
82
+ "guidance_interval_max": guidance_interval_max,
83
+ "noise_scale": noise_scale,
84
+ }
85
+ return dict(profile.extra_call_kwargs)
86
+
87
+
88
+ def _config_from_profile(profile):
89
+ g_start, g_end = _interval_defaults(profile)
90
+ extras = profile.extra_call_kwargs
91
+ show_interval = profile.collection in INTERVAL_COLLECTIONS
92
+ show_pmf = profile.collection == PMF_COLLECTION
93
+ return (
94
+ _model_info_markdown(profile),
95
+ gr.update(value=profile.default_class_label),
96
+ gr.update(value=profile.default_seed),
97
+ gr.update(value=profile.default_steps, maximum=profile.max_steps),
98
+ gr.update(value=profile.default_guidance),
99
+ gr.update(value=profile.default_height or profile.infer_resolution()),
100
+ gr.update(value=profile.default_width or profile.infer_resolution()),
101
+ gr.update(value=g_start),
102
+ gr.update(value=g_end),
103
+ gr.update(value=float(extras.get("guidance_interval_min", 0.2))),
104
+ gr.update(value=float(extras.get("guidance_interval_max", 0.6))),
105
+ gr.update(value=float(extras.get("noise_scale", 4.0))),
106
+ gr.update(visible=show_interval, open=show_interval),
107
+ gr.update(visible=show_pmf, open=show_pmf),
108
+ )
109
+
110
+
111
+ def on_model_change(model_label: str):
112
+ return _config_from_profile(get_profile_by_label(model_label))
113
+
114
+
115
+ def _gpu_duration(
116
+ model_label: str,
117
+ num_steps: int,
118
+ guidance_scale: float,
119
+ seed: int,
120
+ class_label: str,
121
+ height: int,
122
+ width: int,
123
+ guidance_interval_start: float,
124
+ guidance_interval_end: float,
125
+ guidance_interval_min: float,
126
+ guidance_interval_max: float,
127
+ noise_scale: float,
128
+ ) -> int:
129
+ profile = get_profile_by_label(model_label)
130
+ step_budget = num_steps if not profile.steps_are_list else max(num_steps, 40)
131
+ base = 45 if profile.gpu_size == "large" else 90
132
+ return int(min(300, max(base, step_budget * 0.6 + 30)))
133
+
134
+
135
+ def _load_model_core(model_label: str) -> str:
136
+ collection, variant = parse_model_label(model_label)
137
+ message, _ = PIPELINE_MANAGER.load(collection, variant)
138
+ return message
139
+
140
+
141
+ def load_model(model_label: str):
142
+ try:
143
+ message = _load_model_core(model_label)
144
+ except Exception as exc:
145
+ raise gr.Error(f"Failed to load `{model_label}`: {exc}") from exc
146
+ return (message, *_config_from_profile(get_profile_by_label(model_label)))
147
+
148
+
149
+ @spaces.GPU(size="xlarge", duration=_gpu_duration)
150
+ def _generate_on_gpu(
151
+ model_label: str,
152
+ class_label: str,
153
+ seed: int,
154
+ num_steps: int,
155
+ guidance_scale: float,
156
+ height: int,
157
+ width: int,
158
+ guidance_interval_start: float,
159
+ guidance_interval_end: float,
160
+ guidance_interval_min: float,
161
+ guidance_interval_max: float,
162
+ noise_scale: float,
163
+ ):
164
+ profile = get_profile_by_label(model_label)
165
+ pipe = PIPELINE_MANAGER.pipe
166
+ if pipe is None or PIPELINE_MANAGER.loaded_label != model_label:
167
+ raise gr.Error(f"Model `{model_label}` is not loaded.")
168
+
169
+ extra_kwargs = _build_extra_kwargs(
170
+ profile,
171
+ guidance_interval_start,
172
+ guidance_interval_end,
173
+ guidance_interval_min,
174
+ guidance_interval_max,
175
+ noise_scale,
176
+ )
177
+ return run_inference(
178
+ profile,
179
+ pipe,
180
+ class_label=class_label,
181
+ seed=seed,
182
+ num_steps=num_steps,
183
+ guidance_scale=guidance_scale,
184
+ height=height,
185
+ width=width,
186
+ extra_kwargs=extra_kwargs,
187
+ )
188
+
189
+
190
+ def generate(
191
+ model_label: str,
192
+ class_label: str,
193
+ seed: int,
194
+ num_steps: int,
195
+ guidance_scale: float,
196
+ height: int,
197
+ width: int,
198
+ guidance_interval_start: float,
199
+ guidance_interval_end: float,
200
+ guidance_interval_min: float,
201
+ guidance_interval_max: float,
202
+ noise_scale: float,
203
+ ):
204
+ try:
205
+ status = _load_model_core(model_label)
206
+ except Exception as exc:
207
+ raise gr.Error(f"Failed to load `{model_label}`: {exc}") from exc
208
+ image = _generate_on_gpu(
209
+ model_label,
210
+ class_label,
211
+ seed,
212
+ num_steps,
213
+ guidance_scale,
214
+ height,
215
+ width,
216
+ guidance_interval_start,
217
+ guidance_interval_end,
218
+ guidance_interval_min,
219
+ guidance_interval_max,
220
+ noise_scale,
221
+ )
222
+ return status, image
223
+
224
+
225
+ def build_demo() -> gr.Blocks:
226
+ g_start, g_end = _interval_defaults(DEFAULT_PROFILE)
227
+ extras = DEFAULT_PROFILE.extra_call_kwargs
228
+
229
+ with gr.Blocks(title="BiliSakura Visual Generation Models") as demo:
230
+ gr.Markdown(
231
+ """
232
+ # BiliSakura Visual Generative Foundation Models
233
+
234
+ Class-conditional image generation for [`BiliSakura/*-diffusers`](https://huggingface.co/BiliSakura)
235
+ on Hugging Face **ZeroGPU**.
236
+ """
237
+ )
238
+
239
+ with gr.Row(equal_height=False):
240
+ with gr.Column(scale=5):
241
+ model = gr.Dropdown(
242
+ MODEL_LABELS,
243
+ value=DEFAULT_MODEL,
244
+ label="Model",
245
+ info="Select a checkpoint, then configure inference args below",
246
+ )
247
+ model_info = gr.Markdown(_model_info_markdown(DEFAULT_PROFILE))
248
+
249
+ with gr.Accordion("Inference config", open=True):
250
+ class_label = gr.Textbox(
251
+ label="class_labels",
252
+ value=DEFAULT_PROFILE.default_class_label,
253
+ info="ImageNet class name (e.g. golden retriever) or id (e.g. 207)",
254
+ )
255
+ with gr.Row():
256
+ seed = gr.Number(label="seed", value=DEFAULT_PROFILE.default_seed, precision=0)
257
+ num_steps = gr.Slider(
258
+ label="num_inference_steps",
259
+ minimum=1,
260
+ maximum=DEFAULT_PROFILE.max_steps,
261
+ step=1,
262
+ value=DEFAULT_PROFILE.default_steps,
263
+ )
264
+ guidance_scale = gr.Slider(
265
+ label="guidance_scale",
266
+ minimum=0.0,
267
+ maximum=20.0,
268
+ step=0.1,
269
+ value=DEFAULT_PROFILE.default_guidance,
270
+ )
271
+ with gr.Row():
272
+ height = gr.Slider(
273
+ label="height",
274
+ minimum=128,
275
+ maximum=1024,
276
+ step=16,
277
+ value=DEFAULT_PROFILE.default_height or 256,
278
+ )
279
+ width = gr.Slider(
280
+ label="width",
281
+ minimum=128,
282
+ maximum=1024,
283
+ step=16,
284
+ value=DEFAULT_PROFILE.default_width or 256,
285
+ )
286
+
287
+ with gr.Accordion(
288
+ "Advanced: guidance interval",
289
+ open=False,
290
+ visible=DEFAULT_PROFILE.collection in INTERVAL_COLLECTIONS,
291
+ ) as interval_accordion:
292
+ with gr.Row():
293
+ guidance_interval_start = gr.Slider(
294
+ label="guidance_interval_start / [0]",
295
+ minimum=0.0,
296
+ maximum=1.0,
297
+ step=0.05,
298
+ value=g_start,
299
+ )
300
+ guidance_interval_end = gr.Slider(
301
+ label="guidance_interval_end / [1]",
302
+ minimum=0.0,
303
+ maximum=1.0,
304
+ step=0.05,
305
+ value=g_end,
306
+ )
307
+
308
+ with gr.Accordion(
309
+ "Advanced: pMF args",
310
+ open=False,
311
+ visible=DEFAULT_PROFILE.collection == PMF_COLLECTION,
312
+ ) as pmf_accordion:
313
+ with gr.Row():
314
+ guidance_interval_min = gr.Slider(
315
+ label="guidance_interval_min",
316
+ minimum=0.0,
317
+ maximum=1.0,
318
+ step=0.05,
319
+ value=float(extras.get("guidance_interval_min", 0.2)),
320
+ )
321
+ guidance_interval_max = gr.Slider(
322
+ label="guidance_interval_max",
323
+ minimum=0.0,
324
+ maximum=1.0,
325
+ step=0.05,
326
+ value=float(extras.get("guidance_interval_max", 0.6)),
327
+ )
328
+ noise_scale = gr.Slider(
329
+ label="noise_scale",
330
+ minimum=0.0,
331
+ maximum=10.0,
332
+ step=0.1,
333
+ value=float(extras.get("noise_scale", 4.0)),
334
+ )
335
+
336
+ with gr.Row():
337
+ load_btn = gr.Button("Load model", variant="secondary")
338
+ generate_btn = gr.Button("Generate", variant="primary")
339
+
340
+ status = gr.Textbox(label="Status", interactive=False, lines=2)
341
+ gr.Markdown(
342
+ f"**Catalog:** {len(MODEL_LABELS)} variants · {len(COLLECTIONS)} families"
343
+ )
344
+
345
+ with gr.Column(scale=6):
346
+ output = gr.Image(
347
+ label="Generated image",
348
+ type="pil",
349
+ height=720,
350
+ elem_classes=["output-image"],
351
+ )
352
+ gr.Examples(
353
+ examples=[
354
+ [DEFAULT_MODEL, "golden retriever", 42],
355
+ [DEFAULT_MODEL, "207", 0],
356
+ [DEFAULT_MODEL, "tabby, tabby cat", 123],
357
+ ],
358
+ inputs=[model, class_label, seed],
359
+ label="Quick examples",
360
+ )
361
+
362
+ inference_inputs = [
363
+ model,
364
+ class_label,
365
+ seed,
366
+ num_steps,
367
+ guidance_scale,
368
+ height,
369
+ width,
370
+ guidance_interval_start,
371
+ guidance_interval_end,
372
+ guidance_interval_min,
373
+ guidance_interval_max,
374
+ noise_scale,
375
+ ]
376
+ config_outputs = [
377
+ model_info,
378
+ class_label,
379
+ seed,
380
+ num_steps,
381
+ guidance_scale,
382
+ height,
383
+ width,
384
+ guidance_interval_start,
385
+ guidance_interval_end,
386
+ guidance_interval_min,
387
+ guidance_interval_max,
388
+ noise_scale,
389
+ interval_accordion,
390
+ pmf_accordion,
391
+ ]
392
+
393
+ model.change(on_model_change, inputs=model, outputs=config_outputs)
394
+ load_btn.click(load_model, inputs=model, outputs=[status, *config_outputs])
395
+ generate_btn.click(generate, inputs=inference_inputs, outputs=[status, output])
396
+ demo.load(on_model_change, inputs=model, outputs=config_outputs)
397
+
398
+ return demo
399
+
400
+
401
+ if __name__ == "__main__":
402
+ if not torch.cuda.is_available():
403
+ print("CUDA is not available locally; ZeroGPU Spaces will provide GPU at inference time.")
404
+ demo = build_demo()
405
+ demo.queue(max_size=8).launch()
model_catalog.py ADDED
@@ -0,0 +1,480 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Catalog of BiliSakura *-diffusers models hosted on Hugging Face Hub."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from dataclasses import dataclass, field
6
+ from typing import Any, Literal
7
+
8
+
9
+ DtypeName = Literal["bfloat16", "float32"]
10
+ GpuSize = Literal["large", "xlarge"]
11
+
12
+
13
+ @dataclass(frozen=True)
14
+ class ModelProfile:
15
+ collection: str
16
+ variant: str
17
+ dtype: DtypeName = "bfloat16"
18
+ use_custom_pipeline: bool = True
19
+ default_class_label: str = "golden retriever"
20
+ default_steps: int = 50
21
+ default_guidance: float = 4.0
22
+ default_height: int | None = None
23
+ default_width: int | None = None
24
+ default_seed: int = 42
25
+ gpu_size: GpuSize = "large"
26
+ scheduler: str | None = None
27
+ scheduler_kwargs: dict[str, Any] = field(default_factory=dict)
28
+ extra_call_kwargs: dict[str, Any] = field(default_factory=dict)
29
+ steps_are_list: bool = False
30
+ max_steps: int = 250
31
+
32
+ @property
33
+ def hub_repo(self) -> str:
34
+ return f"BiliSakura/{self.collection}"
35
+
36
+ @property
37
+ def hub_model_id(self) -> str:
38
+ return f"{self.hub_repo}/{self.variant}"
39
+
40
+ @property
41
+ def label(self) -> str:
42
+ return f"{self.collection}/{self.variant}"
43
+
44
+ def infer_resolution(self) -> int:
45
+ if self.default_height:
46
+ return self.default_height
47
+ name = self.variant.lower()
48
+ if "512" in name or "img512" in name:
49
+ return 512
50
+ if "1024" in name:
51
+ return 1024
52
+ return 256
53
+
54
+
55
+ def _p(
56
+ collection: str,
57
+ variant: str,
58
+ *,
59
+ dtype: DtypeName = "bfloat16",
60
+ use_custom_pipeline: bool = True,
61
+ default_class_label: str = "golden retriever",
62
+ default_steps: int = 50,
63
+ default_guidance: float = 4.0,
64
+ default_height: int | None = None,
65
+ default_width: int | None = None,
66
+ gpu_size: GpuSize = "large",
67
+ scheduler: str | None = None,
68
+ scheduler_kwargs: dict[str, Any] | None = None,
69
+ extra_call_kwargs: dict[str, Any] | None = None,
70
+ steps_are_list: bool = False,
71
+ max_steps: int = 250,
72
+ ) -> ModelProfile:
73
+ if default_height is None:
74
+ name = variant.lower()
75
+ if "512" in name or "img512" in name:
76
+ res = 512
77
+ elif "1024" in name:
78
+ res = 1024
79
+ else:
80
+ res = 256
81
+ default_height = res
82
+ default_width = res
83
+
84
+ return ModelProfile(
85
+ collection=collection,
86
+ variant=variant,
87
+ dtype=dtype,
88
+ use_custom_pipeline=use_custom_pipeline,
89
+ default_class_label=default_class_label,
90
+ default_steps=default_steps,
91
+ default_guidance=default_guidance,
92
+ default_height=default_height,
93
+ default_width=default_width,
94
+ gpu_size=gpu_size,
95
+ scheduler=scheduler,
96
+ scheduler_kwargs=scheduler_kwargs or {},
97
+ extra_call_kwargs=extra_call_kwargs or {},
98
+ steps_are_list=steps_are_list,
99
+ max_steps=max_steps,
100
+ )
101
+
102
+
103
+ MODEL_PROFILES: list[ModelProfile] = [
104
+ _p("ADM-diffusers", "ADM-G-256", default_steps=50, default_guidance=0.0, scheduler="DDIMScheduler"),
105
+ _p("ADM-diffusers", "ADM-G-512", default_steps=50, default_guidance=0.0, scheduler="DDIMScheduler"),
106
+ _p("DiT-diffusers", "DiT-XL-2-256", default_steps=250, default_guidance=4.0),
107
+ _p("DiT-diffusers", "DiT-XL-2-512", default_steps=250, default_guidance=4.0, gpu_size="xlarge"),
108
+ _p("DiT-MoE-diffusers", "DiT-MoE-S-8E2A", default_steps=50, default_guidance=4.0),
109
+ _p("DiT-MoE-diffusers", "DiT-MoE-B-8E2A", default_steps=50, default_guidance=4.0),
110
+ _p("DiT-MoE-diffusers", "DiT-MoE-XL-8E2A", default_steps=50, default_guidance=4.0, gpu_size="xlarge"),
111
+ _p(
112
+ "EDM2-diffusers",
113
+ "edm2-img512-xs-fid",
114
+ use_custom_pipeline=False,
115
+ default_steps=32,
116
+ default_guidance=1.0,
117
+ default_height=512,
118
+ default_width=512,
119
+ ),
120
+ _p(
121
+ "EDM2-diffusers",
122
+ "edm2-img512-s-fid",
123
+ use_custom_pipeline=False,
124
+ default_steps=32,
125
+ default_guidance=1.0,
126
+ default_height=512,
127
+ default_width=512,
128
+ ),
129
+ _p(
130
+ "EDM2-diffusers",
131
+ "edm2-img512-m-fid",
132
+ use_custom_pipeline=False,
133
+ default_steps=32,
134
+ default_guidance=1.0,
135
+ default_height=512,
136
+ default_width=512,
137
+ ),
138
+ _p(
139
+ "EDM2-diffusers",
140
+ "edm2-img512-l-fid",
141
+ use_custom_pipeline=False,
142
+ default_steps=32,
143
+ default_guidance=1.0,
144
+ default_height=512,
145
+ default_width=512,
146
+ gpu_size="xlarge",
147
+ ),
148
+ _p(
149
+ "EDM2-diffusers",
150
+ "edm2-img512-l-dino",
151
+ use_custom_pipeline=False,
152
+ default_steps=32,
153
+ default_guidance=1.0,
154
+ default_height=512,
155
+ default_width=512,
156
+ gpu_size="xlarge",
157
+ ),
158
+ _p(
159
+ "EDM2-diffusers",
160
+ "edm2-img512-xl-fid",
161
+ use_custom_pipeline=False,
162
+ default_steps=32,
163
+ default_guidance=1.0,
164
+ default_height=512,
165
+ default_width=512,
166
+ gpu_size="xlarge",
167
+ ),
168
+ _p(
169
+ "EDM2-diffusers",
170
+ "edm2-img512-xxl-fid",
171
+ use_custom_pipeline=False,
172
+ default_steps=32,
173
+ default_guidance=1.0,
174
+ default_height=512,
175
+ default_width=512,
176
+ gpu_size="xlarge",
177
+ ),
178
+ _p(
179
+ "FiT-diffusers",
180
+ "FiTv1-XL-2-256",
181
+ default_steps=50,
182
+ default_guidance=1.5,
183
+ scheduler="DDIMScheduler",
184
+ ),
185
+ _p(
186
+ "FiT-diffusers",
187
+ "FiTv2-XL-2-256",
188
+ default_steps=50,
189
+ default_guidance=1.5,
190
+ scheduler="FlowMatchEulerDiscreteScheduler",
191
+ ),
192
+ _p(
193
+ "FiT-diffusers",
194
+ "FiTv2-XL-2-512",
195
+ default_steps=50,
196
+ default_guidance=1.5,
197
+ scheduler="FlowMatchEulerDiscreteScheduler",
198
+ default_height=512,
199
+ default_width=512,
200
+ gpu_size="xlarge",
201
+ ),
202
+ _p(
203
+ "FiT-diffusers",
204
+ "FiTv2-3B-2-256",
205
+ default_steps=50,
206
+ default_guidance=1.5,
207
+ scheduler="FlowMatchEulerDiscreteScheduler",
208
+ gpu_size="xlarge",
209
+ ),
210
+ _p(
211
+ "FiT-diffusers",
212
+ "FiTv2-3B-2-512",
213
+ default_steps=50,
214
+ default_guidance=1.5,
215
+ scheduler="FlowMatchEulerDiscreteScheduler",
216
+ default_height=512,
217
+ default_width=512,
218
+ gpu_size="xlarge",
219
+ ),
220
+ _p(
221
+ "iMF-diffusers",
222
+ "iMF-B-2",
223
+ dtype="float32",
224
+ default_steps=1,
225
+ default_guidance=1.8,
226
+ extra_call_kwargs={
227
+ "guidance_interval_start": 0.0,
228
+ "guidance_interval_end": 1.0,
229
+ },
230
+ ),
231
+ _p(
232
+ "iMF-diffusers",
233
+ "iMF-L-2",
234
+ dtype="float32",
235
+ default_steps=1,
236
+ default_guidance=1.8,
237
+ extra_call_kwargs={
238
+ "guidance_interval_start": 0.0,
239
+ "guidance_interval_end": 1.0,
240
+ },
241
+ ),
242
+ _p(
243
+ "iMF-diffusers",
244
+ "iMF-XL-2",
245
+ dtype="float32",
246
+ default_steps=1,
247
+ default_guidance=1.8,
248
+ extra_call_kwargs={
249
+ "guidance_interval_start": 0.0,
250
+ "guidance_interval_end": 1.0,
251
+ },
252
+ ),
253
+ _p(
254
+ "JiT-diffusers",
255
+ "JiT-B-16",
256
+ dtype="float32",
257
+ default_steps=250,
258
+ default_guidance=2.3,
259
+ scheduler="FlowMatchHeunDiscreteScheduler",
260
+ scheduler_kwargs={"shift": 4.0},
261
+ ),
262
+ _p(
263
+ "JiT-diffusers",
264
+ "JiT-B-32",
265
+ dtype="float32",
266
+ default_steps=250,
267
+ default_guidance=2.3,
268
+ scheduler="FlowMatchHeunDiscreteScheduler",
269
+ scheduler_kwargs={"shift": 4.0},
270
+ ),
271
+ _p(
272
+ "JiT-diffusers",
273
+ "JiT-L-16",
274
+ dtype="float32",
275
+ default_steps=250,
276
+ default_guidance=2.3,
277
+ scheduler="FlowMatchHeunDiscreteScheduler",
278
+ scheduler_kwargs={"shift": 4.0},
279
+ ),
280
+ _p(
281
+ "JiT-diffusers",
282
+ "JiT-L-32",
283
+ dtype="float32",
284
+ default_steps=250,
285
+ default_guidance=2.3,
286
+ scheduler="FlowMatchHeunDiscreteScheduler",
287
+ scheduler_kwargs={"shift": 4.0},
288
+ ),
289
+ _p(
290
+ "JiT-diffusers",
291
+ "JiT-H-16",
292
+ dtype="float32",
293
+ default_steps=250,
294
+ default_guidance=2.3,
295
+ scheduler="FlowMatchHeunDiscreteScheduler",
296
+ scheduler_kwargs={"shift": 4.0},
297
+ gpu_size="xlarge",
298
+ ),
299
+ _p(
300
+ "JiT-diffusers",
301
+ "JiT-H-32",
302
+ dtype="float32",
303
+ default_steps=250,
304
+ default_guidance=2.3,
305
+ scheduler="FlowMatchHeunDiscreteScheduler",
306
+ scheduler_kwargs={"shift": 4.0},
307
+ gpu_size="xlarge",
308
+ ),
309
+ _p(
310
+ "LightningDiT-diffusers",
311
+ "LightningDit-XL-1-256",
312
+ default_steps=50,
313
+ default_guidance=6.7,
314
+ ),
315
+ _p(
316
+ "NiT-diffusers",
317
+ "NiT-S",
318
+ default_steps=250,
319
+ default_guidance=2.25,
320
+ extra_call_kwargs={"guidance_interval": (0.0, 0.7)},
321
+ ),
322
+ _p(
323
+ "NiT-diffusers",
324
+ "NiT-B",
325
+ default_steps=250,
326
+ default_guidance=2.25,
327
+ extra_call_kwargs={"guidance_interval": (0.0, 0.7)},
328
+ ),
329
+ _p(
330
+ "NiT-diffusers",
331
+ "NiT-L",
332
+ default_steps=250,
333
+ default_guidance=2.25,
334
+ extra_call_kwargs={"guidance_interval": (0.0, 0.7)},
335
+ ),
336
+ _p(
337
+ "NiT-diffusers",
338
+ "NiT-XL",
339
+ default_steps=250,
340
+ default_guidance=2.25,
341
+ extra_call_kwargs={"guidance_interval": (0.0, 0.7)},
342
+ gpu_size="xlarge",
343
+ ),
344
+ _p(
345
+ "PixelFlow-diffusers",
346
+ "PixelFlow-256",
347
+ default_steps=40,
348
+ default_guidance=4.0,
349
+ steps_are_list=True,
350
+ extra_call_kwargs={"guidance_interval": (0.0, 0.7)},
351
+ ),
352
+ _p("PixNerd-diffusers", "PixNerd-XL-16-256", default_steps=25, default_guidance=4.0),
353
+ _p(
354
+ "PixNerd-diffusers",
355
+ "PixNerd-XL-16-512",
356
+ default_steps=25,
357
+ default_guidance=4.0,
358
+ default_height=512,
359
+ default_width=512,
360
+ gpu_size="xlarge",
361
+ ),
362
+ _p(
363
+ "pMF-diffusers",
364
+ "pMF-B-16",
365
+ dtype="float32",
366
+ default_steps=1,
367
+ default_guidance=7.5,
368
+ extra_call_kwargs={
369
+ "guidance_interval_min": 0.2,
370
+ "guidance_interval_max": 0.6,
371
+ "noise_scale": 4.0,
372
+ },
373
+ ),
374
+ _p(
375
+ "pMF-diffusers",
376
+ "pMF-B-32",
377
+ dtype="float32",
378
+ default_steps=1,
379
+ default_guidance=7.5,
380
+ extra_call_kwargs={
381
+ "guidance_interval_min": 0.2,
382
+ "guidance_interval_max": 0.6,
383
+ "noise_scale": 4.0,
384
+ },
385
+ ),
386
+ _p(
387
+ "pMF-diffusers",
388
+ "pMF-L-16",
389
+ dtype="float32",
390
+ default_steps=1,
391
+ default_guidance=7.5,
392
+ extra_call_kwargs={
393
+ "guidance_interval_min": 0.2,
394
+ "guidance_interval_max": 0.6,
395
+ "noise_scale": 4.0,
396
+ },
397
+ ),
398
+ _p(
399
+ "pMF-diffusers",
400
+ "pMF-L-32",
401
+ dtype="float32",
402
+ default_steps=1,
403
+ default_guidance=7.5,
404
+ extra_call_kwargs={
405
+ "guidance_interval_min": 0.2,
406
+ "guidance_interval_max": 0.6,
407
+ "noise_scale": 4.0,
408
+ },
409
+ ),
410
+ _p(
411
+ "pMF-diffusers",
412
+ "pMF-H-16",
413
+ dtype="float32",
414
+ default_steps=1,
415
+ default_guidance=7.5,
416
+ extra_call_kwargs={
417
+ "guidance_interval_min": 0.2,
418
+ "guidance_interval_max": 0.6,
419
+ "noise_scale": 4.0,
420
+ },
421
+ gpu_size="xlarge",
422
+ ),
423
+ _p(
424
+ "pMF-diffusers",
425
+ "pMF-H-32",
426
+ dtype="float32",
427
+ default_steps=1,
428
+ default_guidance=7.5,
429
+ extra_call_kwargs={
430
+ "guidance_interval_min": 0.2,
431
+ "guidance_interval_max": 0.6,
432
+ "noise_scale": 4.0,
433
+ },
434
+ gpu_size="xlarge",
435
+ ),
436
+ _p("Self-Flow-diffusers", "Self-Flow-XL-2-256", default_steps=250, default_guidance=3.5),
437
+ _p("SiT-diffusers", "SiT-S-2-256", default_steps=250, default_guidance=4.0, scheduler="FlowMatchEulerDiscreteScheduler"),
438
+ _p("SiT-diffusers", "SiT-B-2-256", default_steps=250, default_guidance=4.0, scheduler="FlowMatchEulerDiscreteScheduler"),
439
+ _p("SiT-diffusers", "SiT-L-2-256", default_steps=250, default_guidance=4.0, scheduler="FlowMatchEulerDiscreteScheduler"),
440
+ _p("SiT-diffusers", "SiT-XL-2-256", default_steps=250, default_guidance=4.0, scheduler="FlowMatchEulerDiscreteScheduler"),
441
+ _p(
442
+ "SiT-diffusers",
443
+ "SiT-XL-2-512",
444
+ default_steps=250,
445
+ default_guidance=4.0,
446
+ scheduler="FlowMatchEulerDiscreteScheduler",
447
+ default_height=512,
448
+ default_width=512,
449
+ gpu_size="xlarge",
450
+ ),
451
+ ]
452
+
453
+
454
+ PROFILE_BY_LABEL: dict[str, ModelProfile] = {profile.label: profile for profile in MODEL_PROFILES}
455
+ COLLECTIONS: list[str] = sorted({profile.collection for profile in MODEL_PROFILES})
456
+ VARIANTS_BY_COLLECTION: dict[str, list[str]] = {
457
+ collection: [profile.variant for profile in MODEL_PROFILES if profile.collection == collection]
458
+ for collection in COLLECTIONS
459
+ }
460
+
461
+
462
+ def get_profile(collection: str, variant: str) -> ModelProfile:
463
+ key = f"{collection}/{variant}"
464
+ if key not in PROFILE_BY_LABEL:
465
+ raise KeyError(f"Unknown model: {key}")
466
+ return PROFILE_BY_LABEL[key]
467
+
468
+
469
+ def get_profile_by_label(label: str) -> ModelProfile:
470
+ if label not in PROFILE_BY_LABEL:
471
+ raise KeyError(f"Unknown model: {label}")
472
+ return PROFILE_BY_LABEL[label]
473
+
474
+
475
+ def parse_model_label(label: str) -> tuple[str, str]:
476
+ collection, variant = label.split("/", 1)
477
+ return collection, variant
478
+
479
+
480
+ MODEL_LABELS: list[str] = [profile.label for profile in MODEL_PROFILES]
model_loader.py ADDED
@@ -0,0 +1,194 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Notebook-style diffusers loader for BiliSakura Hub models."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import gc
6
+ import inspect
7
+ import os
8
+ from pathlib import Path
9
+ from typing import Any, get_args, get_origin
10
+
11
+ import torch
12
+ from diffusers import DiffusionPipeline
13
+ import diffusers.pipelines.pipeline_utils as pipeline_utils
14
+
15
+ from model_catalog import ModelProfile, get_profile
16
+
17
+
18
+ def _patch_diffusers_custom_pipeline_type_check() -> None:
19
+ """Work around diffusers 0.36 KeyError when custom pipelines omit parsed annotations."""
20
+
21
+ if getattr(pipeline_utils, "_bilisakura_type_check_patch", False):
22
+ return
23
+
24
+ @classmethod
25
+ def patched_get_signature_types(cls):
26
+ signature_types = {}
27
+ for name, param in inspect.signature(cls.__init__).parameters.items():
28
+ if name == "self":
29
+ continue
30
+ annotation = param.annotation
31
+ if annotation is inspect.Parameter.empty:
32
+ signature_types[name] = (inspect.Signature.empty,)
33
+ continue
34
+ origin = get_origin(annotation)
35
+ if inspect.isclass(annotation):
36
+ signature_types[name] = (annotation,)
37
+ elif origin is not None:
38
+ args = get_args(annotation)
39
+ signature_types[name] = args if args else (annotation,)
40
+ else:
41
+ signature_types[name] = (inspect.Signature.empty,)
42
+ return signature_types
43
+
44
+ original_from_pretrained = DiffusionPipeline.from_pretrained.__func__
45
+
46
+ @classmethod
47
+ def from_pretrained_patched(cls, pretrained_model_name_or_path, *args, **kwargs):
48
+ original_get_signature_types = DiffusionPipeline._get_signature_types
49
+ DiffusionPipeline._get_signature_types = patched_get_signature_types
50
+ try:
51
+ return original_from_pretrained(cls, pretrained_model_name_or_path, *args, **kwargs)
52
+ finally:
53
+ DiffusionPipeline._get_signature_types = original_get_signature_types
54
+
55
+ DiffusionPipeline.from_pretrained = from_pretrained_patched
56
+ pipeline_utils._bilisakura_type_check_patch = True
57
+
58
+
59
+ _patch_diffusers_custom_pipeline_type_check()
60
+
61
+
62
+ LOCAL_MODELS_ROOT = Path(os.environ.get("LOCAL_MODELS_ROOT", "")).expanduser()
63
+ USE_LOCAL_MODELS = LOCAL_MODELS_ROOT.is_dir()
64
+ HF_ORG = os.environ.get("HF_MODEL_ORG", "BiliSakura")
65
+
66
+ SCHEDULER_CLASSES = {
67
+ "DDIMScheduler": "diffusers.DDIMScheduler",
68
+ "FlowMatchEulerDiscreteScheduler": "diffusers.FlowMatchEulerDiscreteScheduler",
69
+ "FlowMatchHeunDiscreteScheduler": "diffusers.FlowMatchHeunDiscreteScheduler",
70
+ }
71
+
72
+
73
+ class PipelineManager:
74
+ def __init__(self) -> None:
75
+ self._pipe: DiffusionPipeline | None = None
76
+ self._loaded_label: str | None = None
77
+ self._loaded_profile: ModelProfile | None = None
78
+
79
+ @property
80
+ def loaded_label(self) -> str | None:
81
+ return self._loaded_label
82
+
83
+ @property
84
+ def loaded_profile(self) -> ModelProfile | None:
85
+ return self._loaded_profile
86
+
87
+ @property
88
+ def pipe(self) -> DiffusionPipeline | None:
89
+ return self._pipe
90
+
91
+ def _resolve_model_source(self, profile: ModelProfile) -> str:
92
+ if USE_LOCAL_MODELS:
93
+ local_path = LOCAL_MODELS_ROOT / profile.collection / profile.variant
94
+ if local_path.is_dir():
95
+ return str(local_path)
96
+ return f"{HF_ORG}/{profile.collection}/{profile.variant}"
97
+
98
+ def _resolve_dtype(self, profile: ModelProfile) -> torch.dtype:
99
+ return torch.bfloat16 if profile.dtype == "bfloat16" else torch.float32
100
+
101
+ def _apply_scheduler(self, pipe: DiffusionPipeline, profile: ModelProfile) -> None:
102
+ if not profile.scheduler:
103
+ return
104
+ import diffusers
105
+
106
+ scheduler_cls = getattr(diffusers, profile.scheduler)
107
+ pipe.scheduler = scheduler_cls.from_config(pipe.scheduler.config, **profile.scheduler_kwargs)
108
+
109
+ def _apply_post_load(self, pipe: DiffusionPipeline) -> None:
110
+ pipe.set_progress_bar_config(disable=True)
111
+
112
+ def unload(self) -> None:
113
+ if self._pipe is not None:
114
+ del self._pipe
115
+ self._pipe = None
116
+ self._loaded_label = None
117
+ self._loaded_profile = None
118
+ gc.collect()
119
+ if torch.cuda.is_available():
120
+ torch.cuda.empty_cache()
121
+
122
+ def load(self, collection: str, variant: str) -> tuple[str, ModelProfile]:
123
+ profile = get_profile(collection, variant)
124
+ label = profile.label
125
+ if self._loaded_label == label and self._pipe is not None:
126
+ return f"Model already loaded: `{label}`", profile
127
+
128
+ self.unload()
129
+ model_source = self._resolve_model_source(profile)
130
+ dtype = self._resolve_dtype(profile)
131
+
132
+ load_kwargs: dict[str, Any] = {
133
+ "trust_remote_code": True,
134
+ "torch_dtype": dtype,
135
+ }
136
+ if profile.use_custom_pipeline:
137
+ source_path = Path(model_source)
138
+ if source_path.is_dir():
139
+ load_kwargs["custom_pipeline"] = str(source_path / "pipeline.py")
140
+ else:
141
+ load_kwargs["custom_pipeline"] = model_source
142
+
143
+ if USE_LOCAL_MODELS and not model_source.startswith(HF_ORG):
144
+ load_kwargs["local_files_only"] = True
145
+
146
+ pipe = DiffusionPipeline.from_pretrained(model_source, **load_kwargs)
147
+ self._apply_scheduler(pipe, profile)
148
+ pipe = pipe.to("cuda")
149
+ self._apply_post_load(pipe)
150
+
151
+ self._pipe = pipe
152
+ self._loaded_label = label
153
+ self._loaded_profile = profile
154
+ return f"Loaded `{label}` from `{model_source}`.", profile
155
+
156
+
157
+ PIPELINE_MANAGER = PipelineManager()
158
+
159
+
160
+ def build_inference_steps(profile: ModelProfile, steps: int) -> int | list[int]:
161
+ if profile.steps_are_list:
162
+ per_stage = max(1, steps // 4)
163
+ return [per_stage, per_stage, per_stage, per_stage]
164
+ return int(steps)
165
+
166
+
167
+ def run_inference(
168
+ profile: ModelProfile,
169
+ pipe: DiffusionPipeline,
170
+ *,
171
+ class_label: str,
172
+ seed: int,
173
+ num_steps: int,
174
+ guidance_scale: float,
175
+ height: int,
176
+ width: int,
177
+ extra_kwargs: dict[str, Any] | None = None,
178
+ ) -> Any:
179
+ generator = torch.Generator(device="cuda").manual_seed(int(seed))
180
+ call_kwargs: dict[str, Any] = {
181
+ "num_inference_steps": build_inference_steps(profile, num_steps),
182
+ "guidance_scale": float(guidance_scale),
183
+ "generator": generator,
184
+ }
185
+ call_kwargs.update(extra_kwargs if extra_kwargs is not None else profile.extra_call_kwargs)
186
+
187
+ label = class_label.strip() or profile.default_class_label
188
+ call_kwargs["class_labels"] = int(label) if label.isdigit() else label
189
+
190
+ if height > 0 and width > 0:
191
+ call_kwargs["height"] = int(height)
192
+ call_kwargs["width"] = int(width)
193
+
194
+ return pipe(**call_kwargs).images[0]
requirements.txt ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ gradio>=5.0.0
2
+ torch
3
+ diffusers>=0.36.0
4
+ transformers
5
+ accelerate
6
+ safetensors
7
+ huggingface_hub
8
+ einops
9
+ spaces