trixyL commited on
Commit
0d33de8
·
1 Parent(s): 22a9f3a

add: full app

Browse files
Files changed (5) hide show
  1. .gitattributes +0 -1
  2. README.md +6 -5
  3. app.py +49 -0
  4. model.py +656 -0
  5. requirements.txt +7 -0
.gitattributes CHANGED
@@ -33,4 +33,3 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
36
- model/*.safetensors filter=lfs diff=lfs merge=lfs -text
 
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
 
README.md CHANGED
@@ -1,13 +1,14 @@
1
  ---
2
- title: Mnist Flow Demo
3
- emoji: 👁
4
- colorFrom: gray
5
- colorTo: pink
6
  sdk: gradio
7
- sdk_version: 6.9.0
8
  python_version: '3.12'
9
  app_file: app.py
10
  pinned: false
 
11
  ---
12
 
13
  Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
1
  ---
2
+ title: MNIST Flow (TransformerLM)
3
+ emoji: 🌊
4
+ colorFrom: blue
5
+ colorTo: cyan
6
  sdk: gradio
7
+ sdk_version: 6.5.1
8
  python_version: '3.12'
9
  app_file: app.py
10
  pinned: false
11
+ short_description: Flow-matching MNIST digit generation
12
  ---
13
 
14
  Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
app.py ADDED
@@ -0,0 +1,49 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ import spaces
3
+ import torch
4
+
5
+ from model import generate_grid_image, load_model
6
+
7
+ MODEL_READY = False
8
+
9
+
10
+ def ensure_model_loaded():
11
+ global MODEL_READY
12
+ if not MODEL_READY:
13
+ load_model()
14
+ MODEL_READY = True
15
+
16
+
17
+ @spaces.GPU
18
+ @torch.inference_mode()
19
+ def predict(label: int, steps: int, num_samples: int):
20
+ ensure_model_loaded()
21
+ return generate_grid_image(label=label, steps=steps, num_samples=num_samples)
22
+
23
+
24
+ with gr.Blocks(title="MNIST Flow") as demo:
25
+ gr.Markdown("# MNIST Flow")
26
+ gr.Markdown(
27
+ "Flow-matching DiT model for MNIST digits. "
28
+ "Sampling uses fixed CFG=2.0 and caps ODE steps at 128."
29
+ )
30
+
31
+ grid = gr.Image(label="Samples", show_label=True)
32
+
33
+ with gr.Row():
34
+ label = gr.Dropdown([str(i) for i in range(10)], value="1", label="Label")
35
+ steps = gr.Slider(1, 128, value=128, step=1, label="Steps")
36
+ num_samples = gr.Slider(1, 36, value=16, step=1, label="Samples")
37
+
38
+ generate_btn = gr.Button("Generate")
39
+
40
+ generate_btn.click(
41
+ fn=predict,
42
+ inputs=[label, steps, num_samples],
43
+ outputs=grid,
44
+ scroll_to_output=True,
45
+ )
46
+
47
+
48
+ if __name__ == "__main__":
49
+ demo.launch()
model.py ADDED
@@ -0,0 +1,656 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ import os
4
+ from typing import List, Tuple
5
+
6
+ import numpy as np
7
+ import torch
8
+ import torch.nn as nn
9
+ import torch.nn.functional as F
10
+ from einops import einsum, rearrange
11
+ from PIL import Image
12
+ from safetensors.torch import load_file
13
+
14
+
15
+ BASE_DIR = os.path.dirname(os.path.abspath(__file__))
16
+ CHECKPOINT_PATH = os.path.join(BASE_DIR, "model", "model.safetensors")
17
+
18
+ MODEL_CONFIG = {
19
+ "model_type": "image_dit",
20
+ "label_vocab_size": 11,
21
+ "vocab_size": 257,
22
+ "pixel_bins": 256,
23
+ "context_length": 784,
24
+ "d_model": 256,
25
+ "num_layers": 8,
26
+ "num_heads": 16,
27
+ "d_ff": 1024,
28
+ "rope_theta": 10000.0,
29
+ "attention_backend": "torch_sdpa",
30
+ "attention_sdp_backend": "auto",
31
+ "device": "cuda",
32
+ "dtype": "float16",
33
+ "null_label_id": 10,
34
+ "use_rope_2d": True,
35
+ "image_height": 28,
36
+ "image_width": 28,
37
+ }
38
+
39
+ INFER_CONFIG = {
40
+ "steps": 128,
41
+ "cfg_scale": 2.0,
42
+ }
43
+
44
+ DTYPES = {
45
+ "float16": torch.float16,
46
+ "float32": torch.float32,
47
+ "bfloat16": torch.bfloat16,
48
+ }
49
+
50
+ ALLOWED_ATTENTION_BACKENDS = {"custom", "torch_sdpa"}
51
+ ALLOWED_SDP_BACKENDS = {"auto", "flash", "mem_efficient", "math"}
52
+
53
+
54
+ def _resolve_device_dtype(device: str, dtype_name: str) -> Tuple[str, torch.dtype]:
55
+ resolved_device = device
56
+ if device == "cuda" and not torch.cuda.is_available():
57
+ resolved_device = "cpu"
58
+
59
+ resolved_dtype = DTYPES[dtype_name]
60
+ if resolved_device == "cpu" and resolved_dtype == torch.float16:
61
+ resolved_dtype = torch.float32
62
+
63
+ return resolved_device, resolved_dtype
64
+
65
+
66
+ def set_sdp_backend(backend: str) -> None:
67
+ backend = backend.lower()
68
+ if backend not in ALLOWED_SDP_BACKENDS:
69
+ raise ValueError(f"attention_sdp_backend must be one of {sorted(ALLOWED_SDP_BACKENDS)}")
70
+ if not torch.cuda.is_available():
71
+ return
72
+ if backend == "auto":
73
+ torch.backends.cuda.enable_flash_sdp(True)
74
+ torch.backends.cuda.enable_mem_efficient_sdp(True)
75
+ torch.backends.cuda.enable_math_sdp(True)
76
+ return
77
+ torch.backends.cuda.enable_flash_sdp(backend == "flash")
78
+ torch.backends.cuda.enable_mem_efficient_sdp(backend == "mem_efficient")
79
+ torch.backends.cuda.enable_math_sdp(backend == "math")
80
+
81
+
82
+ def softmax(x: torch.Tensor, dim: int):
83
+ x_max = x.max(dim=dim, keepdim=True).values
84
+ x_stable = x - x_max
85
+ exp_x = torch.exp(x_stable)
86
+ sum_exp_x = exp_x.sum(dim=dim, keepdim=True)
87
+ return exp_x / sum_exp_x
88
+
89
+
90
+ class Linear(nn.Module):
91
+ def __init__(self, in_features, out_features, device=None, dtype=None):
92
+ super().__init__()
93
+ self.weight = nn.Parameter(torch.empty(out_features, in_features, device=device, dtype=dtype))
94
+ mean = 0.0
95
+ std = 2 / (in_features + out_features)
96
+ a = mean - 3 * std
97
+ b = mean + 3 * std
98
+ nn.init.trunc_normal_(self.weight, mean=mean, std=std, a=a, b=b)
99
+
100
+ def forward(self, x: torch.Tensor) -> torch.Tensor:
101
+ return einsum(self.weight, x, "out_features in_features, ... in_features -> ... out_features")
102
+
103
+
104
+ class Embedding(nn.Module):
105
+ def __init__(self, num_embeddings, embedding_dim, device=None, dtype=None):
106
+ super().__init__()
107
+ self.weight = nn.Parameter(torch.empty(num_embeddings, embedding_dim, device=device, dtype=dtype))
108
+ nn.init.trunc_normal_(self.weight, mean=0, std=1, a=-3, b=3)
109
+
110
+ def forward(self, token_ids: torch.Tensor) -> torch.Tensor:
111
+ return self.weight[token_ids]
112
+
113
+
114
+ class RMSNorm(nn.Module):
115
+ def __init__(self, d_model: int, eps: float = 1e-5, device=None, dtype=None):
116
+ super().__init__()
117
+ self.eps = eps
118
+ self.weight = nn.Parameter(torch.empty(d_model, device=device, dtype=dtype))
119
+ nn.init.ones_(self.weight)
120
+
121
+ def forward(self, x: torch.Tensor) -> torch.Tensor:
122
+ in_dtype = x.dtype
123
+ x = x.to(torch.float32)
124
+ rms = torch.sqrt(torch.mean(x**2, dim=-1) + self.eps).unsqueeze(-1)
125
+ x = (1.0 / rms) * (x * self.weight)
126
+ return x.to(in_dtype)
127
+
128
+
129
+ class SwiGLU(nn.Module):
130
+ def __init__(self, d_model: int, d_ff: int, device=None, dtype=None):
131
+ super().__init__()
132
+ self.w1 = Linear(d_model, d_ff, device=device, dtype=dtype)
133
+ self.w2 = Linear(d_ff, d_model, device=device, dtype=dtype)
134
+ self.w3 = Linear(d_model, d_ff, device=device, dtype=dtype)
135
+
136
+ def forward(self, x: torch.Tensor) -> torch.Tensor:
137
+ w1x = self.w1(x)
138
+ w3x = self.w3(x)
139
+ silu = w1x * torch.sigmoid(w1x)
140
+ return self.w2(silu * w3x)
141
+
142
+
143
+ class RotaryPositionalEmbedding(nn.Module):
144
+ def __init__(self, theta: float, d_k: int, max_seq_len: int, device=None):
145
+ super().__init__()
146
+ theta_i = theta ** (torch.arange(0, d_k, 2).float() / d_k)
147
+ position = torch.arange(max_seq_len)
148
+ phases = position.unsqueeze(1) / theta_i.unsqueeze(0)
149
+ phases_combined = torch.stack([torch.cos(phases), torch.sin(phases)], dim=-1).to(device=device)
150
+ self.register_buffer("phases", phases_combined, persistent=False)
151
+
152
+ def forward(self, x: torch.Tensor, token_positions: torch.Tensor) -> torch.Tensor:
153
+ x = rearrange(x, "... (d_k p) -> ... d_k p", p=2)
154
+ x1 = x[..., 0]
155
+ x2 = x[..., 1]
156
+ phases_cos = self.phases[..., 0][token_positions].to(dtype=x.dtype)
157
+ phases_sin = self.phases[..., 1][token_positions].to(dtype=x.dtype)
158
+ x_rotated = torch.stack(
159
+ [
160
+ x1 * phases_cos - x2 * phases_sin,
161
+ x1 * phases_sin + x2 * phases_cos,
162
+ ],
163
+ dim=-1,
164
+ )
165
+ return x_rotated.flatten(-2)
166
+
167
+
168
+ def _prepare_attention_mask(attention_mask: torch.Tensor, ref_tensor: torch.Tensor) -> torch.Tensor:
169
+ mask = attention_mask.to(device=ref_tensor.device, dtype=torch.bool)
170
+ if mask.dim() == 2:
171
+ mask = mask[:, None, None, :]
172
+ elif mask.dim() == 3:
173
+ mask = mask[:, None, :, :]
174
+ elif mask.dim() != 4:
175
+ raise ValueError("attention_mask must be 2D, 3D, or 4D")
176
+ return mask
177
+
178
+
179
+ def scaled_dot_product_attention(
180
+ q: torch.Tensor,
181
+ k: torch.Tensor,
182
+ v: torch.Tensor,
183
+ attention_mask: torch.Tensor | None = None,
184
+ ):
185
+ scale = torch.tensor(q.shape[-1], device=q.device, dtype=q.dtype).sqrt()
186
+ qk_score = einsum(q, k, "batch ... n d, batch ... m d -> batch ... n m") / scale
187
+ if attention_mask is not None:
188
+ mask = _prepare_attention_mask(attention_mask, qk_score)
189
+ qk_score = qk_score.masked_fill(~mask, float("-inf"))
190
+ return einsum(softmax(qk_score, dim=-1), v, "batch ... n m, batch ... m d -> batch ... n d")
191
+
192
+
193
+ def torch_scaled_dot_product_attention(
194
+ q: torch.Tensor,
195
+ k: torch.Tensor,
196
+ v: torch.Tensor,
197
+ attention_mask: torch.Tensor | None = None,
198
+ ):
199
+ mask = None
200
+ if attention_mask is not None:
201
+ mask = _prepare_attention_mask(attention_mask, q)
202
+ return F.scaled_dot_product_attention(
203
+ q.contiguous(),
204
+ k.contiguous(),
205
+ v.contiguous(),
206
+ attn_mask=mask,
207
+ dropout_p=0.0,
208
+ is_causal=False,
209
+ )
210
+
211
+
212
+ class MultiheadSelfAttentionRoPE2D(nn.Module):
213
+ def __init__(
214
+ self,
215
+ d_model: int,
216
+ num_heads: int,
217
+ max_height: int,
218
+ max_width: int,
219
+ theta: float,
220
+ attention_backend: str = "custom",
221
+ device=None,
222
+ dtype=None,
223
+ ):
224
+ super().__init__()
225
+ self.d_model = d_model
226
+ self.num_heads = num_heads
227
+ self.d_k = self.d_model // self.num_heads
228
+ if self.d_k % 4 != 0:
229
+ raise ValueError("per-head dimension must be divisible by 4 for 2D RoPE")
230
+ self.d_v = self.d_k
231
+ if attention_backend not in ALLOWED_ATTENTION_BACKENDS:
232
+ raise ValueError(f"attention_backend must be one of {sorted(ALLOWED_ATTENTION_BACKENDS)}")
233
+ self.attention_backend = attention_backend
234
+ self.q_proj = Linear(d_model, d_model, device=device, dtype=dtype)
235
+ self.k_proj = Linear(d_model, d_model, device=device, dtype=dtype)
236
+ self.v_proj = Linear(d_model, d_model, device=device, dtype=dtype)
237
+ self.output_proj = Linear(d_model, d_model, device=device, dtype=dtype)
238
+ self.d_k_half = self.d_k // 2
239
+ self.row_rope = RotaryPositionalEmbedding(theta, self.d_k_half, int(max_height), device)
240
+ self.col_rope = RotaryPositionalEmbedding(theta, self.d_k_half, int(max_width), device)
241
+
242
+ def _apply_2d_rope(self, x: torch.Tensor, row_positions: torch.Tensor, col_positions: torch.Tensor) -> torch.Tensor:
243
+ row_part = x[..., : self.d_k_half]
244
+ col_part = x[..., self.d_k_half :]
245
+ return torch.cat(
246
+ [
247
+ self.row_rope(row_part, row_positions),
248
+ self.col_rope(col_part, col_positions),
249
+ ],
250
+ dim=-1,
251
+ )
252
+
253
+ def forward(
254
+ self,
255
+ x: torch.Tensor,
256
+ row_positions: torch.Tensor,
257
+ col_positions: torch.Tensor,
258
+ attention_mask: torch.Tensor | None = None,
259
+ ) -> torch.Tensor:
260
+ wqx = rearrange(self.q_proj(x), "... seq (heads d) -> ... heads seq d", heads=self.num_heads, d=self.d_k)
261
+ wkx = rearrange(self.k_proj(x), "... seq (heads d) -> ... heads seq d", heads=self.num_heads, d=self.d_k)
262
+ wvx = rearrange(self.v_proj(x), "... seq (heads d) -> ... heads seq d", heads=self.num_heads, d=self.d_v)
263
+ q = self._apply_2d_rope(wqx, row_positions, col_positions)
264
+ k = self._apply_2d_rope(wkx, row_positions, col_positions)
265
+ if self.attention_backend == "torch_sdpa":
266
+ attn = torch_scaled_dot_product_attention(q, k, wvx, attention_mask=attention_mask)
267
+ else:
268
+ attn = scaled_dot_product_attention(q, k, wvx, attention_mask=attention_mask)
269
+ out = rearrange(attn, "... heads seq d -> ... seq (heads d)", heads=self.num_heads, d=self.d_v)
270
+ return self.output_proj(out)
271
+
272
+
273
+ class MultiheadCrossAttentionRoPE2D(nn.Module):
274
+ def __init__(
275
+ self,
276
+ d_model: int,
277
+ num_heads: int,
278
+ max_height: int,
279
+ max_width: int,
280
+ theta: float,
281
+ attention_backend: str = "custom",
282
+ device=None,
283
+ dtype=None,
284
+ ):
285
+ super().__init__()
286
+ self.d_model = d_model
287
+ self.num_heads = num_heads
288
+ self.d_k = self.d_model // self.num_heads
289
+ if self.d_k % 4 != 0:
290
+ raise ValueError("per-head dimension must be divisible by 4 for 2D RoPE")
291
+ self.d_v = self.d_k
292
+ if attention_backend not in ALLOWED_ATTENTION_BACKENDS:
293
+ raise ValueError(f"attention_backend must be one of {sorted(ALLOWED_ATTENTION_BACKENDS)}")
294
+ self.attention_backend = attention_backend
295
+ self.q_proj = Linear(d_model, d_model, device=device, dtype=dtype)
296
+ self.k_proj = Linear(d_model, d_model, device=device, dtype=dtype)
297
+ self.v_proj = Linear(d_model, d_model, device=device, dtype=dtype)
298
+ self.output_proj = Linear(d_model, d_model, device=device, dtype=dtype)
299
+ self.d_k_half = self.d_k // 2
300
+ self.row_rope = RotaryPositionalEmbedding(theta, self.d_k_half, int(max_height), device)
301
+ self.col_rope = RotaryPositionalEmbedding(theta, self.d_k_half, int(max_width), device)
302
+
303
+ def _apply_2d_rope(self, x: torch.Tensor, row_positions: torch.Tensor, col_positions: torch.Tensor) -> torch.Tensor:
304
+ row_part = x[..., : self.d_k_half]
305
+ col_part = x[..., self.d_k_half :]
306
+ return torch.cat(
307
+ [
308
+ self.row_rope(row_part, row_positions),
309
+ self.col_rope(col_part, col_positions),
310
+ ],
311
+ dim=-1,
312
+ )
313
+
314
+ def forward(
315
+ self,
316
+ x: torch.Tensor,
317
+ context: torch.Tensor,
318
+ row_positions: torch.Tensor,
319
+ col_positions: torch.Tensor,
320
+ context_row_positions: torch.Tensor,
321
+ context_col_positions: torch.Tensor,
322
+ attention_mask: torch.Tensor | None = None,
323
+ ) -> torch.Tensor:
324
+ wqx = rearrange(self.q_proj(x), "... seq (heads d) -> ... heads seq d", heads=self.num_heads, d=self.d_k)
325
+ wkx = rearrange(self.k_proj(context), "... seq (heads d) -> ... heads seq d", heads=self.num_heads, d=self.d_k)
326
+ wvx = rearrange(self.v_proj(context), "... seq (heads d) -> ... heads seq d", heads=self.num_heads, d=self.d_v)
327
+ q = self._apply_2d_rope(wqx, row_positions, col_positions)
328
+ k = self._apply_2d_rope(wkx, context_row_positions, context_col_positions)
329
+ if self.attention_backend == "torch_sdpa":
330
+ attn = torch_scaled_dot_product_attention(q, k, wvx, attention_mask=attention_mask)
331
+ else:
332
+ attn = scaled_dot_product_attention(q, k, wvx, attention_mask=attention_mask)
333
+ out = rearrange(attn, "... heads seq d -> ... seq (heads d)", heads=self.num_heads, d=self.d_v)
334
+ return self.output_proj(out)
335
+
336
+
337
+ class TransformerImageBlock(nn.Module):
338
+ def __init__(
339
+ self,
340
+ d_model: int,
341
+ num_heads: int,
342
+ max_seq_len: int,
343
+ max_height: int | None,
344
+ max_width: int | None,
345
+ theta: float,
346
+ d_ff: int,
347
+ attention_backend: str = "custom",
348
+ use_rope_2d: bool = False,
349
+ device=None,
350
+ dtype=None,
351
+ ):
352
+ super().__init__()
353
+ self.ffn = SwiGLU(d_model, d_ff, device, dtype)
354
+ self.use_rope_2d = bool(use_rope_2d)
355
+ if not self.use_rope_2d:
356
+ raise ValueError("This demo vendors only the 2D RoPE image path")
357
+ if max_height is None or max_width is None:
358
+ raise ValueError("max_height/max_width must be provided when use_rope_2d is True")
359
+ self.self_attn = MultiheadSelfAttentionRoPE2D(
360
+ d_model,
361
+ num_heads,
362
+ max_height,
363
+ max_width,
364
+ theta,
365
+ attention_backend=attention_backend,
366
+ device=device,
367
+ dtype=dtype,
368
+ )
369
+ self.cross_attn = MultiheadCrossAttentionRoPE2D(
370
+ d_model,
371
+ num_heads,
372
+ max_height,
373
+ max_width,
374
+ theta,
375
+ attention_backend=attention_backend,
376
+ device=device,
377
+ dtype=dtype,
378
+ )
379
+ self.ln1 = RMSNorm(d_model, device=device, dtype=dtype)
380
+ self.ln2 = RMSNorm(d_model, device=device, dtype=dtype)
381
+ self.ln3 = RMSNorm(d_model, device=device, dtype=dtype)
382
+
383
+ def forward(
384
+ self,
385
+ x: torch.Tensor,
386
+ context: torch.Tensor,
387
+ row_positions: torch.Tensor,
388
+ col_positions: torch.Tensor,
389
+ context_row_positions: torch.Tensor,
390
+ context_col_positions: torch.Tensor,
391
+ ) -> torch.Tensor:
392
+ x = x + self.self_attn(self.ln1(x), row_positions, col_positions, attention_mask=None)
393
+ x = x + self.cross_attn(
394
+ self.ln2(x),
395
+ context,
396
+ row_positions,
397
+ col_positions,
398
+ context_row_positions,
399
+ context_col_positions,
400
+ attention_mask=None,
401
+ )
402
+ x = x + self.ffn(self.ln3(x))
403
+ return x
404
+
405
+
406
+ def _timestep_embedding(t: torch.Tensor, dim: int, max_period: float = 10000.0) -> torch.Tensor:
407
+ if t.dim() != 1:
408
+ raise ValueError("t must be 1D with shape (batch,)")
409
+ half = dim // 2
410
+ if half == 0:
411
+ return t[:, None]
412
+ freqs = torch.exp(
413
+ -torch.log(torch.tensor(max_period, device=t.device, dtype=torch.float32))
414
+ * torch.arange(half, device=t.device, dtype=torch.float32)
415
+ / max(half - 1, 1)
416
+ )
417
+ args = t.to(torch.float32)[:, None] * freqs[None, :]
418
+ emb = torch.cat([torch.sin(args), torch.cos(args)], dim=-1)
419
+ if dim % 2 == 1:
420
+ emb = torch.cat([emb, torch.zeros((t.shape[0], 1), device=t.device, dtype=emb.dtype)], dim=-1)
421
+ return emb
422
+
423
+
424
+ class DiTImage(nn.Module):
425
+ def __init__(
426
+ self,
427
+ context_length: int,
428
+ d_model: int,
429
+ num_layers: int,
430
+ num_heads: int,
431
+ d_ff: int,
432
+ rope_theta: float,
433
+ label_vocab_size: int,
434
+ attention_backend: str = "custom",
435
+ image_height: int | None = None,
436
+ image_width: int | None = None,
437
+ use_rope_2d: bool = False,
438
+ device=None,
439
+ dtype=None,
440
+ ):
441
+ super().__init__()
442
+ self.context_length = int(context_length)
443
+ self.use_rope_2d = bool(use_rope_2d)
444
+ if not self.use_rope_2d:
445
+ raise ValueError("This demo expects use_rope_2d=True")
446
+ if image_height is None or image_width is None:
447
+ raise ValueError("image_height/image_width must be set for the flow demo")
448
+ self.image_height = int(image_height)
449
+ self.image_width = int(image_width)
450
+ self.input_proj = Linear(1, d_model, device, dtype)
451
+ self.time_proj = Linear(d_model, d_model, device, dtype)
452
+ self.label_embeddings = Embedding(label_vocab_size, d_model, device, dtype)
453
+ self.layers = nn.ModuleList(
454
+ [
455
+ TransformerImageBlock(
456
+ d_model,
457
+ num_heads,
458
+ context_length,
459
+ self.image_height,
460
+ self.image_width,
461
+ rope_theta,
462
+ d_ff,
463
+ attention_backend=attention_backend,
464
+ use_rope_2d=True,
465
+ device=device,
466
+ dtype=dtype,
467
+ )
468
+ for _ in range(num_layers)
469
+ ]
470
+ )
471
+ self.ln_final = RMSNorm(d_model, device=device, dtype=dtype)
472
+ self.output_proj = Linear(d_model, 1, device, dtype)
473
+
474
+ def forward(self, x: torch.Tensor, t: torch.Tensor, context: torch.Tensor | None = None) -> torch.Tensor:
475
+ if x.dim() != 2:
476
+ raise ValueError("x must be 2D with shape (batch, seq)")
477
+ if context is None or context.dim() != 1 or context.shape[0] != x.shape[0]:
478
+ raise ValueError("context must be 1D with matching batch size")
479
+ if t.dim() == 2 and t.shape[1] == 1:
480
+ t = t[:, 0]
481
+ if t.dim() != 1 or t.shape[0] != x.shape[0]:
482
+ raise ValueError("t must be 1D with matching batch size")
483
+
484
+ model_dtype = self.input_proj.weight.dtype
485
+ output_seq = self.input_proj(x.to(dtype=model_dtype).unsqueeze(-1))
486
+ t_emb = _timestep_embedding(t, output_seq.shape[-1]).to(dtype=model_dtype)
487
+ context_emb = (self.time_proj(t_emb) + self.label_embeddings(context)).unsqueeze(-2)
488
+
489
+ seq_len = output_seq.shape[-2]
490
+ expected = self.image_height * self.image_width
491
+ if seq_len != expected:
492
+ raise ValueError(f"sequence length {seq_len} does not match image_height*image_width {expected}")
493
+ row_positions = torch.arange(self.image_height, device=output_seq.device, dtype=torch.long).repeat_interleave(
494
+ self.image_width
495
+ )
496
+ col_positions = torch.arange(self.image_width, device=output_seq.device, dtype=torch.long).repeat(
497
+ self.image_height
498
+ )
499
+ context_row_positions = torch.zeros(context_emb.shape[-2], device=output_seq.device, dtype=torch.long)
500
+ context_col_positions = torch.zeros(context_emb.shape[-2], device=output_seq.device, dtype=torch.long)
501
+
502
+ for layer in self.layers:
503
+ output_seq = layer(
504
+ output_seq,
505
+ context_emb,
506
+ row_positions,
507
+ col_positions,
508
+ context_row_positions,
509
+ context_col_positions,
510
+ )
511
+ return self.output_proj(self.ln_final(output_seq)).squeeze(-1)
512
+
513
+
514
+ @torch.no_grad()
515
+ def flow_image_generate(
516
+ model,
517
+ prompt_indices: torch.Tensor,
518
+ *,
519
+ context: torch.Tensor,
520
+ steps: int,
521
+ cfg_scale: float = 0.0,
522
+ uncond_context: torch.Tensor | None = None,
523
+ generator: torch.Generator | None = None,
524
+ ) -> torch.Tensor:
525
+ if prompt_indices.dim() != 2:
526
+ raise ValueError("prompt_indices must be 2D (batch, seq)")
527
+ if context.dim() != 1 or prompt_indices.shape[0] != context.shape[0]:
528
+ raise ValueError("context must be 1D with matching batch size")
529
+ if prompt_indices.shape[1] != 0:
530
+ raise ValueError("flow_image_generate expects empty prompt_indices for full-image generation")
531
+ steps = max(1, min(int(steps), 128))
532
+
533
+ batch_size = context.shape[0]
534
+ gen_length = int(model.context_length)
535
+ x = torch.randn((batch_size, gen_length), device=prompt_indices.device, dtype=torch.float32, generator=generator)
536
+ dt = 1.0 / float(steps)
537
+
538
+ if uncond_context is not None:
539
+ if uncond_context.dim() != 1 or uncond_context.shape[0] != batch_size:
540
+ raise ValueError("uncond_context must be 1D with matching batch size")
541
+ uncond_context = uncond_context.to(device=context.device, dtype=context.dtype)
542
+
543
+ for k in range(steps):
544
+ t = torch.full((batch_size,), float(k) / float(steps), device=x.device, dtype=x.dtype)
545
+ if cfg_scale > 0.0:
546
+ if uncond_context is None:
547
+ raise ValueError("uncond_context must be set when cfg_scale > 0 for flow_image_generate")
548
+ v_cond = model(x, t, context=context)
549
+ v_uncond = model(x, t, context=uncond_context)
550
+ v = v_uncond + (cfg_scale + 1.0) * (v_cond - v_uncond)
551
+ else:
552
+ v = model(x, t, context=context)
553
+ x = x + dt * v
554
+ return x
555
+
556
+
557
+ def flow_pixels_to_uint8(values: np.ndarray) -> np.ndarray:
558
+ clipped = np.clip(values.astype(np.float32), -1.0, 1.0)
559
+ restored = np.round((clipped + 1.0) * 127.5)
560
+ return np.clip(restored, 0, 255).astype(np.uint8)
561
+
562
+
563
+ MODEL = None
564
+ DEVICE = None
565
+ DTYPE = None
566
+
567
+
568
+ def load_model():
569
+ global MODEL, DEVICE, DTYPE
570
+ if MODEL is not None:
571
+ return MODEL, DEVICE, DTYPE
572
+ if not os.path.exists(CHECKPOINT_PATH):
573
+ raise FileNotFoundError(f"Missing checkpoint at {CHECKPOINT_PATH}")
574
+
575
+ device, dtype = _resolve_device_dtype(MODEL_CONFIG["device"], MODEL_CONFIG["dtype"])
576
+ set_sdp_backend(MODEL_CONFIG["attention_sdp_backend"])
577
+
578
+ model = DiTImage(
579
+ context_length=MODEL_CONFIG["context_length"],
580
+ d_model=MODEL_CONFIG["d_model"],
581
+ num_layers=MODEL_CONFIG["num_layers"],
582
+ num_heads=MODEL_CONFIG["num_heads"],
583
+ d_ff=MODEL_CONFIG["d_ff"],
584
+ rope_theta=MODEL_CONFIG["rope_theta"],
585
+ label_vocab_size=MODEL_CONFIG["label_vocab_size"],
586
+ attention_backend=MODEL_CONFIG["attention_backend"],
587
+ image_height=MODEL_CONFIG["image_height"],
588
+ image_width=MODEL_CONFIG["image_width"],
589
+ use_rope_2d=MODEL_CONFIG["use_rope_2d"],
590
+ device=device,
591
+ dtype=dtype,
592
+ )
593
+ model.load_state_dict(load_file(CHECKPOINT_PATH))
594
+ model.eval().to(device)
595
+
596
+ MODEL = model
597
+ DEVICE = device
598
+ DTYPE = dtype
599
+ return MODEL, DEVICE, DTYPE
600
+
601
+
602
+ @torch.inference_mode()
603
+ def generate_images(label: int, steps: int, num_samples: int) -> List[Image.Image]:
604
+ model, device, _ = load_model()
605
+ num_samples = int(num_samples)
606
+ label = int(label)
607
+ steps = max(1, min(int(steps), 128))
608
+
609
+ context = torch.full((num_samples,), label, device=device, dtype=torch.long)
610
+ prompt = torch.empty((num_samples, 0), device=device, dtype=torch.long)
611
+ cfg_scale = float(INFER_CONFIG["cfg_scale"])
612
+ null_label_id = int(MODEL_CONFIG["null_label_id"])
613
+ uncond_context = torch.full((num_samples,), null_label_id, device=device, dtype=torch.long)
614
+
615
+ out = flow_image_generate(
616
+ model,
617
+ prompt,
618
+ context=context,
619
+ steps=steps,
620
+ cfg_scale=cfg_scale,
621
+ uncond_context=uncond_context,
622
+ generator=None,
623
+ )
624
+
625
+ h = int(MODEL_CONFIG["image_height"])
626
+ w = int(MODEL_CONFIG["image_width"])
627
+ images: List[Image.Image] = []
628
+ scale = 10
629
+ for i in range(num_samples):
630
+ arr = out[i].detach().cpu().to(torch.float32).numpy().reshape(h, w)
631
+ img = Image.fromarray(flow_pixels_to_uint8(arr), mode="L")
632
+ if scale > 1:
633
+ img = img.resize((w * scale, h * scale), resample=Image.NEAREST)
634
+ images.append(img)
635
+ return images
636
+
637
+
638
+ def _grid_dims(num_samples: int) -> Tuple[int, int]:
639
+ cols = int(np.ceil(np.sqrt(num_samples)))
640
+ rows = int(np.ceil(num_samples / cols))
641
+ return rows, cols
642
+
643
+
644
+ @torch.inference_mode()
645
+ def generate_grid_image(label: int, steps: int, num_samples: int) -> Image.Image:
646
+ images = generate_images(label=label, steps=steps, num_samples=num_samples)
647
+ if not images:
648
+ return Image.new("L", (1, 1), color=0)
649
+ rows, cols = _grid_dims(len(images))
650
+ w, h = images[0].size
651
+ grid = Image.new("L", (cols * w, rows * h))
652
+ for idx, img in enumerate(images):
653
+ r = idx // cols
654
+ c = idx % cols
655
+ grid.paste(img, (c * w, r * h))
656
+ return grid
requirements.txt ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ gradio
2
+ spaces
3
+ torch
4
+ einops
5
+ safetensors
6
+ numpy
7
+ pillow