jennyxue commited on
Commit
c3f65df
·
verified ·
1 Parent(s): 001eaab

4bit build of VOSR 2.0

Browse files
Files changed (3) hide show
  1. 4bit/config.json +67 -0
  2. 4bit/mlx_model.py +579 -0
  3. 4bit/model.safetensors +3 -0
4bit/config.json ADDED
@@ -0,0 +1,67 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "model_type": "vosr",
3
+ "version": "2.0",
4
+ "dim": 1536,
5
+ "depth": 36,
6
+ "num_heads": 24,
7
+ "mlp_ratio": 4,
8
+ "patch_size": 2,
9
+ "latent_channels": 16,
10
+ "train_tokens": 32,
11
+ "enc_dim": 1024,
12
+ "encdim_ratio": 3,
13
+ "dino_depth": 18,
14
+ "dino_heads": 16,
15
+ "dino_size": 448,
16
+ "dino_patch": 14,
17
+ "vae_base_dim": 96,
18
+ "vae_dim_mult": [
19
+ 1,
20
+ 2,
21
+ 4,
22
+ 4
23
+ ],
24
+ "vae_res_blocks": 2,
25
+ "latents_mean": [
26
+ -0.7571,
27
+ -0.7089,
28
+ -0.9113,
29
+ 0.1075,
30
+ -0.1745,
31
+ 0.9653,
32
+ -0.1517,
33
+ 1.5508,
34
+ 0.4134,
35
+ -0.0715,
36
+ 0.5517,
37
+ -0.3632,
38
+ -0.1922,
39
+ -0.9497,
40
+ 0.2503,
41
+ -0.2921
42
+ ],
43
+ "latents_std": [
44
+ 2.8184,
45
+ 1.4541,
46
+ 2.3275,
47
+ 2.6558,
48
+ 1.2196,
49
+ 1.7708,
50
+ 2.6052,
51
+ 2.0743,
52
+ 3.2687,
53
+ 2.1526,
54
+ 2.8652,
55
+ 1.5579,
56
+ 1.6382,
57
+ 1.1253,
58
+ 2.8251,
59
+ 1.916
60
+ ],
61
+ "quantization": {
62
+ "group_size": 64,
63
+ "bits": 4,
64
+ "mode": "affine"
65
+ },
66
+ "precision": "4bit"
67
+ }
4bit/mlx_model.py ADDED
@@ -0,0 +1,579 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """VOSR 2.0 (cswry/VOSR, Apache-2.0) in MLX: one-step generative super-resolution.
2
+
3
+ The pipeline of inference_vosr_onestep.py (tile_size 0, the default): the low-resolution picture is bicubic-upscaled
4
+ to the output size, encoded by the 2D Qwen-Image VAE, and a 1.4B LightningDiT turns noise into the restored latent in
5
+ one flow step (t 1 -> 0), conditioned on the VAE latent (concatenated) and on DINOv2-L block 18 features of the
6
+ upscaled picture (cross-attention). The VAE decodes, and the wavelet color fix (cv2 GaussianBlur, sigma 5) takes the
7
+ low frequencies from the bicubic upscale.
8
+
9
+ Layout: NHWC throughout. Only the first 18 DINOv2 blocks are kept (VOSR reads block 18 and never the rest); its
10
+ position embedding is stored already interpolated to the 448x448 input VOSR always uses. Norm statistics, the
11
+ timestep embedding and the color fix run in float32.
12
+
13
+ import mlx_model
14
+ model = mlx_model.load("bf16")
15
+ sr = mlx_model.upscale(model, rgb_uint8, scale=4, seed=42)
16
+ """
17
+
18
+ from __future__ import annotations
19
+
20
+ import json
21
+ import math
22
+ from pathlib import Path
23
+
24
+ import mlx.core as mx
25
+ import mlx.nn as nn
26
+ import numpy as np
27
+
28
+ CONFIG = {"model_type": "vosr", "version": "2.0", "dim": 1536, "depth": 36, "num_heads": 24, "mlp_ratio": 4,
29
+ "patch_size": 2, "latent_channels": 16, "train_tokens": 32, "enc_dim": 1024, "encdim_ratio": 3,
30
+ "dino_depth": 18, "dino_heads": 16, "dino_size": 448, "dino_patch": 14, "vae_base_dim": 96,
31
+ "vae_dim_mult": [1, 2, 4, 4], "vae_res_blocks": 2,
32
+ "latents_mean": [-0.7571, -0.7089, -0.9113, 0.1075, -0.1745, 0.9653, -0.1517, 1.5508, 0.4134, -0.0715,
33
+ 0.5517, -0.3632, -0.1922, -0.9497, 0.2503, -0.2921],
34
+ "latents_std": [2.8184, 1.4541, 2.3275, 2.6558, 1.2196, 1.7708, 2.6052, 2.0743, 3.2687, 2.1526, 2.8652,
35
+ 1.5579, 1.6382, 1.1253, 2.8251, 1.916]}
36
+ GROUP = 64
37
+ IMAGENET_MEAN = np.array([0.485, 0.456, 0.406], np.float32)
38
+ IMAGENET_STD = np.array([0.229, 0.224, 0.225], np.float32)
39
+
40
+
41
+ def quantizable(path: str, module) -> bool:
42
+ return isinstance(module, nn.Linear) and module.weight.shape[-1] % GROUP == 0
43
+
44
+
45
+ # ------------------------------------------------------------------------------------------------ ops
46
+
47
+ def rms(x, weight, eps=1e-6):
48
+ y = x.astype(mx.float32)
49
+ y = y * mx.rsqrt(mx.mean(y * y, axis=-1, keepdims=True) + eps)
50
+ return y.astype(x.dtype) * weight
51
+
52
+
53
+ class RMSNorm(nn.Module):
54
+ def __init__(self, dim):
55
+ super().__init__()
56
+ self.weight = mx.ones((dim,))
57
+
58
+ def __call__(self, x):
59
+ return rms(x, self.weight)
60
+
61
+
62
+ def layer_norm(ln, x):
63
+ return mx.fast.layer_norm(x.astype(mx.float32), ln.weight.astype(mx.float32), ln.bias.astype(mx.float32),
64
+ ln.eps).astype(x.dtype)
65
+
66
+
67
+ def bicubic_matrix(n_in: int, n_out: int) -> np.ndarray:
68
+ """torch F.interpolate(mode="bicubic", align_corners=False, antialias=False) along one axis, (n_out, n_in)."""
69
+ A = -0.75
70
+ m = np.zeros((n_out, n_in), np.float64)
71
+ scale = n_in / n_out
72
+ for o in range(n_out):
73
+ s = (o + 0.5) * scale - 0.5
74
+ f = int(np.floor(s))
75
+ t = s - f
76
+ w = [((A * (t + 1) - 5 * A) * (t + 1) + 8 * A) * (t + 1) - 4 * A,
77
+ ((A + 2) * t - (A + 3)) * t * t + 1,
78
+ ((A + 2) * (1 - t) - (A + 3)) * (1 - t) * (1 - t) + 1,
79
+ ((A * (2 - t) - 5 * A) * (2 - t) + 8 * A) * (2 - t) - 4 * A]
80
+ for j in range(4):
81
+ m[o, min(max(f - 1 + j, 0), n_in - 1)] += w[j]
82
+ return m.astype(np.float32)
83
+
84
+
85
+ def bicubic(x, size):
86
+ """(B, H, W, C) float32 -> (B, size[0], size[1], C)."""
87
+ _, h, w, _ = x.shape
88
+ mh, mw = mx.array(bicubic_matrix(h, size[0])), mx.array(bicubic_matrix(w, size[1]))
89
+ y = mx.einsum("oh,bhwc->bowc", mh, x)
90
+ return mx.einsum("pw,bowc->bopc", mw, y)
91
+
92
+
93
+ # ------------------------------------------------------------------------------------------------ Qwen-Image VAE (2D)
94
+
95
+ class RMSNorm2D(nn.Module):
96
+ """F.normalize over channels * sqrt(C) * gamma, in float32."""
97
+
98
+ def __init__(self, dim):
99
+ super().__init__()
100
+ self.gamma = mx.ones((dim,))
101
+
102
+ def __call__(self, x):
103
+ y = x.astype(mx.float32)
104
+ n = mx.sqrt(mx.sum(y * y, axis=-1, keepdims=True))
105
+ y = y / mx.maximum(n, 1e-12) * math.sqrt(y.shape[-1])
106
+ return (y * self.gamma.astype(mx.float32)).astype(x.dtype)
107
+
108
+
109
+ class ResBlock(nn.Module):
110
+ def __init__(self, cin, cout):
111
+ super().__init__()
112
+ self.norm1 = RMSNorm2D(cin)
113
+ self.conv1 = nn.Conv2d(cin, cout, 3, padding=1)
114
+ self.norm2 = RMSNorm2D(cout)
115
+ self.conv2 = nn.Conv2d(cout, cout, 3, padding=1)
116
+ if cin != cout:
117
+ self.conv_shortcut = nn.Conv2d(cin, cout, 1)
118
+
119
+ def __call__(self, x):
120
+ h = self.conv_shortcut(x) if "conv_shortcut" in self else x
121
+ x = self.conv1(nn.silu(self.norm1(x)))
122
+ x = self.conv2(nn.silu(self.norm2(x)))
123
+ return x + h
124
+
125
+
126
+ class VAEAttention(nn.Module):
127
+ def __init__(self, dim):
128
+ super().__init__()
129
+ self.norm = RMSNorm2D(dim)
130
+ self.to_qkv = nn.Conv2d(dim, dim * 3, 1)
131
+ self.proj = nn.Conv2d(dim, dim, 1)
132
+
133
+ def __call__(self, x):
134
+ b, h, w, c = x.shape
135
+ q, k, v = mx.split(self.to_qkv(self.norm(x)).reshape(b, 1, h * w, 3 * c), 3, axis=-1)
136
+ y = mx.fast.scaled_dot_product_attention(q, k, v, scale=c ** -0.5)
137
+ return self.proj(y.reshape(b, h, w, c)) + x
138
+
139
+
140
+ class Down(nn.Module):
141
+ def __init__(self, dim):
142
+ super().__init__()
143
+ self.conv = nn.Conv2d(dim, dim, 3, stride=2)
144
+
145
+ def __call__(self, x):
146
+ return self.conv(mx.pad(x, [(0, 0), (0, 1), (0, 1), (0, 0)]))
147
+
148
+
149
+ class Up(nn.Module):
150
+ def __init__(self, dim):
151
+ super().__init__()
152
+ self.conv = nn.Conv2d(dim, dim // 2, 3, padding=1)
153
+
154
+ def __call__(self, x):
155
+ b, h, w, c = x.shape
156
+ x = mx.broadcast_to(x[:, :, None, :, None, :], (b, h, 2, w, 2, c)).reshape(b, h * 2, w * 2, c)
157
+ return self.conv(x)
158
+
159
+
160
+ class Mid(nn.Module):
161
+ def __init__(self, dim):
162
+ super().__init__()
163
+ self.resnets = [ResBlock(dim, dim), ResBlock(dim, dim)]
164
+ self.attentions = [VAEAttention(dim)]
165
+
166
+ def __call__(self, x):
167
+ return self.resnets[1](self.attentions[0](self.resnets[0](x)))
168
+
169
+
170
+ class Encoder(nn.Module):
171
+ def __init__(self, dim, z_dim, mult, nres):
172
+ super().__init__()
173
+ dims = [dim * u for u in [1] + mult]
174
+ self.conv_in = nn.Conv2d(3, dims[0], 3, padding=1)
175
+ self.down_blocks = []
176
+ for i, (cin, cout) in enumerate(zip(dims[:-1], dims[1:])):
177
+ for _ in range(nres):
178
+ self.down_blocks.append(ResBlock(cin, cout))
179
+ cin = cout
180
+ if i != len(mult) - 1:
181
+ self.down_blocks.append(Down(cout))
182
+ self.mid_block = Mid(cout)
183
+ self.norm_out = RMSNorm2D(cout)
184
+ self.conv_out = nn.Conv2d(cout, z_dim, 3, padding=1)
185
+
186
+ def __call__(self, x):
187
+ x = self.conv_in(x)
188
+ for layer in self.down_blocks:
189
+ x = layer(x)
190
+ return self.conv_out(nn.silu(self.norm_out(self.mid_block(x))))
191
+
192
+
193
+ class UpBlock(nn.Module):
194
+ def __init__(self, cin, cout, nres, up):
195
+ super().__init__()
196
+ self.resnets = [ResBlock(cin if i == 0 else cout, cout) for i in range(nres + 1)]
197
+ if up:
198
+ self.upsamplers = [Up(cout)]
199
+
200
+ def __call__(self, x):
201
+ for r in self.resnets:
202
+ x = r(x)
203
+ return self.upsamplers[0](x) if "upsamplers" in self else x
204
+
205
+
206
+ class Decoder(nn.Module):
207
+ def __init__(self, dim, z_dim, mult, nres):
208
+ super().__init__()
209
+ dims = [dim * u for u in [mult[-1]] + mult[::-1]]
210
+ self.conv_in = nn.Conv2d(z_dim, dims[0], 3, padding=1)
211
+ self.mid_block = Mid(dims[0])
212
+ self.up_blocks = []
213
+ for i, (cin, cout) in enumerate(zip(dims[:-1], dims[1:])):
214
+ self.up_blocks.append(UpBlock(cin // 2 if i > 0 else cin, cout, nres, i != len(mult) - 1))
215
+ self.norm_out = RMSNorm2D(cout)
216
+ self.conv_out = nn.Conv2d(cout, 3, 3, padding=1)
217
+
218
+ def __call__(self, x):
219
+ x = self.mid_block(self.conv_in(x))
220
+ for b in self.up_blocks:
221
+ x = b(x)
222
+ return self.conv_out(nn.silu(self.norm_out(x)))
223
+
224
+
225
+ class VAE(nn.Module):
226
+ def __init__(self, cfg):
227
+ super().__init__()
228
+ d, z, mult, nres = cfg["vae_base_dim"], cfg["latent_channels"], cfg["vae_dim_mult"], cfg["vae_res_blocks"]
229
+ self.encoder = Encoder(d, 2 * z, mult, nres)
230
+ self.quant_conv = nn.Conv2d(2 * z, 2 * z, 1)
231
+ self.post_quant_conv = nn.Conv2d(z, z, 1)
232
+ self.decoder = Decoder(d, z, mult, nres)
233
+ self.z = z
234
+
235
+ def encode(self, x):
236
+ """Pixels in [-1, 1] -> the posterior mode (the mean)."""
237
+ h = self.quant_conv(self.encoder(x.astype(self.quant_conv.weight.dtype)))
238
+ return h[..., :self.z].astype(mx.float32)
239
+
240
+ def decode(self, z):
241
+ x = self.decoder(self.post_quant_conv(z.astype(self.post_quant_conv.weight.dtype)))
242
+ return mx.clip(x.astype(mx.float32), -1, 1)
243
+
244
+
245
+ # ------------------------------------------------------------------------------------------------ DINOv2-L
246
+
247
+ class DinoBlock(nn.Module):
248
+ def __init__(self, dim, heads):
249
+ super().__init__()
250
+ self.heads = heads
251
+ self.norm1 = nn.LayerNorm(dim, eps=1e-6)
252
+ self.qkv = nn.Linear(dim, dim * 3)
253
+ self.proj = nn.Linear(dim, dim)
254
+ self.ls1 = mx.ones((dim,))
255
+ self.norm2 = nn.LayerNorm(dim, eps=1e-6)
256
+ self.fc1 = nn.Linear(dim, dim * 4)
257
+ self.fc2 = nn.Linear(dim * 4, dim)
258
+ self.ls2 = mx.ones((dim,))
259
+
260
+ def __call__(self, x):
261
+ b, n, c = x.shape
262
+ hd = c // self.heads
263
+ qkv = self.qkv(layer_norm(self.norm1, x)).reshape(b, n, 3, self.heads, hd).transpose(2, 0, 3, 1, 4)
264
+ y = mx.fast.scaled_dot_product_attention(qkv[0], qkv[1], qkv[2], scale=hd ** -0.5)
265
+ x = x + self.ls1 * self.proj(y.transpose(0, 2, 1, 3).reshape(b, n, c))
266
+ return x + self.ls2 * self.fc2(nn.gelu(self.fc1(layer_norm(self.norm2, x))))
267
+
268
+
269
+ class Dino(nn.Module):
270
+ def __init__(self, cfg):
271
+ super().__init__()
272
+ d, p = cfg["enc_dim"], cfg["dino_patch"]
273
+ g = cfg["dino_size"] // p
274
+ self.patch_embed = nn.Conv2d(3, d, p, stride=p)
275
+ self.cls_token = mx.zeros((1, 1, d))
276
+ self.pos_embed = mx.zeros((1, 1 + g * g, d))
277
+ self.blocks = [DinoBlock(d, cfg["dino_heads"]) for _ in range(cfg["dino_depth"])]
278
+
279
+ def __call__(self, x):
280
+ """(B, 448, 448, 3) ImageNet-normalized -> (B, 1024, 1024) patch tokens after the last kept block."""
281
+ x = self.patch_embed(x.astype(self.patch_embed.weight.dtype))
282
+ b, h, w, c = x.shape
283
+ x = mx.concatenate([mx.broadcast_to(self.cls_token, (b, 1, c)), x.reshape(b, h * w, c)], axis=1)
284
+ x = x + self.pos_embed
285
+ for blk in self.blocks:
286
+ x = blk(x)
287
+ return x[:, 1:]
288
+
289
+
290
+ # ------------------------------------------------------------------------------------------------ LightningDiT
291
+
292
+ def rope_tables(h, w, head_dim, train_tokens):
293
+ """VisionRotaryEmbeddingFast / _get_dynamic_rope: positions scaled to the 32-token training grid; the first half
294
+ of each head rotates with the row, the second with the column, in interleaved pairs."""
295
+ half = head_dim // 2
296
+ inv = 1.0 / (10000 ** (np.arange(0, half, 2)[:half // 2].astype(np.float32) / half))
297
+
298
+ def axis(n):
299
+ t = np.arange(n, dtype=np.float32) / n * train_tokens
300
+ return np.repeat(np.einsum("i,f->if", t, inv), 2, axis=-1) # (n, half)
301
+
302
+ fh, fw = axis(h), axis(w)
303
+ f = np.concatenate([np.broadcast_to(fh[:, None], (h, w, half)), np.broadcast_to(fw[None], (h, w, half))], -1)
304
+ f = f.reshape(h * w, head_dim)
305
+ return mx.array(np.cos(f)), mx.array(np.sin(f))
306
+
307
+
308
+ def rope(x, cos, sin):
309
+ s = x.shape
310
+ pairs = x.reshape(*s[:-1], s[-1] // 2, 2)
311
+ rot = mx.stack([-pairs[..., 1], pairs[..., 0]], axis=-1).reshape(s)
312
+ return x * cos.astype(x.dtype) + rot * sin.astype(x.dtype)
313
+
314
+
315
+ class SelfAttention(nn.Module):
316
+ def __init__(self, dim, heads):
317
+ super().__init__()
318
+ self.heads = heads
319
+ self.qkv = nn.Linear(dim, dim * 3)
320
+ self.q_norm = RMSNorm(dim // heads)
321
+ self.k_norm = RMSNorm(dim // heads)
322
+ self.proj = nn.Linear(dim, dim)
323
+
324
+ def __call__(self, x, cos, sin):
325
+ b, n, c = x.shape
326
+ hd = c // self.heads
327
+ qkv = self.qkv(x).reshape(b, n, 3, self.heads, hd).transpose(2, 0, 3, 1, 4)
328
+ q, k = rope(self.q_norm(qkv[0]), cos, sin), rope(self.k_norm(qkv[1]), cos, sin)
329
+ y = mx.fast.scaled_dot_product_attention(q, k, qkv[2], scale=hd ** -0.5)
330
+ return self.proj(y.transpose(0, 2, 1, 3).reshape(b, n, c))
331
+
332
+
333
+ class CrossAttention(nn.Module):
334
+ def __init__(self, dim, heads):
335
+ super().__init__()
336
+ self.heads = heads
337
+ self.q_linear = nn.Linear(dim, dim)
338
+ self.k_linear = nn.Linear(dim, dim)
339
+ self.v_linear = nn.Linear(dim, dim)
340
+ self.q_norm = RMSNorm(dim // heads)
341
+ self.k_norm = RMSNorm(dim // heads)
342
+ self.proj = nn.Linear(dim, dim)
343
+
344
+ def __call__(self, x, cond):
345
+ b, n, c = x.shape
346
+ m, hd = cond.shape[1], c // self.heads
347
+ q = self.q_norm(self.q_linear(x).reshape(b, n, self.heads, hd).transpose(0, 2, 1, 3))
348
+ k = self.k_norm(self.k_linear(cond).reshape(b, m, self.heads, hd).transpose(0, 2, 1, 3))
349
+ v = self.v_linear(cond).reshape(b, m, self.heads, hd).transpose(0, 2, 1, 3)
350
+ y = mx.fast.scaled_dot_product_attention(q, k, v, scale=hd ** -0.5)
351
+ return self.proj(y.transpose(0, 2, 1, 3).reshape(b, n, c))
352
+
353
+
354
+ class SwiGLU(nn.Module):
355
+ def __init__(self, dim, hidden):
356
+ super().__init__()
357
+ self.w12 = nn.Linear(dim, 2 * hidden)
358
+ self.w3 = nn.Linear(hidden, dim)
359
+
360
+ def __call__(self, x):
361
+ a, g = mx.split(self.w12(x), 2, axis=-1)
362
+ return self.w3(nn.silu(a) * g)
363
+
364
+
365
+ class Block(nn.Module):
366
+ def __init__(self, dim, heads, mlp_ratio):
367
+ super().__init__()
368
+ self.norm1 = RMSNorm(dim)
369
+ self.norm2 = RMSNorm(dim)
370
+ self.attn = SelfAttention(dim, heads)
371
+ self.mlp = SwiGLU(dim, int(2 / 3 * dim * mlp_ratio))
372
+ self.scale_shift_table = mx.zeros((6, dim))
373
+ self.cross_attn = CrossAttention(dim, heads)
374
+
375
+ def __call__(self, x, c0, z, cos, sin):
376
+ b = x.shape[0]
377
+ mod = self.scale_shift_table[None] + c0.reshape(b, 6, -1)
378
+ sh1, sc1, g1, sh2, sc2, g2 = [mod[:, i:i + 1] for i in range(6)]
379
+ x = x + g1 * self.attn(self.norm1(x) * (1 + sc1) + sh1, cos, sin)
380
+ x = x + self.cross_attn(x, z)
381
+ return x + g2 * self.mlp(self.norm2(x) * (1 + sc2) + sh2)
382
+
383
+
384
+ class TimestepEmbedder(nn.Module):
385
+ def __init__(self, dim, freq=256):
386
+ super().__init__()
387
+ self.freq = freq
388
+ self.fc1 = nn.Linear(freq, dim)
389
+ self.fc2 = nn.Linear(dim, dim)
390
+
391
+ def __call__(self, t):
392
+ half = self.freq // 2
393
+ freqs = mx.exp(-math.log(10000) * mx.arange(half, dtype=mx.float32) / half)
394
+ a = t.astype(mx.float32)[:, None] * freqs[None]
395
+ e = mx.concatenate([mx.cos(a), mx.sin(a)], axis=-1).astype(self.fc1.bias.dtype)
396
+ return self.fc2(nn.silu(self.fc1(e)))
397
+
398
+
399
+ class DiT(nn.Module):
400
+ def __init__(self, cfg):
401
+ super().__init__()
402
+ d, p, lc = cfg["dim"], cfg["patch_size"], cfg["latent_channels"]
403
+ self.cfg = cfg
404
+ self.x_embedder = nn.Conv2d(2 * lc, d, p, stride=p)
405
+ self.t_embedder = TimestepEmbedder(d)
406
+ self.t_block = nn.Linear(d, 6 * d)
407
+ self.blocks = [Block(d, cfg["num_heads"], cfg["mlp_ratio"]) for _ in range(cfg["depth"])]
408
+ self.norm_final = RMSNorm(d)
409
+ self.final_linear = nn.Linear(d, p * p * lc)
410
+ self.final_adaln = nn.Linear(d, 2 * d)
411
+ self.layer_norm = nn.LayerNorm(cfg["enc_dim"], eps=1e-5)
412
+ self.mlp_ca_fc1 = nn.Linear(cfg["enc_dim"], d * cfg["encdim_ratio"])
413
+ self.mlp_ca_fc2 = nn.Linear(d * cfg["encdim_ratio"], d)
414
+
415
+ def __call__(self, x, t, feats):
416
+ """x: (B, H, W, 32) [lq latent, z]; t: (B,); feats: (B, M, 1024) DINOv2 tokens -> velocity (B, H, W, 16)."""
417
+ cfg, dt = self.cfg, self.x_embedder.weight.dtype
418
+ p, lc = cfg["patch_size"], cfg["latent_channels"]
419
+ x = self.x_embedder(x.astype(dt))
420
+ b, h, w, d = x.shape
421
+ x = x.reshape(b, h * w, d)
422
+ c = self.t_embedder(t)
423
+ c0 = self.t_block(nn.silu(c))
424
+ z = self.mlp_ca_fc2(nn.gelu_approx(self.mlp_ca_fc1(layer_norm(self.layer_norm, feats.astype(dt)))))
425
+ cos, sin = rope_tables(h, w, d // cfg["num_heads"], cfg["train_tokens"])
426
+ for blk in self.blocks:
427
+ x = blk(x, c0, z, cos, sin)
428
+ shift, scale = mx.split(self.final_adaln(nn.silu(c)), 2, axis=-1)
429
+ x = self.final_linear(self.norm_final(x) * (1 + scale[:, None]) + shift[:, None])
430
+ x = x.reshape(b, h, w, p, p, lc).transpose(0, 1, 3, 2, 4, 5).reshape(b, h * p, w * p, lc)
431
+ return x.astype(mx.float32)
432
+
433
+
434
+ class Model(nn.Module):
435
+ def __init__(self, config=None):
436
+ super().__init__()
437
+ self.config = dict(config or CONFIG)
438
+ self.vae = VAE(self.config)
439
+ self.dino = Dino(self.config)
440
+ self.dit = DiT(self.config)
441
+
442
+
443
+ def load(folder) -> Model:
444
+ folder = Path(folder)
445
+ config = json.loads((folder / "config.json").read_text())
446
+ model = Model(config)
447
+ q = config.get("quantization")
448
+ if q:
449
+ nn.quantize(model, group_size=q["group_size"], bits=q["bits"], class_predicate=quantizable)
450
+ model.load_weights(str(folder / "model.safetensors"), strict=True)
451
+ mx.eval(model.parameters())
452
+ return model
453
+
454
+
455
+ # ------------------------------------------------------------------------------------------------ pipeline
456
+
457
+ def gaussian_blur(x: np.ndarray, sigma: float = 5.0) -> np.ndarray:
458
+ """cv2.GaussianBlur(x, (0, 0), sigma) for float32 HxWxC: kernel 8*sigma+1, BORDER_REFLECT_101."""
459
+ k = int(round(sigma * 4 * 2 + 1)) | 1
460
+ r = k // 2
461
+ g = np.exp(-((np.arange(k) - r) ** 2) / (2 * sigma * sigma))
462
+ g = (g / g.sum()).astype(np.float32)
463
+ y = mx.array(x)
464
+ for axis in (0, 1):
465
+ n = y.shape[axis]
466
+ idx = np.abs(np.arange(-r, n + r))
467
+ idx = np.where(idx > n - 1, 2 * (n - 1) - idx, idx)
468
+ idx = mx.array(idx)
469
+ yp = mx.take(y, idx, axis=axis)
470
+ out = 0
471
+ for j in range(k):
472
+ sl = [slice(None)] * 3
473
+ sl[axis] = slice(j, j + n)
474
+ out = out + float(g[j]) * yp[tuple(sl)]
475
+ y = out
476
+ return np.array(y)
477
+
478
+
479
+ def wavelet_color_fix(sr: np.ndarray, ref: np.ndarray) -> np.ndarray:
480
+ t, s = sr.astype(np.float32) / 255.0, ref.astype(np.float32) / 255.0
481
+ out = np.clip(gaussian_blur(s) + (t - gaussian_blur(t)), 0, 1) * 255.0
482
+ return out.astype(np.uint8)
483
+
484
+
485
+ def restore(model: Model, lq: np.ndarray, noise=None, seed: int = 42) -> np.ndarray:
486
+ """lq: (H, W, 3) float32 in [-1, 1] at the output size, H and W multiples of 16 -> (H, W, 3) in [-1, 1]."""
487
+ cfg = model.config
488
+ x = mx.array(lq)[None]
489
+ mean, std = mx.array(cfg["latents_mean"]), mx.array(cfg["latents_std"])
490
+ lat = (model.vae.encode(x) - mean) / std
491
+ s = cfg["dino_size"]
492
+ d = mx.clip(bicubic((x + 1) * 0.5, (s, s)), 0, 1)
493
+ d = (d - mx.array(IMAGENET_MEAN)) / mx.array(IMAGENET_STD)
494
+ feats = model.dino(d)
495
+ z = mx.array(noise) if noise is not None else mx.random.normal(lat.shape, key=mx.random.key(seed))
496
+ u = model.dit(mx.concatenate([lat, z], axis=-1), mx.ones((1,)), feats)
497
+ out = model.vae.decode((z - u) * std + mean)
498
+ return np.array(out[0])
499
+
500
+
501
+ def upscale(model: Model, rgb: np.ndarray, scale: int = 4, noise=None, seed: int = 42,
502
+ color_fix: str = "wavelet") -> np.ndarray:
503
+ """(h, w, 3) uint8 -> (h*scale, w*scale, 3) uint8, as inference_vosr_onestep.py (bicubic upscale with PIL first).
504
+ `noise`: (1, H/8, W/8, 16) float32 to fix the starting noise (H, W: the output size rounded up to 16)."""
505
+ from PIL import Image
506
+ h, w = rgb.shape[:2]
507
+ H, W = h * scale, w * scale
508
+ up = np.asarray(Image.fromarray(rgb).resize((W, H), Image.BICUBIC))
509
+ ph, pw = -H % 16, -W % 16
510
+ x = np.pad(up, [(0, ph), (0, pw), (0, 0)], mode="reflect") if ph or pw else up
511
+ sr = restore(model, x.astype(np.float32) / 255.0 * 2 - 1, noise, seed)[:H, :W]
512
+ sr = (np.clip(sr * 0.5 + 0.5, 0, 1) * 255).astype(np.uint8) # ToPILImage truncates
513
+ return wavelet_color_fix(sr, up) if color_fix == "wavelet" else sr
514
+
515
+
516
+ # ------------------------------------------------------------------------------------------------ conversion
517
+
518
+ def _conv(w):
519
+ return w.transpose(0, 2, 3, 1)
520
+
521
+
522
+ def convert(source: Path) -> dict:
523
+ """runs/source (CSWRY/VOSR: VOSR2/, Qwen-Image-vae-2d/, torch_cache/checkpoints/dinov2_vitl14_pretrain.pth)
524
+ -> fp32 numpy in this file's layout."""
525
+ import torch
526
+ import torch.nn.functional as F
527
+ from safetensors.numpy import load_file
528
+
529
+ source, cfg, out = Path(source), CONFIG, {}
530
+
531
+ dit = load_file(str(source / "VOSR2" / "checkpoints" / "ema_model.safetensors"))
532
+ ren = {"t_embedder.mlp.0.": "t_embedder.fc1.", "t_embedder.mlp.2.": "t_embedder.fc2.", "t_block.1.": "t_block.",
533
+ "final_layer.norm_final.": "norm_final.", "final_layer.linear.": "final_linear.",
534
+ "final_layer.adaLN_modulation.1.": "final_adaln.", "mlp_ca.fc1.": "mlp_ca_fc1.",
535
+ "mlp_ca.fc2.": "mlp_ca_fc2.", "x_embedder.proj.": "x_embedder."}
536
+ for k, v in dit.items():
537
+ if "freqs_" in k:
538
+ continue # RoPE tables: recomputed per size
539
+ name = k
540
+ for a, b in ren.items():
541
+ if name.startswith(a):
542
+ name = b + name[len(a):]
543
+ v = v.astype(np.float32)
544
+ out["dit." + name] = _conv(v) if v.ndim == 4 else v
545
+
546
+ vae = load_file(str(source / "Qwen-Image-vae-2d" / "diffusion_pytorch_model.safetensors"))
547
+ for k, v in vae.items():
548
+ name = k.replace("resample.1.", "conv.")
549
+ v = v.astype(np.float32)
550
+ if name.endswith(".gamma"):
551
+ v = v.reshape(-1)
552
+ out["vae." + name] = _conv(v) if v.ndim == 4 else v
553
+
554
+ sd = torch.load(source / "torch_cache" / "checkpoints" / "dinov2_vitl14_pretrain.pth", map_location="cpu",
555
+ weights_only=True)
556
+ g = cfg["dino_size"] // cfg["dino_patch"]
557
+ pos = sd["pos_embed"].float()
558
+ m = int(math.sqrt(pos.shape[1] - 1))
559
+ patch = F.interpolate(pos[:, 1:].reshape(1, m, m, -1).permute(0, 3, 1, 2), mode="bicubic", antialias=False,
560
+ scale_factor=((g + 0.1) / m, (g + 0.1) / m))
561
+ assert patch.shape[-2:] == (g, g)
562
+ out["dino.pos_embed"] = torch.cat([pos[:, :1], patch.permute(0, 2, 3, 1).reshape(1, g * g, -1)], 1).numpy()
563
+ out["dino.cls_token"] = sd["cls_token"].float().numpy()
564
+ out["dino.patch_embed.weight"] = _conv(sd["patch_embed.proj.weight"].float().numpy())
565
+ out["dino.patch_embed.bias"] = sd["patch_embed.proj.bias"].float().numpy()
566
+ bren = {"attn.qkv.": "qkv.", "attn.proj.": "proj.", "mlp.fc1.": "fc1.", "mlp.fc2.": "fc2.",
567
+ "ls1.gamma": "ls1", "ls2.gamma": "ls2"}
568
+ for k, v in sd.items():
569
+ if not k.startswith("blocks."):
570
+ continue
571
+ i = int(k.split(".")[1])
572
+ if i >= cfg["dino_depth"]:
573
+ continue
574
+ rest = k.split(".", 2)[2]
575
+ for a, b in bren.items():
576
+ if rest.startswith(a):
577
+ rest = b + rest[len(a):]
578
+ out[f"dino.blocks.{i}.{rest}"] = v.float().numpy()
579
+ return out
4bit/model.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:1e2f7b023f9ce25fac5dd49d7d0e4f90a19c4b11e3919a4e7f79b544f4fdee56
3
+ size 1006876833