Spaces:
Running on Zero
Running on Zero
File size: 9,440 Bytes
d155c16 bb40d9f d155c16 2d8e30a d155c16 2d8e30a d155c16 2d8e30a | 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 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 | import os
os.environ.setdefault("PYTORCH_CUDA_ALLOC_CONF", "expandable_segments:True")
import json
import random
import sys
import time
from pathlib import Path
import spaces # MUST come before torch / any CUDA-touching import
import torch
sys.path.insert(0, str(Path(__file__).parent / "pixelmoe_src"))
from denoiser_inference import Denoiser
import gradio as gr
from huggingface_hub import hf_hub_download
CHECKPOINT_REPO = "KlingTeam/PixelMoE"
CHECKPOINT_FILE = "pixelmoe_b_e32a4_512.pth"
MODEL_NAME = "PixelMoE-B-E32A4/16"
IMG_SIZE = 512
NUM_CLASSES = 1000
# Generation defaults follow configs/pixelmoe_b_e32a4_512.sh + scripts/sample.sh
DEFAULT_SIGMA_MIN = 0.03
DEFAULT_CFG = 3.7
DEFAULT_INTERVAL_MAX = 0.8
DEFAULT_STEPS = 25 # per stage; scripts/sample.sh uses --num_sampling_steps 25 25
_imagenet_classes = json.loads(
(Path(__file__).parent / "pixelmoe_src" / "imagenet_classes.json").read_text()
)
def _build_model() -> Denoiser:
model = Denoiser(
model=MODEL_NAME,
img_size=IMG_SIZE,
num_classes=NUM_CLASSES,
label_drop_prob=0.1,
sigma_min=DEFAULT_SIGMA_MIN,
noise_scale=2.0,
hierarchical_stages=(0, 1),
hierarchical_end_timesteps=(0.75, 0.0),
hierarchical_logit_mean=0.0,
hierarchical_logit_std=1.0,
hierarchical_noise_rho=-1.0 / 3.0,
hierarchical_min_stage_sigma=0.0,
sampling_method="heun",
num_sampling_steps=(DEFAULT_STEPS, DEFAULT_STEPS),
cfg=DEFAULT_CFG,
cfg_interval=(0.0, DEFAULT_INTERVAL_MAX),
)
ckpt_path = hf_hub_download(CHECKPOINT_REPO, CHECKPOINT_FILE)
checkpoint = torch.load(ckpt_path, map_location="cpu", weights_only=False)
# sample.sh evaluates with the second EMA copy of the weights.
state_dict = checkpoint["model_ema2"]
missing, unexpected = model.load_state_dict(state_dict, strict=False)
if missing:
raise RuntimeError(f"Missing keys when loading checkpoint: {missing[:5]}...")
if unexpected:
raise RuntimeError(f"Unexpected keys when loading checkpoint: {unexpected[:5]}...")
del checkpoint
model.eval()
return model
denoiser = _build_model().to("cuda")
def _estimate_duration(class_name, num_steps, *args, **kwargs):
# Measured on ZeroGPU (sm_120): ~4.6s of model time at 10 steps, ~8.3s at
# 25 steps, ~17.3s at 50 steps (Heun = 2 NFE per step, two hierarchical
# stages). ~0.35s/step; budget 1.5x that to absorb cold-start weight
# streaming and load variance, floored for the smallest runs.
return min(60, max(10, 2 + int(num_steps * 0.5)))
@spaces.GPU(duration=_estimate_duration)
def generate(
class_name: str,
num_steps: int = DEFAULT_STEPS,
cfg: float = DEFAULT_CFG,
interval_max: float = DEFAULT_INTERVAL_MAX,
sigma_min: float = DEFAULT_SIGMA_MIN,
seed: int = 0,
randomize_seed: bool = True,
progress=gr.Progress(track_tqdm=True),
):
"""Generate an ImageNet-1K class-conditional image with PixelMoE (512x512).
Args:
class_name: One of the 1000 ImageNet-1K class names.
num_steps: Denoising steps per hierarchical stage (25 in the paper's
sampling script; each Heun step costs two model evaluations).
cfg: Classifier-free guidance scale.
interval_max: Only apply CFG while the global timestep is below this
value (0.8 in the official config).
sigma_min: Lower bound clamped into the velocity denominator.
seed: RNG seed for reproducible sampling.
randomize_seed: Draw a fresh seed each run instead of the given one.
"""
t0 = time.perf_counter()
try:
class_idx = _imagenet_classes.index(class_name)
except ValueError:
raise gr.Error(f"Unknown class name: {class_name!r}")
if randomize_seed:
seed = random.randint(0, 2**31 - 1)
seed = int(seed)
denoiser.cfg_scale = float(cfg)
denoiser.cfg_interval = (0.0, float(interval_max))
denoiser.sigma_min = float(sigma_min)
denoiser.sampling_steps = [int(num_steps), int(num_steps)]
generator_labels = torch.tensor([class_idx], dtype=torch.long, device="cuda")
g_state = torch.get_rng_state()
try:
torch.manual_seed(seed)
with torch.amp.autocast("cuda", dtype=torch.bfloat16):
sampled = denoiser.generate(generator_labels)
finally:
torch.set_rng_state(g_state)
sampled = (
((sampled + 1.0) / 2.0)
.detach()
.float()
.mul_(255)
.round_()
.clamp_(0, 255)
.to(dtype=torch.uint8)
)
image = sampled[0].permute(1, 2, 0).cpu().numpy()
elapsed = time.perf_counter() - t0
print(f"PixelMoE generated class={class_name!r} in {elapsed:.1f}s", flush=True)
return image, seed
CSS = """
#col-container { max-width: 1100px; margin: 0 auto; }
main, .gradio-container, .fillable { max-width: 1100px !important; margin: 0 auto !important; }
.dark .gradio-container { color: var(--body-text-color); }
"""
# Gradio 6 moved `theme` and `css` from the Blocks constructor to launch().
with gr.Blocks() as demo:
with gr.Column(elem_id="col-container"):
gr.HTML(
"""
<div style="text-align: center; margin-bottom: 8px;">
<h1 style="margin-bottom: 4px;">🧩 PixelMoE</h1>
<p style="margin: 0; font-size: 1.05em; opacity: 0.85;">
Pixel-space diffusion with LLM-style Mixture-of-Experts — pick an
ImageNet-1K class and sample a 512×512 image.
</p>
<p style="margin: 6px 0 0; font-size: 0.9em; opacity: 0.75;">
<a href="https://arxiv.org/abs/2610.18857">Paper</a> ·
<a href="https://github.com/KlingAIResearch/PixelMoE">Code</a> ·
<a href="https://huggingface.co/KlingTeam/PixelMoE">Weights</a> ·
Base model, 512×512
</p>
</div>
"""
)
with gr.Column():
class_name = gr.Dropdown(
choices=_imagenet_classes,
value="golden retriever",
label="ImageNet-1K class",
filterable=True,
)
with gr.Row():
run_button = gr.Button("Generate", variant="primary", scale=3)
seed_used = gr.Number(
label="Seed used", value=0, precision=0, interactive=True, scale=1
)
# Gradio 6: toolbar buttons are configured via `buttons` (download is shown by default).
output = gr.Image(label="Generated image", height=512, buttons=["download", "fullscreen"])
with gr.Accordion("Advanced settings", open=False):
with gr.Row():
num_steps = gr.Slider(
minimum=5, maximum=50, value=DEFAULT_STEPS, step=1,
label="Steps per stage",
info="The paper's sampling script uses 25 per stage (Heun ≈ 2 model calls each).",
)
cfg = gr.Slider(
minimum=1.0, maximum=6.0, value=DEFAULT_CFG, step=0.1,
label="CFG scale",
info="3.7 in the official 512×512 Base config.",
)
with gr.Row():
interval_max = gr.Slider(
minimum=0.1, maximum=1.0, value=DEFAULT_INTERVAL_MAX, step=0.01,
label="CFG interval max",
info="Guidance is applied only while t < this value.",
)
sigma_min = gr.Slider(
minimum=0.0, maximum=0.1, value=DEFAULT_SIGMA_MIN, step=0.005,
label="Sigma min",
info="Clamp on the velocity denominator (0.03 for the Base model).",
)
with gr.Row():
seed = gr.Number(label="Seed", value=0, precision=0)
randomize_seed = gr.Checkbox(label="Randomize seed", value=True)
gr.Examples(
examples=[
["golden retriever"],
["giant panda"],
["yellow lady's slipper"],
["tiger shark"],
["school bus"],
],
inputs=[class_name],
outputs=[output, seed_used],
fn=generate,
cache_examples=True,
cache_mode="lazy",
)
gr.Markdown(
"""
PixelMoE runs diffusion directly in pixel space: each image token routes to 4 of
32 experts (plus a shared expert) in every MoE block, and images are generated in
two hierarchical flow-matching stages — coarse structure first, then detail at the
target resolution. This Space serves the released **PixelMoE-B-E32A4/16** base
model with its second-EMA weights and the official 512×512 sampling settings
(Heun solver, 25 steps per stage, CFG 3.7 with interval max 0.8).
"""
)
inputs = [
class_name,
num_steps,
cfg,
interval_max,
sigma_min,
seed,
randomize_seed,
]
outputs = [output, seed_used]
run_button.click(
generate, inputs=inputs, outputs=outputs, api_name="generate",
queue=True,
)
demo.launch(theme=gr.themes.Citrus(), css=CSS, mcp_server=True)
|