cpral commited on
Commit
7d38f83
·
verified ·
1 Parent(s): 6925b30

Upload folder using huggingface_hub

Browse files
.gitattributes CHANGED
@@ -33,3 +33,4 @@ 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
 
 
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
+ samples/grid.png filter=lfs diff=lfs merge=lfs -text
model_index.json ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "_class_name": "AuroraIMG2Pipeline",
3
+ "_diffusers_version": "0.42.1",
4
+ "text_encoder": [
5
+ "pipeline",
6
+ "AuroraLMTextEncoder"
7
+ ],
8
+ "tokenizer": [
9
+ "transformers",
10
+ "PreTrainedTokenizerFast"
11
+ ],
12
+ "transformer": [
13
+ "pipeline",
14
+ "AuroraDenoiser"
15
+ ],
16
+ "vae": [
17
+ "diffusers",
18
+ "AutoencoderKLFlux2"
19
+ ]
20
+ }
pipeline.py ADDED
@@ -0,0 +1,595 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """AuroraIMG2-style text-to-image pipeline (load with trust_remote_code=True).
2
+
3
+ Based on the AuroraIMG2-50M-Preview pipeline (Apache-2.0), extended with a second text encoder type:
4
+ * AuroraTextEncoder -> the released from-scratch encoder (unchanged)
5
+ * AuroraLMTextEncoder -> a frozen pretrained causal LM (SlayerLab/Slayer149 GPT, Apache-2.0) + a small bidirectional
6
+ refiner; its LM weights are stored inside text_encoder/ so the repo is self-contained
7
+ Generated by trenowanie_generatora_obrazkow/export.py.
8
+
9
+ Components (all stored in the repo as separate sub-folders):
10
+ * tokenizer -> from-scratch byte-level BPE (8192 tokens), loaded with transformers
11
+ * text_encoder -> AuroraTextEncoder, ~12.7M params, trained from scratch (defined below)
12
+ * transformer -> AuroraDenoiser, ~50M params flow-matching DiT, trained from scratch (defined below)
13
+ * vae -> pretrained FLUX.2 small decoder (AutoencoderKLFlux2, Apache-2.0), frozen
14
+
15
+ Sampling is plain Euler on t = linspace(1, 0, steps + 1) with classifier-free guidance, i.e. exactly
16
+ what was used for the reported benchmark numbers.
17
+
18
+ NOTE: diffusers' custom-pipeline loader expects exactly ONE DiffusionPipeline subclass in this file.
19
+ """
20
+ import contextlib
21
+ import math
22
+ from typing import List, Optional, Union
23
+
24
+ import torch
25
+ import torch.nn as nn
26
+ import torch.nn.functional as F
27
+
28
+ from diffusers import ConfigMixin, DiffusionPipeline, ModelMixin
29
+ from diffusers.configuration_utils import register_to_config
30
+ from diffusers.pipelines.pipeline_utils import ImagePipelineOutput
31
+ from diffusers.utils.torch_utils import randn_tensor
32
+
33
+
34
+ # =============================================================================
35
+ # Building blocks (identical to the training code)
36
+ # =============================================================================
37
+ class RMSNorm(nn.Module):
38
+ def __init__(self, dim: int, eps: float = 1e-6):
39
+ super().__init__()
40
+ self.weight = nn.Parameter(torch.ones(dim))
41
+ self.eps = eps
42
+
43
+ def forward(self, x: torch.Tensor) -> torch.Tensor:
44
+ return x * torch.rsqrt(x.float().pow(2).mean(dim=-1, keepdim=True) + self.eps).to(x.dtype) * self.weight
45
+
46
+
47
+ def rotate_half_pairs(x: torch.Tensor) -> torch.Tensor:
48
+ even = x[..., 0::2]
49
+ odd = x[..., 1::2]
50
+ return torch.stack((-odd, even), dim=-1).flatten(-2)
51
+
52
+
53
+ def _angles(positions: torch.Tensor, dim: int) -> torch.Tensor:
54
+ inv_freq = 1.0 / (10000 ** (torch.arange(0, dim, 2, dtype=torch.float32) / dim))
55
+ return torch.outer(positions.float(), inv_freq)
56
+
57
+
58
+ def build_rope_1d(n: int, d: int):
59
+ ang = _angles(torch.arange(n), d)
60
+ return ang.cos().repeat_interleave(2, dim=-1)[None, None], ang.sin().repeat_interleave(2, dim=-1)[None, None]
61
+
62
+
63
+ def build_rope_2d(side: int, d: int):
64
+ """First half of the head dim encodes the row, second half the column. Tokens are row-major."""
65
+ coords = torch.arange(side)
66
+ rows, cols = torch.meshgrid(coords, coords, indexing="ij")
67
+ ang = torch.cat([_angles(rows.reshape(-1), d // 2), _angles(cols.reshape(-1), d // 2)], dim=-1)
68
+ return ang.cos().repeat_interleave(2, dim=-1)[None, None], ang.sin().repeat_interleave(2, dim=-1)[None, None]
69
+
70
+
71
+ def apply_rope(x: torch.Tensor, cos: torch.Tensor, sin: torch.Tensor) -> torch.Tensor:
72
+ return x * cos.to(x.dtype) + rotate_half_pairs(x) * sin.to(x.dtype)
73
+
74
+
75
+ class SwiGLU(nn.Module):
76
+ def __init__(self, dim: int, hidden: int):
77
+ super().__init__()
78
+ self.in_proj = nn.Linear(dim, hidden * 2, bias=False)
79
+ self.out_proj = nn.Linear(hidden, dim, bias=False)
80
+
81
+ def forward(self, x: torch.Tensor) -> torch.Tensor:
82
+ a, b = self.in_proj(x).chunk(2, dim=-1)
83
+ return self.out_proj(F.silu(a) * b)
84
+
85
+
86
+ class TextAttention(nn.Module):
87
+ def __init__(self, dim: int, heads: int, kv_heads: int, head_dim: int):
88
+ super().__init__()
89
+ self.heads, self.kv_heads, self.head_dim = heads, kv_heads, head_dim
90
+ self.qkv = nn.Linear(dim, (heads + 2 * kv_heads) * head_dim, bias=False)
91
+ self.out = nn.Linear(heads * head_dim, dim, bias=False)
92
+
93
+ def forward(self, x, valid_mask, cos, sin):
94
+ b, t, _ = x.shape
95
+ q, k, v = self.qkv(x).split(
96
+ [self.heads * self.head_dim, self.kv_heads * self.head_dim, self.kv_heads * self.head_dim], dim=-1)
97
+ q = q.view(b, t, self.heads, self.head_dim).transpose(1, 2)
98
+ k = k.view(b, t, self.kv_heads, self.head_dim).transpose(1, 2)
99
+ v = v.view(b, t, self.kv_heads, self.head_dim).transpose(1, 2)
100
+ q, k = apply_rope(q, cos, sin), apply_rope(k, cos, sin)
101
+ rep = self.heads // self.kv_heads
102
+ k, v = k.repeat_interleave(rep, dim=1), v.repeat_interleave(rep, dim=1)
103
+ h = F.scaled_dot_product_attention(q, k, v, attn_mask=valid_mask[:, None, None, :].bool())
104
+ return self.out(h.transpose(1, 2).reshape(b, t, -1))
105
+
106
+
107
+ class TextBlock(nn.Module):
108
+ def __init__(self, dim: int, heads: int, kv_heads: int, head_dim: int, mlp_hidden: int):
109
+ super().__init__()
110
+ self.norm1 = RMSNorm(dim)
111
+ self.attn = TextAttention(dim, heads, kv_heads, head_dim)
112
+ self.norm2 = RMSNorm(dim)
113
+ self.mlp = SwiGLU(dim, mlp_hidden)
114
+
115
+ def forward(self, x, valid_mask, cos, sin):
116
+ x = x + self.attn(self.norm1(x), valid_mask, cos, sin)
117
+ return x + self.mlp(self.norm2(x))
118
+
119
+
120
+ class SelfAttention(nn.Module):
121
+ """Global self-attention over the 16x16 latent tokens with 2D RoPE and QK-norm."""
122
+
123
+ def __init__(self, dim: int, heads: int):
124
+ super().__init__()
125
+ self.heads = heads
126
+ self.head_dim = dim // heads
127
+ self.qkv = nn.Linear(dim, 3 * dim, bias=False)
128
+ self.out = nn.Linear(dim, dim, bias=False)
129
+ self.q_norm = RMSNorm(self.head_dim)
130
+ self.k_norm = RMSNorm(self.head_dim)
131
+
132
+ def forward(self, x, cos, sin):
133
+ b, t, c = x.shape
134
+ q, k, v = self.qkv(x).view(b, t, 3, self.heads, self.head_dim).permute(2, 0, 3, 1, 4).unbind(0)
135
+ q, k = apply_rope(self.q_norm(q), cos, sin), apply_rope(self.k_norm(k), cos, sin)
136
+ h = F.scaled_dot_product_attention(q, k, v)
137
+ return self.out(h.transpose(1, 2).reshape(b, t, c))
138
+
139
+
140
+ class CrossAttention(nn.Module):
141
+ def __init__(self, dim: int, heads: int, text_dim: int):
142
+ super().__init__()
143
+ self.heads = heads
144
+ self.head_dim = dim // heads
145
+ self.q = nn.Linear(dim, dim, bias=False)
146
+ self.kv = nn.Linear(text_dim, 2 * dim, bias=False)
147
+ self.out = nn.Linear(dim, dim, bias=False)
148
+
149
+ def forward(self, x, text, text_valid):
150
+ b, t, c = x.shape
151
+ n = text.shape[1]
152
+ q = self.q(x).view(b, t, self.heads, self.head_dim).transpose(1, 2)
153
+ k, v = self.kv(text).chunk(2, dim=-1)
154
+ k = k.view(b, n, self.heads, self.head_dim).transpose(1, 2)
155
+ v = v.view(b, n, self.heads, self.head_dim).transpose(1, 2)
156
+ h = F.scaled_dot_product_attention(q, k, v, attn_mask=text_valid[:, None, None, :].bool())
157
+ return self.out(h.transpose(1, 2).reshape(b, t, c))
158
+
159
+
160
+ class DenoiserBlock(nn.Module):
161
+ """DiT block with PixArt-style adaLN-single: a shared time->(6*dim) projection plus a per-block table."""
162
+
163
+ def __init__(self, dim: int, heads: int, text_dim: int, mlp_hidden: int):
164
+ super().__init__()
165
+ self.norm1 = RMSNorm(dim)
166
+ self.attn = SelfAttention(dim, heads)
167
+ self.norm2 = RMSNorm(dim)
168
+ self.cross = CrossAttention(dim, heads, text_dim)
169
+ self.norm3 = RMSNorm(dim)
170
+ self.mlp = SwiGLU(dim, mlp_hidden)
171
+ self.mod_table = nn.Parameter(torch.zeros(6, dim))
172
+
173
+ def forward(self, x, t6, text, text_valid, cos, sin):
174
+ shift1, scale1, gate1, shift2, scale2, gate2 = (t6 + self.mod_table[None]).to(x.dtype).unbind(1)
175
+ h = self.norm1(x) * (1 + scale1[:, None]) + shift1[:, None]
176
+ x = x + gate1[:, None] * self.attn(h, cos, sin)
177
+ x = x + self.cross(self.norm2(x), text, text_valid)
178
+ h = self.norm3(x) * (1 + scale2[:, None]) + shift2[:, None]
179
+ return x + gate2[:, None] * self.mlp(h)
180
+
181
+
182
+ def timestep_embedding(t: torch.Tensor, dim: int = 256) -> torch.Tensor:
183
+ t = t.float() * 1000.0
184
+ half = dim // 2
185
+ freqs = torch.exp(-math.log(10000) * torch.arange(half, device=t.device).float() / max(half - 1, 1))
186
+ angles = t[:, None] * freqs[None, :]
187
+ return torch.cat([angles.sin(), angles.cos()], dim=-1)
188
+
189
+
190
+ # =============================================================================
191
+ # Frozen-LM text encoder (GoLLeM / Slayer GPT, inference architecture, Apache-2.0)
192
+ # =============================================================================
193
+ class LMRMSNorm(nn.Module):
194
+ def __init__(self, d, eps=1e-6):
195
+ super().__init__()
196
+ self.weight = nn.Parameter(torch.ones(d))
197
+ self.eps = eps
198
+
199
+ def forward(self, x):
200
+ return x * torch.rsqrt(x.float().pow(2).mean(-1, keepdim=True) + self.eps).to(x.dtype) * self.weight
201
+
202
+
203
+ def lm_apply_rope(x, base=100000.0):
204
+ """Parameter-free RoPE on [B, H, T, D], interleaved convention (even/odd channel pairs)."""
205
+ _, _, T, dim = x.shape
206
+ pos = torch.arange(T, device=x.device, dtype=torch.float32)
207
+ freq = 1.0 / (base ** (torch.arange(0, dim, 2, device=x.device, dtype=torch.float32) / dim))
208
+ ang = torch.outer(pos, freq)
209
+ cos, sin = ang.cos().to(x.dtype)[None, None], ang.sin().to(x.dtype)[None, None]
210
+ even, odd = x[..., ::2], x[..., 1::2]
211
+ return torch.stack((even * cos - odd * sin, even * sin + odd * cos), dim=-1).flatten(-2)
212
+
213
+
214
+ class LMSwiGLU(nn.Module):
215
+ def __init__(self, d, hidden):
216
+ super().__init__()
217
+ self.gate = nn.Linear(d, hidden, bias=False)
218
+ self.up = nn.Linear(d, hidden, bias=False)
219
+ self.down = nn.Linear(hidden, d, bias=False)
220
+
221
+ def forward(self, x):
222
+ return self.down(F.silu(self.gate(x)) * self.up(x))
223
+
224
+
225
+ class LMBlock(nn.Module):
226
+ def __init__(self, d, nh, ffn_mult, eps, rope_theta, value_residual, qk_norm, is_first=False):
227
+ super().__init__()
228
+ self.ln1, self.ln2 = LMRMSNorm(d, eps), LMRMSNorm(d, eps)
229
+ self.qkv = nn.Linear(d, 3 * d)
230
+ self.proj = nn.Linear(d, d)
231
+ self.mlp = LMSwiGLU(d, int(round(ffn_mult * d)))
232
+ self.nh, self.d, self.rope_theta = nh, d, rope_theta
233
+ self.value_residual, self.qk_norm, self.is_first = value_residual, qk_norm, is_first
234
+ if value_residual and not is_first:
235
+ self.vr_lambda = nn.Parameter(torch.zeros(1))
236
+ if qk_norm:
237
+ self.q_norm, self.k_norm = LMRMSNorm(d // nh, eps), LMRMSNorm(d // nh, eps)
238
+
239
+ def forward(self, x, v0=None):
240
+ B, T, D = x.size()
241
+ q, k, v = self.qkv(self.ln1(x)).split(self.d, dim=2)
242
+ hd = D // self.nh
243
+ q, k, v = (t.view(B, T, self.nh, hd).transpose(1, 2) for t in (q, k, v))
244
+ if self.qk_norm:
245
+ q, k = self.q_norm(q), self.k_norm(k)
246
+ q, k = lm_apply_rope(q, self.rope_theta), lm_apply_rope(k, self.rope_theta)
247
+ if self.value_residual:
248
+ if self.is_first:
249
+ v0 = v
250
+ else:
251
+ v = v + self.vr_lambda * v0
252
+ y = F.scaled_dot_product_attention(q, k, v, is_causal=True)
253
+ x = x + self.proj(y.transpose(1, 2).contiguous().view(B, T, D))
254
+ return x + self.mlp(self.ln2(x)), v0
255
+
256
+
257
+ class LMTrunk(nn.Module):
258
+ """Token embedding + the first n blocks of the LM (no final norm, no head)."""
259
+
260
+ def __init__(self, vocab, n_blocks, d, nh, ffn_mult, eps, rope_theta, value_residual, qk_norm):
261
+ super().__init__()
262
+ self.tok = nn.Embedding(vocab, d)
263
+ self.blocks = nn.ModuleList([LMBlock(d, nh, ffn_mult, eps, rope_theta, value_residual, qk_norm, i == 0)
264
+ for i in range(n_blocks)])
265
+
266
+ def forward(self, idx):
267
+ x, v0 = self.tok(idx), None
268
+ for b in self.blocks:
269
+ x, v0 = b(x, v0)
270
+ return x
271
+
272
+
273
+ # =============================================================================
274
+ # FLUX.2 latent packing layouts (used to turn the model's (128,16,16) latents into the decoder's input)
275
+ # =============================================================================
276
+ def _unpatchify_flux2(z): # official FLUX.2 pipeline layout: channel = c*4 + dh*2 + dw
277
+ b, c4, h, w = z.shape
278
+ return z.reshape(b, c4 // 4, 2, 2, h, w).permute(0, 1, 4, 2, 5, 3).reshape(b, c4 // 4, h * 2, w * 2)
279
+
280
+
281
+ def _unpatchify_swap_subpixel(z):
282
+ b, c4, h, w = z.shape
283
+ return z.reshape(b, c4 // 4, 2, 2, h, w).permute(0, 1, 4, 3, 5, 2).reshape(b, c4 // 4, h * 2, w * 2)
284
+
285
+
286
+ def _unpatchify_subpixel_major(z):
287
+ b, c4, h, w = z.shape
288
+ return z.reshape(b, 2, 2, c4 // 4, h, w).permute(0, 3, 4, 1, 5, 2).reshape(b, c4 // 4, h * 2, w * 2)
289
+
290
+
291
+ def _unpatchify_subpixel_major_swapped(z):
292
+ b, c4, h, w = z.shape
293
+ return z.reshape(b, 2, 2, c4 // 4, h, w).permute(0, 3, 4, 2, 5, 1).reshape(b, c4 // 4, h * 2, w * 2)
294
+
295
+
296
+ UNPATCHIFY_LAYOUTS = {
297
+ "flux2_pipeline": _unpatchify_flux2,
298
+ "swap_subpixel": _unpatchify_swap_subpixel,
299
+ "subpixel_major": _unpatchify_subpixel_major,
300
+ "subpixel_major_swapped": _unpatchify_subpixel_major_swapped,
301
+ }
302
+
303
+
304
+ # =============================================================================
305
+ # Diffusers-style models
306
+ # =============================================================================
307
+ class AuroraTextEncoder(ModelMixin, ConfigMixin):
308
+ """Small from-scratch text encoder: GQA transformer with 1D RoPE, SwiGLU and RMSNorm."""
309
+
310
+ @register_to_config
311
+ def __init__(
312
+ self,
313
+ vocab_size: int = 8192,
314
+ dim: int = 384,
315
+ depth: int = 6,
316
+ heads: int = 6,
317
+ kv_heads: int = 2,
318
+ head_dim: int = 64,
319
+ mlp_hidden: int = 1024,
320
+ max_tokens: int = 64,
321
+ pad_id: int = 0,
322
+ bos_id: int = 2,
323
+ eos_id: int = 3,
324
+ null_id: int = 4,
325
+ mask_id: int = 5,
326
+ ):
327
+ super().__init__()
328
+ self.token_embedding = nn.Embedding(vocab_size, dim)
329
+ self.blocks = nn.ModuleList([TextBlock(dim, heads, kv_heads, head_dim, mlp_hidden) for _ in range(depth)])
330
+ self.norm = RMSNorm(dim)
331
+ cos, sin = build_rope_1d(max_tokens, head_dim)
332
+ self.register_buffer("rope_cos", cos, persistent=False)
333
+ self.register_buffer("rope_sin", sin, persistent=False)
334
+
335
+ def forward(self, ids: torch.Tensor):
336
+ valid = ids.ne(self.config.pad_id)
337
+ h = self.token_embedding(ids)
338
+ for block in self.blocks:
339
+ h = block(h, valid, self.rope_cos, self.rope_sin)
340
+ return self.norm(h), valid
341
+
342
+
343
+ class AuroraLMTextEncoder(ModelMixin, ConfigMixin):
344
+ """Frozen causal LM trunk -> RMSNorm (+ learned null prompt) -> bidirectional refiner blocks -> denoiser text tokens.
345
+ Captions: [BOS] ids[:max_tokens-1] [PAD]...; the CFG null prompt is [NULL] [PAD]... (ids outside the LM vocab)."""
346
+
347
+ @register_to_config
348
+ def __init__(
349
+ self,
350
+ kind: str = "lm",
351
+ vocab_size: int = 12288,
352
+ lm_blocks: int = 18, # LM blocks used (the trained run takes the stream before the last of 19)
353
+ lm_dim: int = 768,
354
+ lm_heads: int = 12,
355
+ lm_ffn_mult: float = 2.8125,
356
+ lm_norm_eps: float = 1e-6,
357
+ lm_rope_theta: float = 100000.0,
358
+ lm_value_residual: bool = True,
359
+ lm_qk_norm: bool = True,
360
+ dim: int = 768,
361
+ depth: int = 2,
362
+ heads: int = 12,
363
+ kv_heads: int = 4,
364
+ head_dim: int = 64,
365
+ mlp_hidden: int = 2048,
366
+ max_tokens: int = 96,
367
+ bos_id: int = 12285,
368
+ pad_id: int = 65535,
369
+ null_id: int = 65534,
370
+ ):
371
+ super().__init__()
372
+ self.lm = LMTrunk(vocab_size, lm_blocks, lm_dim, lm_heads, lm_ffn_mult, lm_norm_eps, lm_rope_theta,
373
+ lm_value_residual, lm_qk_norm)
374
+ self.in_norm = RMSNorm(lm_dim)
375
+ self.null = nn.Parameter(torch.zeros(1, 1, lm_dim))
376
+ self.in_proj = nn.Linear(lm_dim, dim, bias=False) if dim != lm_dim else nn.Identity()
377
+ self.blocks = nn.ModuleList([TextBlock(dim, heads, kv_heads, head_dim, mlp_hidden) for _ in range(depth)])
378
+ self.norm = RMSNorm(dim)
379
+ cos, sin = build_rope_1d(max_tokens, head_dim)
380
+ self.register_buffer("rope_cos", cos, persistent=False)
381
+ self.register_buffer("rope_sin", sin, persistent=False)
382
+
383
+ def forward(self, ids: torch.Tensor):
384
+ cfg = self.config
385
+ valid = ids.ne(cfg.pad_id)
386
+ is_null = ids[:, 0].eq(cfg.null_id)
387
+ h = self.lm(ids.masked_fill(~valid | ids.eq(cfg.null_id), 0)) # causal + right padding: pads never seen
388
+ h = self.in_norm(h)
389
+ h = torch.where(is_null[:, None, None], self.null.to(h.dtype).expand_as(h), h)
390
+ h = self.in_proj(h)
391
+ for block in self.blocks:
392
+ h = block(h, valid, self.rope_cos, self.rope_sin)
393
+ return self.norm(h), valid
394
+
395
+
396
+ class AuroraDenoiser(ModelMixin, ConfigMixin):
397
+ """Flow-matching DiT: velocity predictor on (B, 128, 16, 16) FLUX.2 latents, one latent pixel per token."""
398
+
399
+ @register_to_config
400
+ def __init__(
401
+ self,
402
+ latent_channels: int = 128,
403
+ latent_size: int = 16,
404
+ dim: int = 640,
405
+ depth: int = 8,
406
+ heads: int = 10,
407
+ mlp_hidden: int = 1536,
408
+ text_dim: int = 384,
409
+ latent_layout: str = "flux2_pipeline",
410
+ ):
411
+ super().__init__()
412
+ self.patch_in = nn.Linear(latent_channels, dim, bias=False)
413
+ self.time_mlp = nn.Sequential(nn.Linear(256, dim), nn.SiLU(), nn.Linear(dim, dim))
414
+ self.ada = nn.Linear(dim, 6 * dim)
415
+ self.blocks = nn.ModuleList([DenoiserBlock(dim, heads, text_dim, mlp_hidden) for _ in range(depth)])
416
+ self.norm = RMSNorm(dim)
417
+ self.patch_out = nn.Linear(dim, latent_channels, bias=False)
418
+ cos, sin = build_rope_2d(latent_size, dim // heads)
419
+ self.register_buffer("rope_cos", cos, persistent=False)
420
+ self.register_buffer("rope_sin", sin, persistent=False)
421
+
422
+ def forward(self, z_t: torch.Tensor, t: torch.Tensor, text: torch.Tensor, text_valid: torch.Tensor):
423
+ b, c, h, w = z_t.shape
424
+ dim = self.config.dim
425
+ x = self.patch_in(z_t.flatten(2).transpose(1, 2))
426
+ t_emb = self.time_mlp(timestep_embedding(t, 256))
427
+ t6 = self.ada(F.silu(t_emb)).view(b, 6, dim)
428
+ for block in self.blocks:
429
+ x = block(x, t6, text, text_valid, self.rope_cos, self.rope_sin)
430
+ return self.patch_out(self.norm(x)).transpose(1, 2).reshape(b, c, h, w)
431
+
432
+
433
+ # =============================================================================
434
+ # Pipeline
435
+ # =============================================================================
436
+ class AuroraIMG2Pipeline(DiffusionPipeline):
437
+ """Text-to-image pipeline (256x256). Usage:
438
+
439
+ pipe = DiffusionPipeline.from_pretrained(repo, custom_pipeline=repo, trust_remote_code=True)
440
+ image = pipe("a red fox in the snow", num_inference_steps=50, guidance_scale=3.0).images[0]
441
+ """
442
+
443
+ def __init__(self, tokenizer, text_encoder, transformer, vae):
444
+ super().__init__()
445
+ self.register_modules(tokenizer=tokenizer, text_encoder=text_encoder, transformer=transformer, vae=vae)
446
+
447
+ # ---- helpers -------------------------------------------------------------------------------
448
+ def _autocast(self):
449
+ device = self._execution_device
450
+ if device.type == "cuda":
451
+ dtype = torch.bfloat16 if torch.cuda.is_bf16_supported() else torch.float16
452
+ return torch.autocast(device_type="cuda", dtype=dtype)
453
+ return contextlib.nullcontext()
454
+
455
+ def _is_lm(self) -> bool:
456
+ return getattr(self.text_encoder.config, "kind", "") == "lm"
457
+
458
+ def _tokenize(self, texts: List[str]) -> torch.Tensor:
459
+ """[BOS] + BPE ids (max 62) + [EOS], padded with [PAD] to max_tokens. Same as in training."""
460
+ cfg = self.text_encoder.config
461
+ if self._is_lm(): # [BOS] ids[:max-1] [PAD]..., no leading space (prepare_data.py --encoder slayer149)
462
+ cleaned = [" ".join(str(t).split()) for t in texts]
463
+ encoded = self.tokenizer(cleaned, add_special_tokens=False)["input_ids"]
464
+ rows = torch.full((len(cleaned), int(cfg.max_tokens)), int(cfg.pad_id), dtype=torch.long)
465
+ for i, ids in enumerate(encoded):
466
+ ids = list(ids)[: int(cfg.max_tokens) - 1]
467
+ rows[i, 0] = int(cfg.bos_id)
468
+ if ids:
469
+ rows[i, 1:1 + len(ids)] = torch.tensor(ids, dtype=torch.long)
470
+ return rows
471
+ max_len = int(cfg.max_tokens)
472
+ cleaned = [" ".join(str(t).split()) for t in texts]
473
+ # The training tokenizer uses ByteLevel(add_prefix_space=True), i.e. every caption was tokenised with a
474
+ # leading space. The transformers wrapper can drop that setting when the tokenizer is saved/reloaded, so the
475
+ # space is added explicitly here (a no-op if the backend already adds it).
476
+ cleaned = [(" " + t) if t else t for t in cleaned]
477
+ encoded = self.tokenizer(cleaned, add_special_tokens=False)["input_ids"]
478
+ rows = torch.full((len(cleaned), max_len), int(cfg.pad_id), dtype=torch.long)
479
+ for i, ids in enumerate(encoded):
480
+ ids = list(ids)[: max_len - 2]
481
+ rows[i, 0] = int(cfg.bos_id)
482
+ if ids:
483
+ rows[i, 1:1 + len(ids)] = torch.tensor(ids, dtype=torch.long)
484
+ rows[i, 1 + len(ids)] = int(cfg.eos_id)
485
+ return rows
486
+
487
+ def _null_ids(self, batch: int) -> torch.Tensor:
488
+ """The learned empty prompt used for classifier-free guidance: [BOS] [NULL] [EOS] [PAD]..."""
489
+ cfg = self.text_encoder.config
490
+ row = torch.full((int(cfg.max_tokens),), int(cfg.pad_id), dtype=torch.long)
491
+ if self._is_lm():
492
+ row[0] = int(cfg.null_id)
493
+ return row[None, :].repeat(batch, 1)
494
+ row[0], row[1], row[2] = int(cfg.bos_id), int(cfg.null_id), int(cfg.eos_id)
495
+ return row[None, :].repeat(batch, 1)
496
+
497
+ def encode_prompt(self, prompts: List[str], negative_prompts: Optional[List[str]], do_cfg: bool, device):
498
+ ids = self._tokenize(prompts).to(device)
499
+ with self._autocast():
500
+ cond, cond_valid = self.text_encoder(ids)
501
+ if not do_cfg:
502
+ return cond, cond_valid
503
+ if negative_prompts is None:
504
+ uncond_ids = self._null_ids(len(prompts))
505
+ else:
506
+ uncond_ids = self._tokenize(negative_prompts)
507
+ uncond, uncond_valid = self.text_encoder(uncond_ids.to(device))
508
+ # unconditional first, conditional second (the order the model was evaluated with)
509
+ return torch.cat([uncond, cond], dim=0), torch.cat([uncond_valid, cond_valid], dim=0)
510
+
511
+ @torch.no_grad()
512
+ def decode_latents(self, latents: torch.Tensor, chunk_size: int = 32) -> torch.Tensor:
513
+ """Model latents (B,128,16,16) -> images in [-1, 1] via the FLUX.2 small decoder."""
514
+ vae = self.vae
515
+ eps = float(getattr(vae.config, "batch_norm_eps", 1e-4))
516
+ mean = vae.bn.running_mean.detach().float().view(1, -1, 1, 1).to(latents.device)
517
+ std = torch.sqrt(vae.bn.running_var.detach().float() + eps).view(1, -1, 1, 1).to(latents.device)
518
+ unpatchify = UNPATCHIFY_LAYOUTS[self.transformer.config.latent_layout]
519
+ outs = []
520
+ for i in range(0, latents.shape[0], chunk_size):
521
+ z = latents[i:i + chunk_size].float() * std + mean
522
+ z = unpatchify(z)
523
+ outs.append(vae.decode(z.to(vae.dtype)).sample.float().clamp(-1, 1))
524
+ return torch.cat(outs, dim=0)
525
+
526
+ # ---- main entry point ----------------------------------------------------------------------
527
+ @torch.no_grad()
528
+ def __call__(
529
+ self,
530
+ prompt: Union[str, List[str]],
531
+ negative_prompt: Optional[Union[str, List[str]]] = None,
532
+ num_inference_steps: int = 50,
533
+ guidance_scale: float = 3.0,
534
+ num_images_per_prompt: int = 1,
535
+ generator: Optional[torch.Generator] = None,
536
+ latents: Optional[torch.Tensor] = None,
537
+ output_type: str = "pil",
538
+ return_dict: bool = True,
539
+ ):
540
+ """
541
+ Args:
542
+ prompt: text prompt(s). English; prompts longer than 62 BPE tokens are truncated.
543
+ negative_prompt: optional. None (default) uses the learned empty prompt the model was trained
544
+ with for classifier-free guidance. Arbitrary negative prompts were not part of training.
545
+ num_inference_steps: Euler steps. 50 = recommended (used for the benchmarks), 20 = fast.
546
+ guidance_scale: classifier-free guidance scale. 3.0 recommended.
547
+ output_type: "pil", "np", "pt" (tensor in [0,1]) or "latent" (raw model latents).
548
+ """
549
+ base = [prompt] if isinstance(prompt, str) else list(prompt)
550
+ prompts = [p for p in base for _ in range(num_images_per_prompt)]
551
+ negs = None
552
+ if negative_prompt is not None:
553
+ neg_base = [negative_prompt] * len(base) if isinstance(negative_prompt, str) else list(negative_prompt)
554
+ if len(neg_base) != len(base):
555
+ raise ValueError("negative_prompt must be a string or have the same length as prompt.")
556
+ negs = [n for n in neg_base for _ in range(num_images_per_prompt)]
557
+
558
+ device = self._execution_device
559
+ b = len(prompts)
560
+ do_cfg = guidance_scale != 1.0
561
+ context, context_valid = self.encode_prompt(prompts, negs, do_cfg, device)
562
+
563
+ tcfg = self.transformer.config
564
+ shape = (b, tcfg.latent_channels, tcfg.latent_size, tcfg.latent_size)
565
+ if latents is None:
566
+ z = randn_tensor(shape, generator=generator, device=device, dtype=torch.float32)
567
+ else:
568
+ z = latents.to(device=device, dtype=torch.float32)
569
+
570
+ ts = torch.linspace(1.0, 0.0, num_inference_steps + 1, device=device)
571
+ for i in self.progress_bar(range(num_inference_steps)):
572
+ t0, t1 = ts[i], ts[i + 1]
573
+ with self._autocast():
574
+ if do_cfg:
575
+ v = self.transformer(torch.cat([z, z], dim=0), t0.expand(2 * b), context, context_valid)
576
+ v_uncond, v_cond = v.chunk(2, dim=0)
577
+ velocity = v_uncond + guidance_scale * (v_cond - v_uncond)
578
+ else:
579
+ velocity = self.transformer(z, t0.expand(b), context, context_valid)
580
+ z = z + (t1 - t0) * velocity.float()
581
+
582
+ if output_type == "latent":
583
+ images = z
584
+ else:
585
+ img = (self.decode_latents(z) + 1.0) / 2.0
586
+ if output_type == "pt":
587
+ images = img
588
+ else:
589
+ arr = img.cpu().permute(0, 2, 3, 1).float().numpy()
590
+ images = self.numpy_to_pil(arr) if output_type == "pil" else arr
591
+
592
+ self.maybe_free_model_hooks()
593
+ if not return_dict:
594
+ return (images,)
595
+ return ImagePipelineOutput(images=images)
samples/grid.png ADDED

Git LFS Details

  • SHA256: 2210cb88995311ad7315a041698a4a71d3328753806a7d0f058fa8950e74cbb2
  • Pointer size: 131 Bytes
  • Size of remote file: 697 kB
text_encoder/LICENSE ADDED
@@ -0,0 +1,202 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ Apache License
3
+ Version 2.0, January 2004
4
+ http://www.apache.org/licenses/
5
+
6
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
7
+
8
+ 1. Definitions.
9
+
10
+ "License" shall mean the terms and conditions for use, reproduction,
11
+ and distribution as defined by Sections 1 through 9 of this document.
12
+
13
+ "Licensor" shall mean the copyright owner or entity authorized by
14
+ the copyright owner that is granting the License.
15
+
16
+ "Legal Entity" shall mean the union of the acting entity and all
17
+ other entities that control, are controlled by, or are under common
18
+ control with that entity. For the purposes of this definition,
19
+ "control" means (i) the power, direct or indirect, to cause the
20
+ direction or management of such entity, whether by contract or
21
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
22
+ outstanding shares, or (iii) beneficial ownership of such entity.
23
+
24
+ "You" (or "Your") shall mean an individual or Legal Entity
25
+ exercising permissions granted by this License.
26
+
27
+ "Source" form shall mean the preferred form for making modifications,
28
+ including but not limited to software source code, documentation
29
+ source, and configuration files.
30
+
31
+ "Object" form shall mean any form resulting from mechanical
32
+ transformation or translation of a Source form, including but
33
+ not limited to compiled object code, generated documentation,
34
+ and conversions to other media types.
35
+
36
+ "Work" shall mean the work of authorship, whether in Source or
37
+ Object form, made available under the License, as indicated by a
38
+ copyright notice that is included in or attached to the work
39
+ (an example is provided in the Appendix below).
40
+
41
+ "Derivative Works" shall mean any work, whether in Source or Object
42
+ form, that is based on (or derived from) the Work and for which the
43
+ editorial revisions, annotations, elaborations, or other modifications
44
+ represent, as a whole, an original work of authorship. For the purposes
45
+ of this License, Derivative Works shall not include works that remain
46
+ separable from, or merely link (or bind by name) to the interfaces of,
47
+ the Work and Derivative Works thereof.
48
+
49
+ "Contribution" shall mean any work of authorship, including
50
+ the original version of the Work and any modifications or additions
51
+ to that Work or Derivative Works thereof, that is intentionally
52
+ submitted to Licensor for inclusion in the Work by the copyright owner
53
+ or by an individual or Legal Entity authorized to submit on behalf of
54
+ the copyright owner. For the purposes of this definition, "submitted"
55
+ means any form of electronic, verbal, or written communication sent
56
+ to the Licensor or its representatives, including but not limited to
57
+ communication on electronic mailing lists, source code control systems,
58
+ and issue tracking systems that are managed by, or on behalf of, the
59
+ Licensor for the purpose of discussing and improving the Work, but
60
+ excluding communication that is conspicuously marked or otherwise
61
+ designated in writing by the copyright owner as "Not a Contribution."
62
+
63
+ "Contributor" shall mean Licensor and any individual or Legal Entity
64
+ on behalf of whom a Contribution has been received by Licensor and
65
+ subsequently incorporated within the Work.
66
+
67
+ 2. Grant of Copyright License. Subject to the terms and conditions of
68
+ this License, each Contributor hereby grants to You a perpetual,
69
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
70
+ copyright license to reproduce, prepare Derivative Works of,
71
+ publicly display, publicly perform, sublicense, and distribute the
72
+ Work and such Derivative Works in Source or Object form.
73
+
74
+ 3. Grant of Patent License. Subject to the terms and conditions of
75
+ this License, each Contributor hereby grants to You a perpetual,
76
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
77
+ (except as stated in this section) patent license to make, have made,
78
+ use, offer to sell, sell, import, and otherwise transfer the Work,
79
+ where such license applies only to those patent claims licensable
80
+ by such Contributor that are necessarily infringed by their
81
+ Contribution(s) alone or by combination of their Contribution(s)
82
+ with the Work to which such Contribution(s) was submitted. If You
83
+ institute patent litigation against any entity (including a
84
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
85
+ or a Contribution incorporated within the Work constitutes direct
86
+ or contributory patent infringement, then any patent licenses
87
+ granted to You under this License for that Work shall terminate
88
+ as of the date such litigation is filed.
89
+
90
+ 4. Redistribution. You may reproduce and distribute copies of the
91
+ Work or Derivative Works thereof in any medium, with or without
92
+ modifications, and in Source or Object form, provided that You
93
+ meet the following conditions:
94
+
95
+ (a) You must give any other recipients of the Work or
96
+ Derivative Works a copy of this License; and
97
+
98
+ (b) You must cause any modified files to carry prominent notices
99
+ stating that You changed the files; and
100
+
101
+ (c) You must retain, in the Source form of any Derivative Works
102
+ that You distribute, all copyright, patent, trademark, and
103
+ attribution notices from the Source form of the Work,
104
+ excluding those notices that do not pertain to any part of
105
+ the Derivative Works; and
106
+
107
+ (d) If the Work includes a "NOTICE" text file as part of its
108
+ distribution, then any Derivative Works that You distribute must
109
+ include a readable copy of the attribution notices contained
110
+ within such NOTICE file, excluding those notices that do not
111
+ pertain to any part of the Derivative Works, in at least one
112
+ of the following places: within a NOTICE text file distributed
113
+ as part of the Derivative Works; within the Source form or
114
+ documentation, if provided along with the Derivative Works; or,
115
+ within a display generated by the Derivative Works, if and
116
+ wherever such third-party notices normally appear. The contents
117
+ of the NOTICE file are for informational purposes only and
118
+ do not modify the License. You may add Your own attribution
119
+ notices within Derivative Works that You distribute, alongside
120
+ or as an addendum to the NOTICE text from the Work, provided
121
+ that such additional attribution notices cannot be construed
122
+ as modifying the License.
123
+
124
+ You may add Your own copyright statement to Your modifications and
125
+ may provide additional or different license terms and conditions
126
+ for use, reproduction, or distribution of Your modifications, or
127
+ for any such Derivative Works as a whole, provided Your use,
128
+ reproduction, and distribution of the Work otherwise complies with
129
+ the conditions stated in this License.
130
+
131
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
132
+ any Contribution intentionally submitted for inclusion in the Work
133
+ by You to the Licensor shall be under the terms and conditions of
134
+ this License, without any additional terms or conditions.
135
+ Notwithstanding the above, nothing herein shall supersede or modify
136
+ the terms of any separate license agreement you may have executed
137
+ with Licensor regarding such Contributions.
138
+
139
+ 6. Trademarks. This License does not grant permission to use the trade
140
+ names, trademarks, service marks, or product names of the Licensor,
141
+ except as required for reasonable and customary use in describing the
142
+ origin of the Work and reproducing the content of the NOTICE file.
143
+
144
+ 7. Disclaimer of Warranty. Unless required by applicable law or
145
+ agreed to in writing, Licensor provides the Work (and each
146
+ Contributor provides its Contributions) on an "AS IS" BASIS,
147
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
148
+ implied, including, without limitation, any warranties or conditions
149
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
150
+ PARTICULAR PURPOSE. You are solely responsible for determining the
151
+ appropriateness of using or redistributing the Work and assume any
152
+ risks associated with Your exercise of permissions under this License.
153
+
154
+ 8. Limitation of Liability. In no event and under no legal theory,
155
+ whether in tort (including negligence), contract, or otherwise,
156
+ unless required by applicable law (such as deliberate and grossly
157
+ negligent acts) or agreed to in writing, shall any Contributor be
158
+ liable to You for damages, including any direct, indirect, special,
159
+ incidental, or consequential damages of any character arising as a
160
+ result of this License or out of the use or inability to use the
161
+ Work (including but not limited to damages for loss of goodwill,
162
+ work stoppage, computer failure or malfunction, or any and all
163
+ other commercial damages or losses), even if such Contributor
164
+ has been advised of the possibility of such damages.
165
+
166
+ 9. Accepting Warranty or Additional Liability. While redistributing
167
+ the Work or Derivative Works thereof, You may choose to offer,
168
+ and charge a fee for, acceptance of support, warranty, indemnity,
169
+ or other liability obligations and/or rights consistent with this
170
+ License. However, in accepting such obligations, You may act only
171
+ on Your own behalf and on Your sole responsibility, not on behalf
172
+ of any other Contributor, and only if You agree to indemnify,
173
+ defend, and hold each Contributor harmless for any liability
174
+ incurred by, or claims asserted against, such Contributor by reason
175
+ of your accepting any such warranty or additional liability.
176
+
177
+ END OF TERMS AND CONDITIONS
178
+
179
+ APPENDIX: How to apply the Apache License to your work.
180
+
181
+ To apply the Apache License to your work, attach the following
182
+ boilerplate notice, with the fields enclosed by brackets "[]"
183
+ replaced with your own identifying information. (Don't include
184
+ the brackets!) The text should be enclosed in the appropriate
185
+ comment syntax for the file format. We also recommend that a
186
+ file or class name and description of purpose be included on the
187
+ same "printed page" as the copyright notice for easier
188
+ identification within third-party archives.
189
+
190
+ Copyright [yyyy] [name of copyright owner]
191
+
192
+ Licensed under the Apache License, Version 2.0 (the "License");
193
+ you may not use this file except in compliance with the License.
194
+ You may obtain a copy of the License at
195
+
196
+ http://www.apache.org/licenses/LICENSE-2.0
197
+
198
+ Unless required by applicable law or agreed to in writing, software
199
+ distributed under the License is distributed on an "AS IS" BASIS,
200
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
201
+ See the License for the specific language governing permissions and
202
+ limitations under the License.
text_encoder/NOTICE ADDED
@@ -0,0 +1,30 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Slayer149
2
+
3
+ Weights and architecture derive from SlayerLab/gollem-v5-ckpts
4
+ revision 963440ce6ab4ada7e95da4c1faa28ebb00c082d4, published under Apache-2.0.
5
+ modeling_gollem.py extracts the inference classes unchanged from the pinned
6
+ GoLLeM r6 training implementation. The continuation, export and release scripts
7
+ were prepared for this 149M run.
8
+
9
+ The likelihood routines in glint_metrics.py are adapted from
10
+ Glint-Research/Glint-1.3/benchmark.py, revision
11
+ c3f99e246aa2c64382f9668dc864533617482d0a, whose repository declares MIT.
12
+ Copyright remains with the original contributors. MIT license text:
13
+
14
+ Permission is hereby granted, free of charge, to any person obtaining a copy
15
+ of this software and associated documentation files (the "Software"), to deal
16
+ in the Software without restriction, including without limitation the rights
17
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
18
+ copies of the Software, and to permit persons to whom the Software is
19
+ furnished to do so, subject to the following conditions:
20
+
21
+ The above copyright notice and this permission notice shall be included in
22
+ all copies or substantial portions of the Software.
23
+
24
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
25
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
26
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
27
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
28
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
29
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
30
+ THE SOFTWARE.
text_encoder/config.json ADDED
@@ -0,0 +1,24 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "_class_name": "AuroraLMTextEncoder",
3
+ "_diffusers_version": "0.42.1",
4
+ "kind": "lm",
5
+ "vocab_size": 12288,
6
+ "lm_blocks": 18,
7
+ "lm_dim": 768,
8
+ "lm_heads": 12,
9
+ "lm_ffn_mult": 2.8125,
10
+ "lm_norm_eps": 1e-06,
11
+ "lm_rope_theta": 100000.0,
12
+ "lm_value_residual": true,
13
+ "lm_qk_norm": true,
14
+ "dim": 768,
15
+ "depth": 2,
16
+ "heads": 12,
17
+ "kv_heads": 4,
18
+ "head_dim": 64,
19
+ "mlp_hidden": 2048,
20
+ "max_tokens": 96,
21
+ "bos_id": 12285,
22
+ "pad_id": 65535,
23
+ "null_id": 65534
24
+ }
text_encoder/diffusion_pytorch_model.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:6ebdabb5c5cb8a2d8244f3d6ef254f26506df358110216d526bee019d02e2b67
3
+ size 616652324
text_encoder/pipeline.py ADDED
@@ -0,0 +1,595 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """AuroraIMG2-style text-to-image pipeline (load with trust_remote_code=True).
2
+
3
+ Based on the AuroraIMG2-50M-Preview pipeline (Apache-2.0), extended with a second text encoder type:
4
+ * AuroraTextEncoder -> the released from-scratch encoder (unchanged)
5
+ * AuroraLMTextEncoder -> a frozen pretrained causal LM (SlayerLab/Slayer149 GPT, Apache-2.0) + a small bidirectional
6
+ refiner; its LM weights are stored inside text_encoder/ so the repo is self-contained
7
+ Generated by trenowanie_generatora_obrazkow/export.py.
8
+
9
+ Components (all stored in the repo as separate sub-folders):
10
+ * tokenizer -> from-scratch byte-level BPE (8192 tokens), loaded with transformers
11
+ * text_encoder -> AuroraTextEncoder, ~12.7M params, trained from scratch (defined below)
12
+ * transformer -> AuroraDenoiser, ~50M params flow-matching DiT, trained from scratch (defined below)
13
+ * vae -> pretrained FLUX.2 small decoder (AutoencoderKLFlux2, Apache-2.0), frozen
14
+
15
+ Sampling is plain Euler on t = linspace(1, 0, steps + 1) with classifier-free guidance, i.e. exactly
16
+ what was used for the reported benchmark numbers.
17
+
18
+ NOTE: diffusers' custom-pipeline loader expects exactly ONE DiffusionPipeline subclass in this file.
19
+ """
20
+ import contextlib
21
+ import math
22
+ from typing import List, Optional, Union
23
+
24
+ import torch
25
+ import torch.nn as nn
26
+ import torch.nn.functional as F
27
+
28
+ from diffusers import ConfigMixin, DiffusionPipeline, ModelMixin
29
+ from diffusers.configuration_utils import register_to_config
30
+ from diffusers.pipelines.pipeline_utils import ImagePipelineOutput
31
+ from diffusers.utils.torch_utils import randn_tensor
32
+
33
+
34
+ # =============================================================================
35
+ # Building blocks (identical to the training code)
36
+ # =============================================================================
37
+ class RMSNorm(nn.Module):
38
+ def __init__(self, dim: int, eps: float = 1e-6):
39
+ super().__init__()
40
+ self.weight = nn.Parameter(torch.ones(dim))
41
+ self.eps = eps
42
+
43
+ def forward(self, x: torch.Tensor) -> torch.Tensor:
44
+ return x * torch.rsqrt(x.float().pow(2).mean(dim=-1, keepdim=True) + self.eps).to(x.dtype) * self.weight
45
+
46
+
47
+ def rotate_half_pairs(x: torch.Tensor) -> torch.Tensor:
48
+ even = x[..., 0::2]
49
+ odd = x[..., 1::2]
50
+ return torch.stack((-odd, even), dim=-1).flatten(-2)
51
+
52
+
53
+ def _angles(positions: torch.Tensor, dim: int) -> torch.Tensor:
54
+ inv_freq = 1.0 / (10000 ** (torch.arange(0, dim, 2, dtype=torch.float32) / dim))
55
+ return torch.outer(positions.float(), inv_freq)
56
+
57
+
58
+ def build_rope_1d(n: int, d: int):
59
+ ang = _angles(torch.arange(n), d)
60
+ return ang.cos().repeat_interleave(2, dim=-1)[None, None], ang.sin().repeat_interleave(2, dim=-1)[None, None]
61
+
62
+
63
+ def build_rope_2d(side: int, d: int):
64
+ """First half of the head dim encodes the row, second half the column. Tokens are row-major."""
65
+ coords = torch.arange(side)
66
+ rows, cols = torch.meshgrid(coords, coords, indexing="ij")
67
+ ang = torch.cat([_angles(rows.reshape(-1), d // 2), _angles(cols.reshape(-1), d // 2)], dim=-1)
68
+ return ang.cos().repeat_interleave(2, dim=-1)[None, None], ang.sin().repeat_interleave(2, dim=-1)[None, None]
69
+
70
+
71
+ def apply_rope(x: torch.Tensor, cos: torch.Tensor, sin: torch.Tensor) -> torch.Tensor:
72
+ return x * cos.to(x.dtype) + rotate_half_pairs(x) * sin.to(x.dtype)
73
+
74
+
75
+ class SwiGLU(nn.Module):
76
+ def __init__(self, dim: int, hidden: int):
77
+ super().__init__()
78
+ self.in_proj = nn.Linear(dim, hidden * 2, bias=False)
79
+ self.out_proj = nn.Linear(hidden, dim, bias=False)
80
+
81
+ def forward(self, x: torch.Tensor) -> torch.Tensor:
82
+ a, b = self.in_proj(x).chunk(2, dim=-1)
83
+ return self.out_proj(F.silu(a) * b)
84
+
85
+
86
+ class TextAttention(nn.Module):
87
+ def __init__(self, dim: int, heads: int, kv_heads: int, head_dim: int):
88
+ super().__init__()
89
+ self.heads, self.kv_heads, self.head_dim = heads, kv_heads, head_dim
90
+ self.qkv = nn.Linear(dim, (heads + 2 * kv_heads) * head_dim, bias=False)
91
+ self.out = nn.Linear(heads * head_dim, dim, bias=False)
92
+
93
+ def forward(self, x, valid_mask, cos, sin):
94
+ b, t, _ = x.shape
95
+ q, k, v = self.qkv(x).split(
96
+ [self.heads * self.head_dim, self.kv_heads * self.head_dim, self.kv_heads * self.head_dim], dim=-1)
97
+ q = q.view(b, t, self.heads, self.head_dim).transpose(1, 2)
98
+ k = k.view(b, t, self.kv_heads, self.head_dim).transpose(1, 2)
99
+ v = v.view(b, t, self.kv_heads, self.head_dim).transpose(1, 2)
100
+ q, k = apply_rope(q, cos, sin), apply_rope(k, cos, sin)
101
+ rep = self.heads // self.kv_heads
102
+ k, v = k.repeat_interleave(rep, dim=1), v.repeat_interleave(rep, dim=1)
103
+ h = F.scaled_dot_product_attention(q, k, v, attn_mask=valid_mask[:, None, None, :].bool())
104
+ return self.out(h.transpose(1, 2).reshape(b, t, -1))
105
+
106
+
107
+ class TextBlock(nn.Module):
108
+ def __init__(self, dim: int, heads: int, kv_heads: int, head_dim: int, mlp_hidden: int):
109
+ super().__init__()
110
+ self.norm1 = RMSNorm(dim)
111
+ self.attn = TextAttention(dim, heads, kv_heads, head_dim)
112
+ self.norm2 = RMSNorm(dim)
113
+ self.mlp = SwiGLU(dim, mlp_hidden)
114
+
115
+ def forward(self, x, valid_mask, cos, sin):
116
+ x = x + self.attn(self.norm1(x), valid_mask, cos, sin)
117
+ return x + self.mlp(self.norm2(x))
118
+
119
+
120
+ class SelfAttention(nn.Module):
121
+ """Global self-attention over the 16x16 latent tokens with 2D RoPE and QK-norm."""
122
+
123
+ def __init__(self, dim: int, heads: int):
124
+ super().__init__()
125
+ self.heads = heads
126
+ self.head_dim = dim // heads
127
+ self.qkv = nn.Linear(dim, 3 * dim, bias=False)
128
+ self.out = nn.Linear(dim, dim, bias=False)
129
+ self.q_norm = RMSNorm(self.head_dim)
130
+ self.k_norm = RMSNorm(self.head_dim)
131
+
132
+ def forward(self, x, cos, sin):
133
+ b, t, c = x.shape
134
+ q, k, v = self.qkv(x).view(b, t, 3, self.heads, self.head_dim).permute(2, 0, 3, 1, 4).unbind(0)
135
+ q, k = apply_rope(self.q_norm(q), cos, sin), apply_rope(self.k_norm(k), cos, sin)
136
+ h = F.scaled_dot_product_attention(q, k, v)
137
+ return self.out(h.transpose(1, 2).reshape(b, t, c))
138
+
139
+
140
+ class CrossAttention(nn.Module):
141
+ def __init__(self, dim: int, heads: int, text_dim: int):
142
+ super().__init__()
143
+ self.heads = heads
144
+ self.head_dim = dim // heads
145
+ self.q = nn.Linear(dim, dim, bias=False)
146
+ self.kv = nn.Linear(text_dim, 2 * dim, bias=False)
147
+ self.out = nn.Linear(dim, dim, bias=False)
148
+
149
+ def forward(self, x, text, text_valid):
150
+ b, t, c = x.shape
151
+ n = text.shape[1]
152
+ q = self.q(x).view(b, t, self.heads, self.head_dim).transpose(1, 2)
153
+ k, v = self.kv(text).chunk(2, dim=-1)
154
+ k = k.view(b, n, self.heads, self.head_dim).transpose(1, 2)
155
+ v = v.view(b, n, self.heads, self.head_dim).transpose(1, 2)
156
+ h = F.scaled_dot_product_attention(q, k, v, attn_mask=text_valid[:, None, None, :].bool())
157
+ return self.out(h.transpose(1, 2).reshape(b, t, c))
158
+
159
+
160
+ class DenoiserBlock(nn.Module):
161
+ """DiT block with PixArt-style adaLN-single: a shared time->(6*dim) projection plus a per-block table."""
162
+
163
+ def __init__(self, dim: int, heads: int, text_dim: int, mlp_hidden: int):
164
+ super().__init__()
165
+ self.norm1 = RMSNorm(dim)
166
+ self.attn = SelfAttention(dim, heads)
167
+ self.norm2 = RMSNorm(dim)
168
+ self.cross = CrossAttention(dim, heads, text_dim)
169
+ self.norm3 = RMSNorm(dim)
170
+ self.mlp = SwiGLU(dim, mlp_hidden)
171
+ self.mod_table = nn.Parameter(torch.zeros(6, dim))
172
+
173
+ def forward(self, x, t6, text, text_valid, cos, sin):
174
+ shift1, scale1, gate1, shift2, scale2, gate2 = (t6 + self.mod_table[None]).to(x.dtype).unbind(1)
175
+ h = self.norm1(x) * (1 + scale1[:, None]) + shift1[:, None]
176
+ x = x + gate1[:, None] * self.attn(h, cos, sin)
177
+ x = x + self.cross(self.norm2(x), text, text_valid)
178
+ h = self.norm3(x) * (1 + scale2[:, None]) + shift2[:, None]
179
+ return x + gate2[:, None] * self.mlp(h)
180
+
181
+
182
+ def timestep_embedding(t: torch.Tensor, dim: int = 256) -> torch.Tensor:
183
+ t = t.float() * 1000.0
184
+ half = dim // 2
185
+ freqs = torch.exp(-math.log(10000) * torch.arange(half, device=t.device).float() / max(half - 1, 1))
186
+ angles = t[:, None] * freqs[None, :]
187
+ return torch.cat([angles.sin(), angles.cos()], dim=-1)
188
+
189
+
190
+ # =============================================================================
191
+ # Frozen-LM text encoder (GoLLeM / Slayer GPT, inference architecture, Apache-2.0)
192
+ # =============================================================================
193
+ class LMRMSNorm(nn.Module):
194
+ def __init__(self, d, eps=1e-6):
195
+ super().__init__()
196
+ self.weight = nn.Parameter(torch.ones(d))
197
+ self.eps = eps
198
+
199
+ def forward(self, x):
200
+ return x * torch.rsqrt(x.float().pow(2).mean(-1, keepdim=True) + self.eps).to(x.dtype) * self.weight
201
+
202
+
203
+ def lm_apply_rope(x, base=100000.0):
204
+ """Parameter-free RoPE on [B, H, T, D], interleaved convention (even/odd channel pairs)."""
205
+ _, _, T, dim = x.shape
206
+ pos = torch.arange(T, device=x.device, dtype=torch.float32)
207
+ freq = 1.0 / (base ** (torch.arange(0, dim, 2, device=x.device, dtype=torch.float32) / dim))
208
+ ang = torch.outer(pos, freq)
209
+ cos, sin = ang.cos().to(x.dtype)[None, None], ang.sin().to(x.dtype)[None, None]
210
+ even, odd = x[..., ::2], x[..., 1::2]
211
+ return torch.stack((even * cos - odd * sin, even * sin + odd * cos), dim=-1).flatten(-2)
212
+
213
+
214
+ class LMSwiGLU(nn.Module):
215
+ def __init__(self, d, hidden):
216
+ super().__init__()
217
+ self.gate = nn.Linear(d, hidden, bias=False)
218
+ self.up = nn.Linear(d, hidden, bias=False)
219
+ self.down = nn.Linear(hidden, d, bias=False)
220
+
221
+ def forward(self, x):
222
+ return self.down(F.silu(self.gate(x)) * self.up(x))
223
+
224
+
225
+ class LMBlock(nn.Module):
226
+ def __init__(self, d, nh, ffn_mult, eps, rope_theta, value_residual, qk_norm, is_first=False):
227
+ super().__init__()
228
+ self.ln1, self.ln2 = LMRMSNorm(d, eps), LMRMSNorm(d, eps)
229
+ self.qkv = nn.Linear(d, 3 * d)
230
+ self.proj = nn.Linear(d, d)
231
+ self.mlp = LMSwiGLU(d, int(round(ffn_mult * d)))
232
+ self.nh, self.d, self.rope_theta = nh, d, rope_theta
233
+ self.value_residual, self.qk_norm, self.is_first = value_residual, qk_norm, is_first
234
+ if value_residual and not is_first:
235
+ self.vr_lambda = nn.Parameter(torch.zeros(1))
236
+ if qk_norm:
237
+ self.q_norm, self.k_norm = LMRMSNorm(d // nh, eps), LMRMSNorm(d // nh, eps)
238
+
239
+ def forward(self, x, v0=None):
240
+ B, T, D = x.size()
241
+ q, k, v = self.qkv(self.ln1(x)).split(self.d, dim=2)
242
+ hd = D // self.nh
243
+ q, k, v = (t.view(B, T, self.nh, hd).transpose(1, 2) for t in (q, k, v))
244
+ if self.qk_norm:
245
+ q, k = self.q_norm(q), self.k_norm(k)
246
+ q, k = lm_apply_rope(q, self.rope_theta), lm_apply_rope(k, self.rope_theta)
247
+ if self.value_residual:
248
+ if self.is_first:
249
+ v0 = v
250
+ else:
251
+ v = v + self.vr_lambda * v0
252
+ y = F.scaled_dot_product_attention(q, k, v, is_causal=True)
253
+ x = x + self.proj(y.transpose(1, 2).contiguous().view(B, T, D))
254
+ return x + self.mlp(self.ln2(x)), v0
255
+
256
+
257
+ class LMTrunk(nn.Module):
258
+ """Token embedding + the first n blocks of the LM (no final norm, no head)."""
259
+
260
+ def __init__(self, vocab, n_blocks, d, nh, ffn_mult, eps, rope_theta, value_residual, qk_norm):
261
+ super().__init__()
262
+ self.tok = nn.Embedding(vocab, d)
263
+ self.blocks = nn.ModuleList([LMBlock(d, nh, ffn_mult, eps, rope_theta, value_residual, qk_norm, i == 0)
264
+ for i in range(n_blocks)])
265
+
266
+ def forward(self, idx):
267
+ x, v0 = self.tok(idx), None
268
+ for b in self.blocks:
269
+ x, v0 = b(x, v0)
270
+ return x
271
+
272
+
273
+ # =============================================================================
274
+ # FLUX.2 latent packing layouts (used to turn the model's (128,16,16) latents into the decoder's input)
275
+ # =============================================================================
276
+ def _unpatchify_flux2(z): # official FLUX.2 pipeline layout: channel = c*4 + dh*2 + dw
277
+ b, c4, h, w = z.shape
278
+ return z.reshape(b, c4 // 4, 2, 2, h, w).permute(0, 1, 4, 2, 5, 3).reshape(b, c4 // 4, h * 2, w * 2)
279
+
280
+
281
+ def _unpatchify_swap_subpixel(z):
282
+ b, c4, h, w = z.shape
283
+ return z.reshape(b, c4 // 4, 2, 2, h, w).permute(0, 1, 4, 3, 5, 2).reshape(b, c4 // 4, h * 2, w * 2)
284
+
285
+
286
+ def _unpatchify_subpixel_major(z):
287
+ b, c4, h, w = z.shape
288
+ return z.reshape(b, 2, 2, c4 // 4, h, w).permute(0, 3, 4, 1, 5, 2).reshape(b, c4 // 4, h * 2, w * 2)
289
+
290
+
291
+ def _unpatchify_subpixel_major_swapped(z):
292
+ b, c4, h, w = z.shape
293
+ return z.reshape(b, 2, 2, c4 // 4, h, w).permute(0, 3, 4, 2, 5, 1).reshape(b, c4 // 4, h * 2, w * 2)
294
+
295
+
296
+ UNPATCHIFY_LAYOUTS = {
297
+ "flux2_pipeline": _unpatchify_flux2,
298
+ "swap_subpixel": _unpatchify_swap_subpixel,
299
+ "subpixel_major": _unpatchify_subpixel_major,
300
+ "subpixel_major_swapped": _unpatchify_subpixel_major_swapped,
301
+ }
302
+
303
+
304
+ # =============================================================================
305
+ # Diffusers-style models
306
+ # =============================================================================
307
+ class AuroraTextEncoder(ModelMixin, ConfigMixin):
308
+ """Small from-scratch text encoder: GQA transformer with 1D RoPE, SwiGLU and RMSNorm."""
309
+
310
+ @register_to_config
311
+ def __init__(
312
+ self,
313
+ vocab_size: int = 8192,
314
+ dim: int = 384,
315
+ depth: int = 6,
316
+ heads: int = 6,
317
+ kv_heads: int = 2,
318
+ head_dim: int = 64,
319
+ mlp_hidden: int = 1024,
320
+ max_tokens: int = 64,
321
+ pad_id: int = 0,
322
+ bos_id: int = 2,
323
+ eos_id: int = 3,
324
+ null_id: int = 4,
325
+ mask_id: int = 5,
326
+ ):
327
+ super().__init__()
328
+ self.token_embedding = nn.Embedding(vocab_size, dim)
329
+ self.blocks = nn.ModuleList([TextBlock(dim, heads, kv_heads, head_dim, mlp_hidden) for _ in range(depth)])
330
+ self.norm = RMSNorm(dim)
331
+ cos, sin = build_rope_1d(max_tokens, head_dim)
332
+ self.register_buffer("rope_cos", cos, persistent=False)
333
+ self.register_buffer("rope_sin", sin, persistent=False)
334
+
335
+ def forward(self, ids: torch.Tensor):
336
+ valid = ids.ne(self.config.pad_id)
337
+ h = self.token_embedding(ids)
338
+ for block in self.blocks:
339
+ h = block(h, valid, self.rope_cos, self.rope_sin)
340
+ return self.norm(h), valid
341
+
342
+
343
+ class AuroraLMTextEncoder(ModelMixin, ConfigMixin):
344
+ """Frozen causal LM trunk -> RMSNorm (+ learned null prompt) -> bidirectional refiner blocks -> denoiser text tokens.
345
+ Captions: [BOS] ids[:max_tokens-1] [PAD]...; the CFG null prompt is [NULL] [PAD]... (ids outside the LM vocab)."""
346
+
347
+ @register_to_config
348
+ def __init__(
349
+ self,
350
+ kind: str = "lm",
351
+ vocab_size: int = 12288,
352
+ lm_blocks: int = 18, # LM blocks used (the trained run takes the stream before the last of 19)
353
+ lm_dim: int = 768,
354
+ lm_heads: int = 12,
355
+ lm_ffn_mult: float = 2.8125,
356
+ lm_norm_eps: float = 1e-6,
357
+ lm_rope_theta: float = 100000.0,
358
+ lm_value_residual: bool = True,
359
+ lm_qk_norm: bool = True,
360
+ dim: int = 768,
361
+ depth: int = 2,
362
+ heads: int = 12,
363
+ kv_heads: int = 4,
364
+ head_dim: int = 64,
365
+ mlp_hidden: int = 2048,
366
+ max_tokens: int = 96,
367
+ bos_id: int = 12285,
368
+ pad_id: int = 65535,
369
+ null_id: int = 65534,
370
+ ):
371
+ super().__init__()
372
+ self.lm = LMTrunk(vocab_size, lm_blocks, lm_dim, lm_heads, lm_ffn_mult, lm_norm_eps, lm_rope_theta,
373
+ lm_value_residual, lm_qk_norm)
374
+ self.in_norm = RMSNorm(lm_dim)
375
+ self.null = nn.Parameter(torch.zeros(1, 1, lm_dim))
376
+ self.in_proj = nn.Linear(lm_dim, dim, bias=False) if dim != lm_dim else nn.Identity()
377
+ self.blocks = nn.ModuleList([TextBlock(dim, heads, kv_heads, head_dim, mlp_hidden) for _ in range(depth)])
378
+ self.norm = RMSNorm(dim)
379
+ cos, sin = build_rope_1d(max_tokens, head_dim)
380
+ self.register_buffer("rope_cos", cos, persistent=False)
381
+ self.register_buffer("rope_sin", sin, persistent=False)
382
+
383
+ def forward(self, ids: torch.Tensor):
384
+ cfg = self.config
385
+ valid = ids.ne(cfg.pad_id)
386
+ is_null = ids[:, 0].eq(cfg.null_id)
387
+ h = self.lm(ids.masked_fill(~valid | ids.eq(cfg.null_id), 0)) # causal + right padding: pads never seen
388
+ h = self.in_norm(h)
389
+ h = torch.where(is_null[:, None, None], self.null.to(h.dtype).expand_as(h), h)
390
+ h = self.in_proj(h)
391
+ for block in self.blocks:
392
+ h = block(h, valid, self.rope_cos, self.rope_sin)
393
+ return self.norm(h), valid
394
+
395
+
396
+ class AuroraDenoiser(ModelMixin, ConfigMixin):
397
+ """Flow-matching DiT: velocity predictor on (B, 128, 16, 16) FLUX.2 latents, one latent pixel per token."""
398
+
399
+ @register_to_config
400
+ def __init__(
401
+ self,
402
+ latent_channels: int = 128,
403
+ latent_size: int = 16,
404
+ dim: int = 640,
405
+ depth: int = 8,
406
+ heads: int = 10,
407
+ mlp_hidden: int = 1536,
408
+ text_dim: int = 384,
409
+ latent_layout: str = "flux2_pipeline",
410
+ ):
411
+ super().__init__()
412
+ self.patch_in = nn.Linear(latent_channels, dim, bias=False)
413
+ self.time_mlp = nn.Sequential(nn.Linear(256, dim), nn.SiLU(), nn.Linear(dim, dim))
414
+ self.ada = nn.Linear(dim, 6 * dim)
415
+ self.blocks = nn.ModuleList([DenoiserBlock(dim, heads, text_dim, mlp_hidden) for _ in range(depth)])
416
+ self.norm = RMSNorm(dim)
417
+ self.patch_out = nn.Linear(dim, latent_channels, bias=False)
418
+ cos, sin = build_rope_2d(latent_size, dim // heads)
419
+ self.register_buffer("rope_cos", cos, persistent=False)
420
+ self.register_buffer("rope_sin", sin, persistent=False)
421
+
422
+ def forward(self, z_t: torch.Tensor, t: torch.Tensor, text: torch.Tensor, text_valid: torch.Tensor):
423
+ b, c, h, w = z_t.shape
424
+ dim = self.config.dim
425
+ x = self.patch_in(z_t.flatten(2).transpose(1, 2))
426
+ t_emb = self.time_mlp(timestep_embedding(t, 256))
427
+ t6 = self.ada(F.silu(t_emb)).view(b, 6, dim)
428
+ for block in self.blocks:
429
+ x = block(x, t6, text, text_valid, self.rope_cos, self.rope_sin)
430
+ return self.patch_out(self.norm(x)).transpose(1, 2).reshape(b, c, h, w)
431
+
432
+
433
+ # =============================================================================
434
+ # Pipeline
435
+ # =============================================================================
436
+ class AuroraIMG2Pipeline(DiffusionPipeline):
437
+ """Text-to-image pipeline (256x256). Usage:
438
+
439
+ pipe = DiffusionPipeline.from_pretrained(repo, custom_pipeline=repo, trust_remote_code=True)
440
+ image = pipe("a red fox in the snow", num_inference_steps=50, guidance_scale=3.0).images[0]
441
+ """
442
+
443
+ def __init__(self, tokenizer, text_encoder, transformer, vae):
444
+ super().__init__()
445
+ self.register_modules(tokenizer=tokenizer, text_encoder=text_encoder, transformer=transformer, vae=vae)
446
+
447
+ # ---- helpers -------------------------------------------------------------------------------
448
+ def _autocast(self):
449
+ device = self._execution_device
450
+ if device.type == "cuda":
451
+ dtype = torch.bfloat16 if torch.cuda.is_bf16_supported() else torch.float16
452
+ return torch.autocast(device_type="cuda", dtype=dtype)
453
+ return contextlib.nullcontext()
454
+
455
+ def _is_lm(self) -> bool:
456
+ return getattr(self.text_encoder.config, "kind", "") == "lm"
457
+
458
+ def _tokenize(self, texts: List[str]) -> torch.Tensor:
459
+ """[BOS] + BPE ids (max 62) + [EOS], padded with [PAD] to max_tokens. Same as in training."""
460
+ cfg = self.text_encoder.config
461
+ if self._is_lm(): # [BOS] ids[:max-1] [PAD]..., no leading space (prepare_data.py --encoder slayer149)
462
+ cleaned = [" ".join(str(t).split()) for t in texts]
463
+ encoded = self.tokenizer(cleaned, add_special_tokens=False)["input_ids"]
464
+ rows = torch.full((len(cleaned), int(cfg.max_tokens)), int(cfg.pad_id), dtype=torch.long)
465
+ for i, ids in enumerate(encoded):
466
+ ids = list(ids)[: int(cfg.max_tokens) - 1]
467
+ rows[i, 0] = int(cfg.bos_id)
468
+ if ids:
469
+ rows[i, 1:1 + len(ids)] = torch.tensor(ids, dtype=torch.long)
470
+ return rows
471
+ max_len = int(cfg.max_tokens)
472
+ cleaned = [" ".join(str(t).split()) for t in texts]
473
+ # The training tokenizer uses ByteLevel(add_prefix_space=True), i.e. every caption was tokenised with a
474
+ # leading space. The transformers wrapper can drop that setting when the tokenizer is saved/reloaded, so the
475
+ # space is added explicitly here (a no-op if the backend already adds it).
476
+ cleaned = [(" " + t) if t else t for t in cleaned]
477
+ encoded = self.tokenizer(cleaned, add_special_tokens=False)["input_ids"]
478
+ rows = torch.full((len(cleaned), max_len), int(cfg.pad_id), dtype=torch.long)
479
+ for i, ids in enumerate(encoded):
480
+ ids = list(ids)[: max_len - 2]
481
+ rows[i, 0] = int(cfg.bos_id)
482
+ if ids:
483
+ rows[i, 1:1 + len(ids)] = torch.tensor(ids, dtype=torch.long)
484
+ rows[i, 1 + len(ids)] = int(cfg.eos_id)
485
+ return rows
486
+
487
+ def _null_ids(self, batch: int) -> torch.Tensor:
488
+ """The learned empty prompt used for classifier-free guidance: [BOS] [NULL] [EOS] [PAD]..."""
489
+ cfg = self.text_encoder.config
490
+ row = torch.full((int(cfg.max_tokens),), int(cfg.pad_id), dtype=torch.long)
491
+ if self._is_lm():
492
+ row[0] = int(cfg.null_id)
493
+ return row[None, :].repeat(batch, 1)
494
+ row[0], row[1], row[2] = int(cfg.bos_id), int(cfg.null_id), int(cfg.eos_id)
495
+ return row[None, :].repeat(batch, 1)
496
+
497
+ def encode_prompt(self, prompts: List[str], negative_prompts: Optional[List[str]], do_cfg: bool, device):
498
+ ids = self._tokenize(prompts).to(device)
499
+ with self._autocast():
500
+ cond, cond_valid = self.text_encoder(ids)
501
+ if not do_cfg:
502
+ return cond, cond_valid
503
+ if negative_prompts is None:
504
+ uncond_ids = self._null_ids(len(prompts))
505
+ else:
506
+ uncond_ids = self._tokenize(negative_prompts)
507
+ uncond, uncond_valid = self.text_encoder(uncond_ids.to(device))
508
+ # unconditional first, conditional second (the order the model was evaluated with)
509
+ return torch.cat([uncond, cond], dim=0), torch.cat([uncond_valid, cond_valid], dim=0)
510
+
511
+ @torch.no_grad()
512
+ def decode_latents(self, latents: torch.Tensor, chunk_size: int = 32) -> torch.Tensor:
513
+ """Model latents (B,128,16,16) -> images in [-1, 1] via the FLUX.2 small decoder."""
514
+ vae = self.vae
515
+ eps = float(getattr(vae.config, "batch_norm_eps", 1e-4))
516
+ mean = vae.bn.running_mean.detach().float().view(1, -1, 1, 1).to(latents.device)
517
+ std = torch.sqrt(vae.bn.running_var.detach().float() + eps).view(1, -1, 1, 1).to(latents.device)
518
+ unpatchify = UNPATCHIFY_LAYOUTS[self.transformer.config.latent_layout]
519
+ outs = []
520
+ for i in range(0, latents.shape[0], chunk_size):
521
+ z = latents[i:i + chunk_size].float() * std + mean
522
+ z = unpatchify(z)
523
+ outs.append(vae.decode(z.to(vae.dtype)).sample.float().clamp(-1, 1))
524
+ return torch.cat(outs, dim=0)
525
+
526
+ # ---- main entry point ----------------------------------------------------------------------
527
+ @torch.no_grad()
528
+ def __call__(
529
+ self,
530
+ prompt: Union[str, List[str]],
531
+ negative_prompt: Optional[Union[str, List[str]]] = None,
532
+ num_inference_steps: int = 50,
533
+ guidance_scale: float = 3.0,
534
+ num_images_per_prompt: int = 1,
535
+ generator: Optional[torch.Generator] = None,
536
+ latents: Optional[torch.Tensor] = None,
537
+ output_type: str = "pil",
538
+ return_dict: bool = True,
539
+ ):
540
+ """
541
+ Args:
542
+ prompt: text prompt(s). English; prompts longer than 62 BPE tokens are truncated.
543
+ negative_prompt: optional. None (default) uses the learned empty prompt the model was trained
544
+ with for classifier-free guidance. Arbitrary negative prompts were not part of training.
545
+ num_inference_steps: Euler steps. 50 = recommended (used for the benchmarks), 20 = fast.
546
+ guidance_scale: classifier-free guidance scale. 3.0 recommended.
547
+ output_type: "pil", "np", "pt" (tensor in [0,1]) or "latent" (raw model latents).
548
+ """
549
+ base = [prompt] if isinstance(prompt, str) else list(prompt)
550
+ prompts = [p for p in base for _ in range(num_images_per_prompt)]
551
+ negs = None
552
+ if negative_prompt is not None:
553
+ neg_base = [negative_prompt] * len(base) if isinstance(negative_prompt, str) else list(negative_prompt)
554
+ if len(neg_base) != len(base):
555
+ raise ValueError("negative_prompt must be a string or have the same length as prompt.")
556
+ negs = [n for n in neg_base for _ in range(num_images_per_prompt)]
557
+
558
+ device = self._execution_device
559
+ b = len(prompts)
560
+ do_cfg = guidance_scale != 1.0
561
+ context, context_valid = self.encode_prompt(prompts, negs, do_cfg, device)
562
+
563
+ tcfg = self.transformer.config
564
+ shape = (b, tcfg.latent_channels, tcfg.latent_size, tcfg.latent_size)
565
+ if latents is None:
566
+ z = randn_tensor(shape, generator=generator, device=device, dtype=torch.float32)
567
+ else:
568
+ z = latents.to(device=device, dtype=torch.float32)
569
+
570
+ ts = torch.linspace(1.0, 0.0, num_inference_steps + 1, device=device)
571
+ for i in self.progress_bar(range(num_inference_steps)):
572
+ t0, t1 = ts[i], ts[i + 1]
573
+ with self._autocast():
574
+ if do_cfg:
575
+ v = self.transformer(torch.cat([z, z], dim=0), t0.expand(2 * b), context, context_valid)
576
+ v_uncond, v_cond = v.chunk(2, dim=0)
577
+ velocity = v_uncond + guidance_scale * (v_cond - v_uncond)
578
+ else:
579
+ velocity = self.transformer(z, t0.expand(b), context, context_valid)
580
+ z = z + (t1 - t0) * velocity.float()
581
+
582
+ if output_type == "latent":
583
+ images = z
584
+ else:
585
+ img = (self.decode_latents(z) + 1.0) / 2.0
586
+ if output_type == "pt":
587
+ images = img
588
+ else:
589
+ arr = img.cpu().permute(0, 2, 3, 1).float().numpy()
590
+ images = self.numpy_to_pil(arr) if output_type == "pil" else arr
591
+
592
+ self.maybe_free_model_hooks()
593
+ if not return_dict:
594
+ return (images,)
595
+ return ImagePipelineOutput(images=images)
tokenizer/special_tokens_map.json ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ {
2
+ "bos_token": "<|endoftext|>",
3
+ "eos_token": "<|endoftext|>",
4
+ "unk_token": "<|endoftext|>"
5
+ }
tokenizer/tokenizer.json ADDED
The diff for this file is too large to render. See raw diff
 
tokenizer/tokenizer_config.json ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ {
2
+ "tokenizer_class": "PreTrainedTokenizerFast",
3
+ "model_max_length": 1024,
4
+ "bos_token": "<|endoftext|>",
5
+ "eos_token": "<|endoftext|>",
6
+ "unk_token": "<|endoftext|>"
7
+ }
transformer/config.json ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "_class_name": "AuroraDenoiser",
3
+ "_diffusers_version": "0.42.1",
4
+ "depth": 16,
5
+ "dim": 1024,
6
+ "heads": 16,
7
+ "latent_channels": 128,
8
+ "latent_layout": "flux2_pipeline",
9
+ "latent_size": 16,
10
+ "mlp_hidden": 2432,
11
+ "text_dim": 768
12
+ }
transformer/diffusion_pytorch_model.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:c9d0b1f02b32f369b126644af4015db9720746d6e4b3ce0d3fb3e5ed02a86776
3
+ size 1013580384
transformer/pipeline.py ADDED
@@ -0,0 +1,595 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """AuroraIMG2-style text-to-image pipeline (load with trust_remote_code=True).
2
+
3
+ Based on the AuroraIMG2-50M-Preview pipeline (Apache-2.0), extended with a second text encoder type:
4
+ * AuroraTextEncoder -> the released from-scratch encoder (unchanged)
5
+ * AuroraLMTextEncoder -> a frozen pretrained causal LM (SlayerLab/Slayer149 GPT, Apache-2.0) + a small bidirectional
6
+ refiner; its LM weights are stored inside text_encoder/ so the repo is self-contained
7
+ Generated by trenowanie_generatora_obrazkow/export.py.
8
+
9
+ Components (all stored in the repo as separate sub-folders):
10
+ * tokenizer -> from-scratch byte-level BPE (8192 tokens), loaded with transformers
11
+ * text_encoder -> AuroraTextEncoder, ~12.7M params, trained from scratch (defined below)
12
+ * transformer -> AuroraDenoiser, ~50M params flow-matching DiT, trained from scratch (defined below)
13
+ * vae -> pretrained FLUX.2 small decoder (AutoencoderKLFlux2, Apache-2.0), frozen
14
+
15
+ Sampling is plain Euler on t = linspace(1, 0, steps + 1) with classifier-free guidance, i.e. exactly
16
+ what was used for the reported benchmark numbers.
17
+
18
+ NOTE: diffusers' custom-pipeline loader expects exactly ONE DiffusionPipeline subclass in this file.
19
+ """
20
+ import contextlib
21
+ import math
22
+ from typing import List, Optional, Union
23
+
24
+ import torch
25
+ import torch.nn as nn
26
+ import torch.nn.functional as F
27
+
28
+ from diffusers import ConfigMixin, DiffusionPipeline, ModelMixin
29
+ from diffusers.configuration_utils import register_to_config
30
+ from diffusers.pipelines.pipeline_utils import ImagePipelineOutput
31
+ from diffusers.utils.torch_utils import randn_tensor
32
+
33
+
34
+ # =============================================================================
35
+ # Building blocks (identical to the training code)
36
+ # =============================================================================
37
+ class RMSNorm(nn.Module):
38
+ def __init__(self, dim: int, eps: float = 1e-6):
39
+ super().__init__()
40
+ self.weight = nn.Parameter(torch.ones(dim))
41
+ self.eps = eps
42
+
43
+ def forward(self, x: torch.Tensor) -> torch.Tensor:
44
+ return x * torch.rsqrt(x.float().pow(2).mean(dim=-1, keepdim=True) + self.eps).to(x.dtype) * self.weight
45
+
46
+
47
+ def rotate_half_pairs(x: torch.Tensor) -> torch.Tensor:
48
+ even = x[..., 0::2]
49
+ odd = x[..., 1::2]
50
+ return torch.stack((-odd, even), dim=-1).flatten(-2)
51
+
52
+
53
+ def _angles(positions: torch.Tensor, dim: int) -> torch.Tensor:
54
+ inv_freq = 1.0 / (10000 ** (torch.arange(0, dim, 2, dtype=torch.float32) / dim))
55
+ return torch.outer(positions.float(), inv_freq)
56
+
57
+
58
+ def build_rope_1d(n: int, d: int):
59
+ ang = _angles(torch.arange(n), d)
60
+ return ang.cos().repeat_interleave(2, dim=-1)[None, None], ang.sin().repeat_interleave(2, dim=-1)[None, None]
61
+
62
+
63
+ def build_rope_2d(side: int, d: int):
64
+ """First half of the head dim encodes the row, second half the column. Tokens are row-major."""
65
+ coords = torch.arange(side)
66
+ rows, cols = torch.meshgrid(coords, coords, indexing="ij")
67
+ ang = torch.cat([_angles(rows.reshape(-1), d // 2), _angles(cols.reshape(-1), d // 2)], dim=-1)
68
+ return ang.cos().repeat_interleave(2, dim=-1)[None, None], ang.sin().repeat_interleave(2, dim=-1)[None, None]
69
+
70
+
71
+ def apply_rope(x: torch.Tensor, cos: torch.Tensor, sin: torch.Tensor) -> torch.Tensor:
72
+ return x * cos.to(x.dtype) + rotate_half_pairs(x) * sin.to(x.dtype)
73
+
74
+
75
+ class SwiGLU(nn.Module):
76
+ def __init__(self, dim: int, hidden: int):
77
+ super().__init__()
78
+ self.in_proj = nn.Linear(dim, hidden * 2, bias=False)
79
+ self.out_proj = nn.Linear(hidden, dim, bias=False)
80
+
81
+ def forward(self, x: torch.Tensor) -> torch.Tensor:
82
+ a, b = self.in_proj(x).chunk(2, dim=-1)
83
+ return self.out_proj(F.silu(a) * b)
84
+
85
+
86
+ class TextAttention(nn.Module):
87
+ def __init__(self, dim: int, heads: int, kv_heads: int, head_dim: int):
88
+ super().__init__()
89
+ self.heads, self.kv_heads, self.head_dim = heads, kv_heads, head_dim
90
+ self.qkv = nn.Linear(dim, (heads + 2 * kv_heads) * head_dim, bias=False)
91
+ self.out = nn.Linear(heads * head_dim, dim, bias=False)
92
+
93
+ def forward(self, x, valid_mask, cos, sin):
94
+ b, t, _ = x.shape
95
+ q, k, v = self.qkv(x).split(
96
+ [self.heads * self.head_dim, self.kv_heads * self.head_dim, self.kv_heads * self.head_dim], dim=-1)
97
+ q = q.view(b, t, self.heads, self.head_dim).transpose(1, 2)
98
+ k = k.view(b, t, self.kv_heads, self.head_dim).transpose(1, 2)
99
+ v = v.view(b, t, self.kv_heads, self.head_dim).transpose(1, 2)
100
+ q, k = apply_rope(q, cos, sin), apply_rope(k, cos, sin)
101
+ rep = self.heads // self.kv_heads
102
+ k, v = k.repeat_interleave(rep, dim=1), v.repeat_interleave(rep, dim=1)
103
+ h = F.scaled_dot_product_attention(q, k, v, attn_mask=valid_mask[:, None, None, :].bool())
104
+ return self.out(h.transpose(1, 2).reshape(b, t, -1))
105
+
106
+
107
+ class TextBlock(nn.Module):
108
+ def __init__(self, dim: int, heads: int, kv_heads: int, head_dim: int, mlp_hidden: int):
109
+ super().__init__()
110
+ self.norm1 = RMSNorm(dim)
111
+ self.attn = TextAttention(dim, heads, kv_heads, head_dim)
112
+ self.norm2 = RMSNorm(dim)
113
+ self.mlp = SwiGLU(dim, mlp_hidden)
114
+
115
+ def forward(self, x, valid_mask, cos, sin):
116
+ x = x + self.attn(self.norm1(x), valid_mask, cos, sin)
117
+ return x + self.mlp(self.norm2(x))
118
+
119
+
120
+ class SelfAttention(nn.Module):
121
+ """Global self-attention over the 16x16 latent tokens with 2D RoPE and QK-norm."""
122
+
123
+ def __init__(self, dim: int, heads: int):
124
+ super().__init__()
125
+ self.heads = heads
126
+ self.head_dim = dim // heads
127
+ self.qkv = nn.Linear(dim, 3 * dim, bias=False)
128
+ self.out = nn.Linear(dim, dim, bias=False)
129
+ self.q_norm = RMSNorm(self.head_dim)
130
+ self.k_norm = RMSNorm(self.head_dim)
131
+
132
+ def forward(self, x, cos, sin):
133
+ b, t, c = x.shape
134
+ q, k, v = self.qkv(x).view(b, t, 3, self.heads, self.head_dim).permute(2, 0, 3, 1, 4).unbind(0)
135
+ q, k = apply_rope(self.q_norm(q), cos, sin), apply_rope(self.k_norm(k), cos, sin)
136
+ h = F.scaled_dot_product_attention(q, k, v)
137
+ return self.out(h.transpose(1, 2).reshape(b, t, c))
138
+
139
+
140
+ class CrossAttention(nn.Module):
141
+ def __init__(self, dim: int, heads: int, text_dim: int):
142
+ super().__init__()
143
+ self.heads = heads
144
+ self.head_dim = dim // heads
145
+ self.q = nn.Linear(dim, dim, bias=False)
146
+ self.kv = nn.Linear(text_dim, 2 * dim, bias=False)
147
+ self.out = nn.Linear(dim, dim, bias=False)
148
+
149
+ def forward(self, x, text, text_valid):
150
+ b, t, c = x.shape
151
+ n = text.shape[1]
152
+ q = self.q(x).view(b, t, self.heads, self.head_dim).transpose(1, 2)
153
+ k, v = self.kv(text).chunk(2, dim=-1)
154
+ k = k.view(b, n, self.heads, self.head_dim).transpose(1, 2)
155
+ v = v.view(b, n, self.heads, self.head_dim).transpose(1, 2)
156
+ h = F.scaled_dot_product_attention(q, k, v, attn_mask=text_valid[:, None, None, :].bool())
157
+ return self.out(h.transpose(1, 2).reshape(b, t, c))
158
+
159
+
160
+ class DenoiserBlock(nn.Module):
161
+ """DiT block with PixArt-style adaLN-single: a shared time->(6*dim) projection plus a per-block table."""
162
+
163
+ def __init__(self, dim: int, heads: int, text_dim: int, mlp_hidden: int):
164
+ super().__init__()
165
+ self.norm1 = RMSNorm(dim)
166
+ self.attn = SelfAttention(dim, heads)
167
+ self.norm2 = RMSNorm(dim)
168
+ self.cross = CrossAttention(dim, heads, text_dim)
169
+ self.norm3 = RMSNorm(dim)
170
+ self.mlp = SwiGLU(dim, mlp_hidden)
171
+ self.mod_table = nn.Parameter(torch.zeros(6, dim))
172
+
173
+ def forward(self, x, t6, text, text_valid, cos, sin):
174
+ shift1, scale1, gate1, shift2, scale2, gate2 = (t6 + self.mod_table[None]).to(x.dtype).unbind(1)
175
+ h = self.norm1(x) * (1 + scale1[:, None]) + shift1[:, None]
176
+ x = x + gate1[:, None] * self.attn(h, cos, sin)
177
+ x = x + self.cross(self.norm2(x), text, text_valid)
178
+ h = self.norm3(x) * (1 + scale2[:, None]) + shift2[:, None]
179
+ return x + gate2[:, None] * self.mlp(h)
180
+
181
+
182
+ def timestep_embedding(t: torch.Tensor, dim: int = 256) -> torch.Tensor:
183
+ t = t.float() * 1000.0
184
+ half = dim // 2
185
+ freqs = torch.exp(-math.log(10000) * torch.arange(half, device=t.device).float() / max(half - 1, 1))
186
+ angles = t[:, None] * freqs[None, :]
187
+ return torch.cat([angles.sin(), angles.cos()], dim=-1)
188
+
189
+
190
+ # =============================================================================
191
+ # Frozen-LM text encoder (GoLLeM / Slayer GPT, inference architecture, Apache-2.0)
192
+ # =============================================================================
193
+ class LMRMSNorm(nn.Module):
194
+ def __init__(self, d, eps=1e-6):
195
+ super().__init__()
196
+ self.weight = nn.Parameter(torch.ones(d))
197
+ self.eps = eps
198
+
199
+ def forward(self, x):
200
+ return x * torch.rsqrt(x.float().pow(2).mean(-1, keepdim=True) + self.eps).to(x.dtype) * self.weight
201
+
202
+
203
+ def lm_apply_rope(x, base=100000.0):
204
+ """Parameter-free RoPE on [B, H, T, D], interleaved convention (even/odd channel pairs)."""
205
+ _, _, T, dim = x.shape
206
+ pos = torch.arange(T, device=x.device, dtype=torch.float32)
207
+ freq = 1.0 / (base ** (torch.arange(0, dim, 2, device=x.device, dtype=torch.float32) / dim))
208
+ ang = torch.outer(pos, freq)
209
+ cos, sin = ang.cos().to(x.dtype)[None, None], ang.sin().to(x.dtype)[None, None]
210
+ even, odd = x[..., ::2], x[..., 1::2]
211
+ return torch.stack((even * cos - odd * sin, even * sin + odd * cos), dim=-1).flatten(-2)
212
+
213
+
214
+ class LMSwiGLU(nn.Module):
215
+ def __init__(self, d, hidden):
216
+ super().__init__()
217
+ self.gate = nn.Linear(d, hidden, bias=False)
218
+ self.up = nn.Linear(d, hidden, bias=False)
219
+ self.down = nn.Linear(hidden, d, bias=False)
220
+
221
+ def forward(self, x):
222
+ return self.down(F.silu(self.gate(x)) * self.up(x))
223
+
224
+
225
+ class LMBlock(nn.Module):
226
+ def __init__(self, d, nh, ffn_mult, eps, rope_theta, value_residual, qk_norm, is_first=False):
227
+ super().__init__()
228
+ self.ln1, self.ln2 = LMRMSNorm(d, eps), LMRMSNorm(d, eps)
229
+ self.qkv = nn.Linear(d, 3 * d)
230
+ self.proj = nn.Linear(d, d)
231
+ self.mlp = LMSwiGLU(d, int(round(ffn_mult * d)))
232
+ self.nh, self.d, self.rope_theta = nh, d, rope_theta
233
+ self.value_residual, self.qk_norm, self.is_first = value_residual, qk_norm, is_first
234
+ if value_residual and not is_first:
235
+ self.vr_lambda = nn.Parameter(torch.zeros(1))
236
+ if qk_norm:
237
+ self.q_norm, self.k_norm = LMRMSNorm(d // nh, eps), LMRMSNorm(d // nh, eps)
238
+
239
+ def forward(self, x, v0=None):
240
+ B, T, D = x.size()
241
+ q, k, v = self.qkv(self.ln1(x)).split(self.d, dim=2)
242
+ hd = D // self.nh
243
+ q, k, v = (t.view(B, T, self.nh, hd).transpose(1, 2) for t in (q, k, v))
244
+ if self.qk_norm:
245
+ q, k = self.q_norm(q), self.k_norm(k)
246
+ q, k = lm_apply_rope(q, self.rope_theta), lm_apply_rope(k, self.rope_theta)
247
+ if self.value_residual:
248
+ if self.is_first:
249
+ v0 = v
250
+ else:
251
+ v = v + self.vr_lambda * v0
252
+ y = F.scaled_dot_product_attention(q, k, v, is_causal=True)
253
+ x = x + self.proj(y.transpose(1, 2).contiguous().view(B, T, D))
254
+ return x + self.mlp(self.ln2(x)), v0
255
+
256
+
257
+ class LMTrunk(nn.Module):
258
+ """Token embedding + the first n blocks of the LM (no final norm, no head)."""
259
+
260
+ def __init__(self, vocab, n_blocks, d, nh, ffn_mult, eps, rope_theta, value_residual, qk_norm):
261
+ super().__init__()
262
+ self.tok = nn.Embedding(vocab, d)
263
+ self.blocks = nn.ModuleList([LMBlock(d, nh, ffn_mult, eps, rope_theta, value_residual, qk_norm, i == 0)
264
+ for i in range(n_blocks)])
265
+
266
+ def forward(self, idx):
267
+ x, v0 = self.tok(idx), None
268
+ for b in self.blocks:
269
+ x, v0 = b(x, v0)
270
+ return x
271
+
272
+
273
+ # =============================================================================
274
+ # FLUX.2 latent packing layouts (used to turn the model's (128,16,16) latents into the decoder's input)
275
+ # =============================================================================
276
+ def _unpatchify_flux2(z): # official FLUX.2 pipeline layout: channel = c*4 + dh*2 + dw
277
+ b, c4, h, w = z.shape
278
+ return z.reshape(b, c4 // 4, 2, 2, h, w).permute(0, 1, 4, 2, 5, 3).reshape(b, c4 // 4, h * 2, w * 2)
279
+
280
+
281
+ def _unpatchify_swap_subpixel(z):
282
+ b, c4, h, w = z.shape
283
+ return z.reshape(b, c4 // 4, 2, 2, h, w).permute(0, 1, 4, 3, 5, 2).reshape(b, c4 // 4, h * 2, w * 2)
284
+
285
+
286
+ def _unpatchify_subpixel_major(z):
287
+ b, c4, h, w = z.shape
288
+ return z.reshape(b, 2, 2, c4 // 4, h, w).permute(0, 3, 4, 1, 5, 2).reshape(b, c4 // 4, h * 2, w * 2)
289
+
290
+
291
+ def _unpatchify_subpixel_major_swapped(z):
292
+ b, c4, h, w = z.shape
293
+ return z.reshape(b, 2, 2, c4 // 4, h, w).permute(0, 3, 4, 2, 5, 1).reshape(b, c4 // 4, h * 2, w * 2)
294
+
295
+
296
+ UNPATCHIFY_LAYOUTS = {
297
+ "flux2_pipeline": _unpatchify_flux2,
298
+ "swap_subpixel": _unpatchify_swap_subpixel,
299
+ "subpixel_major": _unpatchify_subpixel_major,
300
+ "subpixel_major_swapped": _unpatchify_subpixel_major_swapped,
301
+ }
302
+
303
+
304
+ # =============================================================================
305
+ # Diffusers-style models
306
+ # =============================================================================
307
+ class AuroraTextEncoder(ModelMixin, ConfigMixin):
308
+ """Small from-scratch text encoder: GQA transformer with 1D RoPE, SwiGLU and RMSNorm."""
309
+
310
+ @register_to_config
311
+ def __init__(
312
+ self,
313
+ vocab_size: int = 8192,
314
+ dim: int = 384,
315
+ depth: int = 6,
316
+ heads: int = 6,
317
+ kv_heads: int = 2,
318
+ head_dim: int = 64,
319
+ mlp_hidden: int = 1024,
320
+ max_tokens: int = 64,
321
+ pad_id: int = 0,
322
+ bos_id: int = 2,
323
+ eos_id: int = 3,
324
+ null_id: int = 4,
325
+ mask_id: int = 5,
326
+ ):
327
+ super().__init__()
328
+ self.token_embedding = nn.Embedding(vocab_size, dim)
329
+ self.blocks = nn.ModuleList([TextBlock(dim, heads, kv_heads, head_dim, mlp_hidden) for _ in range(depth)])
330
+ self.norm = RMSNorm(dim)
331
+ cos, sin = build_rope_1d(max_tokens, head_dim)
332
+ self.register_buffer("rope_cos", cos, persistent=False)
333
+ self.register_buffer("rope_sin", sin, persistent=False)
334
+
335
+ def forward(self, ids: torch.Tensor):
336
+ valid = ids.ne(self.config.pad_id)
337
+ h = self.token_embedding(ids)
338
+ for block in self.blocks:
339
+ h = block(h, valid, self.rope_cos, self.rope_sin)
340
+ return self.norm(h), valid
341
+
342
+
343
+ class AuroraLMTextEncoder(ModelMixin, ConfigMixin):
344
+ """Frozen causal LM trunk -> RMSNorm (+ learned null prompt) -> bidirectional refiner blocks -> denoiser text tokens.
345
+ Captions: [BOS] ids[:max_tokens-1] [PAD]...; the CFG null prompt is [NULL] [PAD]... (ids outside the LM vocab)."""
346
+
347
+ @register_to_config
348
+ def __init__(
349
+ self,
350
+ kind: str = "lm",
351
+ vocab_size: int = 12288,
352
+ lm_blocks: int = 18, # LM blocks used (the trained run takes the stream before the last of 19)
353
+ lm_dim: int = 768,
354
+ lm_heads: int = 12,
355
+ lm_ffn_mult: float = 2.8125,
356
+ lm_norm_eps: float = 1e-6,
357
+ lm_rope_theta: float = 100000.0,
358
+ lm_value_residual: bool = True,
359
+ lm_qk_norm: bool = True,
360
+ dim: int = 768,
361
+ depth: int = 2,
362
+ heads: int = 12,
363
+ kv_heads: int = 4,
364
+ head_dim: int = 64,
365
+ mlp_hidden: int = 2048,
366
+ max_tokens: int = 96,
367
+ bos_id: int = 12285,
368
+ pad_id: int = 65535,
369
+ null_id: int = 65534,
370
+ ):
371
+ super().__init__()
372
+ self.lm = LMTrunk(vocab_size, lm_blocks, lm_dim, lm_heads, lm_ffn_mult, lm_norm_eps, lm_rope_theta,
373
+ lm_value_residual, lm_qk_norm)
374
+ self.in_norm = RMSNorm(lm_dim)
375
+ self.null = nn.Parameter(torch.zeros(1, 1, lm_dim))
376
+ self.in_proj = nn.Linear(lm_dim, dim, bias=False) if dim != lm_dim else nn.Identity()
377
+ self.blocks = nn.ModuleList([TextBlock(dim, heads, kv_heads, head_dim, mlp_hidden) for _ in range(depth)])
378
+ self.norm = RMSNorm(dim)
379
+ cos, sin = build_rope_1d(max_tokens, head_dim)
380
+ self.register_buffer("rope_cos", cos, persistent=False)
381
+ self.register_buffer("rope_sin", sin, persistent=False)
382
+
383
+ def forward(self, ids: torch.Tensor):
384
+ cfg = self.config
385
+ valid = ids.ne(cfg.pad_id)
386
+ is_null = ids[:, 0].eq(cfg.null_id)
387
+ h = self.lm(ids.masked_fill(~valid | ids.eq(cfg.null_id), 0)) # causal + right padding: pads never seen
388
+ h = self.in_norm(h)
389
+ h = torch.where(is_null[:, None, None], self.null.to(h.dtype).expand_as(h), h)
390
+ h = self.in_proj(h)
391
+ for block in self.blocks:
392
+ h = block(h, valid, self.rope_cos, self.rope_sin)
393
+ return self.norm(h), valid
394
+
395
+
396
+ class AuroraDenoiser(ModelMixin, ConfigMixin):
397
+ """Flow-matching DiT: velocity predictor on (B, 128, 16, 16) FLUX.2 latents, one latent pixel per token."""
398
+
399
+ @register_to_config
400
+ def __init__(
401
+ self,
402
+ latent_channels: int = 128,
403
+ latent_size: int = 16,
404
+ dim: int = 640,
405
+ depth: int = 8,
406
+ heads: int = 10,
407
+ mlp_hidden: int = 1536,
408
+ text_dim: int = 384,
409
+ latent_layout: str = "flux2_pipeline",
410
+ ):
411
+ super().__init__()
412
+ self.patch_in = nn.Linear(latent_channels, dim, bias=False)
413
+ self.time_mlp = nn.Sequential(nn.Linear(256, dim), nn.SiLU(), nn.Linear(dim, dim))
414
+ self.ada = nn.Linear(dim, 6 * dim)
415
+ self.blocks = nn.ModuleList([DenoiserBlock(dim, heads, text_dim, mlp_hidden) for _ in range(depth)])
416
+ self.norm = RMSNorm(dim)
417
+ self.patch_out = nn.Linear(dim, latent_channels, bias=False)
418
+ cos, sin = build_rope_2d(latent_size, dim // heads)
419
+ self.register_buffer("rope_cos", cos, persistent=False)
420
+ self.register_buffer("rope_sin", sin, persistent=False)
421
+
422
+ def forward(self, z_t: torch.Tensor, t: torch.Tensor, text: torch.Tensor, text_valid: torch.Tensor):
423
+ b, c, h, w = z_t.shape
424
+ dim = self.config.dim
425
+ x = self.patch_in(z_t.flatten(2).transpose(1, 2))
426
+ t_emb = self.time_mlp(timestep_embedding(t, 256))
427
+ t6 = self.ada(F.silu(t_emb)).view(b, 6, dim)
428
+ for block in self.blocks:
429
+ x = block(x, t6, text, text_valid, self.rope_cos, self.rope_sin)
430
+ return self.patch_out(self.norm(x)).transpose(1, 2).reshape(b, c, h, w)
431
+
432
+
433
+ # =============================================================================
434
+ # Pipeline
435
+ # =============================================================================
436
+ class AuroraIMG2Pipeline(DiffusionPipeline):
437
+ """Text-to-image pipeline (256x256). Usage:
438
+
439
+ pipe = DiffusionPipeline.from_pretrained(repo, custom_pipeline=repo, trust_remote_code=True)
440
+ image = pipe("a red fox in the snow", num_inference_steps=50, guidance_scale=3.0).images[0]
441
+ """
442
+
443
+ def __init__(self, tokenizer, text_encoder, transformer, vae):
444
+ super().__init__()
445
+ self.register_modules(tokenizer=tokenizer, text_encoder=text_encoder, transformer=transformer, vae=vae)
446
+
447
+ # ---- helpers -------------------------------------------------------------------------------
448
+ def _autocast(self):
449
+ device = self._execution_device
450
+ if device.type == "cuda":
451
+ dtype = torch.bfloat16 if torch.cuda.is_bf16_supported() else torch.float16
452
+ return torch.autocast(device_type="cuda", dtype=dtype)
453
+ return contextlib.nullcontext()
454
+
455
+ def _is_lm(self) -> bool:
456
+ return getattr(self.text_encoder.config, "kind", "") == "lm"
457
+
458
+ def _tokenize(self, texts: List[str]) -> torch.Tensor:
459
+ """[BOS] + BPE ids (max 62) + [EOS], padded with [PAD] to max_tokens. Same as in training."""
460
+ cfg = self.text_encoder.config
461
+ if self._is_lm(): # [BOS] ids[:max-1] [PAD]..., no leading space (prepare_data.py --encoder slayer149)
462
+ cleaned = [" ".join(str(t).split()) for t in texts]
463
+ encoded = self.tokenizer(cleaned, add_special_tokens=False)["input_ids"]
464
+ rows = torch.full((len(cleaned), int(cfg.max_tokens)), int(cfg.pad_id), dtype=torch.long)
465
+ for i, ids in enumerate(encoded):
466
+ ids = list(ids)[: int(cfg.max_tokens) - 1]
467
+ rows[i, 0] = int(cfg.bos_id)
468
+ if ids:
469
+ rows[i, 1:1 + len(ids)] = torch.tensor(ids, dtype=torch.long)
470
+ return rows
471
+ max_len = int(cfg.max_tokens)
472
+ cleaned = [" ".join(str(t).split()) for t in texts]
473
+ # The training tokenizer uses ByteLevel(add_prefix_space=True), i.e. every caption was tokenised with a
474
+ # leading space. The transformers wrapper can drop that setting when the tokenizer is saved/reloaded, so the
475
+ # space is added explicitly here (a no-op if the backend already adds it).
476
+ cleaned = [(" " + t) if t else t for t in cleaned]
477
+ encoded = self.tokenizer(cleaned, add_special_tokens=False)["input_ids"]
478
+ rows = torch.full((len(cleaned), max_len), int(cfg.pad_id), dtype=torch.long)
479
+ for i, ids in enumerate(encoded):
480
+ ids = list(ids)[: max_len - 2]
481
+ rows[i, 0] = int(cfg.bos_id)
482
+ if ids:
483
+ rows[i, 1:1 + len(ids)] = torch.tensor(ids, dtype=torch.long)
484
+ rows[i, 1 + len(ids)] = int(cfg.eos_id)
485
+ return rows
486
+
487
+ def _null_ids(self, batch: int) -> torch.Tensor:
488
+ """The learned empty prompt used for classifier-free guidance: [BOS] [NULL] [EOS] [PAD]..."""
489
+ cfg = self.text_encoder.config
490
+ row = torch.full((int(cfg.max_tokens),), int(cfg.pad_id), dtype=torch.long)
491
+ if self._is_lm():
492
+ row[0] = int(cfg.null_id)
493
+ return row[None, :].repeat(batch, 1)
494
+ row[0], row[1], row[2] = int(cfg.bos_id), int(cfg.null_id), int(cfg.eos_id)
495
+ return row[None, :].repeat(batch, 1)
496
+
497
+ def encode_prompt(self, prompts: List[str], negative_prompts: Optional[List[str]], do_cfg: bool, device):
498
+ ids = self._tokenize(prompts).to(device)
499
+ with self._autocast():
500
+ cond, cond_valid = self.text_encoder(ids)
501
+ if not do_cfg:
502
+ return cond, cond_valid
503
+ if negative_prompts is None:
504
+ uncond_ids = self._null_ids(len(prompts))
505
+ else:
506
+ uncond_ids = self._tokenize(negative_prompts)
507
+ uncond, uncond_valid = self.text_encoder(uncond_ids.to(device))
508
+ # unconditional first, conditional second (the order the model was evaluated with)
509
+ return torch.cat([uncond, cond], dim=0), torch.cat([uncond_valid, cond_valid], dim=0)
510
+
511
+ @torch.no_grad()
512
+ def decode_latents(self, latents: torch.Tensor, chunk_size: int = 32) -> torch.Tensor:
513
+ """Model latents (B,128,16,16) -> images in [-1, 1] via the FLUX.2 small decoder."""
514
+ vae = self.vae
515
+ eps = float(getattr(vae.config, "batch_norm_eps", 1e-4))
516
+ mean = vae.bn.running_mean.detach().float().view(1, -1, 1, 1).to(latents.device)
517
+ std = torch.sqrt(vae.bn.running_var.detach().float() + eps).view(1, -1, 1, 1).to(latents.device)
518
+ unpatchify = UNPATCHIFY_LAYOUTS[self.transformer.config.latent_layout]
519
+ outs = []
520
+ for i in range(0, latents.shape[0], chunk_size):
521
+ z = latents[i:i + chunk_size].float() * std + mean
522
+ z = unpatchify(z)
523
+ outs.append(vae.decode(z.to(vae.dtype)).sample.float().clamp(-1, 1))
524
+ return torch.cat(outs, dim=0)
525
+
526
+ # ---- main entry point ----------------------------------------------------------------------
527
+ @torch.no_grad()
528
+ def __call__(
529
+ self,
530
+ prompt: Union[str, List[str]],
531
+ negative_prompt: Optional[Union[str, List[str]]] = None,
532
+ num_inference_steps: int = 50,
533
+ guidance_scale: float = 3.0,
534
+ num_images_per_prompt: int = 1,
535
+ generator: Optional[torch.Generator] = None,
536
+ latents: Optional[torch.Tensor] = None,
537
+ output_type: str = "pil",
538
+ return_dict: bool = True,
539
+ ):
540
+ """
541
+ Args:
542
+ prompt: text prompt(s). English; prompts longer than 62 BPE tokens are truncated.
543
+ negative_prompt: optional. None (default) uses the learned empty prompt the model was trained
544
+ with for classifier-free guidance. Arbitrary negative prompts were not part of training.
545
+ num_inference_steps: Euler steps. 50 = recommended (used for the benchmarks), 20 = fast.
546
+ guidance_scale: classifier-free guidance scale. 3.0 recommended.
547
+ output_type: "pil", "np", "pt" (tensor in [0,1]) or "latent" (raw model latents).
548
+ """
549
+ base = [prompt] if isinstance(prompt, str) else list(prompt)
550
+ prompts = [p for p in base for _ in range(num_images_per_prompt)]
551
+ negs = None
552
+ if negative_prompt is not None:
553
+ neg_base = [negative_prompt] * len(base) if isinstance(negative_prompt, str) else list(negative_prompt)
554
+ if len(neg_base) != len(base):
555
+ raise ValueError("negative_prompt must be a string or have the same length as prompt.")
556
+ negs = [n for n in neg_base for _ in range(num_images_per_prompt)]
557
+
558
+ device = self._execution_device
559
+ b = len(prompts)
560
+ do_cfg = guidance_scale != 1.0
561
+ context, context_valid = self.encode_prompt(prompts, negs, do_cfg, device)
562
+
563
+ tcfg = self.transformer.config
564
+ shape = (b, tcfg.latent_channels, tcfg.latent_size, tcfg.latent_size)
565
+ if latents is None:
566
+ z = randn_tensor(shape, generator=generator, device=device, dtype=torch.float32)
567
+ else:
568
+ z = latents.to(device=device, dtype=torch.float32)
569
+
570
+ ts = torch.linspace(1.0, 0.0, num_inference_steps + 1, device=device)
571
+ for i in self.progress_bar(range(num_inference_steps)):
572
+ t0, t1 = ts[i], ts[i + 1]
573
+ with self._autocast():
574
+ if do_cfg:
575
+ v = self.transformer(torch.cat([z, z], dim=0), t0.expand(2 * b), context, context_valid)
576
+ v_uncond, v_cond = v.chunk(2, dim=0)
577
+ velocity = v_uncond + guidance_scale * (v_cond - v_uncond)
578
+ else:
579
+ velocity = self.transformer(z, t0.expand(b), context, context_valid)
580
+ z = z + (t1 - t0) * velocity.float()
581
+
582
+ if output_type == "latent":
583
+ images = z
584
+ else:
585
+ img = (self.decode_latents(z) + 1.0) / 2.0
586
+ if output_type == "pt":
587
+ images = img
588
+ else:
589
+ arr = img.cpu().permute(0, 2, 3, 1).float().numpy()
590
+ images = self.numpy_to_pil(arr) if output_type == "pil" else arr
591
+
592
+ self.maybe_free_model_hooks()
593
+ if not return_dict:
594
+ return (images,)
595
+ return ImagePipelineOutput(images=images)
vae/config.json ADDED
@@ -0,0 +1,46 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "_class_name": "AutoencoderKLFlux2",
3
+ "_diffusers_version": "0.42.1",
4
+ "_name_or_path": "black-forest-labs/FLUX.2-small-decoder",
5
+ "act_fn": "silu",
6
+ "batch_norm_eps": 0.0001,
7
+ "batch_norm_momentum": 0.1,
8
+ "block_out_channels": [
9
+ 128,
10
+ 256,
11
+ 512,
12
+ 512
13
+ ],
14
+ "decoder_block_out_channels": [
15
+ 96,
16
+ 192,
17
+ 384,
18
+ 384
19
+ ],
20
+ "down_block_types": [
21
+ "DownEncoderBlock2D",
22
+ "DownEncoderBlock2D",
23
+ "DownEncoderBlock2D",
24
+ "DownEncoderBlock2D"
25
+ ],
26
+ "force_upcast": true,
27
+ "in_channels": 3,
28
+ "latent_channels": 32,
29
+ "layers_per_block": 2,
30
+ "mid_block_add_attention": true,
31
+ "norm_num_groups": 32,
32
+ "out_channels": 3,
33
+ "patch_size": [
34
+ 2,
35
+ 2
36
+ ],
37
+ "sample_size": 1024,
38
+ "up_block_types": [
39
+ "UpDecoderBlock2D",
40
+ "UpDecoderBlock2D",
41
+ "UpDecoderBlock2D",
42
+ "UpDecoderBlock2D"
43
+ ],
44
+ "use_post_quant_conv": true,
45
+ "use_quant_conv": true
46
+ }
vae/diffusion_pytorch_model.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:377e8be52f1971978b0af970f2c7db9e40e4e0c9e2da0785ec5d0de461e51f04
3
+ size 249521372