Spaces:
Running on Zero
Running on Zero
Upload folder using huggingface_hub
Browse files- README.md +35 -7
- app.py +252 -0
- pixelmoe_src/deepseek_moe.py +344 -0
- pixelmoe_src/denoiser_inference.py +314 -0
- pixelmoe_src/hierarchical_image_noise.py +475 -0
- pixelmoe_src/imagenet_classes.json +1 -0
- pixelmoe_src/model_pixelmoe.py +1222 -0
- pixelmoe_src/util/__init__.py +0 -0
- pixelmoe_src/util/model_util.py +74 -0
- requirements.txt +3 -0
README.md
CHANGED
|
@@ -1,13 +1,41 @@
|
|
| 1 |
---
|
| 2 |
-
title:
|
| 3 |
-
emoji:
|
| 4 |
-
colorFrom:
|
| 5 |
-
colorTo:
|
| 6 |
sdk: gradio
|
| 7 |
sdk_version: 6.31.0
|
| 8 |
-
python_version: '3.12'
|
| 9 |
app_file: app.py
|
| 10 |
-
|
|
|
|
|
|
|
| 11 |
---
|
| 12 |
|
| 13 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
---
|
| 2 |
+
title: PixelMoE
|
| 3 |
+
emoji: 🧩
|
| 4 |
+
colorFrom: green
|
| 5 |
+
colorTo: red
|
| 6 |
sdk: gradio
|
| 7 |
sdk_version: 6.31.0
|
|
|
|
| 8 |
app_file: app.py
|
| 9 |
+
short_description: Class-conditional 512x512 pixel-space MoE diffusion
|
| 10 |
+
python_version: "3.12"
|
| 11 |
+
startup_duration_timeout: 30m
|
| 12 |
---
|
| 13 |
|
| 14 |
+
# PixelMoE
|
| 15 |
+
|
| 16 |
+
Interactive demo for **PixelMoE: Scaling Pixel-Space Diffusion with LLM-Style
|
| 17 |
+
Mixture-of-Experts** ([arXiv 2610.18857](https://arxiv.org/abs/2610.18857)).
|
| 18 |
+
|
| 19 |
+
Pick any of the 1000 ImageNet-1K classes and the Space samples a 512×512 image
|
| 20 |
+
with the released **PixelMoE-B-E32A4/16** base model from
|
| 21 |
+
[KlingTeam/PixelMoE](https://huggingface.co/KlingTeam/PixelMoE), using the
|
| 22 |
+
authors' official sampling settings (Heun solver, 25 steps per hierarchical
|
| 23 |
+
stage, CFG 3.7 applied while t < 0.8, second-EMA weights).
|
| 24 |
+
|
| 25 |
+
PixelMoE runs diffusion entirely in pixel space: every image token activates
|
| 26 |
+
4 of 32 routed experts plus a shared expert in each MoE block, and images are
|
| 27 |
+
generated by a two-stage hierarchical flow — coarse structure first, then
|
| 28 |
+
details at the target resolution.
|
| 29 |
+
|
| 30 |
+
## Advanced settings
|
| 31 |
+
|
| 32 |
+
- **Steps per stage** — denoising steps per hierarchical stage (25 in the
|
| 33 |
+
official `scripts/sample.sh`; each Heun step is two model evaluations).
|
| 34 |
+
- **CFG scale / CFG interval max** — classifier-free guidance and the global
|
| 35 |
+
timestep below which it is active.
|
| 36 |
+
- **Sigma min** — lower bound clamped into the velocity denominator.
|
| 37 |
+
|
| 38 |
+
The model code is vendored from the MIT-licensed official repository
|
| 39 |
+
[KlingAIResearch/PixelMoE](https://github.com/KlingAIResearch/PixelMoE)
|
| 40 |
+
(`torch.compile` decorators and the deterministic-scatter toggle are adapted
|
| 41 |
+
for the ZeroGPU runtime, otherwise inference is unchanged).
|
app.py
ADDED
|
@@ -0,0 +1,252 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
|
| 3 |
+
os.environ.setdefault("PYTORCH_CUDA_ALLOC_CONF", "expandable_segments:True")
|
| 4 |
+
|
| 5 |
+
import json
|
| 6 |
+
import random
|
| 7 |
+
import sys
|
| 8 |
+
import time
|
| 9 |
+
from pathlib import Path
|
| 10 |
+
|
| 11 |
+
import spaces # MUST come before torch / any CUDA-touching import
|
| 12 |
+
import torch
|
| 13 |
+
|
| 14 |
+
sys.path.insert(0, str(Path(__file__).parent / "pixelmoe_src"))
|
| 15 |
+
|
| 16 |
+
from denoiser_inference import Denoiser
|
| 17 |
+
|
| 18 |
+
import gradio as gr
|
| 19 |
+
from huggingface_hub import hf_hub_download
|
| 20 |
+
|
| 21 |
+
CHECKPOINT_REPO = "KlingTeam/PixelMoE"
|
| 22 |
+
CHECKPOINT_FILE = "pixelmoe_b_e32a4_512.pth"
|
| 23 |
+
MODEL_NAME = "PixelMoE-B-E32A4/16"
|
| 24 |
+
IMG_SIZE = 512
|
| 25 |
+
NUM_CLASSES = 1000
|
| 26 |
+
# Generation defaults follow configs/pixelmoe_b_e32a4_512.sh + scripts/sample.sh
|
| 27 |
+
DEFAULT_SIGMA_MIN = 0.03
|
| 28 |
+
DEFAULT_CFG = 3.7
|
| 29 |
+
DEFAULT_INTERVAL_MAX = 0.8
|
| 30 |
+
DEFAULT_STEPS = 25 # per stage; scripts/sample.sh uses --num_sampling_steps 25 25
|
| 31 |
+
|
| 32 |
+
_imagenet_classes = json.loads(
|
| 33 |
+
(Path(__file__).parent / "pixelmoe_src" / "imagenet_classes.json").read_text()
|
| 34 |
+
)
|
| 35 |
+
|
| 36 |
+
|
| 37 |
+
def _build_model() -> Denoiser:
|
| 38 |
+
model = Denoiser(
|
| 39 |
+
model=MODEL_NAME,
|
| 40 |
+
img_size=IMG_SIZE,
|
| 41 |
+
num_classes=NUM_CLASSES,
|
| 42 |
+
label_drop_prob=0.1,
|
| 43 |
+
sigma_min=DEFAULT_SIGMA_MIN,
|
| 44 |
+
noise_scale=2.0,
|
| 45 |
+
hierarchical_stages=(0, 1),
|
| 46 |
+
hierarchical_end_timesteps=(0.75, 0.0),
|
| 47 |
+
hierarchical_logit_mean=0.0,
|
| 48 |
+
hierarchical_logit_std=1.0,
|
| 49 |
+
hierarchical_noise_rho=-1.0 / 3.0,
|
| 50 |
+
hierarchical_min_stage_sigma=0.0,
|
| 51 |
+
sampling_method="heun",
|
| 52 |
+
num_sampling_steps=(DEFAULT_STEPS, DEFAULT_STEPS),
|
| 53 |
+
cfg=DEFAULT_CFG,
|
| 54 |
+
cfg_interval=(0.0, DEFAULT_INTERVAL_MAX),
|
| 55 |
+
)
|
| 56 |
+
ckpt_path = hf_hub_download(CHECKPOINT_REPO, CHECKPOINT_FILE)
|
| 57 |
+
checkpoint = torch.load(ckpt_path, map_location="cpu", weights_only=False)
|
| 58 |
+
# sample.sh evaluates with the second EMA copy of the weights.
|
| 59 |
+
state_dict = checkpoint["model_ema2"]
|
| 60 |
+
missing, unexpected = model.load_state_dict(state_dict, strict=False)
|
| 61 |
+
if missing:
|
| 62 |
+
raise RuntimeError(f"Missing keys when loading checkpoint: {missing[:5]}...")
|
| 63 |
+
if unexpected:
|
| 64 |
+
raise RuntimeError(f"Unexpected keys when loading checkpoint: {unexpected[:5]}...")
|
| 65 |
+
del checkpoint
|
| 66 |
+
model.eval()
|
| 67 |
+
return model
|
| 68 |
+
|
| 69 |
+
|
| 70 |
+
denoiser = _build_model().to("cuda")
|
| 71 |
+
|
| 72 |
+
|
| 73 |
+
def _estimate_duration(class_name, num_steps, *args, **kwargs):
|
| 74 |
+
# Heun sampling: ~2 * steps NFE per stage on the two hierarchical stages.
|
| 75 |
+
return min(300, 30 + int(num_steps) * 2 * 2 * 2)
|
| 76 |
+
|
| 77 |
+
|
| 78 |
+
@spaces.GPU(duration=_estimate_duration)
|
| 79 |
+
def generate(
|
| 80 |
+
class_name: str,
|
| 81 |
+
num_steps: int = DEFAULT_STEPS,
|
| 82 |
+
cfg: float = DEFAULT_CFG,
|
| 83 |
+
interval_max: float = DEFAULT_INTERVAL_MAX,
|
| 84 |
+
sigma_min: float = DEFAULT_SIGMA_MIN,
|
| 85 |
+
seed: int = 0,
|
| 86 |
+
randomize_seed: bool = True,
|
| 87 |
+
progress=gr.Progress(track_tqdm=True),
|
| 88 |
+
):
|
| 89 |
+
"""Generate an ImageNet-1K class-conditional image with PixelMoE (512x512).
|
| 90 |
+
|
| 91 |
+
Args:
|
| 92 |
+
class_name: One of the 1000 ImageNet-1K class names.
|
| 93 |
+
num_steps: Denoising steps per hierarchical stage (25 in the paper's
|
| 94 |
+
sampling script; each Heun step costs two model evaluations).
|
| 95 |
+
cfg: Classifier-free guidance scale.
|
| 96 |
+
interval_max: Only apply CFG while the global timestep is below this
|
| 97 |
+
value (0.8 in the official config).
|
| 98 |
+
sigma_min: Lower bound clamped into the velocity denominator.
|
| 99 |
+
seed: RNG seed for reproducible sampling.
|
| 100 |
+
randomize_seed: Draw a fresh seed each run instead of the given one.
|
| 101 |
+
"""
|
| 102 |
+
t0 = time.perf_counter()
|
| 103 |
+
try:
|
| 104 |
+
class_idx = _imagenet_classes.index(class_name)
|
| 105 |
+
except ValueError:
|
| 106 |
+
raise gr.Error(f"Unknown class name: {class_name!r}")
|
| 107 |
+
|
| 108 |
+
if randomize_seed:
|
| 109 |
+
seed = random.randint(0, 2**31 - 1)
|
| 110 |
+
seed = int(seed)
|
| 111 |
+
|
| 112 |
+
denoiser.cfg_scale = float(cfg)
|
| 113 |
+
denoiser.cfg_interval = (0.0, float(interval_max))
|
| 114 |
+
denoiser.sigma_min = float(sigma_min)
|
| 115 |
+
denoiser.sampling_steps = [int(num_steps), int(num_steps)]
|
| 116 |
+
|
| 117 |
+
generator_labels = torch.tensor([class_idx], dtype=torch.long, device="cuda")
|
| 118 |
+
g_state = torch.get_rng_state()
|
| 119 |
+
try:
|
| 120 |
+
torch.manual_seed(seed)
|
| 121 |
+
with torch.amp.autocast("cuda", dtype=torch.bfloat16):
|
| 122 |
+
sampled = denoiser.generate(generator_labels)
|
| 123 |
+
finally:
|
| 124 |
+
torch.set_rng_state(g_state)
|
| 125 |
+
|
| 126 |
+
sampled = (
|
| 127 |
+
((sampled + 1.0) / 2.0)
|
| 128 |
+
.detach()
|
| 129 |
+
.float()
|
| 130 |
+
.mul_(255)
|
| 131 |
+
.round_()
|
| 132 |
+
.clamp_(0, 255)
|
| 133 |
+
.to(dtype=torch.uint8)
|
| 134 |
+
)
|
| 135 |
+
image = sampled[0].permute(1, 2, 0).cpu().numpy()
|
| 136 |
+
elapsed = time.perf_counter() - t0
|
| 137 |
+
print(f"PixelMoE generated class={class_name!r} in {elapsed:.1f}s", flush=True)
|
| 138 |
+
return image, seed
|
| 139 |
+
|
| 140 |
+
|
| 141 |
+
CSS = """
|
| 142 |
+
#col-container { max-width: 1100px; margin: 0 auto; }
|
| 143 |
+
main, .gradio-container, .fillable { max-width: 1100px !important; margin: 0 auto !important; }
|
| 144 |
+
.dark .gradio-container { color: var(--body-text-color); }
|
| 145 |
+
"""
|
| 146 |
+
|
| 147 |
+
with gr.Blocks(theme=gr.themes.Citrus(), css=CSS) as demo:
|
| 148 |
+
with gr.Column(elem_id="col-container"):
|
| 149 |
+
gr.HTML(
|
| 150 |
+
"""
|
| 151 |
+
<div style="text-align: center; margin-bottom: 8px;">
|
| 152 |
+
<h1 style="margin-bottom: 4px;">🧩 PixelMoE</h1>
|
| 153 |
+
<p style="margin: 0; font-size: 1.05em; opacity: 0.85;">
|
| 154 |
+
Pixel-space diffusion with LLM-style Mixture-of-Experts — pick an
|
| 155 |
+
ImageNet-1K class and sample a 512×512 image.
|
| 156 |
+
</p>
|
| 157 |
+
<p style="margin: 6px 0 0; font-size: 0.9em; opacity: 0.75;">
|
| 158 |
+
<a href="https://arxiv.org/abs/2610.18857">Paper</a> ·
|
| 159 |
+
<a href="https://github.com/KlingAIResearch/PixelMoE">Code</a> ·
|
| 160 |
+
<a href="https://huggingface.co/KlingTeam/PixelMoE">Weights</a> ·
|
| 161 |
+
Base model, 512×512
|
| 162 |
+
</p>
|
| 163 |
+
</div>
|
| 164 |
+
"""
|
| 165 |
+
)
|
| 166 |
+
with gr.Column():
|
| 167 |
+
class_name = gr.Dropdown(
|
| 168 |
+
choices=_imagenet_classes,
|
| 169 |
+
value="golden retriever",
|
| 170 |
+
label="ImageNet-1K class",
|
| 171 |
+
filterable=True,
|
| 172 |
+
)
|
| 173 |
+
with gr.Row():
|
| 174 |
+
run_button = gr.Button("Generate", variant="primary", scale=3)
|
| 175 |
+
seed_used = gr.Number(
|
| 176 |
+
label="Seed used", value=0, precision=0, interactive=True, scale=1
|
| 177 |
+
)
|
| 178 |
+
|
| 179 |
+
output = gr.Image(label="Generated image", height=512, show_download_button=True)
|
| 180 |
+
|
| 181 |
+
with gr.Accordion("Advanced settings", open=False):
|
| 182 |
+
with gr.Row():
|
| 183 |
+
num_steps = gr.Slider(
|
| 184 |
+
minimum=5, maximum=50, value=DEFAULT_STEPS, step=1,
|
| 185 |
+
label="Steps per stage",
|
| 186 |
+
info="The paper's sampling script uses 25 per stage (Heun ≈ 2 model calls each).",
|
| 187 |
+
)
|
| 188 |
+
cfg = gr.Slider(
|
| 189 |
+
minimum=1.0, maximum=6.0, value=DEFAULT_CFG, step=0.1,
|
| 190 |
+
label="CFG scale",
|
| 191 |
+
info="3.7 in the official 512×512 Base config.",
|
| 192 |
+
)
|
| 193 |
+
with gr.Row():
|
| 194 |
+
interval_max = gr.Slider(
|
| 195 |
+
minimum=0.1, maximum=1.0, value=DEFAULT_INTERVAL_MAX, step=0.01,
|
| 196 |
+
label="CFG interval max",
|
| 197 |
+
info="Guidance is applied only while t < this value.",
|
| 198 |
+
)
|
| 199 |
+
sigma_min = gr.Slider(
|
| 200 |
+
minimum=0.0, maximum=0.1, value=DEFAULT_SIGMA_MIN, step=0.005,
|
| 201 |
+
label="Sigma min",
|
| 202 |
+
info="Clamp on the velocity denominator (0.03 for the Base model).",
|
| 203 |
+
)
|
| 204 |
+
with gr.Row():
|
| 205 |
+
seed = gr.Number(label="Seed", value=0, precision=0)
|
| 206 |
+
randomize_seed = gr.Checkbox(label="Randomize seed", value=True)
|
| 207 |
+
|
| 208 |
+
gr.Examples(
|
| 209 |
+
examples=[
|
| 210 |
+
["golden retriever"],
|
| 211 |
+
["giant panda"],
|
| 212 |
+
["yellow lady's slipper"],
|
| 213 |
+
["tiger shark"],
|
| 214 |
+
["school bus"],
|
| 215 |
+
],
|
| 216 |
+
inputs=[class_name],
|
| 217 |
+
outputs=[output, seed_used],
|
| 218 |
+
fn=generate,
|
| 219 |
+
cache_examples=True,
|
| 220 |
+
cache_mode="lazy",
|
| 221 |
+
)
|
| 222 |
+
|
| 223 |
+
gr.Markdown(
|
| 224 |
+
"""
|
| 225 |
+
PixelMoE runs diffusion directly in pixel space: each image token routes to 4 of
|
| 226 |
+
32 experts (plus a shared expert) in every MoE block, and images are generated in
|
| 227 |
+
two hierarchical flow-matching stages — coarse structure first, then detail at the
|
| 228 |
+
target resolution. This Space serves the released **PixelMoE-B-E32A4/16** base
|
| 229 |
+
model with its second-EMA weights and the official 512×512 sampling settings
|
| 230 |
+
(Heun solver, 25 steps per stage, CFG 3.7 with interval max 0.8).
|
| 231 |
+
"""
|
| 232 |
+
)
|
| 233 |
+
|
| 234 |
+
inputs = [
|
| 235 |
+
class_name,
|
| 236 |
+
num_steps,
|
| 237 |
+
cfg,
|
| 238 |
+
interval_max,
|
| 239 |
+
sigma_min,
|
| 240 |
+
seed,
|
| 241 |
+
randomize_seed,
|
| 242 |
+
]
|
| 243 |
+
outputs = [output, seed_used]
|
| 244 |
+
run_button.click(
|
| 245 |
+
generate, inputs=inputs, outputs=outputs, api_name="generate",
|
| 246 |
+
queue=True,
|
| 247 |
+
)
|
| 248 |
+
|
| 249 |
+
if __name__ == "__main__":
|
| 250 |
+
demo.launch(mcp_server=True)
|
| 251 |
+
else:
|
| 252 |
+
demo.launch(mcp_server=True)
|
pixelmoe_src/deepseek_moe.py
ADDED
|
@@ -0,0 +1,344 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""DeepSeek-style token-choice MoE using PyTorch grouped matrix multiply.
|
| 2 |
+
"""
|
| 3 |
+
|
| 4 |
+
import math
|
| 5 |
+
|
| 6 |
+
import torch
|
| 7 |
+
import torch.distributed as dist
|
| 8 |
+
import torch.nn as nn
|
| 9 |
+
import torch.nn.functional as F
|
| 10 |
+
|
| 11 |
+
|
| 12 |
+
def normalize_router_bias_update(imbalance, eps=1e-6):
|
| 13 |
+
"""RMS-normalize the expert-load imbalance vector."""
|
| 14 |
+
return imbalance * torch.rsqrt(imbalance.square().mean() + float(eps))
|
| 15 |
+
|
| 16 |
+
|
| 17 |
+
class DeepSeekMoEGate(nn.Module):
|
| 18 |
+
"""Select top-k experts using FP32 sigmoid scores and balancing bias."""
|
| 19 |
+
|
| 20 |
+
def __init__(
|
| 21 |
+
self,
|
| 22 |
+
dim: int,
|
| 23 |
+
num_experts: int,
|
| 24 |
+
top_k: int,
|
| 25 |
+
route_scale: float = 1.0,
|
| 26 |
+
router_bias_update_rate: float = 1e-3,
|
| 27 |
+
):
|
| 28 |
+
super().__init__()
|
| 29 |
+
if num_experts <= 0:
|
| 30 |
+
raise ValueError("moe_num_experts must be positive")
|
| 31 |
+
if top_k <= 0 or top_k > num_experts:
|
| 32 |
+
raise ValueError("moe_top_k must be in [1, moe_num_experts]")
|
| 33 |
+
|
| 34 |
+
self.num_experts = num_experts
|
| 35 |
+
self.top_k = top_k
|
| 36 |
+
self.route_scale = float(route_scale)
|
| 37 |
+
self.router_bias_update_rate = float(router_bias_update_rate)
|
| 38 |
+
|
| 39 |
+
self.weight = nn.Parameter(torch.empty(num_experts, dim))
|
| 40 |
+
# Balancing bias is checkpointed and updated without gradients.
|
| 41 |
+
self.register_buffer(
|
| 42 |
+
"router_bias",
|
| 43 |
+
torch.zeros(num_experts, dtype=torch.float32),
|
| 44 |
+
persistent=True
|
| 45 |
+
)
|
| 46 |
+
# Cache this batch's expert loads until the optimizer update is accepted.
|
| 47 |
+
self.load_counts = None
|
| 48 |
+
self.reset_parameters()
|
| 49 |
+
|
| 50 |
+
def reset_parameters(self):
|
| 51 |
+
nn.init.kaiming_uniform_(self.weight, a=math.sqrt(5))
|
| 52 |
+
self.router_bias.zero_()
|
| 53 |
+
|
| 54 |
+
def forward(self, hidden_states):
|
| 55 |
+
# Route packed tokens [num_tokens, dim] in FP32.
|
| 56 |
+
with torch.autocast(device_type=hidden_states.device.type, enabled=False):
|
| 57 |
+
logits = F.linear(
|
| 58 |
+
hidden_states.type(torch.float32),
|
| 59 |
+
self.weight.type(torch.float32),
|
| 60 |
+
)
|
| 61 |
+
|
| 62 |
+
scores = logits.sigmoid()
|
| 63 |
+
|
| 64 |
+
# Bias affects expert selection; aggregating weights use the unbiased scores.
|
| 65 |
+
selection_scores = scores + self.router_bias.to(
|
| 66 |
+
device=scores.device, dtype=scores.dtype
|
| 67 |
+
).detach()
|
| 68 |
+
indices = torch.topk(selection_scores, self.top_k, dim=-1, sorted=False).indices
|
| 69 |
+
weights = scores.gather(1, indices)
|
| 70 |
+
# Normalize each token's selected weights, then apply the routing scale.
|
| 71 |
+
weights = weights / weights.sum(dim=-1, keepdim=True).clamp_min(1e-20)
|
| 72 |
+
weights = weights * self.route_scale
|
| 73 |
+
if self.training:
|
| 74 |
+
self.record_load_counts(indices)
|
| 75 |
+
return weights, indices
|
| 76 |
+
|
| 77 |
+
@torch.no_grad()
|
| 78 |
+
def record_load_counts(self, topk_idx):
|
| 79 |
+
# Each selected token contributes one count to each of its top-k experts.
|
| 80 |
+
counts = topk_idx.new_zeros(
|
| 81 |
+
(topk_idx.shape[0], self.num_experts),
|
| 82 |
+
dtype=torch.float32,
|
| 83 |
+
)
|
| 84 |
+
counts.scatter_(1, topk_idx, 1)
|
| 85 |
+
self.load_counts = counts.sum(dim=0)
|
| 86 |
+
|
| 87 |
+
@torch.no_grad()
|
| 88 |
+
def update_bias(self, update_rate=1e-3):
|
| 89 |
+
# Called after optimizer.step(); skipped batches clear their counts instead.
|
| 90 |
+
if self.load_counts is None:
|
| 91 |
+
return
|
| 92 |
+
counts = self.load_counts
|
| 93 |
+
if dist.is_initialized():
|
| 94 |
+
# Use the same averaged expert loads on every DDP rank.
|
| 95 |
+
dist.all_reduce(counts, op=dist.ReduceOp.SUM)
|
| 96 |
+
counts /= dist.get_world_size()
|
| 97 |
+
# RMS-normalize the deficit: underloaded experts gain bias, overloaded lose it.
|
| 98 |
+
delta = normalize_router_bias_update(counts.mean() - counts)
|
| 99 |
+
self.router_bias.add_(
|
| 100 |
+
delta.to(dtype=self.router_bias.dtype),
|
| 101 |
+
alpha=float(update_rate),
|
| 102 |
+
)
|
| 103 |
+
self.load_counts = None
|
| 104 |
+
|
| 105 |
+
class DeepSeekTokenReorderer(nn.Module):
|
| 106 |
+
"""Stably group token-expert assignments by expert index."""
|
| 107 |
+
|
| 108 |
+
def __init__(self, num_experts: int):
|
| 109 |
+
super().__init__()
|
| 110 |
+
self.num_experts = num_experts
|
| 111 |
+
|
| 112 |
+
def forward(self, top_scores, selected_experts_indices, token_ids):
|
| 113 |
+
"""Group N * top_k token-expert assignments by expert.
|
| 114 |
+
|
| 115 |
+
Returns:
|
| 116 |
+
top_scores_experts_sorted: [N * top_k] mixing weights in expert
|
| 117 |
+
order, used to weight expert outputs.
|
| 118 |
+
token_indices_experts_sorted: [N * top_k] original token IDs in
|
| 119 |
+
the same order, used to gather inputs and scatter outputs.
|
| 120 |
+
Each token appears top_k times.
|
| 121 |
+
num_tokens_per_expert: [num_experts] route counts defining each
|
| 122 |
+
expert's contiguous input segment.
|
| 123 |
+
"""
|
| 124 |
+
flat_experts = selected_experts_indices.reshape(-1)
|
| 125 |
+
if flat_experts.is_cuda:
|
| 126 |
+
num_tokens_per_expert = torch.histc(
|
| 127 |
+
flat_experts,
|
| 128 |
+
bins=self.num_experts,
|
| 129 |
+
min=0,
|
| 130 |
+
max=self.num_experts,
|
| 131 |
+
)
|
| 132 |
+
else:
|
| 133 |
+
# CPU histc does not support the int64 indices returned by topk.
|
| 134 |
+
num_tokens_per_expert = torch.bincount(
|
| 135 |
+
flat_experts,
|
| 136 |
+
minlength=self.num_experts,
|
| 137 |
+
)
|
| 138 |
+
sorted_idx = torch.argsort(flat_experts, stable=True)
|
| 139 |
+
top_scores_experts_sorted = top_scores.reshape(-1).index_select(0, sorted_idx)
|
| 140 |
+
token_indices_experts_sorted = token_ids.reshape(-1).index_select(0, sorted_idx)
|
| 141 |
+
return (
|
| 142 |
+
top_scores_experts_sorted,
|
| 143 |
+
token_indices_experts_sorted,
|
| 144 |
+
num_tokens_per_expert,
|
| 145 |
+
)
|
| 146 |
+
|
| 147 |
+
|
| 148 |
+
def _run_experts_grouped_mm(
|
| 149 |
+
w1: torch.Tensor,
|
| 150 |
+
w2: torch.Tensor,
|
| 151 |
+
w3: torch.Tensor,
|
| 152 |
+
x: torch.Tensor,
|
| 153 |
+
num_tokens_per_expert: torch.Tensor,
|
| 154 |
+
) -> torch.Tensor:
|
| 155 |
+
"""Run packed SwiGLU experts with grouped matrix multiplication."""
|
| 156 |
+
# Input rows are already grouped by expert; each row is one token-expert route.
|
| 157 |
+
if x.dim() != 2:
|
| 158 |
+
raise ValueError(f"grouped expert input must be 2D, got shape {tuple(x.shape)}")
|
| 159 |
+
if num_tokens_per_expert.numel() != w1.shape[0]:
|
| 160 |
+
raise ValueError("num_tokens_per_expert must contain one count per expert")
|
| 161 |
+
|
| 162 |
+
# Skip experts with no assigned tokens, preserving the order of active experts.
|
| 163 |
+
non_zero_mask = num_tokens_per_expert > 0
|
| 164 |
+
non_zero_indices = torch.nonzero(non_zero_mask, as_tuple=True)[0]
|
| 165 |
+
if len(non_zero_indices) == 0:
|
| 166 |
+
return x.new_empty((0, w2.shape[1]))
|
| 167 |
+
if len(non_zero_indices) < len(num_tokens_per_expert):
|
| 168 |
+
counts = num_tokens_per_expert[non_zero_mask]
|
| 169 |
+
active_w1 = w1[non_zero_mask]
|
| 170 |
+
active_w2 = w2[non_zero_mask]
|
| 171 |
+
active_w3 = w3[non_zero_mask]
|
| 172 |
+
else:
|
| 173 |
+
# Every expert has tokens, so use all expert weights directly.
|
| 174 |
+
counts = num_tokens_per_expert
|
| 175 |
+
active_w1 = w1
|
| 176 |
+
active_w2 = w2
|
| 177 |
+
active_w3 = w3
|
| 178 |
+
# Cumulative segment ends.
|
| 179 |
+
offsets = torch.cumsum(counts, dim=0, dtype=torch.int32)
|
| 180 |
+
|
| 181 |
+
# Compute each expert's gate (w1) and value (w3) projections in BF16.
|
| 182 |
+
x_bf16 = x.bfloat16()
|
| 183 |
+
gate = torch._grouped_mm(
|
| 184 |
+
x_bf16,
|
| 185 |
+
active_w1.bfloat16().transpose(-2, -1),
|
| 186 |
+
offs=offsets,
|
| 187 |
+
)
|
| 188 |
+
linear = torch._grouped_mm(
|
| 189 |
+
x_bf16,
|
| 190 |
+
active_w3.bfloat16().transpose(-2, -1),
|
| 191 |
+
offs=offsets,
|
| 192 |
+
)
|
| 193 |
+
# SwiGLU: multiply the SiLU-activated gate by the value projection.
|
| 194 |
+
activated_gate = F.silu(gate)
|
| 195 |
+
hidden = activated_gate * linear
|
| 196 |
+
# Project through w2, keeping the same expert-grouped row order.
|
| 197 |
+
output = torch._grouped_mm(
|
| 198 |
+
hidden,
|
| 199 |
+
active_w2.bfloat16().transpose(-2, -1),
|
| 200 |
+
offs=offsets,
|
| 201 |
+
)
|
| 202 |
+
return output.type_as(x)
|
| 203 |
+
|
| 204 |
+
|
| 205 |
+
class DeepSeekGroupedExperts(nn.Module):
|
| 206 |
+
"""Routed experts with matrices stacked along the expert dimension."""
|
| 207 |
+
|
| 208 |
+
def __init__(
|
| 209 |
+
self,
|
| 210 |
+
dim: int,
|
| 211 |
+
hidden_dim: int,
|
| 212 |
+
output_dim: int,
|
| 213 |
+
num_experts: int,
|
| 214 |
+
num_layers: int,
|
| 215 |
+
):
|
| 216 |
+
super().__init__()
|
| 217 |
+
self.w1 = nn.Parameter(torch.empty(num_experts, hidden_dim, dim))
|
| 218 |
+
self.w2 = nn.Parameter(torch.empty(num_experts, output_dim, hidden_dim))
|
| 219 |
+
self.w3 = nn.Parameter(torch.empty(num_experts, hidden_dim, dim))
|
| 220 |
+
self.num_layers = num_layers
|
| 221 |
+
self.reset_parameters()
|
| 222 |
+
|
| 223 |
+
def forward(self, x, num_tokens_per_expert):
|
| 224 |
+
return _run_experts_grouped_mm(
|
| 225 |
+
self.w1,
|
| 226 |
+
self.w2,
|
| 227 |
+
self.w3,
|
| 228 |
+
x,
|
| 229 |
+
num_tokens_per_expert,
|
| 230 |
+
)
|
| 231 |
+
|
| 232 |
+
def reset_parameters(self):
|
| 233 |
+
# Initialize expert projections with a truncated normal distribution.
|
| 234 |
+
nn.init.trunc_normal_(self.w1, mean=0.0, std=0.02)
|
| 235 |
+
nn.init.trunc_normal_(self.w2, mean=0.0, std=0.02)
|
| 236 |
+
nn.init.trunc_normal_(self.w3, mean=0.0, std=0.02)
|
| 237 |
+
# Scale the residual output projection by the transformer body depth.
|
| 238 |
+
self.w2.data /= (2 * self.num_layers) ** 0.5
|
| 239 |
+
|
| 240 |
+
|
| 241 |
+
class DeepSeekSharedExpert(nn.Module):
|
| 242 |
+
"""Dense SwiGLU shared expert used for every token."""
|
| 243 |
+
|
| 244 |
+
def __init__(self, dim: int, hidden_dim: int):
|
| 245 |
+
super().__init__()
|
| 246 |
+
self.w1 = nn.Linear(dim, hidden_dim, bias=False)
|
| 247 |
+
self.w2 = nn.Linear(hidden_dim, dim, bias=False)
|
| 248 |
+
self.w3 = nn.Linear(dim, hidden_dim, bias=False)
|
| 249 |
+
|
| 250 |
+
def forward(self, x):
|
| 251 |
+
# Shared SwiGLU processes every image token independently of routed selection.
|
| 252 |
+
gate = self.w1(x)
|
| 253 |
+
linear = self.w3(x)
|
| 254 |
+
activated_gate = F.silu(gate)
|
| 255 |
+
product = activated_gate * linear
|
| 256 |
+
output = self.w2(product)
|
| 257 |
+
return output
|
| 258 |
+
|
| 259 |
+
|
| 260 |
+
class DeepSeekMoEFFN(nn.Module):
|
| 261 |
+
"""Standalone DeepSeek-style routed and shared expert FFN."""
|
| 262 |
+
|
| 263 |
+
def __init__(
|
| 264 |
+
self,
|
| 265 |
+
dim: int,
|
| 266 |
+
routed_inter_dim: int,
|
| 267 |
+
shared_inter_dim: int,
|
| 268 |
+
num_layers: int,
|
| 269 |
+
num_experts: int = 8,
|
| 270 |
+
top_k: int = 2,
|
| 271 |
+
route_scale: float = 1.0,
|
| 272 |
+
router_bias_update_rate: float = 1e-3,
|
| 273 |
+
drop: float = 0.0,
|
| 274 |
+
):
|
| 275 |
+
super().__init__()
|
| 276 |
+
if routed_inter_dim is None or shared_inter_dim is None:
|
| 277 |
+
raise ValueError("MoE requires explicit routed_inter_dim and shared_inter_dim")
|
| 278 |
+
expert_hidden_dim = int(routed_inter_dim)
|
| 279 |
+
shared_hidden_dim = int(shared_inter_dim)
|
| 280 |
+
self.num_experts = int(num_experts)
|
| 281 |
+
self.top_k = top_k
|
| 282 |
+
self.gate = DeepSeekMoEGate(
|
| 283 |
+
dim=dim,
|
| 284 |
+
num_experts=num_experts,
|
| 285 |
+
top_k=top_k,
|
| 286 |
+
route_scale=route_scale,
|
| 287 |
+
router_bias_update_rate=router_bias_update_rate,
|
| 288 |
+
)
|
| 289 |
+
self.experts = DeepSeekGroupedExperts(
|
| 290 |
+
dim=dim,
|
| 291 |
+
hidden_dim=expert_hidden_dim,
|
| 292 |
+
output_dim=dim,
|
| 293 |
+
num_experts=self.num_experts,
|
| 294 |
+
num_layers=num_layers,
|
| 295 |
+
)
|
| 296 |
+
self.reorderer = DeepSeekTokenReorderer(num_experts=self.num_experts)
|
| 297 |
+
self.shared_experts = DeepSeekSharedExpert(dim, shared_hidden_dim)
|
| 298 |
+
self.ffn_dropout = nn.Dropout(drop)
|
| 299 |
+
|
| 300 |
+
@torch.no_grad()
|
| 301 |
+
def update_router_bias_from_latest(self):
|
| 302 |
+
self.gate.update_bias(self.gate.router_bias_update_rate)
|
| 303 |
+
|
| 304 |
+
@torch._dynamo.disable
|
| 305 |
+
def forward(self, hidden_states):
|
| 306 |
+
# Merge packed token dimensions, then select top-k experts per token.
|
| 307 |
+
original_shape = hidden_states.shape
|
| 308 |
+
hidden_states_flat = hidden_states.reshape(-1, original_shape[-1])
|
| 309 |
+
topk_weight, topk_idx = self.gate(hidden_states_flat)
|
| 310 |
+
|
| 311 |
+
# A token appears once per selected expert; token_ids allow restoring the original order.
|
| 312 |
+
token_ids = torch.arange(
|
| 313 |
+
hidden_states_flat.shape[0],
|
| 314 |
+
device=hidden_states.device,
|
| 315 |
+
).unsqueeze(1).expand(-1, self.top_k)
|
| 316 |
+
(
|
| 317 |
+
top_scores_experts_sorted,
|
| 318 |
+
token_indices_experts_sorted,
|
| 319 |
+
num_tokens_per_expert,
|
| 320 |
+
) = self.reorderer(topk_weight, topk_idx, token_ids.reshape(-1))
|
| 321 |
+
|
| 322 |
+
# Repeat each token ID across its features to gather complete rows.
|
| 323 |
+
gather_index = token_indices_experts_sorted.reshape(-1, 1).expand(
|
| 324 |
+
-1,
|
| 325 |
+
hidden_states_flat.shape[-1],
|
| 326 |
+
)
|
| 327 |
+
routed_input = torch.gather(hidden_states_flat, dim=0, index=gather_index)
|
| 328 |
+
routed_output = self.experts(routed_input, num_tokens_per_expert)
|
| 329 |
+
# Weight routed outputs in FP32, then cast to the input dtype.
|
| 330 |
+
routed_output = (
|
| 331 |
+
routed_output.float()
|
| 332 |
+
* top_scores_experts_sorted.float().unsqueeze(-1)
|
| 333 |
+
).to(dtype=hidden_states.dtype)
|
| 334 |
+
|
| 335 |
+
# Start with the shared expert, then add weighted routed outputs back to each token.
|
| 336 |
+
output = self.shared_experts(hidden_states_flat)
|
| 337 |
+
# Inference note: the upstream repo toggles
|
| 338 |
+
# torch.use_deterministic_algorithms() around this scatter_add at
|
| 339 |
+
# inference time; the toggling is unsafe inside a forked ZeroGPU worker
|
| 340 |
+
# and the exact reduction order does not matter for sampling, so we
|
| 341 |
+
# always take the plain scatter_add path.
|
| 342 |
+
output = output.scatter_add(dim=0, index=gather_index, src=routed_output)
|
| 343 |
+
output = self.ffn_dropout(output)
|
| 344 |
+
return output.reshape(original_shape)
|
pixelmoe_src/denoiser_inference.py
ADDED
|
@@ -0,0 +1,314 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Inference-only Denoiser for PixelMoE.
|
| 2 |
+
# Mirrors denoiser.py from https://github.com/KlingAIResearch/PixelMoE
|
| 3 |
+
# (MIT License, Copyright (c) 2026 KlingAI Research) with the training
|
| 4 |
+
# logic (loss computation, EMA bookkeeping) removed and the constructor
|
| 5 |
+
# taking plain values instead of argparse.Namespace.
|
| 6 |
+
import math
|
| 7 |
+
from typing import List
|
| 8 |
+
|
| 9 |
+
import torch
|
| 10 |
+
import torch.nn as nn
|
| 11 |
+
import torch.nn.functional as F
|
| 12 |
+
|
| 13 |
+
from model_pixelmoe import PixelMoE_models
|
| 14 |
+
from hierarchical_image_noise import HierarchicalImageNoiseBuilder
|
| 15 |
+
|
| 16 |
+
|
| 17 |
+
class Denoiser(nn.Module):
|
| 18 |
+
def __init__(
|
| 19 |
+
self,
|
| 20 |
+
model="PixelMoE-B-E32A4/16",
|
| 21 |
+
img_size=512,
|
| 22 |
+
num_classes=1000,
|
| 23 |
+
label_drop_prob=0.1,
|
| 24 |
+
sigma_min=0.03,
|
| 25 |
+
noise_scale=2.0,
|
| 26 |
+
hierarchical_stages=(0, 1),
|
| 27 |
+
hierarchical_end_timesteps=(0.75, 0.0),
|
| 28 |
+
hierarchical_logit_mean=0.0,
|
| 29 |
+
hierarchical_logit_std=1.0,
|
| 30 |
+
hierarchical_noise_rho=-1.0 / 3.0,
|
| 31 |
+
hierarchical_min_stage_sigma=0.0,
|
| 32 |
+
sampling_method="heun",
|
| 33 |
+
num_sampling_steps=(25, 25),
|
| 34 |
+
cfg=3.0,
|
| 35 |
+
cfg_interval=(0.0, 0.8),
|
| 36 |
+
):
|
| 37 |
+
super().__init__()
|
| 38 |
+
self.net = PixelMoE_models[model](
|
| 39 |
+
input_size=img_size,
|
| 40 |
+
in_channels=3,
|
| 41 |
+
num_classes=num_classes,
|
| 42 |
+
proj_drop=0.0,
|
| 43 |
+
flux_attn_proj_dropout=False,
|
| 44 |
+
use_flash_attn=False,
|
| 45 |
+
)
|
| 46 |
+
self.img_size = img_size
|
| 47 |
+
self.num_classes = num_classes
|
| 48 |
+
|
| 49 |
+
self.label_drop_prob = label_drop_prob
|
| 50 |
+
self.sigma_min = sigma_min
|
| 51 |
+
self.noise_scale = noise_scale
|
| 52 |
+
|
| 53 |
+
# Hierarchical Flow training hyperparameters.
|
| 54 |
+
self.hierarchical_stages = list(hierarchical_stages)
|
| 55 |
+
self.hierarchical_logit_mean = hierarchical_logit_mean
|
| 56 |
+
self.hierarchical_logit_std = hierarchical_logit_std
|
| 57 |
+
self.hierarchical_noise_rho = hierarchical_noise_rho
|
| 58 |
+
self.hierarchical_min_stage_sigma = float(hierarchical_min_stage_sigma)
|
| 59 |
+
self.hierarchical_noise_builder = HierarchicalImageNoiseBuilder(
|
| 60 |
+
stages=self.hierarchical_stages,
|
| 61 |
+
end_timesteps=hierarchical_end_timesteps,
|
| 62 |
+
noise_rho=self.hierarchical_noise_rho,
|
| 63 |
+
noise_scale=self.noise_scale,
|
| 64 |
+
logit_mean=self.hierarchical_logit_mean,
|
| 65 |
+
logit_std=self.hierarchical_logit_std,
|
| 66 |
+
stage_logit_means=None,
|
| 67 |
+
stage_logit_stds=None,
|
| 68 |
+
min_stage_sigma=self.hierarchical_min_stage_sigma,
|
| 69 |
+
)
|
| 70 |
+
|
| 71 |
+
# generation hyper-parameters
|
| 72 |
+
self.method = sampling_method
|
| 73 |
+
self.sampling_steps = list(num_sampling_steps)
|
| 74 |
+
self.cfg_scale = cfg
|
| 75 |
+
self.cfg_interval = tuple(cfg_interval)
|
| 76 |
+
|
| 77 |
+
@torch.no_grad()
|
| 78 |
+
def generate(self, labels):
|
| 79 |
+
return self.generate_hierarchical(labels)
|
| 80 |
+
|
| 81 |
+
@torch.no_grad()
|
| 82 |
+
def generate_hierarchical(self, labels):
|
| 83 |
+
"""Generate images from low to high resolution.
|
| 84 |
+
|
| 85 |
+
Each higher-resolution stage starts from the previous output,
|
| 86 |
+
upsampled and re-noised to match its starting distribution.
|
| 87 |
+
"""
|
| 88 |
+
device = labels.device
|
| 89 |
+
dtype = next(self.net.parameters()).dtype
|
| 90 |
+
batch_size = labels.size(0)
|
| 91 |
+
num_stages = len(self.hierarchical_stages)
|
| 92 |
+
|
| 93 |
+
# Downsample full-resolution noise to the first stage. Multiplying by 2
|
| 94 |
+
# restores the noise variance after each 2x bilinear downsampling.
|
| 95 |
+
z = self.noise_scale * torch.randn(
|
| 96 |
+
batch_size,
|
| 97 |
+
3,
|
| 98 |
+
self.img_size,
|
| 99 |
+
self.img_size,
|
| 100 |
+
device=device,
|
| 101 |
+
dtype=dtype,
|
| 102 |
+
)
|
| 103 |
+
height = self.img_size
|
| 104 |
+
width = self.img_size
|
| 105 |
+
for _ in range(num_stages - 1):
|
| 106 |
+
height //= 2
|
| 107 |
+
width //= 2
|
| 108 |
+
z = F.interpolate(z, size=(height, width), mode="bilinear", align_corners=False) * 2
|
| 109 |
+
|
| 110 |
+
for stage_index in range(num_stages):
|
| 111 |
+
if stage_index > 0:
|
| 112 |
+
height *= 2
|
| 113 |
+
width *= 2
|
| 114 |
+
z = F.interpolate(z, size=(height, width), mode="nearest")
|
| 115 |
+
z = self._hierarchical_transition_renoise(z, stage_index)
|
| 116 |
+
|
| 117 |
+
z = self._sample_hierarchical_stage(
|
| 118 |
+
z,
|
| 119 |
+
stage_index,
|
| 120 |
+
labels,
|
| 121 |
+
)
|
| 122 |
+
|
| 123 |
+
return z
|
| 124 |
+
|
| 125 |
+
def _hierarchical_transition_renoise(self, latents, stage_index):
|
| 126 |
+
"""Rescale the upsampled image and add noise for the next stage."""
|
| 127 |
+
previous_t_end = self.hierarchical_noise_builder.end_timesteps[stage_index - 1]
|
| 128 |
+
t_start = self.hierarchical_noise_builder.start_timesteps[stage_index]
|
| 129 |
+
|
| 130 |
+
rho = self.hierarchical_noise_rho
|
| 131 |
+
alpha = t_start / math.sqrt(1. - rho)
|
| 132 |
+
signal_scale = (1. - t_start) / (1. - previous_t_end)
|
| 133 |
+
|
| 134 |
+
# Rescale the previous output and add block-correlated noise with the
|
| 135 |
+
# same noise_scale used during training.
|
| 136 |
+
noise = self.sample_block_noise_like(latents) * self.noise_scale
|
| 137 |
+
return signal_scale * latents + alpha * noise
|
| 138 |
+
|
| 139 |
+
def sample_block_noise_like(self, latents):
|
| 140 |
+
"""Sample correlated noise within each spatial 2x2 block.
|
| 141 |
+
|
| 142 |
+
Each block follows a 4D Gaussian with covariance:
|
| 143 |
+
cov = (1 - rho) * I + rho * 11^T
|
| 144 |
+
|
| 145 |
+
For rho=-1/3, the four pixels in a block are negatively correlated and
|
| 146 |
+
their sum has zero variance.
|
| 147 |
+
"""
|
| 148 |
+
batch_size, channels, height, width = latents.shape
|
| 149 |
+
if height % 2 != 0 or width % 2 != 0:
|
| 150 |
+
raise ValueError("Hierarchical block noise requires even spatial sizes")
|
| 151 |
+
|
| 152 |
+
rho = self.hierarchical_noise_rho
|
| 153 |
+
covariance = (
|
| 154 |
+
torch.eye(4, device=latents.device, dtype=torch.float32) * (1 - rho)
|
| 155 |
+
+ torch.ones(4, 4, device=latents.device, dtype=torch.float32) * rho
|
| 156 |
+
)
|
| 157 |
+
mean = torch.zeros(4, device=latents.device, dtype=torch.float32)
|
| 158 |
+
dist = torch.distributions.multivariate_normal.MultivariateNormal(mean, covariance)
|
| 159 |
+
block_number = batch_size * channels * (height // 2) * (width // 2)
|
| 160 |
+
noise = dist.sample((block_number,)).to(dtype=latents.dtype)
|
| 161 |
+
noise = noise.reshape(batch_size, channels, height // 2, width // 2, 2, 2)
|
| 162 |
+
noise = noise.permute(0, 1, 2, 4, 3, 5).reshape(batch_size, channels, height, width)
|
| 163 |
+
return noise
|
| 164 |
+
|
| 165 |
+
@torch.no_grad()
|
| 166 |
+
def _sample_hierarchical_stage(self, z, stage_index, labels):
|
| 167 |
+
"""Denoise z through one stage using Euler or Heun steps."""
|
| 168 |
+
global_timesteps, sigmas = self._hierarchical_stage_timesteps(stage_index, z.device, z.dtype)
|
| 169 |
+
|
| 170 |
+
for i in range(len(sigmas) - 1):
|
| 171 |
+
t = global_timesteps[i].expand(z.shape[0]).to(device=z.device, dtype=z.dtype)
|
| 172 |
+
sigma = sigmas[i].expand(z.shape[0]).view(-1, 1, 1, 1)
|
| 173 |
+
sigma_next = sigmas[i + 1].expand(z.shape[0]).view(-1, 1, 1, 1)
|
| 174 |
+
|
| 175 |
+
if self.method == "euler":
|
| 176 |
+
z = self._hierarchical_euler_step(
|
| 177 |
+
z, t, sigma, sigma_next, labels
|
| 178 |
+
)
|
| 179 |
+
elif self.method == "heun":
|
| 180 |
+
t_next = global_timesteps[i + 1].expand(z.shape[0]).to(device=z.device, dtype=z.dtype)
|
| 181 |
+
z = self._hierarchical_heun_step(
|
| 182 |
+
z, t, t_next, sigma, sigma_next, labels
|
| 183 |
+
)
|
| 184 |
+
else:
|
| 185 |
+
raise NotImplementedError
|
| 186 |
+
|
| 187 |
+
return z
|
| 188 |
+
|
| 189 |
+
def _hierarchical_stage_timesteps(self, stage_index, device, dtype):
|
| 190 |
+
"""Select this stage's grid points and return global timestep and stage-relative sigma."""
|
| 191 |
+
t_start = self.hierarchical_noise_builder.start_timesteps[stage_index]
|
| 192 |
+
t_end = self.hierarchical_noise_builder.end_timesteps[stage_index]
|
| 193 |
+
# A zero lower bound covers the full stage interval. A positive value
|
| 194 |
+
# excludes near-endpoint grid points, except for the endpoint added below.
|
| 195 |
+
min_sigma = self.hierarchical_min_stage_sigma
|
| 196 |
+
# Convert the stage-relative lower bound to global t. The true endpoint
|
| 197 |
+
# t_end is added below so sampling still finishes at sigma=0.
|
| 198 |
+
sampling_t_end = t_end + min_sigma * (t_start - t_end)
|
| 199 |
+
sampling_t_start = t_start
|
| 200 |
+
global_timesteps = self._hierarchical_global_sampling_timesteps(device, dtype)
|
| 201 |
+
global_timesteps = global_timesteps[
|
| 202 |
+
(global_timesteps <= sampling_t_start) & (global_timesteps >= sampling_t_end)
|
| 203 |
+
]
|
| 204 |
+
global_timesteps = torch.cat([
|
| 205 |
+
global_timesteps,
|
| 206 |
+
torch.tensor([sampling_t_start, sampling_t_end, t_end], device=device, dtype=dtype),
|
| 207 |
+
])
|
| 208 |
+
global_timesteps = torch.unique(global_timesteps, sorted=True).flip(0)
|
| 209 |
+
global_timesteps[0] = sampling_t_start
|
| 210 |
+
global_timesteps[-1] = t_end
|
| 211 |
+
|
| 212 |
+
denom = max(t_start - t_end, torch.finfo(dtype).eps)
|
| 213 |
+
sigmas = (global_timesteps - t_end) / denom
|
| 214 |
+
sigmas[-1] = 0.0
|
| 215 |
+
return global_timesteps, sigmas
|
| 216 |
+
|
| 217 |
+
def _hierarchical_global_sampling_timesteps(self, device, dtype):
|
| 218 |
+
"""Build the shared global timestep grid from t=1 to t=0."""
|
| 219 |
+
if len(self.sampling_steps) == 1:
|
| 220 |
+
total_steps = self.sampling_steps[0] * len(self.hierarchical_stages)
|
| 221 |
+
else:
|
| 222 |
+
total_steps = sum(self.sampling_steps)
|
| 223 |
+
|
| 224 |
+
timesteps = torch.linspace(1.0, 0.0, total_steps + 1, device=device, dtype=dtype)
|
| 225 |
+
timesteps = self._logit_shift_sampling_timesteps(timesteps)
|
| 226 |
+
return timesteps
|
| 227 |
+
|
| 228 |
+
def _logit_shift_sampling_timesteps(self, timesteps):
|
| 229 |
+
"""Adjust timestep spacing using the global Logit-Normal mean and std."""
|
| 230 |
+
eps = torch.finfo(timesteps.dtype).eps
|
| 231 |
+
t = timesteps.clamp(eps, 1.0 - eps)
|
| 232 |
+
exp_mean = math.exp(float(self.hierarchical_logit_mean))
|
| 233 |
+
shifted = exp_mean / (
|
| 234 |
+
exp_mean + torch.pow(1.0 / t - 1.0, float(self.hierarchical_logit_std))
|
| 235 |
+
)
|
| 236 |
+
shifted[0] = 1.0
|
| 237 |
+
shifted[-1] = 0.0
|
| 238 |
+
return shifted
|
| 239 |
+
|
| 240 |
+
@torch.no_grad()
|
| 241 |
+
def _hierarchical_euler_step(self, z, t, sigma, sigma_next, labels):
|
| 242 |
+
"""Euler step: take one Euler step using the current velocity and change in sigma."""
|
| 243 |
+
velocity = self._forward_hierarchical_sample(
|
| 244 |
+
z,
|
| 245 |
+
t,
|
| 246 |
+
sigma,
|
| 247 |
+
labels,
|
| 248 |
+
)
|
| 249 |
+
return z + (sigma_next - sigma) * velocity
|
| 250 |
+
|
| 251 |
+
@torch.no_grad()
|
| 252 |
+
def _hierarchical_heun_step(self, z, t, t_next, sigma, sigma_next, labels):
|
| 253 |
+
"""Heun step: average current and next velocities for a Heun step; use Euler at sigma=0."""
|
| 254 |
+
velocity = self._forward_hierarchical_sample(
|
| 255 |
+
z,
|
| 256 |
+
t,
|
| 257 |
+
sigma,
|
| 258 |
+
labels,
|
| 259 |
+
)
|
| 260 |
+
z_euler = z + (sigma_next - sigma) * velocity
|
| 261 |
+
if torch.all(sigma_next <= 0):
|
| 262 |
+
return z_euler
|
| 263 |
+
|
| 264 |
+
velocity_next = self._forward_hierarchical_sample(
|
| 265 |
+
z_euler,
|
| 266 |
+
t_next,
|
| 267 |
+
sigma_next,
|
| 268 |
+
labels,
|
| 269 |
+
)
|
| 270 |
+
return z + (sigma_next - sigma) * 0.5 * (velocity + velocity_next)
|
| 271 |
+
|
| 272 |
+
@torch.no_grad()
|
| 273 |
+
def _forward_hierarchical_sample(self, z, t, sigma, labels):
|
| 274 |
+
"""Predict endpoint-derived velocity, applying CFG within its global-timestep interval."""
|
| 275 |
+
pred_cond = self.net(z, t, labels)[0]
|
| 276 |
+
v_cond = self._hierarchical_prediction_to_velocity(
|
| 277 |
+
pred_cond,
|
| 278 |
+
z,
|
| 279 |
+
sigma,
|
| 280 |
+
)
|
| 281 |
+
|
| 282 |
+
if self.cfg_scale == 1.0 or self.label_drop_prob <= 0.0:
|
| 283 |
+
return v_cond
|
| 284 |
+
|
| 285 |
+
uncond_labels = torch.full(
|
| 286 |
+
labels.shape,
|
| 287 |
+
self.num_classes,
|
| 288 |
+
device=labels.device,
|
| 289 |
+
dtype=labels.dtype,
|
| 290 |
+
)
|
| 291 |
+
pred_uncond = self.net(z, t, uncond_labels)[0]
|
| 292 |
+
v_uncond = self._hierarchical_prediction_to_velocity(
|
| 293 |
+
pred_uncond,
|
| 294 |
+
z,
|
| 295 |
+
sigma,
|
| 296 |
+
)
|
| 297 |
+
|
| 298 |
+
cfg_t = t.view(-1, 1, 1, 1)
|
| 299 |
+
low, high = self.cfg_interval
|
| 300 |
+
lower_ok = (low == 0) | (cfg_t > low)
|
| 301 |
+
upper_ok = (high == 1) | (cfg_t < high)
|
| 302 |
+
interval_mask = lower_ok & upper_ok
|
| 303 |
+
cfg_scale = torch.where(interval_mask, self.cfg_scale, 1.0)
|
| 304 |
+
|
| 305 |
+
v_guided = v_uncond + cfg_scale * (v_cond - v_uncond)
|
| 306 |
+
return v_guided
|
| 307 |
+
|
| 308 |
+
def _hierarchical_prediction_to_velocity(self, pred, noisy, sigma):
|
| 309 |
+
"""Convert an endpoint prediction to velocity with a clamped sigma denominator."""
|
| 310 |
+
# v_theta = (x_sigma - predicted endpoint) / sigma.
|
| 311 |
+
dtype_eps = torch.finfo(sigma.dtype).eps
|
| 312 |
+
sampling_eps = float(self.sigma_min)
|
| 313 |
+
sigma_eps = max(sampling_eps, dtype_eps)
|
| 314 |
+
return (noisy - pred) / sigma.clamp_min(sigma_eps)
|
pixelmoe_src/hierarchical_image_noise.py
ADDED
|
@@ -0,0 +1,475 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# --------------------------------------------------------
|
| 2 |
+
# References:
|
| 3 |
+
# Pyramid Flow: https://github.com/jy0205/Pyramid-Flow
|
| 4 |
+
# --------------------------------------------------------
|
| 5 |
+
|
| 6 |
+
import math
|
| 7 |
+
from typing import List, NamedTuple
|
| 8 |
+
|
| 9 |
+
import torch
|
| 10 |
+
import torch.nn.functional as F
|
| 11 |
+
|
| 12 |
+
|
| 13 |
+
class HierarchicalImageNoiseOutput(NamedTuple):
|
| 14 |
+
# One entry per active stage in this batch.
|
| 15 |
+
noisy_images_list: List[torch.Tensor]
|
| 16 |
+
# Stage-relative timestep sigma; shape [stage_batch_size, 1, 1, 1].
|
| 17 |
+
sigmas_list: List[torch.Tensor]
|
| 18 |
+
# Global timestep t used to condition the model; shape [stage_batch_size].
|
| 19 |
+
timesteps_list: List[torch.Tensor]
|
| 20 |
+
# Stage endpoints; intermediate-stage endpoints can contain noise.
|
| 21 |
+
targets_list: List[torch.Tensor]
|
| 22 |
+
sample_indices_list: List[torch.Tensor]
|
| 23 |
+
|
| 24 |
+
|
| 25 |
+
class HierarchicalImageNoiseBuilder:
|
| 26 |
+
"""
|
| 27 |
+
Prepare noisy images and prediction targets for each resolution stage.
|
| 28 |
+
|
| 29 |
+
The input is a batch of images with shape [B, C, H, W]. Each image is
|
| 30 |
+
assigned to one stage for this training step; the number of images per
|
| 31 |
+
stage is determined by the fixed-density allocation. Sample a noise level
|
| 32 |
+
within the assigned stage using that stage's Logit-Normal distribution.
|
| 33 |
+
|
| 34 |
+
t is the global timestep and sigma is the timestep
|
| 35 |
+
within a stage: t = t_end + sigma * (t_start - t_end). Construct the noisy
|
| 36 |
+
image as x_sigma = sigma * x_start + (1 - sigma) * x_end.
|
| 37 |
+
|
| 38 |
+
Return noisy images, stage-relative sigmas, global timesteps, endpoint
|
| 39 |
+
targets, and original batch indices grouped by stage. Image tensors use
|
| 40 |
+
[stage_batch_size, C, stage_H, stage_W].
|
| 41 |
+
"""
|
| 42 |
+
|
| 43 |
+
def __init__(
|
| 44 |
+
self,
|
| 45 |
+
stages=(0, 1),
|
| 46 |
+
end_timesteps=(0.75, 0.0),
|
| 47 |
+
noise_rho=-1.0 / 3.0,
|
| 48 |
+
noise_scale=1.0,
|
| 49 |
+
logit_mean=0.0,
|
| 50 |
+
logit_std=1.0,
|
| 51 |
+
stage_logit_means=None,
|
| 52 |
+
stage_logit_stds=None,
|
| 53 |
+
min_stage_sigma=0.0,
|
| 54 |
+
):
|
| 55 |
+
self.stages = list(stages)
|
| 56 |
+
self.noise_scale = float(noise_scale)
|
| 57 |
+
self.logit_mean = logit_mean
|
| 58 |
+
self.logit_std = logit_std
|
| 59 |
+
self.stage_logit_means = [float(value) for value in (
|
| 60 |
+
[0.6] * len(self.stages) if stage_logit_means is None else stage_logit_means
|
| 61 |
+
)]
|
| 62 |
+
self.stage_logit_stds = [float(value) for value in (
|
| 63 |
+
[0.8] * len(self.stages) if stage_logit_stds is None else stage_logit_stds
|
| 64 |
+
)]
|
| 65 |
+
self.min_stage_sigma = float(min_stage_sigma)
|
| 66 |
+
|
| 67 |
+
if not 0.0 <= self.min_stage_sigma < 1.0:
|
| 68 |
+
raise ValueError("min_stage_sigma must be in [0, 1)")
|
| 69 |
+
if len(self.stage_logit_means) != len(self.stages):
|
| 70 |
+
raise ValueError("stage_logit_means must contain one value per hierarchical stage")
|
| 71 |
+
if len(self.stage_logit_stds) != len(self.stages):
|
| 72 |
+
raise ValueError("stage_logit_stds must contain one value per hierarchical stage")
|
| 73 |
+
if any(std <= 0.0 for std in self.stage_logit_stds):
|
| 74 |
+
raise ValueError("stage_logit_stds values must be positive")
|
| 75 |
+
self.end_timesteps = self._parse_end_timesteps(end_timesteps)
|
| 76 |
+
self.start_timesteps = self._compute_start_timesteps(self.end_timesteps, noise_rho)
|
| 77 |
+
self.training_end_timesteps = [
|
| 78 |
+
t_end + self.min_stage_sigma * (t_start - t_end)
|
| 79 |
+
for t_start, t_end in zip(self.start_timesteps, self.end_timesteps)
|
| 80 |
+
]
|
| 81 |
+
self.fixed_density_bins = self._build_fixed_density_bins()
|
| 82 |
+
|
| 83 |
+
@torch.no_grad()
|
| 84 |
+
def __call__(self, x):
|
| 85 |
+
"""
|
| 86 |
+
Build resolution stages and prepare their noisy training inputs.
|
| 87 |
+
|
| 88 |
+
Args
|
| 89 |
+
x (torch.Tensor):
|
| 90 |
+
Clean full-resolution images [B, C, H, W].
|
| 91 |
+
Returns
|
| 92 |
+
HierarchicalImageNoiseOutput: training tensors grouped by active stage.
|
| 93 |
+
"""
|
| 94 |
+
images_list = self.get_hierarchical_images(x)
|
| 95 |
+
return self.add_continuous_hierarchical_noise(images_list)
|
| 96 |
+
|
| 97 |
+
# Stage intervals
|
| 98 |
+
|
| 99 |
+
def _parse_end_timesteps(self, end_timesteps):
|
| 100 |
+
# Convert stage endpoints to floats; each stage needs one end timestep.
|
| 101 |
+
values = [float(v) for v in end_timesteps]
|
| 102 |
+
if len(values) == len(self.stages):
|
| 103 |
+
end_timesteps = values
|
| 104 |
+
else:
|
| 105 |
+
raise ValueError("end_timesteps must contain one end t per hierarchical stage")
|
| 106 |
+
|
| 107 |
+
# Global timesteps lie in [0, 1].
|
| 108 |
+
if any(s < 0.0 or s > 1.0 for s in end_timesteps):
|
| 109 |
+
raise ValueError("end timesteps must be in [0, 1]")
|
| 110 |
+
# Later stages must end at an equal or smaller timestep, e.g. [0.75, 0.0].
|
| 111 |
+
if any(end_timesteps[i] < end_timesteps[i + 1] for i in range(len(end_timesteps) - 1)):
|
| 112 |
+
raise ValueError("end timesteps must be monotonically non-increasing")
|
| 113 |
+
return end_timesteps
|
| 114 |
+
|
| 115 |
+
@staticmethod
|
| 116 |
+
def _compute_start_timesteps(end_timesteps, rho):
|
| 117 |
+
start_timesteps = []
|
| 118 |
+
for i_s, _ in enumerate(end_timesteps):
|
| 119 |
+
# The first stage starts from pure noise at t=1.
|
| 120 |
+
if i_s == 0:
|
| 121 |
+
start_timesteps.append(1.0)
|
| 122 |
+
continue
|
| 123 |
+
# Match the next stage's start distribution after upsampling, rescaling, and re-noising.
|
| 124 |
+
previous_t_end = end_timesteps[i_s - 1]
|
| 125 |
+
t_start = (math.sqrt(1. - rho) * previous_t_end) / (math.sqrt(1. - rho) * previous_t_end + (1. - previous_t_end) * math.sqrt(-rho))
|
| 126 |
+
start_timesteps.append(t_start)
|
| 127 |
+
return start_timesteps
|
| 128 |
+
|
| 129 |
+
# Fixed-density allocation
|
| 130 |
+
|
| 131 |
+
@staticmethod
|
| 132 |
+
def _normal_cdf(value):
|
| 133 |
+
return 0.5 * (1.0 + math.erf(value / math.sqrt(2.0)))
|
| 134 |
+
|
| 135 |
+
@staticmethod
|
| 136 |
+
def _logit(value):
|
| 137 |
+
return math.log(value / (1.0 - value))
|
| 138 |
+
|
| 139 |
+
def _timestep_cdf(self, t):
|
| 140 |
+
t = min(max(float(t), 0.0), 1.0)
|
| 141 |
+
if t <= 0.0:
|
| 142 |
+
return 0.0
|
| 143 |
+
if t >= 1.0:
|
| 144 |
+
return 1.0
|
| 145 |
+
z = (self._logit(t) - float(self.logit_mean)) / float(self.logit_std)
|
| 146 |
+
return self._normal_cdf(z)
|
| 147 |
+
|
| 148 |
+
def _interval_mass(self, low, high):
|
| 149 |
+
if high <= low:
|
| 150 |
+
return 0.0
|
| 151 |
+
return max(0.0, self._timestep_cdf(high) - self._timestep_cdf(low))
|
| 152 |
+
|
| 153 |
+
def _build_fixed_density_bins(self):
|
| 154 |
+
"""
|
| 155 |
+
Build stage-allocation probabilities from the global timestep distribution.
|
| 156 |
+
Overlapping intervals share their probability equally between covering stages.
|
| 157 |
+
|
| 158 |
+
Returns
|
| 159 |
+
List[dict]: entries with a stage index and normalized probability mass.
|
| 160 |
+
A stage can have multiple entries; all masses sum to 1.
|
| 161 |
+
"""
|
| 162 |
+
# Split [0, 1] at each stage's training interval boundaries.
|
| 163 |
+
boundaries = [0.0, 1.0]
|
| 164 |
+
boundaries.extend(float(v) for v in self.start_timesteps) # Add stage starts
|
| 165 |
+
boundaries.extend(float(v) for v in self.training_end_timesteps) # Add training lower bounds.
|
| 166 |
+
boundaries = sorted(set(min(max(v, 0.0), 1.0) for v in boundaries)) # Clamp to [0, 1], remove duplicates, and sort.
|
| 167 |
+
|
| 168 |
+
bins = [] # One entry for each interval/stage pair.
|
| 169 |
+
for low, high in zip(boundaries[:-1], boundaries[1:]):
|
| 170 |
+
if high <= low:
|
| 171 |
+
continue
|
| 172 |
+
|
| 173 |
+
# Stage coverage is constant within each bin; check its midpoint.
|
| 174 |
+
midpoint = 0.5 * (low + high) # A representative global t in this interval.
|
| 175 |
+
candidates = [] # Stages whose training intervals cover this bin.
|
| 176 |
+
for stage_index, (t_start, training_t_end) in enumerate(zip(self.start_timesteps, self.training_end_timesteps)):
|
| 177 |
+
stage_low = min(t_start, training_t_end)
|
| 178 |
+
stage_high = max(t_start, training_t_end)
|
| 179 |
+
if stage_low <= midpoint <= stage_high:
|
| 180 |
+
candidates.append(stage_index)
|
| 181 |
+
|
| 182 |
+
if not candidates:
|
| 183 |
+
continue
|
| 184 |
+
|
| 185 |
+
# Divide this bin's global Logit-Normal probability among covering stages.
|
| 186 |
+
mass = self._interval_mass(low, high) # P(low <= t <= high), not interval width.
|
| 187 |
+
if mass <= 0.0:
|
| 188 |
+
continue
|
| 189 |
+
|
| 190 |
+
split_mass = mass / len(candidates) # Each covering stage receives an equal share.
|
| 191 |
+
for stage_index in candidates:
|
| 192 |
+
bins.append(
|
| 193 |
+
{
|
| 194 |
+
"stage": stage_index,
|
| 195 |
+
"mass": split_mass,
|
| 196 |
+
}
|
| 197 |
+
)
|
| 198 |
+
|
| 199 |
+
# Normalize over valid training intervals; uncovered bins have no quota.
|
| 200 |
+
total_mass = sum(item["mass"] for item in bins)
|
| 201 |
+
if total_mass <= 0.0:
|
| 202 |
+
raise ValueError("Fixed-density stage allocation produced zero probability mass")
|
| 203 |
+
|
| 204 |
+
for item in bins:
|
| 205 |
+
item["mass"] /= total_mass
|
| 206 |
+
|
| 207 |
+
return bins
|
| 208 |
+
|
| 209 |
+
@staticmethod
|
| 210 |
+
def _apportion_counts(batch_size, masses):
|
| 211 |
+
"""
|
| 212 |
+
Convert bin probabilities to integer image quotas.
|
| 213 |
+
|
| 214 |
+
Args
|
| 215 |
+
batch_size (int):
|
| 216 |
+
Total number of images to assign.
|
| 217 |
+
masses (List[float]):
|
| 218 |
+
Normalized probability per bin entry.
|
| 219 |
+
Returns
|
| 220 |
+
List[int]: quotas per bin entry, summing to batch_size.
|
| 221 |
+
"""
|
| 222 |
+
# Expected image counts may be fractional; first round each down.
|
| 223 |
+
expected = [mass * batch_size for mass in masses]
|
| 224 |
+
counts = [int(math.floor(value)) for value in expected] # Initial integer quotas per bin entry.
|
| 225 |
+
remaining = batch_size - sum(counts) # Number of images still unassigned.
|
| 226 |
+
|
| 227 |
+
# Sort bin indices by fractional remainder, largest first.
|
| 228 |
+
remainders = sorted(
|
| 229 |
+
range(len(expected)),
|
| 230 |
+
key=lambda i: expected[i] - counts[i],
|
| 231 |
+
reverse=True,
|
| 232 |
+
)
|
| 233 |
+
|
| 234 |
+
# Add one image to each of the top remaining entries.
|
| 235 |
+
for i in remainders[:remaining]:
|
| 236 |
+
counts[i] += 1
|
| 237 |
+
|
| 238 |
+
return counts
|
| 239 |
+
|
| 240 |
+
def _fixed_batch_bin_counts(self, batch_size):
|
| 241 |
+
"""
|
| 242 |
+
Get integer image quotas in fixed_density_bins order.
|
| 243 |
+
|
| 244 |
+
Args
|
| 245 |
+
batch_size (int):
|
| 246 |
+
Number of images in the batch.
|
| 247 |
+
Returns
|
| 248 |
+
List[int]: one quota per bin entry, summing to batch_size.
|
| 249 |
+
"""
|
| 250 |
+
# Keep separate bin entries even when they belong to the same stage.
|
| 251 |
+
masses = [item["mass"] for item in self.fixed_density_bins] # Preserve bin order.
|
| 252 |
+
return self._apportion_counts(batch_size, masses)
|
| 253 |
+
|
| 254 |
+
# Stage-local sampling
|
| 255 |
+
|
| 256 |
+
def _sample_stage_local_timesteps(self, count, stage_index, device, dtype):
|
| 257 |
+
"""
|
| 258 |
+
Sample noise levels within the selected stage's training interval.
|
| 259 |
+
|
| 260 |
+
Args
|
| 261 |
+
count (int):
|
| 262 |
+
Number of images assigned to this bin entry.
|
| 263 |
+
stage_index (int):
|
| 264 |
+
Stage whose interval and Logit-Normal parameters are used.
|
| 265 |
+
device (torch.device):
|
| 266 |
+
Device for sampled tensors.
|
| 267 |
+
dtype (torch.dtype):
|
| 268 |
+
Dtype for returned timesteps.
|
| 269 |
+
Returns
|
| 270 |
+
torch.Tensor: global timesteps [count], between the training lower bound and stage start.
|
| 271 |
+
"""
|
| 272 |
+
low = float(self.training_end_timesteps[stage_index]) # Lower global timestep allowed during training.
|
| 273 |
+
high = float(self.start_timesteps[stage_index]) # Stage start in global timestep.
|
| 274 |
+
|
| 275 |
+
# Standard normal -> stage-specific normal -> sigmoid values in (0, 1).
|
| 276 |
+
normal = torch.randn(count, device=device, dtype=torch.float64) # [count]
|
| 277 |
+
# u is relative to the truncated training interval. With a lower bound,
|
| 278 |
+
# the full-stage sigma is min_stage_sigma + (1 - min_stage_sigma) * u.
|
| 279 |
+
u = torch.sigmoid(
|
| 280 |
+
float(self.stage_logit_means[stage_index])
|
| 281 |
+
+ float(self.stage_logit_stds[stage_index]) * normal
|
| 282 |
+
)
|
| 283 |
+
|
| 284 |
+
# Map the relative position u to the stage's global-timestep training interval.
|
| 285 |
+
timesteps = low + (high - low) * u # [count]
|
| 286 |
+
return timesteps.clamp(min=low, max=high).to(dtype=dtype)
|
| 287 |
+
|
| 288 |
+
def _sample_fixed_batch_timesteps_and_stages(
|
| 289 |
+
self,
|
| 290 |
+
batch_size,
|
| 291 |
+
device,
|
| 292 |
+
dtype,
|
| 293 |
+
):
|
| 294 |
+
"""
|
| 295 |
+
Randomly assign images to fixed quotas, then sample their global timesteps.
|
| 296 |
+
|
| 297 |
+
Args
|
| 298 |
+
batch_size (int):
|
| 299 |
+
Number of images in the batch.
|
| 300 |
+
device (torch.device):
|
| 301 |
+
Device for sampled tensors.
|
| 302 |
+
dtype (torch.dtype):
|
| 303 |
+
Dtype for returned timesteps.
|
| 304 |
+
Returns
|
| 305 |
+
tuple: global timesteps [B] and stage indices [B], in original image order.
|
| 306 |
+
"""
|
| 307 |
+
counts = self._fixed_batch_bin_counts(batch_size) # Integer quota per bin entry; sum = B.
|
| 308 |
+
sampled_timesteps = torch.empty(batch_size, device=device, dtype=dtype) # [B], global t per image.
|
| 309 |
+
chosen_stages = torch.empty(batch_size, device=device, dtype=torch.long) # [B], stage index per image.
|
| 310 |
+
|
| 311 |
+
# Shuffle image indices so stage assignment does not depend on batch order.
|
| 312 |
+
permutation = torch.randperm(batch_size, device=device)
|
| 313 |
+
|
| 314 |
+
offset = 0 # Number of indices already taken from permutation.
|
| 315 |
+
for bin_info, count in zip(self.fixed_density_bins, counts):
|
| 316 |
+
if count == 0:
|
| 317 |
+
continue
|
| 318 |
+
indices = permutation[offset:offset + count] # [count], original image indices for this quota.
|
| 319 |
+
stage_index = int(bin_info["stage"])
|
| 320 |
+
|
| 321 |
+
# Bin masses control counts; sample t over the stage's full training interval.
|
| 322 |
+
sampled_timesteps[indices] = self._sample_stage_local_timesteps(
|
| 323 |
+
count,
|
| 324 |
+
stage_index,
|
| 325 |
+
device,
|
| 326 |
+
dtype,
|
| 327 |
+
)
|
| 328 |
+
chosen_stages[indices] = stage_index # Write results back in original batch order.
|
| 329 |
+
offset += count
|
| 330 |
+
|
| 331 |
+
return sampled_timesteps, chosen_stages
|
| 332 |
+
|
| 333 |
+
# Hierarchical images and noise
|
| 334 |
+
|
| 335 |
+
@torch.no_grad()
|
| 336 |
+
def get_hierarchical_images(self, x):
|
| 337 |
+
"""
|
| 338 |
+
Build `[low_res, ..., high_res]` image tensors from a clean image batch.
|
| 339 |
+
|
| 340 |
+
Args
|
| 341 |
+
x (torch.Tensor):
|
| 342 |
+
Clean full-resolution images [B, C, H, W].
|
| 343 |
+
Returns
|
| 344 |
+
List[torch.Tensor]: clean images [B, C, H_stage, W_stage], from low to high resolution.
|
| 345 |
+
"""
|
| 346 |
+
if x.ndim != 4:
|
| 347 |
+
raise ValueError(f"Expected image tensor [B, C, H, W], got shape {tuple(x.shape)}")
|
| 348 |
+
|
| 349 |
+
# Collect images from high to low resolution, then reverse to stage order.
|
| 350 |
+
image_list = [x]
|
| 351 |
+
height, width = x.shape[-2], x.shape[-1]
|
| 352 |
+
for _ in range(len(self.stages) - 1):
|
| 353 |
+
height //= 2
|
| 354 |
+
width //= 2
|
| 355 |
+
# Halve the image height and width for the next lower-resolution stage.
|
| 356 |
+
x = F.interpolate(x, size=(height, width), mode="bilinear", align_corners=False)
|
| 357 |
+
image_list.append(x)
|
| 358 |
+
|
| 359 |
+
image_list = list(reversed(image_list))
|
| 360 |
+
return image_list
|
| 361 |
+
|
| 362 |
+
@torch.no_grad()
|
| 363 |
+
def add_continuous_hierarchical_noise(self, images_list):
|
| 364 |
+
"""
|
| 365 |
+
Construct noisy inputs and endpoint targets for the assigned images at each stage.
|
| 366 |
+
|
| 367 |
+
Args
|
| 368 |
+
images_list (List[torch.Tensor]):
|
| 369 |
+
Clean images [B, C, H_stage, W_stage], ordered from low to high resolution.
|
| 370 |
+
Every stage keeps the same original batch order.
|
| 371 |
+
Returns
|
| 372 |
+
HierarchicalImageNoiseOutput: per-active-stage noisy inputs, sigmas,
|
| 373 |
+
global timesteps, endpoint targets, and original image indices.
|
| 374 |
+
"""
|
| 375 |
+
stages = len(self.stages)
|
| 376 |
+
if stages != len(images_list):
|
| 377 |
+
raise ValueError("Number of clean hierarchical images must match len(stages)")
|
| 378 |
+
|
| 379 |
+
base_image = images_list[-1] # [B, C, H, W], the full-resolution clean images.
|
| 380 |
+
device = base_image.device
|
| 381 |
+
dtype = base_image.dtype
|
| 382 |
+
batch_size = base_image.shape[0]
|
| 383 |
+
|
| 384 |
+
# Scale full-resolution Gaussian noise, then downsample it for each stage.
|
| 385 |
+
# Multiplying by 2 after downsampling restores the noise variance.
|
| 386 |
+
noise = torch.randn_like(base_image) * self.noise_scale # [B, C, H, W]
|
| 387 |
+
height, width = noise.shape[-2], noise.shape[-1]
|
| 388 |
+
noise_list = [noise]
|
| 389 |
+
cur_noise = noise
|
| 390 |
+
for _ in range(stages - 1):
|
| 391 |
+
height //= 2
|
| 392 |
+
width //= 2
|
| 393 |
+
cur_noise = F.interpolate(
|
| 394 |
+
cur_noise,
|
| 395 |
+
size=(height, width),
|
| 396 |
+
mode="bilinear",
|
| 397 |
+
align_corners=False,
|
| 398 |
+
) * 2.0
|
| 399 |
+
noise_list.append(cur_noise)
|
| 400 |
+
noise_list = list(reversed(noise_list)) # Match images_list's low-to-high resolution order.
|
| 401 |
+
|
| 402 |
+
# Sample only where the stage-relative sigma is at least min_stage_sigma.
|
| 403 |
+
sampled_timesteps, chosen_stages = self._sample_fixed_batch_timesteps_and_stages(
|
| 404 |
+
batch_size,
|
| 405 |
+
device,
|
| 406 |
+
dtype,
|
| 407 |
+
)
|
| 408 |
+
|
| 409 |
+
stage_to_indices = [
|
| 410 |
+
torch.nonzero(chosen_stages == i_s, as_tuple=False).flatten()
|
| 411 |
+
for i_s in range(stages)
|
| 412 |
+
] # stage_to_indices[i] contains the original indices assigned to stage i.
|
| 413 |
+
|
| 414 |
+
# One tensor per active stage; stages with no assigned images are omitted.
|
| 415 |
+
noisy_images_list = []
|
| 416 |
+
sigmas_list = []
|
| 417 |
+
targets_list = []
|
| 418 |
+
timesteps_list = []
|
| 419 |
+
sample_indices_list = []
|
| 420 |
+
|
| 421 |
+
for i_s, indices in enumerate(stage_to_indices):
|
| 422 |
+
# Small batches can leave some stages empty.
|
| 423 |
+
if indices.numel() == 0:
|
| 424 |
+
continue
|
| 425 |
+
|
| 426 |
+
# Gather matching images, noise, and timesteps using original batch indices.
|
| 427 |
+
clean_image = images_list[i_s].index_select(0, indices) # [B_stage, C, H_stage, W_stage]
|
| 428 |
+
stage_noise = noise_list[i_s].index_select(0, indices) # Same shape as clean_image.
|
| 429 |
+
timesteps = sampled_timesteps.index_select(0, indices) # [B_stage], global t.
|
| 430 |
+
t_start = torch.as_tensor(self.start_timesteps[i_s], device=device, dtype=dtype)
|
| 431 |
+
t_end = torch.as_tensor(self.end_timesteps[i_s], device=device, dtype=dtype)
|
| 432 |
+
|
| 433 |
+
# Stage-relative sigma is 1 at t_start and 0 at t_end.
|
| 434 |
+
denom = (t_start - t_end).clamp_min(torch.finfo(dtype).eps)
|
| 435 |
+
sigma = ((timesteps - t_end) / denom).clamp(0.0, 1.0) # [B_stage]
|
| 436 |
+
|
| 437 |
+
# Construct the start image at global t_start.
|
| 438 |
+
if i_s == 0:
|
| 439 |
+
x_start = stage_noise # First stage starts from pure noise.
|
| 440 |
+
else:
|
| 441 |
+
# Use the same images at the previous resolution, upsampled to this stage.
|
| 442 |
+
last_clean_image = images_list[i_s - 1].index_select(0, indices)
|
| 443 |
+
last_clean_image = F.interpolate(
|
| 444 |
+
last_clean_image,
|
| 445 |
+
size=(last_clean_image.shape[-2] * 2, last_clean_image.shape[-1] * 2),
|
| 446 |
+
mode="nearest",
|
| 447 |
+
)
|
| 448 |
+
x_start = t_start * stage_noise + (1.0 - t_start) * last_clean_image
|
| 449 |
+
|
| 450 |
+
# Construct the endpoint the model must predict.
|
| 451 |
+
if i_s == stages - 1:
|
| 452 |
+
x_end = clean_image # Final endpoint is clean, at t=0 in our configuration.
|
| 453 |
+
else:
|
| 454 |
+
x_end = t_end * stage_noise + (1.0 - t_end) * clean_image # Intermediate endpoint retains noise.
|
| 455 |
+
|
| 456 |
+
# Broadcast sigma from [B_stage] to [B_stage, 1, 1, 1].
|
| 457 |
+
while sigma.ndim < x_start.ndim:
|
| 458 |
+
sigma = sigma.unsqueeze(-1)
|
| 459 |
+
|
| 460 |
+
# Interpolate from the stage's start image toward its endpoint target.
|
| 461 |
+
noisy_images = sigma * x_start + (1.0 - sigma) * x_end # [B_stage, C, H_stage, W_stage]
|
| 462 |
+
|
| 463 |
+
noisy_images_list.append(noisy_images.to(dtype))
|
| 464 |
+
sigmas_list.append(sigma.to(dtype)) # [B_stage, 1, 1, 1], used by the loss.
|
| 465 |
+
timesteps_list.append(timesteps.to(dtype)) # [B_stage], used to condition the model.
|
| 466 |
+
targets_list.append(x_end.to(dtype))
|
| 467 |
+
sample_indices_list.append(indices) # [B_stage], used to select matching class labels.
|
| 468 |
+
|
| 469 |
+
return HierarchicalImageNoiseOutput(
|
| 470 |
+
noisy_images_list=noisy_images_list,
|
| 471 |
+
sigmas_list=sigmas_list,
|
| 472 |
+
timesteps_list=timesteps_list,
|
| 473 |
+
targets_list=targets_list,
|
| 474 |
+
sample_indices_list=sample_indices_list,
|
| 475 |
+
)
|
pixelmoe_src/imagenet_classes.json
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
["tench", "goldfish", "great white shark", "tiger shark", "hammerhead", "electric ray", "stingray", "cock", "hen", "ostrich", "brambling", "goldfinch", "house finch", "junco", "indigo bunting", "robin", "bulbul", "jay", "magpie", "chickadee", "water ouzel", "kite", "bald eagle", "vulture", "great grey owl", "European fire salamander", "common newt", "eft", "spotted salamander", "axolotl", "bullfrog", "tree frog", "tailed frog", "loggerhead", "leatherback turtle", "mud turtle", "terrapin", "box turtle", "banded gecko", "common iguana", "American chameleon", "whiptail", "agama", "frilled lizard", "alligator lizard", "Gila monster", "green lizard", "African chameleon", "Komodo dragon", "African crocodile", "American alligator", "triceratops", "thunder snake", "ringneck snake", "hognose snake", "green snake", "king snake", "garter snake", "water snake", "vine snake", "night snake", "boa constrictor", "rock python", "Indian cobra", "green mamba", "sea snake", "horned viper", "diamondback", "sidewinder", "trilobite", "harvestman", "scorpion", "black and gold garden spider", "barn spider", "garden spider", "black widow", "tarantula", "wolf spider", "tick", "centipede", "black grouse", "ptarmigan", "ruffed grouse", "prairie chicken", "peacock", "quail", "partridge", "African grey", "macaw", "sulphur-crested cockatoo", "lorikeet", "coucal", "bee eater", "hornbill", "hummingbird", "jacamar", "toucan", "drake", "red-breasted merganser", "goose", "black swan", "tusker", "echidna", "platypus", "wallaby", "koala", "wombat", "jellyfish", "sea anemone", "brain coral", "flatworm", "nematode", "conch", "snail", "slug", "sea slug", "chiton", "chambered nautilus", "Dungeness crab", "rock crab", "fiddler crab", "king crab", "American lobster", "spiny lobster", "crayfish", "hermit crab", "isopod", "white stork", "black stork", "spoonbill", "flamingo", "little blue heron", "American egret", "bittern", "crane", "limpkin", "European gallinule", "American coot", "bustard", "ruddy turnstone", "red-backed sandpiper", "redshank", "dowitcher", "oystercatcher", "pelican", "king penguin", "albatross", "grey whale", "killer whale", "dugong", "sea lion", "Chihuahua", "Japanese spaniel", "Maltese dog", "Pekinese", "Shih-Tzu", "Blenheim spaniel", "papillon", "toy terrier", "Rhodesian ridgeback", "Afghan hound", "basset", "beagle", "bloodhound", "bluetick", "black-and-tan coonhound", "Walker hound", "English foxhound", "redbone", "borzoi", "Irish wolfhound", "Italian greyhound", "whippet", "Ibizan hound", "Norwegian elkhound", "otterhound", "Saluki", "Scottish deerhound", "Weimaraner", "Staffordshire bullterrier", "American Staffordshire terrier", "Bedlington terrier", "Border terrier", "Kerry blue terrier", "Irish terrier", "Norfolk terrier", "Norwich terrier", "Yorkshire terrier", "wire-haired fox terrier", "Lakeland terrier", "Sealyham terrier", "Airedale", "cairn", "Australian terrier", "Dandie Dinmont", "Boston bull", "miniature schnauzer", "giant schnauzer", "standard schnauzer", "Scotch terrier", "Tibetan terrier", "silky terrier", "soft-coated wheaten terrier", "West Highland white terrier", "Lhasa", "flat-coated retriever", "curly-coated retriever", "golden retriever", "Labrador retriever", "Chesapeake Bay retriever", "German short-haired pointer", "vizsla", "English setter", "Irish setter", "Gordon setter", "Brittany spaniel", "clumber", "English springer", "Welsh springer spaniel", "cocker spaniel", "Sussex spaniel", "Irish water spaniel", "kuvasz", "schipperke", "groenendael", "malinois", "briard", "kelpie", "komondor", "Old English sheepdog", "Shetland sheepdog", "collie", "Border collie", "Bouvier des Flandres", "Rottweiler", "German shepherd", "Doberman", "miniature pinscher", "Greater Swiss Mountain dog", "Bernese mountain dog", "Appenzeller", "EntleBucher", "boxer", "bull mastiff", "Tibetan mastiff", "French bulldog", "Great Dane", "Saint Bernard", "Eskimo dog", "malamute", "Siberian husky", "dalmatian", "affenpinscher", "basenji", "pug", "Leonberg", "Newfoundland", "Great Pyrenees", "Samoyed", "Pomeranian", "chow", "keeshond", "Brabancon griffon", "Pembroke", "Cardigan", "toy poodle", "miniature poodle", "standard poodle", "Mexican hairless", "timber wolf", "white wolf", "red wolf", "coyote", "dingo", "dhole", "African hunting dog", "hyena", "red fox", "kit fox", "Arctic fox", "grey fox", "tabby", "tiger cat", "Persian cat", "Siamese cat", "Egyptian cat", "cougar", "lynx", "leopard", "snow leopard", "jaguar", "lion", "tiger", "cheetah", "brown bear", "American black bear", "ice bear", "sloth bear", "mongoose", "meerkat", "tiger beetle", "ladybug", "ground beetle", "long-horned beetle", "leaf beetle", "dung beetle", "rhinoceros beetle", "weevil", "fly", "bee", "ant", "grasshopper", "cricket", "walking stick", "cockroach", "mantis", "cicada", "leafhopper", "lacewing", "dragonfly", "damselfly", "admiral", "ringlet", "monarch", "cabbage butterfly", "sulphur butterfly", "lycaenid", "starfish", "sea urchin", "sea cucumber", "wood rabbit", "hare", "Angora", "hamster", "porcupine", "fox squirrel", "marmot", "beaver", "guinea pig", "sorrel", "zebra", "hog", "wild boar", "warthog", "hippopotamus", "ox", "water buffalo", "bison", "ram", "bighorn", "ibex", "hartebeest", "impala", "gazelle", "Arabian camel", "llama", "weasel", "mink", "polecat", "black-footed ferret", "otter", "skunk", "badger", "armadillo", "three-toed sloth", "orangutan", "gorilla", "chimpanzee", "gibbon", "siamang", "guenon", "patas", "baboon", "macaque", "langur", "colobus", "proboscis monkey", "marmoset", "capuchin", "howler monkey", "titi", "spider monkey", "squirrel monkey", "Madagascar cat", "indri", "Indian elephant", "African elephant", "lesser panda", "giant panda", "barracouta", "eel", "coho", "rock beauty", "anemone fish", "sturgeon", "gar", "lionfish", "puffer", "abacus", "abaya", "academic gown", "accordion", "acoustic guitar", "aircraft carrier", "airliner", "airship", "altar", "ambulance", "amphibian", "analog clock", "apiary", "apron", "ashcan", "assault rifle", "backpack", "bakery", "balance beam", "balloon", "ballpoint", "Band Aid", "banjo", "bannister", "barbell", "barber chair", "barbershop", "barn", "barometer", "barrel", "barrow", "baseball", "basketball", "bassinet", "bassoon", "bathing cap", "bath towel", "bathtub", "beach wagon", "beacon", "beaker", "bearskin", "beer bottle", "beer glass", "bell cote", "bib", "bicycle-built-for-two", "bikini", "binder", "binoculars", "birdhouse", "boathouse", "bobsled", "bolo tie", "bonnet", "bookcase", "bookshop", "bottlecap", "bow", "bow tie", "brass", "brassiere", "breakwater", "breastplate", "broom", "bucket", "buckle", "bulletproof vest", "bullet train", "butcher shop", "cab", "caldron", "candle", "cannon", "canoe", "can opener", "cardigan", "car mirror", "carousel", "carpenter's kit", "carton", "car wheel", "cash machine", "cassette", "cassette player", "castle", "catamaran", "CD player", "cello", "cellular telephone", "chain", "chainlink fence", "chain mail", "chain saw", "chest", "chiffonier", "chime", "china cabinet", "Christmas stocking", "church", "cinema", "cleaver", "cliff dwelling", "cloak", "clog", "cocktail shaker", "coffee mug", "coffeepot", "coil", "combination lock", "computer keyboard", "confectionery", "container ship", "convertible", "corkscrew", "cornet", "cowboy boot", "cowboy hat", "cradle", "crane", "crash helmet", "crate", "crib", "Crock Pot", "croquet ball", "crutch", "cuirass", "dam", "desk", "desktop computer", "dial telephone", "diaper", "digital clock", "digital watch", "dining table", "dishrag", "dishwasher", "disk brake", "dock", "dogsled", "dome", "doormat", "drilling platform", "drum", "drumstick", "dumbbell", "Dutch oven", "electric fan", "electric guitar", "electric locomotive", "entertainment center", "envelope", "espresso maker", "face powder", "feather boa", "file", "fireboat", "fire engine", "fire screen", "flagpole", "flute", "folding chair", "football helmet", "forklift", "fountain", "fountain pen", "four-poster", "freight car", "French horn", "frying pan", "fur coat", "garbage truck", "gasmask", "gas pump", "goblet", "go-kart", "golf ball", "golfcart", "gondola", "gong", "gown", "grand piano", "greenhouse", "grille", "grocery store", "guillotine", "hair slide", "hair spray", "half track", "hammer", "hamper", "hand blower", "hand-held computer", "handkerchief", "hard disc", "harmonica", "harp", "harvester", "hatchet", "holster", "home theater", "honeycomb", "hook", "hoopskirt", "horizontal bar", "horse cart", "hourglass", "iPod", "iron", "jack-o'-lantern", "jean", "jeep", "jersey", "jigsaw puzzle", "jinrikisha", "joystick", "kimono", "knee pad", "knot", "lab coat", "ladle", "lampshade", "laptop", "lawn mower", "lens cap", "letter opener", "library", "lifeboat", "lighter", "limousine", "liner", "lipstick", "Loafer", "lotion", "loudspeaker", "loupe", "lumbermill", "magnetic compass", "mailbag", "mailbox", "maillot", "maillot", "manhole cover", "maraca", "marimba", "mask", "matchstick", "maypole", "maze", "measuring cup", "medicine chest", "megalith", "microphone", "microwave", "military uniform", "milk can", "minibus", "miniskirt", "minivan", "missile", "mitten", "mixing bowl", "mobile home", "Model T", "modem", "monastery", "monitor", "moped", "mortar", "mortarboard", "mosque", "mosquito net", "motor scooter", "mountain bike", "mountain tent", "mouse", "mousetrap", "moving van", "muzzle", "nail", "neck brace", "necklace", "nipple", "notebook", "obelisk", "oboe", "ocarina", "odometer", "oil filter", "organ", "oscilloscope", "overskirt", "oxcart", "oxygen mask", "packet", "paddle", "paddlewheel", "padlock", "paintbrush", "pajama", "palace", "panpipe", "paper towel", "parachute", "parallel bars", "park bench", "parking meter", "passenger car", "patio", "pay-phone", "pedestal", "pencil box", "pencil sharpener", "perfume", "Petri dish", "photocopier", "pick", "pickelhaube", "picket fence", "pickup", "pier", "piggy bank", "pill bottle", "pillow", "ping-pong ball", "pinwheel", "pirate", "pitcher", "plane", "planetarium", "plastic bag", "plate rack", "plow", "plunger", "Polaroid camera", "pole", "police van", "poncho", "pool table", "pop bottle", "pot", "potter's wheel", "power drill", "prayer rug", "printer", "prison", "projectile", "projector", "puck", "punching bag", "purse", "quill", "quilt", "racer", "racket", "radiator", "radio", "radio telescope", "rain barrel", "recreational vehicle", "reel", "reflex camera", "refrigerator", "remote control", "restaurant", "revolver", "rifle", "rocking chair", "rotisserie", "rubber eraser", "rugby ball", "rule", "running shoe", "safe", "safety pin", "saltshaker", "sandal", "sarong", "sax", "scabbard", "scale", "school bus", "schooner", "scoreboard", "screen", "screw", "screwdriver", "seat belt", "sewing machine", "shield", "shoe shop", "shoji", "shopping basket", "shopping cart", "shovel", "shower cap", "shower curtain", "ski", "ski mask", "sleeping bag", "slide rule", "sliding door", "slot", "snorkel", "snowmobile", "snowplow", "soap dispenser", "soccer ball", "sock", "solar dish", "sombrero", "soup bowl", "space bar", "space heater", "space shuttle", "spatula", "speedboat", "spider web", "spindle", "sports car", "spotlight", "stage", "steam locomotive", "steel arch bridge", "steel drum", "stethoscope", "stole", "stone wall", "stopwatch", "stove", "strainer", "streetcar", "stretcher", "studio couch", "stupa", "submarine", "suit", "sundial", "sunglass", "sunglasses", "sunscreen", "suspension bridge", "swab", "sweatshirt", "swimming trunks", "swing", "switch", "syringe", "table lamp", "tank", "tape player", "teapot", "teddy", "television", "tennis ball", "thatch", "theater curtain", "thimble", "thresher", "throne", "tile roof", "toaster", "tobacco shop", "toilet seat", "torch", "totem pole", "tow truck", "toyshop", "tractor", "trailer truck", "tray", "trench coat", "tricycle", "trimaran", "tripod", "triumphal arch", "trolleybus", "trombone", "tub", "turnstile", "typewriter keyboard", "umbrella", "unicycle", "upright", "vacuum", "vase", "vault", "velvet", "vending machine", "vestment", "viaduct", "violin", "volleyball", "waffle iron", "wall clock", "wallet", "wardrobe", "warplane", "washbasin", "washer", "water bottle", "water jug", "water tower", "whiskey jug", "whistle", "wig", "window screen", "window shade", "Windsor tie", "wine bottle", "wing", "wok", "wooden spoon", "wool", "worm fence", "wreck", "yawl", "yurt", "web site", "comic book", "crossword puzzle", "street sign", "traffic light", "book jacket", "menu", "plate", "guacamole", "consomme", "hot pot", "trifle", "ice cream", "ice lolly", "French loaf", "bagel", "pretzel", "cheeseburger", "hotdog", "mashed potato", "head cabbage", "broccoli", "cauliflower", "zucchini", "spaghetti squash", "acorn squash", "butternut squash", "cucumber", "artichoke", "bell pepper", "cardoon", "mushroom", "Granny Smith", "strawberry", "orange", "lemon", "fig", "pineapple", "banana", "jackfruit", "custard apple", "pomegranate", "hay", "carbonara", "chocolate sauce", "dough", "meat loaf", "pizza", "potpie", "burrito", "red wine", "espresso", "cup", "eggnog", "alp", "bubble", "cliff", "coral reef", "geyser", "lakeside", "promontory", "sandbar", "seashore", "valley", "volcano", "ballplayer", "groom", "scuba diver", "rapeseed", "daisy", "yellow lady's slipper", "corn", "acorn", "hip", "buckeye", "coral fungus", "agaric", "gyromitra", "stinkhorn", "earthstar", "hen-of-the-woods", "bolete", "ear", "toilet tissue"]
|
pixelmoe_src/model_pixelmoe.py
ADDED
|
@@ -0,0 +1,1222 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# --------------------------------------------------------
|
| 2 |
+
# References:
|
| 3 |
+
# JiT: https://github.com/LTH14/JiT
|
| 4 |
+
# FLUX: https://github.com/black-forest-labs/flux
|
| 5 |
+
# Megatron-LM (weight initialization): https://github.com/NVIDIA/Megatron-LM
|
| 6 |
+
# --------------------------------------------------------
|
| 7 |
+
|
| 8 |
+
import torch
|
| 9 |
+
import torch.nn as nn
|
| 10 |
+
import math
|
| 11 |
+
import torch.nn.functional as F
|
| 12 |
+
|
| 13 |
+
from deepseek_moe import (
|
| 14 |
+
DeepSeekMoEFFN,
|
| 15 |
+
DeepSeekMoEGate,
|
| 16 |
+
DeepSeekSharedExpert,
|
| 17 |
+
)
|
| 18 |
+
|
| 19 |
+
from typing import List, Optional
|
| 20 |
+
from einops import rearrange
|
| 21 |
+
from util.model_util import get_2d_sincos_pos_embed, RMSNorm
|
| 22 |
+
|
| 23 |
+
try:
|
| 24 |
+
from flash_attn.flash_attn_interface import flash_attn_varlen_func
|
| 25 |
+
except Exception:
|
| 26 |
+
flash_attn_varlen_func = None
|
| 27 |
+
|
| 28 |
+
# Spaces note: torch.compile is not supported inside ZeroGPU's forked GPU
|
| 29 |
+
# worker; the upstream decorators were plain torch.compile which would raise
|
| 30 |
+
# at import time there. The forward passes stay eager.
|
| 31 |
+
|
| 32 |
+
|
| 33 |
+
# Helpers
|
| 34 |
+
|
| 35 |
+
def modulate(x, shift: torch.Tensor, scale: torch.Tensor):
|
| 36 |
+
# Apply scale and shift to packed tokens; inputs and output: [1, total_tokens, D].
|
| 37 |
+
return x * (1 + scale) + shift
|
| 38 |
+
|
| 39 |
+
|
| 40 |
+
def expand_segment_embeddings(embed: torch.Tensor, hidden_length: List[int], batch_size: int):
|
| 41 |
+
"""
|
| 42 |
+
Repeat each image's conditioning over its packed tokens.
|
| 43 |
+
|
| 44 |
+
Args
|
| 45 |
+
embed (torch.Tensor):
|
| 46 |
+
Conditioning embeddings with shape [num_images, D].
|
| 47 |
+
hidden_length (List[int]):
|
| 48 |
+
Token count per image, including context tokens when present.
|
| 49 |
+
batch_size (int):
|
| 50 |
+
1 for packed tokens.
|
| 51 |
+
Returns
|
| 52 |
+
token embeddings with shape [1, sum(hidden_length), D].
|
| 53 |
+
"""
|
| 54 |
+
num_segments = len(hidden_length)
|
| 55 |
+
lengths = torch.as_tensor(hidden_length, device=embed.device, dtype=torch.long)
|
| 56 |
+
embed = embed.reshape(batch_size, num_segments, -1).transpose(0, 1) # [num_segments, 1, D]
|
| 57 |
+
return torch.repeat_interleave(embed, lengths, dim=0).transpose(0, 1).contiguous() # [1, sum(hidden_length), D]
|
| 58 |
+
|
| 59 |
+
|
| 60 |
+
@torch._dynamo.disable
|
| 61 |
+
def split_ragged_context_image_tokens(mlp_input, lengths, context_len):
|
| 62 |
+
"""
|
| 63 |
+
Split packed tokens into context and image groups.
|
| 64 |
+
|
| 65 |
+
Args
|
| 66 |
+
mlp_input (torch.Tensor):
|
| 67 |
+
Packed tokens with shape [1, sum(lengths), D].
|
| 68 |
+
lengths (List[int]):
|
| 69 |
+
Token count per image, including context tokens.
|
| 70 |
+
context_len (int):
|
| 71 |
+
Context token count per image.
|
| 72 |
+
Returns
|
| 73 |
+
context tokens [1, num_images * context_len, D],
|
| 74 |
+
image tokens [1, sum(image_lengths), D],
|
| 75 |
+
image token counts.
|
| 76 |
+
"""
|
| 77 |
+
segments = torch.split(mlp_input, lengths, dim=1) # Each segment: [1, lengths[i], D].
|
| 78 |
+
context_input = torch.cat(
|
| 79 |
+
[segment[:, :context_len] for segment in segments],
|
| 80 |
+
dim=1,
|
| 81 |
+
) # [1, num_images * context_len, D]
|
| 82 |
+
image_lengths = [length - context_len for length in lengths] # Image token count per image.
|
| 83 |
+
image_input = torch.cat(
|
| 84 |
+
[segment[:, context_len:] for segment in segments],
|
| 85 |
+
dim=1,
|
| 86 |
+
) # [1, sum(image_lengths), D]
|
| 87 |
+
return context_input, image_input, image_lengths
|
| 88 |
+
|
| 89 |
+
|
| 90 |
+
@torch._dynamo.disable
|
| 91 |
+
def merge_ragged_context_image_tokens(
|
| 92 |
+
context_output,
|
| 93 |
+
image_output,
|
| 94 |
+
image_lengths,
|
| 95 |
+
context_len,
|
| 96 |
+
):
|
| 97 |
+
"""
|
| 98 |
+
Reassemble packed tokens in each image's context-then-image order.
|
| 99 |
+
|
| 100 |
+
Args
|
| 101 |
+
context_output (torch.Tensor):
|
| 102 |
+
Context tokens with shape [1, num_images * context_len, D].
|
| 103 |
+
image_output (torch.Tensor):
|
| 104 |
+
Image tokens with shape [1, sum(image_lengths), D].
|
| 105 |
+
image_lengths (List[int]):
|
| 106 |
+
Image token count per image, excluding context tokens.
|
| 107 |
+
context_len (int):
|
| 108 |
+
Context token count per image.
|
| 109 |
+
Returns
|
| 110 |
+
packed tokens [1, num_images * context_len + sum(image_lengths), D].
|
| 111 |
+
"""
|
| 112 |
+
context_segments = torch.split(
|
| 113 |
+
context_output,
|
| 114 |
+
[context_len] * len(image_lengths),
|
| 115 |
+
dim=1,
|
| 116 |
+
) # Each segment: [1, context_len, D].
|
| 117 |
+
image_segments = torch.split(image_output, image_lengths, dim=1) # Each segment: [1, image_lengths[i], D].
|
| 118 |
+
return torch.cat(
|
| 119 |
+
[
|
| 120 |
+
torch.cat([context_segment, image_segment], dim=1) # [1, context_len + image_lengths[i], D]
|
| 121 |
+
for context_segment, image_segment in zip(
|
| 122 |
+
context_segments,
|
| 123 |
+
image_segments,
|
| 124 |
+
)
|
| 125 |
+
],
|
| 126 |
+
dim=1,
|
| 127 |
+
) # [1, num_images * context_len + sum(image_lengths), D]
|
| 128 |
+
|
| 129 |
+
|
| 130 |
+
# RoPE
|
| 131 |
+
|
| 132 |
+
def rope(pos: torch.Tensor, dim: int, theta: int) -> torch.Tensor:
|
| 133 |
+
"""
|
| 134 |
+
Build 2x2 rotation matrices for one position axis.
|
| 135 |
+
|
| 136 |
+
Args
|
| 137 |
+
pos (torch.Tensor):
|
| 138 |
+
Positions with shape [B, L].
|
| 139 |
+
dim (int):
|
| 140 |
+
Even feature dimension for this axis.
|
| 141 |
+
theta (int):
|
| 142 |
+
Frequency base.
|
| 143 |
+
Returns
|
| 144 |
+
FP32 rotation matrices with shape [B, L, dim // 2, 2, 2].
|
| 145 |
+
"""
|
| 146 |
+
assert dim % 2 == 0, "The dimension must be even."
|
| 147 |
+
|
| 148 |
+
scale = torch.arange(0, dim, 2, dtype=torch.float64, device=pos.device) / dim
|
| 149 |
+
omega = 1.0 / (theta**scale)
|
| 150 |
+
|
| 151 |
+
batch_size = pos.shape[0]
|
| 152 |
+
out = torch.einsum("...n,d->...nd", pos, omega) # [B, L, dim // 2]
|
| 153 |
+
cos_out = torch.cos(out)
|
| 154 |
+
sin_out = torch.sin(out)
|
| 155 |
+
|
| 156 |
+
# Rotation matrix: [[cos, -sin], [sin, cos]].
|
| 157 |
+
stacked_out = torch.stack([cos_out, -sin_out, sin_out, cos_out], dim=-1)
|
| 158 |
+
out = stacked_out.view(batch_size, -1, dim // 2, 2, 2)
|
| 159 |
+
return out.float() # [B, L, dim // 2, 2, 2]
|
| 160 |
+
|
| 161 |
+
|
| 162 |
+
def apply_rope(xq, xk, freqs_cis):
|
| 163 |
+
"""
|
| 164 |
+
Rotate query and key channel pairs in FP32 (B=1 for packed tokens).
|
| 165 |
+
|
| 166 |
+
Args
|
| 167 |
+
xq (torch.Tensor):
|
| 168 |
+
Query with shape [B, L, heads, head_dim].
|
| 169 |
+
xk (torch.Tensor):
|
| 170 |
+
Key with shape [B, L, heads, head_dim].
|
| 171 |
+
freqs_cis (torch.Tensor):
|
| 172 |
+
Rotation matrices [B, L, 1, head_dim // 2, 2, 2], shared across heads.
|
| 173 |
+
Returns
|
| 174 |
+
rotated query and key with their original shapes and dtypes.
|
| 175 |
+
"""
|
| 176 |
+
xq_ = xq.float().reshape(*xq.shape[:-1], -1, 1, 2) # [B, L, heads, head_dim // 2, 1, 2]
|
| 177 |
+
xk_ = xk.float().reshape(*xk.shape[:-1], -1, 1, 2)
|
| 178 |
+
xq_out = freqs_cis[..., 0] * xq_[..., 0] + freqs_cis[..., 1] * xq_[..., 1] # [B, L, heads, head_dim // 2, 2]
|
| 179 |
+
xk_out = freqs_cis[..., 0] * xk_[..., 0] + freqs_cis[..., 1] * xk_[..., 1]
|
| 180 |
+
return xq_out.reshape(*xq.shape).type_as(xq), xk_out.reshape(*xk.shape).type_as(xk)
|
| 181 |
+
|
| 182 |
+
|
| 183 |
+
class EmbedND(nn.Module):
|
| 184 |
+
"""
|
| 185 |
+
Build RoPE matrices for each position axis and concatenate them.
|
| 186 |
+
|
| 187 |
+
Args
|
| 188 |
+
theta (int):
|
| 189 |
+
Frequency base.
|
| 190 |
+
axes_dim (List[int]):
|
| 191 |
+
Feature dimension per axis; height and width in this model.
|
| 192 |
+
"""
|
| 193 |
+
def __init__(self, theta: int, axes_dim: List[int]):
|
| 194 |
+
super().__init__()
|
| 195 |
+
self.theta = theta
|
| 196 |
+
self.axes_dim = axes_dim
|
| 197 |
+
|
| 198 |
+
def forward(self, ids: torch.Tensor) -> torch.Tensor:
|
| 199 |
+
"""
|
| 200 |
+
Embed each token's position axes as RoPE matrices.
|
| 201 |
+
|
| 202 |
+
Args
|
| 203 |
+
ids (torch.Tensor):
|
| 204 |
+
Positions [B, L, num_axes]; B=1 and num_axes=2 for image tokens.
|
| 205 |
+
Returns
|
| 206 |
+
torch.Tensor: FP32 matrices [B, L, 1, sum(axes_dim) // 2, 2, 2], shared across heads.
|
| 207 |
+
"""
|
| 208 |
+
n_axes = ids.shape[-1]
|
| 209 |
+
emb = torch.cat(
|
| 210 |
+
[rope(ids[..., i], self.axes_dim[i], self.theta) for i in range(n_axes)],
|
| 211 |
+
dim=-3,
|
| 212 |
+
) # [B, L, sum(axes_dim) // 2, 2, 2]
|
| 213 |
+
return emb.unsqueeze(2) # [B, L, 1, sum(axes_dim) // 2, 2, 2]
|
| 214 |
+
|
| 215 |
+
|
| 216 |
+
# Condition embedders
|
| 217 |
+
|
| 218 |
+
class TimestepEmbedder(nn.Module):
|
| 219 |
+
"""
|
| 220 |
+
Embeds global timestep t into a vector; stage-relative sigma
|
| 221 |
+
is used by the loss and solver, and is not passed to this embedder.
|
| 222 |
+
"""
|
| 223 |
+
def __init__(self, hidden_size, frequency_embedding_size=256):
|
| 224 |
+
super().__init__()
|
| 225 |
+
self.mlp = nn.Sequential(
|
| 226 |
+
nn.Linear(frequency_embedding_size, hidden_size, bias=True),
|
| 227 |
+
nn.SiLU(),
|
| 228 |
+
nn.Linear(hidden_size, hidden_size, bias=True),
|
| 229 |
+
)
|
| 230 |
+
self.frequency_embedding_size = frequency_embedding_size
|
| 231 |
+
|
| 232 |
+
@staticmethod
|
| 233 |
+
def timestep_embedding(
|
| 234 |
+
timesteps: torch.Tensor,
|
| 235 |
+
embedding_dim: int,
|
| 236 |
+
max_period: int = 10000,
|
| 237 |
+
):
|
| 238 |
+
"""
|
| 239 |
+
Create sinusoidal embeddings of global timesteps.
|
| 240 |
+
|
| 241 |
+
Args
|
| 242 |
+
timesteps (torch.Tensor):
|
| 243 |
+
a 1-D tensor of N global timestep values, one per image.
|
| 244 |
+
embedding_dim (int):
|
| 245 |
+
the dimension of the output.
|
| 246 |
+
max_period (int):
|
| 247 |
+
Sets the largest period in the frequency scale.
|
| 248 |
+
Returns
|
| 249 |
+
timestep embeddings with shape [N, embedding_dim].
|
| 250 |
+
"""
|
| 251 |
+
assert len(timesteps.shape) == 1, "Timesteps should be a 1D tensor."
|
| 252 |
+
|
| 253 |
+
half_dim = embedding_dim // 2
|
| 254 |
+
exponent = -math.log(max_period) * torch.arange(
|
| 255 |
+
start=0, end=half_dim, dtype=torch.float32, device=timesteps.device
|
| 256 |
+
) # [Dim // 2]
|
| 257 |
+
exponent = exponent / (half_dim - 1)
|
| 258 |
+
|
| 259 |
+
emb = torch.exp(exponent)
|
| 260 |
+
emb = timesteps[:, None].float() * emb[None, :] # [B, Dim // 2]
|
| 261 |
+
|
| 262 |
+
# Concatenate cosine and sine embeddings.
|
| 263 |
+
emb = torch.cat([torch.cos(emb), torch.sin(emb)], dim=-1) # [B, Dim]
|
| 264 |
+
|
| 265 |
+
# zero pad
|
| 266 |
+
if embedding_dim % 2 == 1:
|
| 267 |
+
emb = torch.nn.functional.pad(emb, (0, 1, 0, 0))
|
| 268 |
+
return emb
|
| 269 |
+
|
| 270 |
+
def forward(self, t):
|
| 271 |
+
t_freq = self.timestep_embedding(t, self.frequency_embedding_size)
|
| 272 |
+
t_freq = t_freq.to(dtype=self.mlp[0].weight.dtype)
|
| 273 |
+
t_emb = self.mlp(t_freq)
|
| 274 |
+
return t_emb
|
| 275 |
+
|
| 276 |
+
|
| 277 |
+
class LabelEmbedder(nn.Module):
|
| 278 |
+
"""
|
| 279 |
+
Embed class labels; index num_classes represents the unconditional label.
|
| 280 |
+
"""
|
| 281 |
+
def __init__(self, num_classes, hidden_size):
|
| 282 |
+
super().__init__()
|
| 283 |
+
self.embedding_table = nn.Embedding(num_classes + 1, hidden_size)
|
| 284 |
+
|
| 285 |
+
def forward(self, labels):
|
| 286 |
+
embeddings = self.embedding_table(labels)
|
| 287 |
+
return embeddings
|
| 288 |
+
|
| 289 |
+
|
| 290 |
+
# Attention
|
| 291 |
+
|
| 292 |
+
@torch._dynamo.disable
|
| 293 |
+
def build_flash_cu_seqlens(hidden_length, device):
|
| 294 |
+
"""
|
| 295 |
+
Build cumulative token offsets for FlashAttention in eager mode.
|
| 296 |
+
|
| 297 |
+
Args
|
| 298 |
+
hidden_length (List[int]):
|
| 299 |
+
Token count per image in packed order.
|
| 300 |
+
device (torch.device):
|
| 301 |
+
Device for the offsets tensor.
|
| 302 |
+
Returns
|
| 303 |
+
offsets with shape [num_images + 1], starting at zero.
|
| 304 |
+
"""
|
| 305 |
+
offsets = [0]
|
| 306 |
+
total = 0
|
| 307 |
+
for length in hidden_length:
|
| 308 |
+
length = int(length)
|
| 309 |
+
total += length
|
| 310 |
+
offsets.append(total)
|
| 311 |
+
return torch.tensor(offsets, device=device, dtype=torch.int32)
|
| 312 |
+
|
| 313 |
+
|
| 314 |
+
class VarlenFlashAttn(nn.Module):
|
| 315 |
+
@torch._dynamo.disable
|
| 316 |
+
def forward(self, query, key, value, cu_seqlens, max_seqlen, scale=None):
|
| 317 |
+
cu_seqlens = cu_seqlens.to(device=query.device, dtype=torch.int32).contiguous()
|
| 318 |
+
return flash_attn_varlen_func(
|
| 319 |
+
query,
|
| 320 |
+
key,
|
| 321 |
+
value,
|
| 322 |
+
cu_seqlens_q=cu_seqlens,
|
| 323 |
+
cu_seqlens_k=cu_seqlens,
|
| 324 |
+
max_seqlen_q=max_seqlen,
|
| 325 |
+
max_seqlen_k=max_seqlen,
|
| 326 |
+
dropout_p=0.0,
|
| 327 |
+
causal=False,
|
| 328 |
+
softmax_scale=scale,
|
| 329 |
+
)
|
| 330 |
+
|
| 331 |
+
|
| 332 |
+
class VarlenFlashSelfAttnSingle(nn.Module):
|
| 333 |
+
def __init__(self):
|
| 334 |
+
super().__init__()
|
| 335 |
+
self.flash_attn = VarlenFlashAttn()
|
| 336 |
+
|
| 337 |
+
def forward(
|
| 338 |
+
self,
|
| 339 |
+
query,
|
| 340 |
+
key,
|
| 341 |
+
value,
|
| 342 |
+
scale,
|
| 343 |
+
hidden_length=None,
|
| 344 |
+
image_rotary_emb=None,
|
| 345 |
+
):
|
| 346 |
+
if flash_attn_varlen_func is None:
|
| 347 |
+
raise ImportError("flash-attn is required when use_flash_attn=True")
|
| 348 |
+
|
| 349 |
+
# Stack Q/K/V and apply RoPE to Q/K only.
|
| 350 |
+
qkv = torch.stack([query, key, value], dim=2)
|
| 351 |
+
if image_rotary_emb is not None:
|
| 352 |
+
qkv[:, :, 0], qkv[:, :, 1] = apply_rope(
|
| 353 |
+
qkv[:, :, 0],
|
| 354 |
+
qkv[:, :, 1],
|
| 355 |
+
image_rotary_emb,
|
| 356 |
+
)
|
| 357 |
+
|
| 358 |
+
qkv = rearrange(qkv, "b s ... -> (b s) ...") # [total_tokens, 3, heads, head_dim]
|
| 359 |
+
query, key, value = qkv.unbind(1)
|
| 360 |
+
|
| 361 |
+
# Token offsets keep attention separate for each image in the packed sequence.
|
| 362 |
+
max_seqlen_q = max(hidden_length)
|
| 363 |
+
cu_seqlens_q = build_flash_cu_seqlens(
|
| 364 |
+
hidden_length,
|
| 365 |
+
query.device,
|
| 366 |
+
)
|
| 367 |
+
output = self.flash_attn(query, key, value, cu_seqlens_q, max_seqlen_q, scale)
|
| 368 |
+
return output.reshape(1, -1, output.shape[-2], output.shape[-1]).flatten(2, 3) # [1, total_tokens, heads * head_dim]
|
| 369 |
+
|
| 370 |
+
|
| 371 |
+
class VarlenSelfAttnSingle:
|
| 372 |
+
def __init__(self):
|
| 373 |
+
pass
|
| 374 |
+
|
| 375 |
+
def __call__(
|
| 376 |
+
self,
|
| 377 |
+
query,
|
| 378 |
+
key,
|
| 379 |
+
value,
|
| 380 |
+
hidden_length=None,
|
| 381 |
+
image_rotary_emb=None,
|
| 382 |
+
):
|
| 383 |
+
|
| 384 |
+
# Stack Q/K/V and apply RoPE to Q/K only.
|
| 385 |
+
qkv = torch.stack([query, key, value], dim=2) # [1, total_tokens, 3, heads, head_dim]
|
| 386 |
+
if image_rotary_emb is not None:
|
| 387 |
+
qkv[:, :, 0], qkv[:, :, 1] = apply_rope(
|
| 388 |
+
qkv[:, :, 0],
|
| 389 |
+
qkv[:, :, 1],
|
| 390 |
+
image_rotary_emb,
|
| 391 |
+
)
|
| 392 |
+
|
| 393 |
+
i_sum = 0
|
| 394 |
+
output_hidden_list = []
|
| 395 |
+
# Compute attention within each image's token segment.
|
| 396 |
+
for length in hidden_length:
|
| 397 |
+
qkv_tokens = qkv[:, i_sum:i_sum + length] # [1, length, 3, heads, head_dim]
|
| 398 |
+
|
| 399 |
+
query, key, value = qkv_tokens.unbind(2)
|
| 400 |
+
# SDPA expects Q/K/V in [1, heads, length, head_dim].
|
| 401 |
+
query = query.transpose(1, 2).contiguous()
|
| 402 |
+
key = key.transpose(1, 2).contiguous()
|
| 403 |
+
value = value.transpose(1, 2).contiguous()
|
| 404 |
+
|
| 405 |
+
stage_hidden_states = F.scaled_dot_product_attention(
|
| 406 |
+
query,
|
| 407 |
+
key,
|
| 408 |
+
value,
|
| 409 |
+
dropout_p=0.0,
|
| 410 |
+
is_causal=False,
|
| 411 |
+
)
|
| 412 |
+
stage_hidden_states = stage_hidden_states.transpose(1, 2).flatten(2, 3) # [1, length, heads * head_dim]
|
| 413 |
+
output_hidden_list.append(stage_hidden_states)
|
| 414 |
+
i_sum += length
|
| 415 |
+
|
| 416 |
+
return torch.cat(output_hidden_list, dim=1) # [1, total_tokens, heads * head_dim]
|
| 417 |
+
|
| 418 |
+
|
| 419 |
+
class FLUXAttention(nn.Module):
|
| 420 |
+
|
| 421 |
+
def __init__(
|
| 422 |
+
self,
|
| 423 |
+
query_dim: int,
|
| 424 |
+
heads: int = 8,
|
| 425 |
+
dim_head: int = 64,
|
| 426 |
+
dropout: float = 0.0,
|
| 427 |
+
eps: float = 1e-6,
|
| 428 |
+
use_flash_attn=False,
|
| 429 |
+
):
|
| 430 |
+
super().__init__()
|
| 431 |
+
self.heads = heads
|
| 432 |
+
self.scale = dim_head**-0.5
|
| 433 |
+
inner_dim = heads * dim_head
|
| 434 |
+
self.norm_q = RMSNorm(dim_head, eps=eps)
|
| 435 |
+
self.norm_k = RMSNorm(dim_head, eps=eps)
|
| 436 |
+
self.to_q = nn.Linear(query_dim, inner_dim, bias=False)
|
| 437 |
+
self.to_k = nn.Linear(query_dim, inner_dim, bias=False)
|
| 438 |
+
self.to_v = nn.Linear(query_dim, inner_dim, bias=False)
|
| 439 |
+
self.to_out = nn.ModuleList([
|
| 440 |
+
nn.Linear(inner_dim, query_dim, bias=False),
|
| 441 |
+
nn.Dropout(dropout),
|
| 442 |
+
])
|
| 443 |
+
self.use_flash_attn = use_flash_attn
|
| 444 |
+
if self.use_flash_attn:
|
| 445 |
+
self.varlen_flash_attn = VarlenFlashSelfAttnSingle()
|
| 446 |
+
else:
|
| 447 |
+
self.varlen_attn = VarlenSelfAttnSingle()
|
| 448 |
+
|
| 449 |
+
def forward(
|
| 450 |
+
self,
|
| 451 |
+
hidden_states: torch.Tensor,
|
| 452 |
+
hidden_length: List = None,
|
| 453 |
+
image_rotary_emb: Optional[torch.Tensor] = None,
|
| 454 |
+
) -> torch.Tensor:
|
| 455 |
+
|
| 456 |
+
# Project packed tokens to Q/K/V: [1, total_tokens, inner_dim].
|
| 457 |
+
query = self.to_q(hidden_states)
|
| 458 |
+
key = self.to_k(hidden_states)
|
| 459 |
+
value = self.to_v(hidden_states)
|
| 460 |
+
|
| 461 |
+
inner_dim = key.shape[-1]
|
| 462 |
+
head_dim = inner_dim // self.heads
|
| 463 |
+
|
| 464 |
+
# Split attention heads: [1, total_tokens, heads, head_dim].
|
| 465 |
+
query = query.view(query.shape[0], -1, self.heads, head_dim)
|
| 466 |
+
key = key.view(key.shape[0], -1, self.heads, head_dim)
|
| 467 |
+
value = value.view(value.shape[0], -1, self.heads, head_dim)
|
| 468 |
+
|
| 469 |
+
# Apply RMSNorm to each head's Q/K features.
|
| 470 |
+
query = self.norm_q(query)
|
| 471 |
+
key = self.norm_k(key)
|
| 472 |
+
|
| 473 |
+
# Compute per-image attention and merge heads: [1, total_tokens, inner_dim].
|
| 474 |
+
if self.use_flash_attn:
|
| 475 |
+
hidden_states = self.varlen_flash_attn(
|
| 476 |
+
query, key, value,
|
| 477 |
+
self.scale, hidden_length,
|
| 478 |
+
image_rotary_emb,
|
| 479 |
+
)
|
| 480 |
+
else:
|
| 481 |
+
hidden_states = self.varlen_attn(
|
| 482 |
+
query, key, value,
|
| 483 |
+
hidden_length,
|
| 484 |
+
image_rotary_emb,
|
| 485 |
+
)
|
| 486 |
+
|
| 487 |
+
# Project back to the input width, then apply dropout.
|
| 488 |
+
hidden_states = self.to_out[0](hidden_states)
|
| 489 |
+
hidden_states = self.to_out[1](hidden_states)
|
| 490 |
+
|
| 491 |
+
return hidden_states
|
| 492 |
+
|
| 493 |
+
|
| 494 |
+
# Model components
|
| 495 |
+
|
| 496 |
+
class BottleneckPatchEmbed(nn.Module):
|
| 497 |
+
"""
|
| 498 |
+
Image to Patch Embedding
|
| 499 |
+
"""
|
| 500 |
+
def __init__(self, img_size=224, patch_size=16, in_chans=3, pca_dim=768, embed_dim=768):
|
| 501 |
+
super().__init__()
|
| 502 |
+
img_size = (img_size, img_size)
|
| 503 |
+
patch_size = (patch_size, patch_size)
|
| 504 |
+
num_patches = (img_size[1] // patch_size[1]) * (img_size[0] // patch_size[0])
|
| 505 |
+
self.num_patches = num_patches
|
| 506 |
+
|
| 507 |
+
self.proj1 = nn.Conv2d(in_chans, pca_dim, kernel_size=patch_size, stride=patch_size, bias=False)
|
| 508 |
+
self.proj2 = nn.Conv2d(pca_dim, embed_dim, kernel_size=1, stride=1, bias=True)
|
| 509 |
+
|
| 510 |
+
def forward(self, x):
|
| 511 |
+
x = self.proj2(self.proj1(x)).flatten(2).transpose(1, 2)
|
| 512 |
+
return x
|
| 513 |
+
|
| 514 |
+
|
| 515 |
+
class SwiGLUFFN(nn.Module):
|
| 516 |
+
def __init__(
|
| 517 |
+
self,
|
| 518 |
+
dim: int,
|
| 519 |
+
hidden_dim: int,
|
| 520 |
+
drop=0.0,
|
| 521 |
+
) -> None:
|
| 522 |
+
super().__init__()
|
| 523 |
+
hidden_dim = int(hidden_dim * 2 / 3)
|
| 524 |
+
self.w12 = nn.Linear(dim, 2 * hidden_dim, bias=False)
|
| 525 |
+
self.w3 = nn.Linear(hidden_dim, dim, bias=False)
|
| 526 |
+
self.ffn_dropout = nn.Dropout(drop)
|
| 527 |
+
|
| 528 |
+
def forward(self, x):
|
| 529 |
+
x12 = self.w12(x)
|
| 530 |
+
x1, x2 = x12.chunk(2, dim=-1)
|
| 531 |
+
hidden = F.silu(x1) * x2
|
| 532 |
+
return self.w3(self.ffn_dropout(hidden))
|
| 533 |
+
|
| 534 |
+
|
| 535 |
+
class PixelMoEBlock(nn.Module):
|
| 536 |
+
def __init__(
|
| 537 |
+
self,
|
| 538 |
+
hidden_size,
|
| 539 |
+
num_heads,
|
| 540 |
+
flux_attn_proj_dropout=False,
|
| 541 |
+
use_flash_attn=False,
|
| 542 |
+
mlp_ratio=4.0,
|
| 543 |
+
proj_drop=0.0,
|
| 544 |
+
use_moe=False,
|
| 545 |
+
moe_num_experts=8,
|
| 546 |
+
moe_top_k=2,
|
| 547 |
+
moe_routed_inter_dim=None,
|
| 548 |
+
moe_shared_inter_dim=None,
|
| 549 |
+
moe_route_scale=1.0,
|
| 550 |
+
context_token_count=0,
|
| 551 |
+
moe_router_bias_update_rate=1e-3,
|
| 552 |
+
moe_num_layers=None,
|
| 553 |
+
):
|
| 554 |
+
super().__init__()
|
| 555 |
+
self.norm1 = RMSNorm(hidden_size, eps=1e-6)
|
| 556 |
+
attention_head_dim = hidden_size // num_heads
|
| 557 |
+
self.attn = FLUXAttention(
|
| 558 |
+
query_dim=hidden_size,
|
| 559 |
+
dim_head=attention_head_dim,
|
| 560 |
+
heads=num_heads,
|
| 561 |
+
eps=1e-6,
|
| 562 |
+
use_flash_attn=use_flash_attn,
|
| 563 |
+
dropout=proj_drop if flux_attn_proj_dropout else 0.0,
|
| 564 |
+
)
|
| 565 |
+
|
| 566 |
+
self.norm2 = RMSNorm(hidden_size, eps=1e-6)
|
| 567 |
+
mlp_hidden_dim = int(hidden_size * mlp_ratio)
|
| 568 |
+
if use_moe:
|
| 569 |
+
self.mlp = DeepSeekMoEFFN(
|
| 570 |
+
hidden_size,
|
| 571 |
+
num_experts=moe_num_experts,
|
| 572 |
+
top_k=moe_top_k,
|
| 573 |
+
routed_inter_dim=moe_routed_inter_dim,
|
| 574 |
+
shared_inter_dim=moe_shared_inter_dim,
|
| 575 |
+
route_scale=moe_route_scale,
|
| 576 |
+
router_bias_update_rate=moe_router_bias_update_rate,
|
| 577 |
+
drop=proj_drop,
|
| 578 |
+
num_layers=moe_num_layers,
|
| 579 |
+
)
|
| 580 |
+
else:
|
| 581 |
+
self.mlp = SwiGLUFFN(
|
| 582 |
+
hidden_size,
|
| 583 |
+
mlp_hidden_dim,
|
| 584 |
+
drop=proj_drop,
|
| 585 |
+
)
|
| 586 |
+
self.class_token_mlp = (
|
| 587 |
+
SwiGLUFFN(
|
| 588 |
+
hidden_size,
|
| 589 |
+
mlp_hidden_dim,
|
| 590 |
+
drop=proj_drop,
|
| 591 |
+
)
|
| 592 |
+
if use_moe and context_token_count > 0
|
| 593 |
+
else None
|
| 594 |
+
)
|
| 595 |
+
self.context_token_count = int(context_token_count)
|
| 596 |
+
self.adaLN_modulation = nn.Sequential(
|
| 597 |
+
nn.SiLU(),
|
| 598 |
+
nn.Linear(hidden_size, 6 * hidden_size, bias=True)
|
| 599 |
+
)
|
| 600 |
+
|
| 601 |
+
def _forward_mlp(
|
| 602 |
+
self,
|
| 603 |
+
mlp_input,
|
| 604 |
+
hidden_length=None,
|
| 605 |
+
):
|
| 606 |
+
# Use one FFN when no separate class-token FFN is needed.
|
| 607 |
+
if self.class_token_mlp is None:
|
| 608 |
+
return self.mlp(
|
| 609 |
+
mlp_input,
|
| 610 |
+
)
|
| 611 |
+
|
| 612 |
+
context_len = self.context_token_count
|
| 613 |
+
# Split packed context/image tokens.
|
| 614 |
+
lengths = [int(length) for length in hidden_length]
|
| 615 |
+
if any(length <= context_len for length in lengths):
|
| 616 |
+
raise ValueError(
|
| 617 |
+
"Every ragged segment must contain class-context and image tokens; "
|
| 618 |
+
f"got lengths={lengths}, context_token_count={context_len}"
|
| 619 |
+
)
|
| 620 |
+
context_input, image_input, image_lengths = split_ragged_context_image_tokens(
|
| 621 |
+
mlp_input,
|
| 622 |
+
lengths,
|
| 623 |
+
context_len,
|
| 624 |
+
)
|
| 625 |
+
|
| 626 |
+
context_output = self.class_token_mlp(context_input)
|
| 627 |
+
image_output = self.mlp(
|
| 628 |
+
image_input,
|
| 629 |
+
)
|
| 630 |
+
return merge_ragged_context_image_tokens(
|
| 631 |
+
context_output,
|
| 632 |
+
image_output,
|
| 633 |
+
image_lengths,
|
| 634 |
+
context_len,
|
| 635 |
+
)
|
| 636 |
+
|
| 637 |
+
def forward(
|
| 638 |
+
self,
|
| 639 |
+
x,
|
| 640 |
+
c,
|
| 641 |
+
hidden_length=None,
|
| 642 |
+
image_rotary_emb=None,
|
| 643 |
+
):
|
| 644 |
+
# Expand each image's AdaLN conditioning to its packed tokens.
|
| 645 |
+
embed = self.adaLN_modulation(c)
|
| 646 |
+
batch_emb = expand_segment_embeddings(embed, hidden_length, x.shape[0]) # [1, total_tokens, 6 * D]
|
| 647 |
+
shift_msa, scale_msa, gate_msa, shift_mlp, scale_mlp, gate_mlp = batch_emb.chunk(6, dim=-1)
|
| 648 |
+
|
| 649 |
+
# Attention with AdaLN modulation and a gated residual.
|
| 650 |
+
attn_input = modulate(self.norm1(x), shift_msa, scale_msa)
|
| 651 |
+
attn_output = self.attn(
|
| 652 |
+
hidden_states=attn_input,
|
| 653 |
+
hidden_length=hidden_length,
|
| 654 |
+
image_rotary_emb=image_rotary_emb,
|
| 655 |
+
)
|
| 656 |
+
x = x + gate_msa * attn_output
|
| 657 |
+
|
| 658 |
+
# FFN with AdaLN modulation and a gated residual.
|
| 659 |
+
mlp_input = modulate(self.norm2(x), shift_mlp, scale_mlp)
|
| 660 |
+
mlp_output = self._forward_mlp(
|
| 661 |
+
mlp_input,
|
| 662 |
+
hidden_length=hidden_length,
|
| 663 |
+
)
|
| 664 |
+
x = x + gate_mlp * mlp_output
|
| 665 |
+
return x
|
| 666 |
+
|
| 667 |
+
|
| 668 |
+
class PixelMoEDDTBlock(nn.Module):
|
| 669 |
+
"""
|
| 670 |
+
Wide DDT head block with token-wise AdaLN conditioning of shape [B, L, D].
|
| 671 |
+
"""
|
| 672 |
+
def __init__(
|
| 673 |
+
self,
|
| 674 |
+
hidden_size,
|
| 675 |
+
num_heads,
|
| 676 |
+
use_flash_attn=False,
|
| 677 |
+
mlp_ratio=4.0,
|
| 678 |
+
proj_drop=0.0,
|
| 679 |
+
):
|
| 680 |
+
super().__init__()
|
| 681 |
+
self.norm1 = RMSNorm(hidden_size, eps=1e-6)
|
| 682 |
+
attention_head_dim = hidden_size // num_heads
|
| 683 |
+
self.attn = FLUXAttention(
|
| 684 |
+
query_dim=hidden_size,
|
| 685 |
+
dim_head=attention_head_dim,
|
| 686 |
+
heads=num_heads,
|
| 687 |
+
eps=1e-6,
|
| 688 |
+
use_flash_attn=use_flash_attn,
|
| 689 |
+
)
|
| 690 |
+
|
| 691 |
+
self.norm2 = RMSNorm(hidden_size, eps=1e-6)
|
| 692 |
+
mlp_hidden_dim = int(hidden_size * mlp_ratio)
|
| 693 |
+
self.mlp = SwiGLUFFN(
|
| 694 |
+
hidden_size,
|
| 695 |
+
mlp_hidden_dim,
|
| 696 |
+
drop=proj_drop,
|
| 697 |
+
)
|
| 698 |
+
self.adaLN_modulation = nn.Sequential(
|
| 699 |
+
nn.SiLU(),
|
| 700 |
+
nn.Linear(hidden_size, 6 * hidden_size, bias=True)
|
| 701 |
+
)
|
| 702 |
+
|
| 703 |
+
def forward(
|
| 704 |
+
self,
|
| 705 |
+
x,
|
| 706 |
+
c,
|
| 707 |
+
hidden_length=None,
|
| 708 |
+
image_rotary_emb=None,
|
| 709 |
+
):
|
| 710 |
+
# Per-token AdaLN shift, scale, and gates; c: [1, total_tokens, D].
|
| 711 |
+
embed = self.adaLN_modulation(c)
|
| 712 |
+
shift_msa, scale_msa, gate_msa, shift_mlp, scale_mlp, gate_mlp = embed.chunk(6, dim=-1)
|
| 713 |
+
|
| 714 |
+
# Attention with AdaLN modulation and a gated residual.
|
| 715 |
+
attn_input = modulate(self.norm1(x), shift_msa, scale_msa)
|
| 716 |
+
attn_output = self.attn(
|
| 717 |
+
hidden_states=attn_input,
|
| 718 |
+
hidden_length=hidden_length,
|
| 719 |
+
image_rotary_emb=image_rotary_emb,
|
| 720 |
+
)
|
| 721 |
+
x = x + attn_output * gate_msa
|
| 722 |
+
|
| 723 |
+
# Dense SwiGLU FFN with AdaLN modulation and a gated residual.
|
| 724 |
+
mlp_output = self.mlp(modulate(self.norm2(x), shift_mlp, scale_mlp))
|
| 725 |
+
x = x + mlp_output * gate_mlp
|
| 726 |
+
return x
|
| 727 |
+
|
| 728 |
+
|
| 729 |
+
class DDTFinalLayer(nn.Module):
|
| 730 |
+
"""
|
| 731 |
+
Token-wise conditioned final layer used by PixelMoE.
|
| 732 |
+
"""
|
| 733 |
+
def __init__(self, hidden_size, patch_size, out_channels):
|
| 734 |
+
super().__init__()
|
| 735 |
+
self.norm_final = RMSNorm(hidden_size)
|
| 736 |
+
self.linear = nn.Linear(hidden_size, patch_size * patch_size * out_channels, bias=True)
|
| 737 |
+
self.adaLN_modulation = nn.Sequential(
|
| 738 |
+
nn.SiLU(),
|
| 739 |
+
nn.Linear(hidden_size, 2 * hidden_size, bias=True)
|
| 740 |
+
)
|
| 741 |
+
|
| 742 |
+
def forward(self, x, c):
|
| 743 |
+
# Apply per-token AdaLN conditioning before the output projection.
|
| 744 |
+
embed = self.adaLN_modulation(c)
|
| 745 |
+
shift, scale = embed.chunk(2, dim=-1)
|
| 746 |
+
x = modulate(self.norm_final(x), shift, scale)
|
| 747 |
+
x = self.linear(x) # [1, total_tokens, patch_size * patch_size * out_channels]
|
| 748 |
+
return x
|
| 749 |
+
|
| 750 |
+
|
| 751 |
+
# PixelMoE
|
| 752 |
+
|
| 753 |
+
class PixelMoE(nn.Module):
|
| 754 |
+
"""PixelMoE body with a wide DDT head and FLUX variable-length attention."""
|
| 755 |
+
def __init__(
|
| 756 |
+
self,
|
| 757 |
+
input_size=256,
|
| 758 |
+
patch_size=16,
|
| 759 |
+
in_channels=3,
|
| 760 |
+
hidden_size=1024,
|
| 761 |
+
depth=24,
|
| 762 |
+
num_heads=16,
|
| 763 |
+
mlp_ratio=4.0,
|
| 764 |
+
proj_drop=0.0,
|
| 765 |
+
num_classes=1000,
|
| 766 |
+
bottleneck_dim=128,
|
| 767 |
+
in_context_len=32,
|
| 768 |
+
in_context_start=8,
|
| 769 |
+
flux_attn_proj_dropout=False,
|
| 770 |
+
use_flash_attn=False,
|
| 771 |
+
ddt_head_dim=2048,
|
| 772 |
+
ddt_head_depth=2,
|
| 773 |
+
ddt_head_num_heads=16,
|
| 774 |
+
moe_num_experts=8,
|
| 775 |
+
moe_top_k=2,
|
| 776 |
+
moe_routed_inter_dim=None,
|
| 777 |
+
moe_shared_inter_dim=None,
|
| 778 |
+
moe_route_scale=1.0,
|
| 779 |
+
moe_n_dense_layers=1,
|
| 780 |
+
moe_router_bias_update_rate=1e-3,
|
| 781 |
+
):
|
| 782 |
+
super().__init__()
|
| 783 |
+
self.in_channels = in_channels
|
| 784 |
+
self.out_channels = in_channels
|
| 785 |
+
self.patch_size = patch_size
|
| 786 |
+
self.hidden_size = hidden_size
|
| 787 |
+
self.input_size = input_size
|
| 788 |
+
self.depth = depth
|
| 789 |
+
self.in_context_len = in_context_len
|
| 790 |
+
self.in_context_start = in_context_start
|
| 791 |
+
self.use_flash_attn = use_flash_attn
|
| 792 |
+
# Base initialization std is 0.02; residual outputs use body-depth scaling.
|
| 793 |
+
self.megatron_init_std = 0.02
|
| 794 |
+
self.megatron_init_num_layers = depth
|
| 795 |
+
|
| 796 |
+
# time and class embed
|
| 797 |
+
self.t_embedder = TimestepEmbedder(hidden_size)
|
| 798 |
+
self.y_embedder = LabelEmbedder(num_classes, hidden_size)
|
| 799 |
+
|
| 800 |
+
# linear embed
|
| 801 |
+
self.x_embedder = BottleneckPatchEmbed(input_size, patch_size, in_channels, bottleneck_dim, hidden_size)
|
| 802 |
+
|
| 803 |
+
# use fixed sin-cos embedding
|
| 804 |
+
num_patches = self.x_embedder.num_patches
|
| 805 |
+
self.pos_embed = nn.Parameter(torch.zeros(1, num_patches, hidden_size), requires_grad=False)
|
| 806 |
+
|
| 807 |
+
# in-context cls token
|
| 808 |
+
self.in_context_posemb = nn.Parameter(torch.zeros(1, self.in_context_len, hidden_size), requires_grad=True)
|
| 809 |
+
torch.nn.init.normal_(self.in_context_posemb, std=.02)
|
| 810 |
+
|
| 811 |
+
self.hierarchical_pos_embed_cache = {}
|
| 812 |
+
|
| 813 |
+
attention_head_dim = hidden_size // num_heads
|
| 814 |
+
self.hierarchical_rope_embed = EmbedND(
|
| 815 |
+
theta=10000,
|
| 816 |
+
axes_dim=[attention_head_dim // 2, attention_head_dim // 2],
|
| 817 |
+
)
|
| 818 |
+
|
| 819 |
+
# transformer
|
| 820 |
+
self.blocks = nn.ModuleList([
|
| 821 |
+
PixelMoEBlock(hidden_size, num_heads, mlp_ratio=mlp_ratio, use_flash_attn=use_flash_attn,
|
| 822 |
+
flux_attn_proj_dropout=flux_attn_proj_dropout,
|
| 823 |
+
proj_drop=proj_drop if (depth // 4 * 3 > i >= depth // 4) else 0.0,
|
| 824 |
+
use_moe=i >= moe_n_dense_layers,
|
| 825 |
+
context_token_count=in_context_len if i >= in_context_start else 0,
|
| 826 |
+
moe_num_experts=moe_num_experts,
|
| 827 |
+
moe_top_k=moe_top_k,
|
| 828 |
+
moe_routed_inter_dim=moe_routed_inter_dim,
|
| 829 |
+
moe_shared_inter_dim=moe_shared_inter_dim,
|
| 830 |
+
moe_route_scale=moe_route_scale,
|
| 831 |
+
moe_router_bias_update_rate=moe_router_bias_update_rate,
|
| 832 |
+
moe_num_layers=self.megatron_init_num_layers,
|
| 833 |
+
)
|
| 834 |
+
for i in range(depth)
|
| 835 |
+
])
|
| 836 |
+
|
| 837 |
+
self.initialize_weights()
|
| 838 |
+
|
| 839 |
+
self.ddt_x_embedder = BottleneckPatchEmbed(
|
| 840 |
+
self.input_size,
|
| 841 |
+
self.patch_size,
|
| 842 |
+
self.in_channels,
|
| 843 |
+
ddt_head_dim,
|
| 844 |
+
ddt_head_dim,
|
| 845 |
+
)
|
| 846 |
+
self.ddt_s_projector = nn.Linear(self.hidden_size, ddt_head_dim)
|
| 847 |
+
self.ddt_blocks = nn.ModuleList([
|
| 848 |
+
PixelMoEDDTBlock(
|
| 849 |
+
ddt_head_dim,
|
| 850 |
+
ddt_head_num_heads,
|
| 851 |
+
mlp_ratio=4.0,
|
| 852 |
+
use_flash_attn=self.use_flash_attn,
|
| 853 |
+
)
|
| 854 |
+
for _ in range(ddt_head_depth)
|
| 855 |
+
])
|
| 856 |
+
self.ddt_final_layer = DDTFinalLayer(ddt_head_dim, self.patch_size, self.out_channels)
|
| 857 |
+
|
| 858 |
+
attention_head_dim = ddt_head_dim // ddt_head_num_heads
|
| 859 |
+
self.ddt_hierarchical_rope_embed = EmbedND(
|
| 860 |
+
theta=10000,
|
| 861 |
+
axes_dim=[attention_head_dim // 2, attention_head_dim // 2],
|
| 862 |
+
)
|
| 863 |
+
self.initialize_ddt_weights()
|
| 864 |
+
|
| 865 |
+
def initialize_weights(self):
|
| 866 |
+
self._initialize_megatron_linear_modules(self)
|
| 867 |
+
|
| 868 |
+
# Initialize (and freeze) pos_embed using sinusoidal embeddings:
|
| 869 |
+
pos_embed = get_2d_sincos_pos_embed(self.pos_embed.shape[-1], int(self.x_embedder.num_patches ** 0.5))
|
| 870 |
+
self.pos_embed.data.copy_(torch.from_numpy(pos_embed).float().unsqueeze(0))
|
| 871 |
+
|
| 872 |
+
self._initialize_megatron_patch_embed(self.x_embedder)
|
| 873 |
+
nn.init.normal_(self.y_embedder.embedding_table.weight, mean=0.0, std=self.megatron_init_std)
|
| 874 |
+
nn.init.normal_(self.in_context_posemb, mean=0.0, std=self.megatron_init_std)
|
| 875 |
+
self._initialize_megatron_output_projections(self)
|
| 876 |
+
|
| 877 |
+
# Zero-out adaLN modulation layers:
|
| 878 |
+
for block in self.blocks:
|
| 879 |
+
nn.init.constant_(block.adaLN_modulation[-1].weight, 0)
|
| 880 |
+
nn.init.constant_(block.adaLN_modulation[-1].bias, 0)
|
| 881 |
+
|
| 882 |
+
def initialize_ddt_weights(self):
|
| 883 |
+
self._initialize_megatron_linear_modules(self.ddt_s_projector)
|
| 884 |
+
self._initialize_megatron_linear_modules(self.ddt_blocks)
|
| 885 |
+
self._initialize_megatron_patch_embed(self.ddt_x_embedder)
|
| 886 |
+
self._initialize_megatron_output_projections(self.ddt_blocks)
|
| 887 |
+
|
| 888 |
+
for block in self.ddt_blocks:
|
| 889 |
+
nn.init.constant_(block.adaLN_modulation[-1].weight, 0)
|
| 890 |
+
nn.init.constant_(block.adaLN_modulation[-1].bias, 0)
|
| 891 |
+
|
| 892 |
+
nn.init.constant_(self.ddt_final_layer.adaLN_modulation[-1].weight, 0)
|
| 893 |
+
nn.init.constant_(self.ddt_final_layer.adaLN_modulation[-1].bias, 0)
|
| 894 |
+
nn.init.constant_(self.ddt_final_layer.linear.weight, 0)
|
| 895 |
+
nn.init.constant_(self.ddt_final_layer.linear.bias, 0)
|
| 896 |
+
|
| 897 |
+
@property
|
| 898 |
+
def megatron_output_init_std(self):
|
| 899 |
+
return self.megatron_init_std / math.sqrt(2.0 * self.megatron_init_num_layers)
|
| 900 |
+
|
| 901 |
+
def _initialize_megatron_linear_modules(self, root):
|
| 902 |
+
"""
|
| 903 |
+
Initialize ordinary projections with the Megatron base std.
|
| 904 |
+
"""
|
| 905 |
+
def _init_linear(module):
|
| 906 |
+
if isinstance(module, nn.Linear):
|
| 907 |
+
nn.init.normal_(module.weight, mean=0.0, std=self.megatron_init_std)
|
| 908 |
+
if module.bias is not None:
|
| 909 |
+
nn.init.constant_(module.bias, 0)
|
| 910 |
+
elif isinstance(module, DeepSeekMoEGate):
|
| 911 |
+
nn.init.normal_(module.weight, mean=0.0, std=self.megatron_init_std)
|
| 912 |
+
|
| 913 |
+
root.apply(_init_linear)
|
| 914 |
+
|
| 915 |
+
def _initialize_megatron_patch_embed(self, patch_embed):
|
| 916 |
+
for projection in (patch_embed.proj1, patch_embed.proj2):
|
| 917 |
+
nn.init.normal_(projection.weight, mean=0.0, std=self.megatron_init_std)
|
| 918 |
+
if projection.bias is not None:
|
| 919 |
+
nn.init.constant_(projection.bias, 0)
|
| 920 |
+
|
| 921 |
+
def _initialize_megatron_output_projections(self, root):
|
| 922 |
+
"""
|
| 923 |
+
Initialize attention and FFN output projections with depth-scaled std.
|
| 924 |
+
"""
|
| 925 |
+
for module in root.modules():
|
| 926 |
+
if isinstance(module, FLUXAttention):
|
| 927 |
+
output_projection = module.to_out[0]
|
| 928 |
+
nn.init.normal_(output_projection.weight, mean=0.0, std=self.megatron_output_init_std)
|
| 929 |
+
elif isinstance(module, SwiGLUFFN):
|
| 930 |
+
nn.init.normal_(module.w3.weight, mean=0.0, std=self.megatron_output_init_std)
|
| 931 |
+
elif isinstance(module, DeepSeekSharedExpert):
|
| 932 |
+
nn.init.normal_(module.w2.weight, mean=0.0, std=self.megatron_output_init_std)
|
| 933 |
+
|
| 934 |
+
def get_hierarchical_pos_embed(self, num_patches, device=None, dtype=None):
|
| 935 |
+
"""
|
| 936 |
+
Get cached sin-cos position embeddings for a square patch grid.
|
| 937 |
+
|
| 938 |
+
Args
|
| 939 |
+
num_patches (int):
|
| 940 |
+
Number of image patch tokens.
|
| 941 |
+
device (torch.device, optional):
|
| 942 |
+
Device for the returned embeddings.
|
| 943 |
+
dtype (torch.dtype, optional):
|
| 944 |
+
Dtype for the returned embeddings.
|
| 945 |
+
Returns
|
| 946 |
+
torch.Tensor: position embeddings [1, num_patches, hidden_size].
|
| 947 |
+
"""
|
| 948 |
+
key = str(num_patches)
|
| 949 |
+
if key not in self.hierarchical_pos_embed_cache:
|
| 950 |
+
grid_size = int(num_patches ** 0.5)
|
| 951 |
+
if grid_size * grid_size != num_patches:
|
| 952 |
+
raise ValueError(f"Expected square patch grid, got {num_patches} patches")
|
| 953 |
+
|
| 954 |
+
pos_embed = get_2d_sincos_pos_embed(self.hidden_size, grid_size)
|
| 955 |
+
self.hierarchical_pos_embed_cache[key] = torch.from_numpy(pos_embed).float().unsqueeze(0)
|
| 956 |
+
|
| 957 |
+
return self.hierarchical_pos_embed_cache[key].to(device=device, dtype=dtype)
|
| 958 |
+
|
| 959 |
+
@torch.no_grad()
|
| 960 |
+
def _prepare_image_ids(self, height, width, device):
|
| 961 |
+
"""
|
| 962 |
+
Build height/width coordinates for image-token RoPE.
|
| 963 |
+
|
| 964 |
+
Args
|
| 965 |
+
height (int):
|
| 966 |
+
Patch grid height.
|
| 967 |
+
width (int):
|
| 968 |
+
Patch grid width.
|
| 969 |
+
device (torch.device):
|
| 970 |
+
Device for the coordinates.
|
| 971 |
+
Returns
|
| 972 |
+
coordinates [1, height * width, 2], in row-major order.
|
| 973 |
+
"""
|
| 974 |
+
height_pos = torch.arange(height, device=device, dtype=torch.float32)
|
| 975 |
+
width_pos = torch.arange(width, device=device, dtype=torch.float32)
|
| 976 |
+
image_ids = torch.stack(torch.meshgrid(height_pos, width_pos, indexing="ij"), dim=-1)
|
| 977 |
+
|
| 978 |
+
return rearrange(image_ids.unsqueeze(0), "b h w c -> b (h w) c")
|
| 979 |
+
|
| 980 |
+
|
| 981 |
+
def _ddt_condition(self, body_tokens, t_emb, hidden_length):
|
| 982 |
+
"""
|
| 983 |
+
Combine body features and timestep embeddings to condition the DDT head.
|
| 984 |
+
|
| 985 |
+
Args
|
| 986 |
+
body_tokens (torch.Tensor):
|
| 987 |
+
Packed body features [1, total_tokens, hidden_size].
|
| 988 |
+
t_emb (torch.Tensor):
|
| 989 |
+
Timestep embeddings [num_images, hidden_size].
|
| 990 |
+
hidden_length (List[int]):
|
| 991 |
+
Image token count per image, excluding context tokens.
|
| 992 |
+
Returns
|
| 993 |
+
torch.Tensor: DDT conditioning [1, total_tokens, ddt_head_dim].
|
| 994 |
+
"""
|
| 995 |
+
t_tokens = expand_segment_embeddings(t_emb, hidden_length, body_tokens.shape[0])
|
| 996 |
+
condition = F.silu(body_tokens + t_tokens)
|
| 997 |
+
return self.ddt_s_projector(condition)
|
| 998 |
+
|
| 999 |
+
def forward(self, sample, timesteps, labels):
|
| 1000 |
+
"""
|
| 1001 |
+
Predict stage endpoints conditioned on global timesteps t.
|
| 1002 |
+
|
| 1003 |
+
Args
|
| 1004 |
+
sample (torch.Tensor or List[torch.Tensor]):
|
| 1005 |
+
Images [stage_batch_size, C, H, W] for each active stage.
|
| 1006 |
+
timesteps (torch.Tensor or List[torch.Tensor]):
|
| 1007 |
+
Global timesteps [stage_batch_size] for each active stage.
|
| 1008 |
+
labels (torch.Tensor):
|
| 1009 |
+
Class labels [num_images], concatenated in stage order.
|
| 1010 |
+
Returns
|
| 1011 |
+
List[torch.Tensor]: endpoint predictions [stage_batch_size, C, H, W] per active stage.
|
| 1012 |
+
"""
|
| 1013 |
+
samples = sample if isinstance(sample, list) else [sample]
|
| 1014 |
+
timestep_list = timesteps if isinstance(timesteps, list) else [timesteps]
|
| 1015 |
+
if len(samples) != len(timestep_list):
|
| 1016 |
+
raise ValueError("sample and timesteps must have the same number of active stages")
|
| 1017 |
+
|
| 1018 |
+
stage_tokens = []
|
| 1019 |
+
stage_ddt_tokens = []
|
| 1020 |
+
stage_batch_sizes = []
|
| 1021 |
+
stage_token_counts = []
|
| 1022 |
+
hidden_length = []
|
| 1023 |
+
output_shapes = []
|
| 1024 |
+
image_rotary_emb = []
|
| 1025 |
+
concat_image_rotary_emb = []
|
| 1026 |
+
ddt_image_rotary_emb = []
|
| 1027 |
+
|
| 1028 |
+
for stage_sample in samples:
|
| 1029 |
+
batch_size, _, image_height, image_width = stage_sample.shape # [B_stage, C, H, W]
|
| 1030 |
+
token_height = image_height // self.patch_size # Patch grid height.
|
| 1031 |
+
token_width = image_width // self.patch_size # Patch grid width.
|
| 1032 |
+
# Embed image patches separately for the body and DDT head.
|
| 1033 |
+
tokens = self.x_embedder(stage_sample) # [B_stage, num_patches, hidden_size]
|
| 1034 |
+
tokens = tokens + self.get_hierarchical_pos_embed(tokens.shape[1], tokens.device, tokens.dtype) # Add [1, num_patches, hidden_size].
|
| 1035 |
+
ddt_tokens = self.ddt_x_embedder(stage_sample) # [B_stage, num_patches, ddt_head_dim]
|
| 1036 |
+
token_count = tokens.shape[1] # num_patches = token_height * token_width
|
| 1037 |
+
|
| 1038 |
+
# Image tokens use grid coordinates; class context tokens use (0, 0).
|
| 1039 |
+
base_ids = self._prepare_image_ids(token_height, token_width, tokens.device) # [1, num_patches, 2]
|
| 1040 |
+
label_ids = torch.zeros(
|
| 1041 |
+
1,
|
| 1042 |
+
self.in_context_len,
|
| 1043 |
+
base_ids.shape[-1],
|
| 1044 |
+
device=tokens.device,
|
| 1045 |
+
dtype=base_ids.dtype,
|
| 1046 |
+
) # [1, in_context_len, 2]
|
| 1047 |
+
context_ids = torch.cat([label_ids, base_ids], dim=1) # [1, in_context_len + num_patches, 2]
|
| 1048 |
+
|
| 1049 |
+
# Pack all images into batch_size=1; hidden_length retains image boundaries.
|
| 1050 |
+
stage_tokens.append(rearrange(tokens, "b s d -> 1 (b s) d"))
|
| 1051 |
+
stage_ddt_tokens.append(rearrange(ddt_tokens, "b s d -> 1 (b s) d"))
|
| 1052 |
+
stage_batch_sizes.append(batch_size)
|
| 1053 |
+
stage_token_counts.append(token_count)
|
| 1054 |
+
hidden_length.extend([token_count] * batch_size)
|
| 1055 |
+
output_shapes.append((token_height, token_width))
|
| 1056 |
+
# Images in the same stage share the same positional grid.
|
| 1057 |
+
image_rotary_emb.extend([self.hierarchical_rope_embed(base_ids)] * batch_size)
|
| 1058 |
+
concat_image_rotary_emb.extend([self.hierarchical_rope_embed(context_ids)] * batch_size)
|
| 1059 |
+
ddt_image_rotary_emb.extend([self.ddt_hierarchical_rope_embed(base_ids)] * batch_size)
|
| 1060 |
+
|
| 1061 |
+
# Concatenate stages in the same order for tokens, RoPE, and conditioning.
|
| 1062 |
+
image_rotary_emb = torch.cat(image_rotary_emb, dim=1) # [1, total_tokens, 1, body_head_dim // 2, 2, 2]
|
| 1063 |
+
concat_image_rotary_emb = torch.cat(concat_image_rotary_emb, dim=1) # Image + context tokens, in per-image order.
|
| 1064 |
+
ddt_image_rotary_emb = torch.cat(ddt_image_rotary_emb, dim=1) # [1, total_tokens, 1, ddt_head_dim_per_head // 2, 2, 2]
|
| 1065 |
+
hidden_states = torch.cat(stage_tokens, dim=1) # [1, total_tokens, hidden_size]
|
| 1066 |
+
ddt_hidden_states = torch.cat(stage_ddt_tokens, dim=1) # [1, total_tokens, ddt_head_dim]
|
| 1067 |
+
timesteps = torch.cat(timestep_list, dim=0) # [num_images], in stage order.
|
| 1068 |
+
if labels.shape[0] != timesteps.shape[0]:
|
| 1069 |
+
raise ValueError("labels must be concatenated in the same stage order as sample")
|
| 1070 |
+
t_emb = self.t_embedder(timesteps.reshape(-1)) # [num_images, hidden_size]
|
| 1071 |
+
y_emb = self.y_embedder(labels) # [num_images, hidden_size]
|
| 1072 |
+
c = t_emb + y_emb # [num_images, hidden_size]
|
| 1073 |
+
|
| 1074 |
+
has_label_context = False # Context tokens are inserted at in_context_start.
|
| 1075 |
+
image_hidden_length = list(hidden_length) # Keep image-only lengths for removing context later.
|
| 1076 |
+
|
| 1077 |
+
def add_label_context(states):
|
| 1078 |
+
# Prepend class embeddings plus learned context positions to each image.
|
| 1079 |
+
stage_segments = []
|
| 1080 |
+
token_offset = 0 # Start of the current stage in packed image tokens.
|
| 1081 |
+
label_offset = 0 # Start of the current stage in y_emb.
|
| 1082 |
+
next_hidden_length = [] # Token count per image, including context.
|
| 1083 |
+
|
| 1084 |
+
for batch_size, token_count in zip(stage_batch_sizes, stage_token_counts):
|
| 1085 |
+
stage_state = states[:, token_offset:token_offset + batch_size * token_count] # [1, B_stage * token_count, hidden_size]
|
| 1086 |
+
stage_state = stage_state.reshape(batch_size, token_count, -1) # [B_stage, token_count, hidden_size]
|
| 1087 |
+
context = y_emb[label_offset:label_offset + batch_size].unsqueeze(1).repeat(1, self.in_context_len, 1) # [B_stage, in_context_len, hidden_size]
|
| 1088 |
+
context = context + self.in_context_posemb # Add learned positions [1, in_context_len, hidden_size].
|
| 1089 |
+
stage_state = torch.cat([context, stage_state], dim=1) # [B_stage, in_context_len + token_count, hidden_size]
|
| 1090 |
+
stage_segments.append(rearrange(stage_state, "b s d -> 1 (b s) d")) # Pack [context_1, image_1, context_2, image_2, ...].
|
| 1091 |
+
next_hidden_length.extend([token_count + self.in_context_len] * batch_size)
|
| 1092 |
+
token_offset += batch_size * token_count
|
| 1093 |
+
label_offset += batch_size
|
| 1094 |
+
|
| 1095 |
+
return torch.cat(stage_segments, dim=1), next_hidden_length # [1, total_tokens + num_images * in_context_len, hidden_size], lengths.
|
| 1096 |
+
|
| 1097 |
+
def strip_label_context(states):
|
| 1098 |
+
# Remove each image's context prefix and restore image-only token lengths.
|
| 1099 |
+
stage_segments = []
|
| 1100 |
+
token_offset = 0 # Start of the current stage in packed context + image tokens.
|
| 1101 |
+
|
| 1102 |
+
for batch_size, token_count in zip(stage_batch_sizes, stage_token_counts):
|
| 1103 |
+
full_count = token_count + self.in_context_len # Context + image token count per image.
|
| 1104 |
+
stage_state = states[:, token_offset:token_offset + batch_size * full_count] # [1, B_stage * full_count, hidden_size]
|
| 1105 |
+
stage_state = stage_state.reshape(batch_size, full_count, -1) # [B_stage, full_count, hidden_size]
|
| 1106 |
+
stage_state = stage_state[:, self.in_context_len:] # [B_stage, token_count, hidden_size]
|
| 1107 |
+
stage_segments.append(rearrange(stage_state, "b s d -> 1 (b s) d")) # [1, B_stage * token_count, hidden_size]
|
| 1108 |
+
token_offset += batch_size * full_count
|
| 1109 |
+
|
| 1110 |
+
return torch.cat(stage_segments, dim=1), list(image_hidden_length) # [1, total_tokens, hidden_size], image-only lengths.
|
| 1111 |
+
|
| 1112 |
+
# Insert class context at in_context_start, then process the remaining body blocks.
|
| 1113 |
+
for i, block in enumerate(self.blocks):
|
| 1114 |
+
if i == self.in_context_start:
|
| 1115 |
+
hidden_states, hidden_length = add_label_context(hidden_states)
|
| 1116 |
+
has_label_context = True
|
| 1117 |
+
|
| 1118 |
+
hidden_states = block(
|
| 1119 |
+
hidden_states,
|
| 1120 |
+
c,
|
| 1121 |
+
hidden_length=hidden_length,
|
| 1122 |
+
image_rotary_emb=concat_image_rotary_emb if has_label_context else image_rotary_emb,
|
| 1123 |
+
)
|
| 1124 |
+
|
| 1125 |
+
# The DDT head receives image tokens only.
|
| 1126 |
+
hidden_states, hidden_length = strip_label_context(hidden_states)
|
| 1127 |
+
|
| 1128 |
+
ddt_condition = self._ddt_condition(hidden_states, t_emb, hidden_length)
|
| 1129 |
+
for block in self.ddt_blocks:
|
| 1130 |
+
ddt_hidden_states = block(
|
| 1131 |
+
ddt_hidden_states,
|
| 1132 |
+
ddt_condition,
|
| 1133 |
+
hidden_length=hidden_length,
|
| 1134 |
+
image_rotary_emb=ddt_image_rotary_emb,
|
| 1135 |
+
)
|
| 1136 |
+
|
| 1137 |
+
pred = self.ddt_final_layer(ddt_hidden_states, ddt_condition)
|
| 1138 |
+
|
| 1139 |
+
# Unpack each stage and reshape predicted patch pixels back to images.
|
| 1140 |
+
outputs = []
|
| 1141 |
+
token_offset = 0
|
| 1142 |
+
for batch_size, token_count, (height, width) in zip(stage_batch_sizes, stage_token_counts, output_shapes):
|
| 1143 |
+
stage_pred = pred[:, token_offset:token_offset + batch_size * token_count]
|
| 1144 |
+
stage_pred = stage_pred.reshape(
|
| 1145 |
+
shape=(batch_size, height, width, self.patch_size, self.patch_size, -1)
|
| 1146 |
+
)
|
| 1147 |
+
stage_pred = rearrange(stage_pred, "b h w p1 p2 c -> b (h p1) (w p2) c")
|
| 1148 |
+
outputs.append(rearrange(stage_pred, "b h w c -> b c h w"))
|
| 1149 |
+
token_offset += batch_size * token_count
|
| 1150 |
+
return outputs
|
| 1151 |
+
|
| 1152 |
+
|
| 1153 |
+
# Model variants
|
| 1154 |
+
|
| 1155 |
+
def PixelMoE_B_E32A4_8(**kwargs):
|
| 1156 |
+
return PixelMoE(depth=12, hidden_size=768, num_heads=12,
|
| 1157 |
+
bottleneck_dim=128, in_context_len=32, in_context_start=4,
|
| 1158 |
+
patch_size=8,
|
| 1159 |
+
ddt_head_dim=1536, ddt_head_depth=2, ddt_head_num_heads=12,
|
| 1160 |
+
moe_num_experts=32, moe_top_k=4,
|
| 1161 |
+
moe_routed_inter_dim=384, moe_shared_inter_dim=632,
|
| 1162 |
+
moe_route_scale=2.0, moe_n_dense_layers=4, **kwargs)
|
| 1163 |
+
|
| 1164 |
+
|
| 1165 |
+
def PixelMoE_B_E32A4_16(**kwargs):
|
| 1166 |
+
return PixelMoE(depth=12, hidden_size=768, num_heads=12,
|
| 1167 |
+
bottleneck_dim=128, in_context_len=32, in_context_start=4,
|
| 1168 |
+
patch_size=16,
|
| 1169 |
+
ddt_head_dim=1536, ddt_head_depth=2, ddt_head_num_heads=12,
|
| 1170 |
+
moe_num_experts=32, moe_top_k=4,
|
| 1171 |
+
moe_routed_inter_dim=384, moe_shared_inter_dim=632,
|
| 1172 |
+
moe_route_scale=2.0, moe_n_dense_layers=4, **kwargs)
|
| 1173 |
+
|
| 1174 |
+
|
| 1175 |
+
def PixelMoE_L_E32A4_8(**kwargs):
|
| 1176 |
+
return PixelMoE(depth=24, hidden_size=1024, num_heads=16,
|
| 1177 |
+
bottleneck_dim=128, in_context_len=32, in_context_start=8,
|
| 1178 |
+
patch_size=8,
|
| 1179 |
+
ddt_head_dim=2048, ddt_head_depth=2, ddt_head_num_heads=16,
|
| 1180 |
+
moe_num_experts=32, moe_top_k=4,
|
| 1181 |
+
moe_routed_inter_dim=512, moe_shared_inter_dim=836,
|
| 1182 |
+
moe_route_scale=2.0, moe_n_dense_layers=4, **kwargs)
|
| 1183 |
+
|
| 1184 |
+
|
| 1185 |
+
def PixelMoE_L_E32A4_16(**kwargs):
|
| 1186 |
+
return PixelMoE(depth=24, hidden_size=1024, num_heads=16,
|
| 1187 |
+
bottleneck_dim=128, in_context_len=32, in_context_start=8,
|
| 1188 |
+
patch_size=16,
|
| 1189 |
+
ddt_head_dim=2048, ddt_head_depth=2, ddt_head_num_heads=16,
|
| 1190 |
+
moe_num_experts=32, moe_top_k=4,
|
| 1191 |
+
moe_routed_inter_dim=512, moe_shared_inter_dim=836,
|
| 1192 |
+
moe_route_scale=2.0, moe_n_dense_layers=4, **kwargs)
|
| 1193 |
+
|
| 1194 |
+
|
| 1195 |
+
def PixelMoE_H_E32A4_8(**kwargs):
|
| 1196 |
+
return PixelMoE(depth=32, hidden_size=1280, num_heads=16,
|
| 1197 |
+
bottleneck_dim=256, in_context_len=32, in_context_start=10,
|
| 1198 |
+
patch_size=8,
|
| 1199 |
+
ddt_head_dim=2560, ddt_head_depth=4, ddt_head_num_heads=16,
|
| 1200 |
+
moe_num_experts=32, moe_top_k=4,
|
| 1201 |
+
moe_routed_inter_dim=640, moe_shared_inter_dim=1048,
|
| 1202 |
+
moe_route_scale=2.0, moe_n_dense_layers=4, **kwargs)
|
| 1203 |
+
|
| 1204 |
+
|
| 1205 |
+
def PixelMoE_H_E32A4_16(**kwargs):
|
| 1206 |
+
return PixelMoE(depth=32, hidden_size=1280, num_heads=16,
|
| 1207 |
+
bottleneck_dim=256, in_context_len=32, in_context_start=10,
|
| 1208 |
+
patch_size=16,
|
| 1209 |
+
ddt_head_dim=2560, ddt_head_depth=4, ddt_head_num_heads=16,
|
| 1210 |
+
moe_num_experts=32, moe_top_k=4,
|
| 1211 |
+
moe_routed_inter_dim=640, moe_shared_inter_dim=1048,
|
| 1212 |
+
moe_route_scale=2.0, moe_n_dense_layers=4, **kwargs)
|
| 1213 |
+
|
| 1214 |
+
|
| 1215 |
+
PixelMoE_models = {
|
| 1216 |
+
"PixelMoE-B-E32A4/8": PixelMoE_B_E32A4_8,
|
| 1217 |
+
"PixelMoE-B-E32A4/16": PixelMoE_B_E32A4_16,
|
| 1218 |
+
"PixelMoE-L-E32A4/8": PixelMoE_L_E32A4_8,
|
| 1219 |
+
"PixelMoE-L-E32A4/16": PixelMoE_L_E32A4_16,
|
| 1220 |
+
"PixelMoE-H-E32A4/8": PixelMoE_H_E32A4_8,
|
| 1221 |
+
"PixelMoE-H-E32A4/16": PixelMoE_H_E32A4_16,
|
| 1222 |
+
}
|
pixelmoe_src/util/__init__.py
ADDED
|
File without changes
|
pixelmoe_src/util/model_util.py
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# --------------------------------------------------------
|
| 2 |
+
# References:
|
| 3 |
+
# Lightning-DiT: https://github.com/hustvl/LightningDiT
|
| 4 |
+
# --------------------------------------------------------
|
| 5 |
+
|
| 6 |
+
|
| 7 |
+
import torch
|
| 8 |
+
from torch import nn
|
| 9 |
+
import numpy as np
|
| 10 |
+
|
| 11 |
+
|
| 12 |
+
class RMSNorm(nn.Module):
|
| 13 |
+
def __init__(self, hidden_size, eps=1e-6):
|
| 14 |
+
"""
|
| 15 |
+
Normalize by RMS in FP32, apply a learned scale, and restore input dtype.
|
| 16 |
+
"""
|
| 17 |
+
super().__init__()
|
| 18 |
+
self.weight = nn.Parameter(torch.ones(hidden_size))
|
| 19 |
+
self.variance_epsilon = eps
|
| 20 |
+
|
| 21 |
+
def forward(self, hidden_states):
|
| 22 |
+
input_dtype = hidden_states.dtype
|
| 23 |
+
hidden_states = hidden_states.to(torch.float32)
|
| 24 |
+
variance = hidden_states.pow(2).mean(-1, keepdim=True)
|
| 25 |
+
hidden_states = hidden_states * torch.rsqrt(variance + self.variance_epsilon)
|
| 26 |
+
return (self.weight * hidden_states).to(input_dtype)
|
| 27 |
+
|
| 28 |
+
|
| 29 |
+
def get_2d_sincos_pos_embed(embed_dim, grid_size):
|
| 30 |
+
"""
|
| 31 |
+
grid_size: int of the grid height and width
|
| 32 |
+
return:
|
| 33 |
+
pos_embed: [grid_size*grid_size, embed_dim]
|
| 34 |
+
"""
|
| 35 |
+
grid_h = np.arange(grid_size, dtype=np.float32)
|
| 36 |
+
grid_w = np.arange(grid_size, dtype=np.float32)
|
| 37 |
+
grid = np.meshgrid(grid_w, grid_h) # here w goes first
|
| 38 |
+
grid = np.stack(grid, axis=0)
|
| 39 |
+
|
| 40 |
+
grid = grid.reshape([2, 1, grid_size, grid_size])
|
| 41 |
+
pos_embed = get_2d_sincos_pos_embed_from_grid(embed_dim, grid)
|
| 42 |
+
return pos_embed
|
| 43 |
+
|
| 44 |
+
|
| 45 |
+
def get_2d_sincos_pos_embed_from_grid(embed_dim, grid):
|
| 46 |
+
assert embed_dim % 2 == 0
|
| 47 |
+
|
| 48 |
+
# Allocate half the embedding to each coordinate: width first, then height.
|
| 49 |
+
emb_w = get_1d_sincos_pos_embed_from_grid(embed_dim // 2, grid[0]) # (H*W, D/2)
|
| 50 |
+
emb_h = get_1d_sincos_pos_embed_from_grid(embed_dim // 2, grid[1]) # (H*W, D/2)
|
| 51 |
+
|
| 52 |
+
emb = np.concatenate([emb_w, emb_h], axis=1) # (H*W, D)
|
| 53 |
+
return emb
|
| 54 |
+
|
| 55 |
+
|
| 56 |
+
def get_1d_sincos_pos_embed_from_grid(embed_dim, pos):
|
| 57 |
+
"""
|
| 58 |
+
embed_dim: output dimension for each position
|
| 59 |
+
pos: a list of positions to be encoded: size (M,)
|
| 60 |
+
out: (M, D)
|
| 61 |
+
"""
|
| 62 |
+
assert embed_dim % 2 == 0
|
| 63 |
+
omega = np.arange(embed_dim // 2, dtype=np.float64)
|
| 64 |
+
omega /= embed_dim / 2.
|
| 65 |
+
omega = 1. / 10000**omega # (D/2,)
|
| 66 |
+
|
| 67 |
+
pos = pos.reshape(-1) # (M,)
|
| 68 |
+
out = np.einsum('m,d->md', pos, omega) # (M, D/2), outer product
|
| 69 |
+
|
| 70 |
+
emb_sin = np.sin(out) # (M, D/2)
|
| 71 |
+
emb_cos = np.cos(out) # (M, D/2)
|
| 72 |
+
|
| 73 |
+
emb = np.concatenate([emb_sin, emb_cos], axis=1) # (M, D)
|
| 74 |
+
return emb
|
requirements.txt
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
einops
|
| 2 |
+
numpy
|
| 3 |
+
https://huggingface.co/datasets/multimodalart/zerogpu-blackwell-wheels/resolve/main/wheels/pt211-cu130-cp312/flash_attn-2.8.3-cp312-cp312-linux_x86_64.whl
|