acanadaSpl commited on
Commit
cefb0ae
·
verified ·
1 Parent(s): 5189850

Leaner idol package (inference code only)

Browse files
idol/conditioning.py CHANGED
@@ -13,12 +13,11 @@ DTYPES = {'fp32': torch.float32, 'bf16': torch.bfloat16, 'fp16': torch.float16}
13
 
14
 
15
  class ImageEncoder:
16
- def __init__(self, dinov3_repo: str, flux2_vae_repo: str, resolution: int = 1024,
17
  dinov3_dtype: str = 'bf16', device: str = 'cuda'):
18
  from diffusers.models import AutoencoderKLFlux2
19
  from transformers import AutoModel
20
 
21
- self.resolution = resolution
22
  self.device = torch.device(device)
23
  self.dinov3 = AutoModel.from_pretrained(dinov3_repo, torch_dtype=DTYPES[dinov3_dtype])
24
  self.dinov3 = self.dinov3.eval().requires_grad_(False).to(self.device)
@@ -26,18 +25,6 @@ class ImageEncoder:
26
  torch_dtype=torch.bfloat16)
27
  self.flux2_vae = self.flux2_vae.eval().requires_grad_(False).to(self.device)
28
 
29
- def to(self, device):
30
- self.device = torch.device(device)
31
- self.dinov3.to(self.device)
32
- self.flux2_vae.to(self.device)
33
- return self
34
-
35
- def _resize(self, images):
36
- size = (self.resolution, self.resolution)
37
- if images.shape[-2:] != size:
38
- images = F.interpolate(images, size=size, mode='bilinear', align_corners=False)
39
- return images
40
-
41
  def _dinov3_tokens(self, images):
42
  mean = torch.tensor(IMAGENET_MEAN, device=images.device).view(1, 3, 1, 1)
43
  std = torch.tensor(IMAGENET_STD, device=images.device).view(1, 3, 1, 1)
@@ -60,8 +47,8 @@ class ImageEncoder:
60
 
61
  @torch.no_grad()
62
  def __call__(self, images: torch.Tensor) -> TensorDict:
63
- """images: [B, 3, H, W] in [0, 1] -> TensorDict(feature1=DINOv3 tokens, feature2=FLUX.2 VAE tokens)."""
64
- images = self._resize(images.to(self.device))
65
  feature1 = self._dinov3_tokens(images)
66
  feature2 = self._flux2_tokens(images)
67
  return TensorDict({'feature1': feature1, 'feature2': feature2}, batch_size=feature1.shape[0])
 
13
 
14
 
15
  class ImageEncoder:
16
+ def __init__(self, dinov3_repo: str, flux2_vae_repo: str,
17
  dinov3_dtype: str = 'bf16', device: str = 'cuda'):
18
  from diffusers.models import AutoencoderKLFlux2
19
  from transformers import AutoModel
20
 
 
21
  self.device = torch.device(device)
22
  self.dinov3 = AutoModel.from_pretrained(dinov3_repo, torch_dtype=DTYPES[dinov3_dtype])
23
  self.dinov3 = self.dinov3.eval().requires_grad_(False).to(self.device)
 
25
  torch_dtype=torch.bfloat16)
26
  self.flux2_vae = self.flux2_vae.eval().requires_grad_(False).to(self.device)
27
 
 
 
 
 
 
 
 
 
 
 
 
 
28
  def _dinov3_tokens(self, images):
29
  mean = torch.tensor(IMAGENET_MEAN, device=images.device).view(1, 3, 1, 1)
30
  std = torch.tensor(IMAGENET_STD, device=images.device).view(1, 3, 1, 1)
 
47
 
48
  @torch.no_grad()
49
  def __call__(self, images: torch.Tensor) -> TensorDict:
50
+ """images: [B, 3, 1024, 1024] in [0, 1] -> TensorDict(feature1=DINOv3 tokens, feature2=FLUX.2 VAE tokens)."""
51
+ images = images.to(self.device)
52
  feature1 = self._dinov3_tokens(images)
53
  feature2 = self._flux2_tokens(images)
54
  return TensorDict({'feature1': feature1, 'feature2': feature2}, batch_size=feature1.shape[0])
idol/gaussian.py CHANGED
@@ -1,5 +1,7 @@
1
- import torch
2
  import numpy as np
 
 
3
  from plyfile import PlyData, PlyElement
4
 
5
 
@@ -7,377 +9,54 @@ def inverse_sigmoid(x):
7
  return torch.log(x / (1 - x))
8
 
9
 
10
- class Gaussian:
11
- def __init__(
12
- self,
13
- aabb : list,
14
- sh_degree : int = 0,
15
- mininum_kernel_size : float = 0.0,
16
- scaling_bias : float = 0.01,
17
- opacity_bias : float = 0.1,
18
- scaling_activation : str = "exp",
19
- device='cuda'
20
- ):
21
- self.init_params = {
22
- 'aabb': aabb,
23
- 'sh_degree': sh_degree,
24
- 'mininum_kernel_size': mininum_kernel_size,
25
- 'scaling_bias': scaling_bias,
26
- 'opacity_bias': opacity_bias,
27
- 'scaling_activation': scaling_activation,
28
- }
29
-
30
- self.sh_degree = sh_degree
31
- self.active_sh_degree = sh_degree
32
- self.mininum_kernel_size = mininum_kernel_size
33
- self.scaling_bias = scaling_bias
34
- self.opacity_bias = opacity_bias
35
- self.scaling_activation_type = scaling_activation
36
- self.device = device
37
- self.aabb = torch.tensor(aabb, dtype=torch.float32, device=device)
38
- self.setup_functions()
39
-
40
- self._storage = {}
41
- # Pre-initialize keys
42
- for key in ['_xyz', 'xyz', '_features_dc', 'features_dc',
43
- '_features_rest', 'features_rest',
44
- '_scaling', 'scaling',
45
- '_rotation', 'rotation',
46
- '_opacity', 'opacity',
47
- 'features']:
48
- self._storage[key] = None
49
-
50
- def setup_functions(self):
51
- if self.scaling_activation_type == "exp":
52
- self.scaling_activation = torch.exp
53
- self.inverse_scaling_activation = torch.log
54
- elif self.scaling_activation_type == "softplus":
55
- self.scaling_activation = torch.nn.functional.softplus
56
- self.inverse_scaling_activation = lambda x: x + torch.log(-torch.expm1(-x))
57
-
58
- self.opacity_activation = torch.sigmoid
59
- self.inverse_opacity_activation = inverse_sigmoid
60
-
61
- self.rotation_activation = torch.nn.functional.normalize
62
-
63
- self.scale_bias = self.inverse_scaling_activation(torch.tensor(self.scaling_bias)).to(self.device)
64
- self.rots_bias = torch.zeros((4)).to(self.device)
65
- self.rots_bias[0] = 1
66
- self.opacity_bias = self.inverse_opacity_activation(torch.tensor(self.opacity_bias)).to(self.device)
67
-
68
- def _get_store(self, name):
69
- return self._storage.get(name)
70
-
71
- def _set_store(self, name, value):
72
- self._storage[name] = value
73
-
74
- # --- Opacity ---
75
- @property
76
- def opacity(self):
77
- return self._get_store("opacity")
78
-
79
- @opacity.setter
80
- def opacity(self, value):
81
- self.set_opacity(value)
82
-
83
- @property
84
- def _opacity(self):
85
- return self._get_store("_opacity")
86
-
87
- @_opacity.setter
88
- def _opacity(self, value):
89
- self.set_raw_opacity(value)
90
-
91
- def set_opacity(self, value):
92
- if value is None:
93
- self._set_store("opacity", None)
94
- self._set_store("_opacity", None)
95
- return
96
- self._set_store("opacity", value)
97
- # Update raw: inverse activation
98
- raw = self.inverse_opacity_activation(value) - self.opacity_bias
99
- self._set_store("_opacity", raw)
100
-
101
- def set_raw_opacity(self, value):
102
- if value is None:
103
- self._set_store("_opacity", None)
104
- self._set_store("opacity", None)
105
- return
106
- self._set_store("_opacity", value)
107
- # Update activated
108
- activated = self.opacity_activation(value + self.opacity_bias)
109
- self._set_store("opacity", activated)
110
-
111
- # --- Scaling ---
112
- @property
113
- def scaling(self):
114
- return self._get_store("scaling")
115
-
116
- @scaling.setter
117
- def scaling(self, value):
118
- self.set_scaling(value)
119
-
120
- @property
121
- def _scaling(self):
122
- return self._get_store("_scaling")
123
-
124
- @_scaling.setter
125
- def _scaling(self, value):
126
- self.set_raw_scaling(value)
127
-
128
- def set_scaling(self, value):
129
- if value is None:
130
- self._set_store("scaling", None)
131
- self._set_store("_scaling", None)
132
- return
133
- self._set_store("scaling", value)
134
- # Inverse activation
135
- s = torch.sqrt(torch.square(value) - self.mininum_kernel_size ** 2)
136
- raw = self.inverse_scaling_activation(s) - self.scale_bias
137
- self._set_store("_scaling", raw)
138
-
139
- def set_raw_scaling(self, value):
140
- if value is None:
141
- self._set_store("_scaling", None)
142
- self._set_store("scaling", None)
143
- return
144
- self._set_store("_scaling", value)
145
- # Forward activation
146
- s = self.scaling_activation(value + self.scale_bias)
147
- s = torch.square(s) + self.mininum_kernel_size ** 2
148
- activated = torch.sqrt(s)
149
- self._set_store("scaling", activated)
150
-
151
- # --- Rotation ---
152
- @property
153
- def rotation(self):
154
- return self._get_store("rotation")
155
-
156
- @rotation.setter
157
- def rotation(self, value):
158
- self.set_rotation(value)
159
-
160
- @property
161
- def _rotation(self):
162
- return self._get_store("_rotation")
163
-
164
- @_rotation.setter
165
- def _rotation(self, value):
166
- self.set_raw_rotation(value)
167
-
168
- def set_rotation(self, value):
169
- if value is None:
170
- self._set_store("rotation", None)
171
- self._set_store("_rotation", None)
172
- return
173
- self._set_store("rotation", value)
174
- # Inverse: just subtract bias
175
- raw = value - self.rots_bias[None, :]
176
- self._set_store("_rotation", raw)
177
 
178
- def set_raw_rotation(self, value):
179
- if value is None:
180
- self._set_store("_rotation", None)
181
- self._set_store("rotation", None)
182
- return
183
- self._set_store("_rotation", value)
184
- # Forward activation
185
- activated = self.rotation_activation(value + self.rots_bias[None, :])
186
- self._set_store("rotation", activated)
187
 
188
- # --- XYZ ---
189
- @property
190
- def xyz(self):
191
- return self._get_store("xyz")
192
-
193
- @xyz.setter
194
- def xyz(self, value):
195
- self.set_xyz(value)
196
-
197
- @property
198
- def _xyz(self):
199
- return self._get_store("_xyz")
200
-
201
- @_xyz.setter
202
- def _xyz(self, value):
203
- self.set_raw_xyz(value)
204
-
205
- def set_xyz(self, value):
206
- if value is None:
207
- self._set_store("xyz", None)
208
- self._set_store("_xyz", None)
209
- return
210
- self._set_store("xyz", value)
211
- # Inverse
212
- raw = (value - self.aabb[None, :3]) / self.aabb[None, 3:]
213
- self._set_store("_xyz", raw)
214
-
215
- def set_raw_xyz(self, value):
216
- if value is None:
217
- self._set_store("_xyz", None)
218
- self._set_store("xyz", None)
219
- return
220
- self._set_store("_xyz", value)
221
- # Forward activation
222
- activated = value * self.aabb[None, 3:] + self.aabb[None, :3]
223
- self._set_store("xyz", activated)
224
-
225
- # --- Features (Combined) ---
226
- @property
227
- def features(self):
228
- return self._get_store("features")
229
-
230
- @features.setter
231
- def features(self, value):
232
- self.set_features(value)
233
-
234
- def set_features(self, value):
235
- self._set_store("features", value)
236
- if value is None:
237
- self._set_store("_features_dc", None)
238
- self._set_store("features_dc", None)
239
- self._set_store("_features_rest", None)
240
- self._set_store("features_rest", None)
241
- return
242
-
243
- # Split features into DC and Rest
244
- # Assuming shape (N, 1+K, 3)
245
- dc = value[:, :1, :]
246
- self._set_store("_features_dc", dc)
247
- self._set_store("features_dc", dc)
248
-
249
- if value.shape[1] > 1:
250
- rest = value[:, 1:, :]
251
- self._set_store("_features_rest", rest)
252
- self._set_store("features_rest", rest)
253
- else:
254
- self._set_store("_features_rest", None)
255
- self._set_store("features_rest", None)
256
-
257
- def _update_features_from_components(self):
258
- dc = self._get_store("_features_dc")
259
- rest = self._get_store("_features_rest")
260
- if dc is None:
261
- self._set_store("features", None)
262
- return
263
-
264
- if rest is not None:
265
- # Concatenate on dim 1 (N, 1+K, 3)
266
- f = torch.cat((dc, rest), dim=1)
267
- self._set_store("features", f)
268
- else:
269
- self._set_store("features", dc)
270
-
271
- # --- Features DC ---
272
- @property
273
- def features_dc(self):
274
- return self._get_store("features_dc")
275
-
276
- @features_dc.setter
277
- def features_dc(self, value):
278
- self.set_features_dc(value)
279
-
280
- @property
281
- def _features_dc(self):
282
- return self._get_store("_features_dc")
283
-
284
- @_features_dc.setter
285
- def _features_dc(self, value):
286
- self.set_raw_features_dc(value)
287
-
288
- def set_features_dc(self, value):
289
- self._set_store("features_dc", value)
290
- self._set_store("_features_dc", value) # Identity
291
- self._update_features_from_components()
292
-
293
- def set_raw_features_dc(self, value):
294
- self._set_store("_features_dc", value)
295
- self._set_store("features_dc", value) # Identity
296
- self._update_features_from_components()
297
-
298
- # --- Features Rest ---
299
- @property
300
- def features_rest(self):
301
- return self._get_store("features_rest")
302
-
303
- @features_rest.setter
304
- def features_rest(self, value):
305
- self.set_features_rest(value)
306
-
307
- @property
308
- def _features_rest(self):
309
- return self._get_store("_features_rest")
310
-
311
- @_features_rest.setter
312
- def _features_rest(self, value):
313
- self.set_raw_features_rest(value)
314
-
315
- def set_features_rest(self, value):
316
- self._set_store("features_rest", value)
317
- self._set_store("_features_rest", value)
318
- self._update_features_from_components()
319
 
320
- def set_raw_features_rest(self, value):
321
- self._set_store("_features_rest", value)
322
- self._set_store("features_rest", value)
323
- self._update_features_from_components()
324
-
325
- # --- Old API ---
326
- @property
327
- def get_scaling(self):
328
- return self._get_store("scaling")
329
-
330
- @property
331
- def get_rotation(self):
332
- return self._get_store("rotation")
333
-
334
- @property
335
- def get_xyz(self):
336
- return self._get_store("xyz")
337
-
338
- @property
339
- def get_features(self):
340
- return self.features
341
-
342
- @property
343
- def get_opacity(self):
344
- return self._get_store("opacity")
345
-
346
- # --- Saver and Loader ---
347
- def construct_list_of_attributes(self):
348
- l = ['x', 'y', 'z', 'nx', 'ny', 'nz']
349
- # All channels except the 3 DC
350
- for i in range(self._features_dc.shape[1]*self._features_dc.shape[2]):
351
- l.append('f_dc_{}'.format(i))
352
- l.append('opacity')
353
- for i in range(self._scaling.shape[1]):
354
- l.append('scale_{}'.format(i))
355
- for i in range(self._rotation.shape[1]):
356
- l.append('rot_{}'.format(i))
357
- return l
358
-
359
  def save_ply(self, path):
360
  """Write the standard 3D Gaussian Splatting PLY (z-up object turned to the y-up convention viewers use)."""
361
- write_ply_attributes(path, self.construct_list_of_attributes(), self.ply_attributes())
362
-
363
- def ply_attributes(self):
364
- """The (N, attributes) float array save_ply writes, in construct_list_of_attributes() order."""
365
- xyz = self.get_xyz.detach().cpu().numpy()
366
  normals = np.zeros_like(xyz)
367
- f_dc = self._features_dc.detach().transpose(1, 2).flatten(start_dim=1).contiguous().cpu().numpy()
368
  # Clamp before the inverse activations: a saturated opacity (exactly 1.0 after the sigmoid) or a zero
369
  # scale would otherwise be written as +/-inf, which some PLY viewers reject.
370
  eps = 1e-6
371
- opacities = inverse_sigmoid(self.get_opacity.clamp(eps, 1 - eps)).detach().cpu().numpy()
372
- scale = torch.log(self.get_scaling.clamp_min(eps)).detach().cpu().numpy()
373
- rotation = (self._rotation + self.rots_bias[None, :]).detach().cpu().numpy()
374
 
375
  # z-up -> y-up: rotate +90 degrees about x, i.e. (x, y, z) -> (x, -z, y)
376
  xyz = np.stack([xyz[:, 0], -xyz[:, 2], xyz[:, 1]], axis=1)
377
- rotation = rotation / np.linalg.norm(rotation, axis=1, keepdims=True)
378
  rotation = quaternion_multiply(np.array([np.sqrt(0.5), np.sqrt(0.5), 0.0, 0.0]), rotation)
379
 
380
- return np.concatenate((xyz, normals, f_dc, opacities, scale, rotation), axis=1).astype(np.float32)
 
 
 
 
 
 
381
 
382
 
383
  def quaternion_multiply(a, b):
@@ -390,14 +69,3 @@ def quaternion_multiply(a, b):
390
  aw * by - ax * bz + ay * bw + az * bx,
391
  aw * bz + ax * by - ay * bx + az * bw,
392
  ], axis=1)
393
-
394
-
395
- def write_ply_attributes(path, attribute_names, attributes):
396
- """Write a Gaussian PLY from attribute names and an (N, len(names)) array (see Gaussian.ply_attributes)."""
397
- dtype_full = [(attribute, 'f4') for attribute in attribute_names]
398
- elements = np.empty(attributes.shape[0], dtype=dtype_full)
399
- # Assign whole columns: constructing 262,144 Python tuples per PLY
400
- # dominated validation export time and allocated millions of scalars.
401
- for column_index, (attribute_name, _) in enumerate(dtype_full):
402
- elements[attribute_name] = attributes[:, column_index]
403
- PlyData([PlyElement.describe(elements, 'vertex')]).write(path)
 
1
+ """The generated object: 3D Gaussians with position, colour, scale, rotation and opacity."""
2
  import numpy as np
3
+ import torch
4
+ import torch.nn.functional as F
5
  from plyfile import PlyData, PlyElement
6
 
7
 
 
9
  return torch.log(x / (1 - x))
10
 
11
 
12
+ def inverse_softplus(x):
13
+ return x + torch.log(-torch.expm1(-x))
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
14
 
 
 
 
 
 
 
 
 
 
15
 
16
+ class Gaussian:
17
+ """Built from the Gaussian decoder's raw outputs; the activated values are what renderers and PLY files use.
18
+
19
+ xyz [N, 3] positions, features_dc [N, 1, 3] colour (degree-0 spherical harmonics), scaling [N, 3],
20
+ rotation [N, 4] unit quaternions (w, x, y, z), opacity [N, 1]. The object is z-up, inside [-0.5, 0.5]^3."""
21
+
22
+ def __init__(self, raw_xyz, features_dc, raw_scaling, raw_rotation, raw_opacity,
23
+ scaling_bias: float, opacity_bias: float, minimum_kernel_size: float):
24
+ device = raw_xyz.device
25
+ aabb = torch.tensor([-0.5, -0.5, -0.5, 1.0, 1.0, 1.0], dtype=torch.float32, device=device)
26
+ scaling_offset = inverse_softplus(torch.tensor(scaling_bias)).to(device)
27
+ opacity_offset = inverse_sigmoid(torch.tensor(opacity_bias)).to(device)
28
+ rotation_offset = torch.tensor([1.0, 0.0, 0.0, 0.0], device=device)
29
+
30
+ self.xyz = raw_xyz * aabb[None, 3:] + aabb[None, :3]
31
+ self.features_dc = features_dc
32
+ scaling = F.softplus(raw_scaling + scaling_offset)
33
+ self.scaling = torch.sqrt(torch.square(scaling) + minimum_kernel_size ** 2)
34
+ self.rotation = F.normalize(raw_rotation + rotation_offset[None, :])
35
+ self.opacity = torch.sigmoid(raw_opacity + opacity_offset)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
36
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
37
  def save_ply(self, path):
38
  """Write the standard 3D Gaussian Splatting PLY (z-up object turned to the y-up convention viewers use)."""
39
+ xyz = self.xyz.detach().cpu().numpy()
 
 
 
 
40
  normals = np.zeros_like(xyz)
41
+ f_dc = self.features_dc.detach().transpose(1, 2).flatten(start_dim=1).contiguous().cpu().numpy()
42
  # Clamp before the inverse activations: a saturated opacity (exactly 1.0 after the sigmoid) or a zero
43
  # scale would otherwise be written as +/-inf, which some PLY viewers reject.
44
  eps = 1e-6
45
+ opacities = inverse_sigmoid(self.opacity.clamp(eps, 1 - eps)).detach().cpu().numpy()
46
+ scale = torch.log(self.scaling.clamp_min(eps)).detach().cpu().numpy()
47
+ rotation = self.rotation.detach().cpu().numpy()
48
 
49
  # z-up -> y-up: rotate +90 degrees about x, i.e. (x, y, z) -> (x, -z, y)
50
  xyz = np.stack([xyz[:, 0], -xyz[:, 2], xyz[:, 1]], axis=1)
 
51
  rotation = quaternion_multiply(np.array([np.sqrt(0.5), np.sqrt(0.5), 0.0, 0.0]), rotation)
52
 
53
+ attributes = np.concatenate((xyz, normals, f_dc, opacities, scale, rotation), axis=1).astype(np.float32)
54
+ names = ['x', 'y', 'z', 'nx', 'ny', 'nz', 'f_dc_0', 'f_dc_1', 'f_dc_2', 'opacity',
55
+ 'scale_0', 'scale_1', 'scale_2', 'rot_0', 'rot_1', 'rot_2', 'rot_3']
56
+ elements = np.empty(attributes.shape[0], dtype=[(name, 'f4') for name in names])
57
+ for column, name in enumerate(names):
58
+ elements[name] = attributes[:, column]
59
+ PlyData([PlyElement.describe(elements, 'vertex')]).write(path)
60
 
61
 
62
  def quaternion_multiply(a, b):
 
69
  aw * by - ax * bz + ay * bw + az * bx,
70
  aw * bz + ax * by - ay * bx + az * bw,
71
  ], axis=1)
 
 
 
 
 
 
 
 
 
 
 
idol/models/birefnet.py CHANGED
@@ -4,7 +4,6 @@ image encoders see them."""
4
  import math
5
 
6
  import numpy as np
7
- import safetensors.torch
8
  import torch
9
  import torch.nn as nn
10
  import torch.nn.functional as F
@@ -14,18 +13,6 @@ from torchvision.ops import deform_conv2d
14
  # BiRefNet background removal (Swin-L + ASPP-deformable decoder)
15
  # ---------------------------------------------------------------------------
16
 
17
- # -- timm-style helpers, inlined to avoid a timm dependency --------------------
18
-
19
- def _trunc_normal_(tensor: torch.Tensor, mean: float = 0.0, std: float = 1.0,
20
- a: float = -2.0, b: float = 2.0) -> torch.Tensor:
21
- # Initialization helper — only used at __init__ time, the released ckpt
22
- # overwrites everything in load_safetensors so the exact distribution here
23
- # is unimportant.
24
- with torch.no_grad():
25
- tensor.normal_(mean, std).clamp_(mean + a * std, mean + b * std)
26
- return tensor
27
-
28
-
29
  # -- Swin Transformer (Swin-Large preset) -------------------------------------
30
 
31
  class _SwinMlp(nn.Module):
@@ -77,7 +64,6 @@ class _WindowAttention(nn.Module):
77
 
78
  self.qkv = nn.Linear(dim, dim * 3, bias=True)
79
  self.proj = nn.Linear(dim, dim)
80
- _trunc_normal_(self.relative_position_bias_table, std=0.02)
81
 
82
  def forward(self, x, mask=None):
83
  B_, N, C = x.shape
@@ -153,8 +139,6 @@ class _PatchMerging(nn.Module):
153
  def forward(self, x, H, W):
154
  B, L, C = x.shape
155
  x = x.view(B, H, W, C)
156
- if H % 2 == 1 or W % 2 == 1:
157
- x = F.pad(x, (0, 0, 0, W % 2, 0, H % 2))
158
  x0 = x[:, 0::2, 0::2, :]
159
  x1 = x[:, 1::2, 0::2, :]
160
  x2 = x[:, 0::2, 1::2, :]
@@ -215,11 +199,6 @@ class _SwinPatchEmbed(nn.Module):
215
  self.embed_dim = embed_dim
216
 
217
  def forward(self, x):
218
- _, _, H, W = x.shape
219
- if W % self.patch_size[1] != 0:
220
- x = F.pad(x, (0, self.patch_size[1] - W % self.patch_size[1]))
221
- if H % self.patch_size[0] != 0:
222
- x = F.pad(x, (0, 0, 0, self.patch_size[0] - H % self.patch_size[0]))
223
  x = self.proj(x)
224
  Wh, Ww = x.size(2), x.size(3)
225
  x = x.flatten(2).transpose(1, 2)
@@ -414,12 +393,6 @@ class _BiRefNetDecoder(nn.Module):
414
  self.lateral_block3 = _BasicLatBlk(c[2], c[2])
415
  self.lateral_block2 = _BasicLatBlk(c[3], c[3])
416
 
417
- # multi-scale supervision heads (training only — kept for state_dict
418
- # parity with the released checkpoint; not consumed at inference).
419
- self.conv_ms_spvn_4 = nn.Conv2d(c[1], 1, 1, 1, 0)
420
- self.conv_ms_spvn_3 = nn.Conv2d(c[2], 1, 1, 1, 0)
421
- self.conv_ms_spvn_2 = nn.Conv2d(c[3], 1, 1, 1, 0)
422
-
423
  # gradient-decoder-triggering (gdt) attention: used at inference to
424
  # gate p4/p3/p2.
425
  _N = 16
@@ -431,10 +404,6 @@ class _BiRefNetDecoder(nn.Module):
431
 
432
  def _head_1x1():
433
  return nn.Sequential(nn.Conv2d(_N, 1, 1, 1, 0))
434
- # multi-scale supervision heads on the gdt branch (training only)
435
- self.gdt_convs_pred_4 = _head_1x1()
436
- self.gdt_convs_pred_3 = _head_1x1()
437
- self.gdt_convs_pred_2 = _head_1x1()
438
  # attention heads
439
  self.gdt_convs_attn_4 = _head_1x1()
440
  self.gdt_convs_attn_3 = _head_1x1()
@@ -527,21 +496,9 @@ class BiRefNet(nn.Module):
527
  logits = self.decoder(x, x1, x2, x3, x4)
528
  return torch.sigmoid(logits)
529
 
530
- def load_safetensors(self, path: str) -> None:
531
- sd = safetensors.torch.load_file(path)
532
- # The decoder's gdt_convs_pred_* / conv_ms_spvn_* heads are training-only
533
- # but are kept as submodules for state_dict parity. strict=True works.
534
- missing, unexpected = self.load_state_dict(sd, strict=False)
535
- if unexpected:
536
- raise KeyError(f"[birefnet] unexpected keys (e.g. {unexpected[:3]})")
537
- if missing:
538
- raise KeyError(f"[birefnet] missing keys (e.g. {missing[:3]})")
539
-
540
  @torch.no_grad()
541
  def remove_background(self, image) -> "Image.Image":
542
  from PIL import Image
543
- if image.mode != "RGB":
544
- image = image.convert("RGB")
545
  W, H = image.size
546
  arr = np.array(image, dtype=np.float32) / 255.0
547
  t = torch.from_numpy(arr).permute(2, 0, 1).unsqueeze(0)
 
4
  import math
5
 
6
  import numpy as np
 
7
  import torch
8
  import torch.nn as nn
9
  import torch.nn.functional as F
 
13
  # BiRefNet background removal (Swin-L + ASPP-deformable decoder)
14
  # ---------------------------------------------------------------------------
15
 
 
 
 
 
 
 
 
 
 
 
 
 
16
  # -- Swin Transformer (Swin-Large preset) -------------------------------------
17
 
18
  class _SwinMlp(nn.Module):
 
64
 
65
  self.qkv = nn.Linear(dim, dim * 3, bias=True)
66
  self.proj = nn.Linear(dim, dim)
 
67
 
68
  def forward(self, x, mask=None):
69
  B_, N, C = x.shape
 
139
  def forward(self, x, H, W):
140
  B, L, C = x.shape
141
  x = x.view(B, H, W, C)
 
 
142
  x0 = x[:, 0::2, 0::2, :]
143
  x1 = x[:, 1::2, 0::2, :]
144
  x2 = x[:, 0::2, 1::2, :]
 
199
  self.embed_dim = embed_dim
200
 
201
  def forward(self, x):
 
 
 
 
 
202
  x = self.proj(x)
203
  Wh, Ww = x.size(2), x.size(3)
204
  x = x.flatten(2).transpose(1, 2)
 
393
  self.lateral_block3 = _BasicLatBlk(c[2], c[2])
394
  self.lateral_block2 = _BasicLatBlk(c[3], c[3])
395
 
 
 
 
 
 
 
396
  # gradient-decoder-triggering (gdt) attention: used at inference to
397
  # gate p4/p3/p2.
398
  _N = 16
 
404
 
405
  def _head_1x1():
406
  return nn.Sequential(nn.Conv2d(_N, 1, 1, 1, 0))
 
 
 
 
407
  # attention heads
408
  self.gdt_convs_attn_4 = _head_1x1()
409
  self.gdt_convs_attn_3 = _head_1x1()
 
496
  logits = self.decoder(x, x1, x2, x3, x4)
497
  return torch.sigmoid(logits)
498
 
 
 
 
 
 
 
 
 
 
 
499
  @torch.no_grad()
500
  def remove_background(self, image) -> "Image.Image":
501
  from PIL import Image
 
 
502
  W, H = image.size
503
  arr = np.array(image, dtype=np.float32) / 255.0
504
  t = torch.from_numpy(arr).permute(2, 0, 1).unsqueeze(0)
idol/models/decoder.py CHANGED
@@ -1,424 +1,192 @@
1
- from typing import *
 
 
 
 
 
 
2
  import torch
3
  import torch.nn as nn
4
  import torch.nn.functional as F
5
- from ..sampling_utils import hammersley_sequence, sample_probs
6
  from ..gaussian import Gaussian
7
- from .transformer_base import TransformerBase, ModulatedCrossOnlyTransformerBase, PcdAbsolutePositionEmbedder, PcdAbsolutePositionEmbedderv2
8
- import tensordict
9
- from tensordict import TensorDict
10
 
11
- class FixedlenDecoder(TransformerBase):
12
- def __init__(
13
- self,
14
- in_channels: int,
15
- model_channels: int,
16
- cond_channels: int,
17
- num_blocks: int,
18
- num_heads: Optional[int] = None,
19
- num_head_channels: Optional[int] = 64,
20
- mlp_ratio: float = 4.0,
21
- attn_mode: Literal["full"] = "full",
22
- window_num: Optional[int] = None,
23
- pcd_pe_mode: Literal["pcd_ape", "pcd_ape_v2"] = "pcd_ape_v2",
24
- use_fp16: bool = False,
25
- use_checkpoint: bool = False,
26
- qk_rms_norm: bool = True,
27
- qk_rms_norm_cross: bool = True,
28
- share_mod: bool = False,
29
- ):
30
- """
31
- Gaussian Feature Fixed Length Latent Decoder.
32
- Decode fixed length latent to 3D Gaussian representation, encode condition with cross attention.
33
- Conditions:
34
- - latent: [B, Lq, C]
35
- Input:
36
- - x: [B, L, 3] (points to decode)
37
- """
38
- if attn_mode != "full":
39
- raise ValueError(f"Only attn_mode='full' is supported in the public decoder, got {attn_mode!r}.")
40
- super().__init__(
41
- in_channels=model_channels,
42
- model_channels=model_channels,
43
- cond_channels=cond_channels,
44
- num_blocks=num_blocks,
45
- num_heads=num_heads,
46
- num_head_channels=num_head_channels,
47
- mlp_ratio=mlp_ratio,
48
- use_fp16=use_fp16,
49
- use_checkpoint=use_checkpoint,
50
- qk_rms_norm=qk_rms_norm,
51
- qk_rms_norm_cross=qk_rms_norm_cross,
52
- share_mod=share_mod,
53
- )
54
- self.in_channels = in_channels
55
- self.in_proj = nn.Linear(in_channels, model_channels)
56
- self.pcd_pe_mode = pcd_pe_mode
57
- if pcd_pe_mode == "pcd_ape":
58
- self.pos_embedder = PcdAbsolutePositionEmbedder(
59
- channels=model_channels,
60
- in_channels=3,
61
- )
62
- elif pcd_pe_mode == "pcd_ape_v2":
63
- self.pos_embedder = PcdAbsolutePositionEmbedderv2(
64
- channels=model_channels,
65
- in_channels=3,
66
- )
67
- else:
68
- self.pos_embedder = None
69
-
70
- def forward(self, x: tensordict.TensorDict = None, cond: Optional[torch.Tensor]= None) -> torch.Tensor:
71
- """
72
- Args:
73
- x: a dict containing:
74
- - points: [B, L, 3] the query points to decode
75
- cond: [B, Lq, C] the latent features as condition
76
- Returns:
77
- list of Gaussian representations
78
- """
79
- pcd = x["points"]
80
- B, L, C = pcd.shape
81
- h = pcd
82
-
83
- # token num log scaling
84
- l = torch.full((B,), L).to(h)
85
- l = torch.log2(l).to(h)
86
-
87
- h = self.in_proj(h)
88
- h = h + self.pos_embedder(pcd.view(-1,C)).view(B, L, -1) if hasattr(self, 'pos_embedder') else h
89
- indices = None
90
- # prepare spatial cache
91
 
92
- h = super().forward(h, cond, l=l, indices=indices)
93
- h = h.type(cond.dtype)
94
- return h
 
 
 
 
 
 
 
 
 
 
 
 
 
 
95
 
96
- class GaussianFixedlenDecoder(FixedlenDecoder):
97
- def __init__(
98
- self,
99
- in_channels: int,
100
- model_channels: int,
101
- cond_channels: int,
102
- num_blocks: int,
103
- num_heads: Optional[int] = None,
104
- num_head_channels: Optional[int] = 64,
105
- mlp_ratio: float = 4.0,
106
- attn_mode: Literal["full"] = "full",
107
- window_num: Optional[int] = None,
108
- pcd_pe_mode: Literal["pcd_ape", "pcd_ape_v2"] = "pcd_ape_v2",
109
- use_fp16: bool = False,
110
- use_checkpoint: bool = False,
111
- qk_rms_norm: bool = True,
112
- qk_rms_norm_cross: bool = True,
113
- share_mod: bool = False,
114
- *,
115
- representation_config: dict = None,
116
- use_learned_offset_scale: bool = False,
117
- gaussian_center_pertube_scale: float = 1.0,
118
- use_per_offset=False,
119
- ):
120
- self.rep_config = representation_config
121
- self.gaussian_center_pertube_scale = gaussian_center_pertube_scale
122
- self.use_learned_offset_scale = use_learned_offset_scale
123
- self.use_per_offset = use_per_offset
124
- self.out_channels = self._calc_layout()
125
- super().__init__(
126
- in_channels = in_channels,
127
- model_channels = model_channels,
128
- cond_channels = cond_channels,
129
- num_blocks = num_blocks,
130
- num_heads = num_heads,
131
- num_head_channels = num_head_channels,
132
- mlp_ratio = mlp_ratio,
133
- attn_mode = attn_mode,
134
- window_num = window_num,
135
- pcd_pe_mode = pcd_pe_mode,
136
- use_fp16 = use_fp16,
137
- use_checkpoint = use_checkpoint,
138
- qk_rms_norm = qk_rms_norm,
139
- qk_rms_norm_cross = qk_rms_norm_cross,
140
- share_mod = share_mod,
141
- )
142
- self.out_proj = nn.Linear(model_channels, self.out_channels)
143
- self._build_perturbation()
144
-
145
- # weight init
146
- self.initialize_weights()
147
- if use_fp16:
148
- self.convert_to_fp16()
149
 
150
- def initialize_weights(self) -> None:
151
- super().initialize_weights()
152
- # zero-out output layers:
153
- nn.init.constant_(self.out_proj.weight, 0)
154
- nn.init.constant_(self.out_proj.bias, 0)
155
-
156
- def _calc_layout(self) -> None:
157
- self.layout = {
158
- '_xyz' : {'shape': (self.rep_config['num_gaussians'], 3), 'size': self.rep_config['num_gaussians'] * 3},
159
- '_features_dc' : {'shape': (self.rep_config['num_gaussians'], 1, 3), 'size': self.rep_config['num_gaussians'] * 3},
160
- '_scaling' : {'shape': (self.rep_config['num_gaussians'], 3), 'size': self.rep_config['num_gaussians'] * 3},
161
- '_rotation' : {'shape': (self.rep_config['num_gaussians'], 4), 'size': self.rep_config['num_gaussians'] * 4},
162
- '_opacity' : {'shape': (self.rep_config['num_gaussians'], 1), 'size': self.rep_config['num_gaussians']},
163
- }
164
- if self.use_learned_offset_scale:
165
- if self.use_per_offset:
166
- self.layout['_offset_scale'] = {'shape': (self.rep_config['num_gaussians'], 1), 'size': self.rep_config['num_gaussians']}
167
- else:
168
- raise ValueError("use_per_offset must be True when use_learned_offset_scale is True")
169
- start = 0
170
- for k, v in self.layout.items():
171
- v['range'] = (start, start + v['size'])
172
- start += v['size']
173
- out_channels = start
174
- return out_channels
175
-
176
-
177
- def _build_perturbation(self) -> None:
178
- perturbation = [hammersley_sequence(3, i, self.rep_config['num_gaussians']) for i in range(self.rep_config['num_gaussians'])]
179
- perturbation = torch.tensor(perturbation).float() * 2 - 1
180
- perturbation = perturbation / self.rep_config['perturbe_size']
181
- perturbation = torch.atanh(perturbation).to(self.device)
182
- self.register_buffer('points_offset_perturbation', perturbation)
183
-
184
- if self.use_learned_offset_scale:
185
- base_offset_scale = torch.tensor(self.rep_config['offset_scale'])
186
- base_offset_scale = torch.log(torch.exp(base_offset_scale) - 1.0) # inverse softplus
187
- self.register_buffer('base_offset_scale', base_offset_scale)
188
-
189
- def _get_offset(self, h):
190
- B = h.shape[0]
191
- if self.use_learned_offset_scale:
192
- _offset_scale = h[:,:, self.layout['_offset_scale']['range'][0]:self.layout['_offset_scale']['range'][1]].reshape(B, -1, *self.layout['_offset_scale']['shape'])
193
- _offset_scale = F.softplus(_offset_scale+self.base_offset_scale)
194
-
195
- offset = h[:,:, self.layout['_xyz']['range'][0]:self.layout['_xyz']['range'][1]].reshape(B, -1, *self.layout['_xyz']['shape'])
196
- offset = offset * self.rep_config['lr']['_xyz']
197
- if self.rep_config['perturb_offset']:
198
- offset = offset + self.points_offset_perturbation
199
- offset = torch.tanh(offset) * 0.5 * self.rep_config['perturbe_size']
200
- if self.use_learned_offset_scale:
201
- offset = offset * _offset_scale
202
- else:
203
- offset = offset * self.rep_config['offset_scale']
204
- return offset
205
-
206
 
207
- def to_representation(self, x: torch.Tensor, h: torch.Tensor) -> List[Gaussian]:
208
- """
209
- Convert a batch of network outputs to 3D representations.
 
 
 
 
210
 
211
- Args:
212
- x: the input tensor of shape [B, L, C].
213
- h: the output tensor of shape [B, L, C'] from the MLP.
 
 
 
 
 
 
214
 
215
- Returns:
216
- list of representations
217
- """
218
- x = h["points"] if "points" in h else x["points"]
219
- offset = self._get_offset(h['features'])
220
- h = h["features"]
221
- ret = []
222
- for i in range(h.shape[0]):
223
- representation = Gaussian(
224
- sh_degree=0,
225
- aabb=[-0.5, -0.5, -0.5, 1.0, 1.0, 1.0],
226
- mininum_kernel_size = self.rep_config['3d_filter_kernel_size'],
227
- scaling_bias = self.rep_config['scaling_bias'],
228
- opacity_bias = self.rep_config['opacity_bias'],
229
- scaling_activation = self.rep_config['scaling_activation'],
230
- device = h.device,
231
- )
232
- _x = x[i,:,None,:]
233
 
234
- for k, v in self.layout.items():
235
- if k == '_xyz':
236
- _xyz = offset[i] + _x # NOTE: based on x data range == [0, 1]
237
- setattr(representation, k, _xyz.flatten(0, 1))
238
- elif k == '_xyz_center':
239
- continue
240
- elif k == '_offset_scale':
241
- continue
242
- else:
243
- feats = h[i][:, v['range'][0]:v['range'][1]].reshape(-1, *v['shape']).flatten(0, 1)
244
- feats = feats * self.rep_config['lr'][k]
245
- setattr(representation, k, feats)
246
- ret.append(representation)
247
- return ret
248
-
249
- def forward(self, x = None, cond = None) -> TensorDict:
250
- h = super().forward(x, cond)
251
-
252
- h = F.layer_norm(h, h.shape[-1:])
253
- h_out = self.out_proj(h)
254
- return TensorDict({
255
- 'features': h_out
256
- }, batch_size=h_out.shape[:1], device=h.device)
257
 
258
- class OctreeProbabilityFixedlenDecoder(ModulatedCrossOnlyTransformerBase):
259
- def __init__(
260
- self,
261
- model_channels: int,
262
- cond_channels: Optional[int],
263
- num_blocks: int,
264
- num_heads: Optional[int] = None,
265
- num_head_channels: Optional[int] = 64,
266
- mlp_ratio: float = 4.0,
267
- use_fp16: bool = False,
268
- use_checkpoint: bool = False,
269
- share_mod: bool = False,
270
- qk_rms_norm_cross: bool = True,
271
- *,
272
- pcd_pe_mode: Literal["pcd_ape", "pcd_ape_v2"] = "pcd_ape_v2",
273
- **kwargs,
274
- ):
275
- super().__init__(
276
- in_channels = model_channels, # pe embedded
277
- model_channels = model_channels,
278
- cond_channels = cond_channels,
279
- num_blocks = num_blocks,
280
- num_heads = num_heads,
281
- num_head_channels = num_head_channels,
282
- mlp_ratio = mlp_ratio,
283
- use_fp16 = use_fp16,
284
- use_checkpoint = use_checkpoint,
285
- share_mod = share_mod,
286
- qk_rms_norm_cross = qk_rms_norm_cross,
287
- )
288
- self.out_proj = nn.Linear(self.model_channels, 2 ** 3) # to 8 logits
289
-
290
- self.in_proj = nn.Linear(3, self.model_channels)
291
- self.pcd_pe_mode = pcd_pe_mode
292
- if pcd_pe_mode == "pcd_ape":
293
- self.pos_embedder = PcdAbsolutePositionEmbedder(
294
- channels=model_channels,
295
- in_channels=3,
296
- )
297
- elif pcd_pe_mode == "pcd_ape_v2":
298
- self.pos_embedder = PcdAbsolutePositionEmbedderv2(
299
- channels=model_channels,
300
- in_channels=3,
301
- )
302
- else:
303
- self.pos_embedder = None
304
-
305
- # weight init
306
- self.initialize_weights()
307
- if use_fp16:
308
- self.convert_to_fp16()
309
-
310
- def initialize_weights(self) -> None:
311
- super().initialize_weights()
312
- # zero-out output layers:
313
- nn.init.constant_(self.out_proj.weight, 0)
314
- nn.init.constant_(self.out_proj.bias, 0)
315
-
316
- def forward(self, x: torch.Tensor, l: torch.Tensor, cond: Optional[torch.Tensor]= None, l2: Optional[torch.Tensor] = None) -> torch.Tensor:
317
- """
318
- Args:
319
- x: query points voxel centers, [B(=1), L, 3]
320
- l: query points level, [B(=1)]
321
- cond: [B(=1), Lq, C] the latent features as condition
322
- Returns:
323
- list of Gaussian representations
324
- """
325
- B, L, C = x.shape
326
- h = self.in_proj(x)
327
- h = h + self.pos_embedder(x.view(-1,3)).view(B, L, -1) if hasattr(self, 'pos_embedder') else h
328
- # FIXME: elegant l2 log scaling
329
- if l2 is not None:
330
- l2 = torch.log2(l2)
331
- h = super().forward(h, l, cond, l2)
332
- h = h.type(cond.dtype)
333
- h = F.layer_norm(h, h.shape[-1:])
334
- logits = self.out_proj(h)
335
- probs = torch.softmax(logits, dim=-1)
336
- return TensorDict({'logits': logits, 'probs': probs}, batch_size=logits.shape[:1], device=logits.device)
337
 
338
- @staticmethod
339
- def sample(model, cond, num_points, level, temperature=1.0, algo: Literal["iid", "residual", "systematic"] = "systematic") -> TensorDict:
340
- """
341
- Sample points from the model.
342
- Args:
343
- cond: object latent condition, [B, Lc, C]
344
- num_points: number of points to sample
345
- level: level of the octree
346
- temperature: temperature of the sampling
347
- Returns:
348
- points: sampled points, [B, num_points, 3]
349
- log_probs: log probabilities of the sampled points, [B, num_points]
350
- """
351
- B = cond.shape[0]
352
- device = cond.device
353
- child_offset = torch.tensor([[i, j, k] for k in [0,1] for j in [0,1] for i in [0,1]], dtype=torch.long, device=device)
354
-
355
- prev_coords_int = torch.zeros(B, 1, 3, dtype=torch.long, device=device) # [B, 1, 3]
356
- prev_counts = torch.full((B, 1), num_points, dtype=torch.long, device=device) # [B, 1]
357
- prev_log_probs = torch.zeros(B, 1, dtype=torch.float32, device=device) # [B, 1]
358
-
359
- batch_indices_range = torch.arange(B, device=device).unsqueeze(1)
360
- num_tensor = torch.full((B,), num_points, dtype=torch.long, device=device)
361
 
362
- for l in range(1, level + 1):
363
- res_p = 1 << (l-1)
364
- res = 1 << l
365
-
366
- parent_coords_int = prev_coords_int # [B, Np, 3]
367
- parent_coords_norm = (parent_coords_int.to(torch.float32) + 0.5) / res_p
368
- res_tensor = torch.full((B,), res, dtype=torch.long, device=device)
369
-
370
- pred_logits = model(parent_coords_norm, res_tensor, cond, num_tensor)['logits'] # [B, Np, 8]
371
- pred_logits = pred_logits / temperature
372
- pred_probs = torch.softmax(pred_logits, dim=-1) # [B, Np, 8]
373
- pred_log_probs = torch.log_softmax(pred_logits, dim=-1) # [B, Np, 8]
374
- sampled = sample_probs(pred_probs, prev_counts, algo=algo,
375
- group_rows=getattr(model, 'group_sampling_rows', False)).flatten(1,2)
376
- pred_log_probs = pred_log_probs.flatten(1,2) # [B, Np*8]
377
- prev_log_probs_expanded = prev_log_probs.repeat_interleave(8, dim=1) # [B, Np*8]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
378
 
379
- # prepare coords
380
- child_coords_int = parent_coords_int[:, :, None, :] * 2 + child_offset[None, None, :, :] # [B,. Np, 8, 3]
381
- child_coords_int = child_coords_int.flatten(1,2) # [B, Np*8, 3]
382
-
383
- # mask out and padding
384
- # Vectorized implementation
385
- mask = sampled > 0 # [B, K]
386
- valid_counts = mask.sum(dim=1) # [B]
387
- max_valid = valid_counts.max().item()
388
-
389
- # Create scatter indices
390
- scatter_indices = mask.cumsum(dim=1) - 1
391
- valid_mask = mask # just mask
392
-
393
- valid_scatter_indices = scatter_indices[valid_mask] # [Total_Valid]
394
-
395
- # Batch indices
396
- valid_batch_indices = batch_indices_range.expand_as(mask)[valid_mask]
397
-
398
- next_prev_coords_int = torch.zeros(B, max_valid, 3, dtype=child_coords_int.dtype, device=device)
399
- next_prev_coords_int[valid_batch_indices, valid_scatter_indices] = child_coords_int[valid_mask]
400
-
401
- next_prev_counts = torch.zeros(B, max_valid, dtype=sampled.dtype, device=device)
402
- next_prev_counts[valid_batch_indices, valid_scatter_indices] = sampled[valid_mask]
403
-
404
- next_prev_log_probs = torch.zeros(B, max_valid, dtype=prev_log_probs.dtype, device=device)
405
- next_prev_log_probs[valid_batch_indices, valid_scatter_indices] = (prev_log_probs_expanded + pred_log_probs)[valid_mask]
406
-
407
- prev_coords_int = next_prev_coords_int
408
- prev_counts = next_prev_counts
409
- prev_log_probs = next_prev_log_probs
410
 
411
-
412
- # finially randomly put points in each voxel
413
- res = 1 << level
414
- # unflatten the coords according to counts
415
- prev_log_probs = torch.repeat_interleave(prev_log_probs.flatten(0,1), prev_counts.flatten(0,1), dim=0).reshape(B, num_points,) # [B, num_points]
416
- coords_int = torch.repeat_interleave(prev_coords_int.flatten(0,1), prev_counts.flatten(0,1), dim=0).reshape(B, num_points, -1) # [B, num_points, 3]
417
- coords_norm = (coords_int.to(torch.float32) + torch.rand_like(coords_int, dtype=torch.float32)) / res
418
 
419
- points = coords_norm
420
- log_probs = prev_log_probs
421
- return TensorDict({
422
- 'points': points,
423
- 'log_probs': log_probs,
424
- }, batch_size=points.shape[:1], device=points.device)
 
 
 
 
 
1
+ """The two decoders that turn a latent [B, 8192, 16] into Gaussians.
2
+
3
+ OctreeProbabilityFixedlenDecoder places the anchor points: starting from the unit cube, it predicts how the points
4
+ split among the 8 children of every occupied cell, level by level, then scatters each point inside its leaf cell.
5
+ GaussianFixedlenDecoder turns every anchor point into 32 Gaussians (offsets, colour, scale, rotation, opacity)."""
6
+ from typing import List
7
+
8
  import torch
9
  import torch.nn as nn
10
  import torch.nn.functional as F
11
+
12
  from ..gaussian import Gaussian
13
+ from .layers import (ModulatedTransformerCrossOnlyBlock, PcdAbsolutePositionEmbedderv2, SinusoidalEmbedder,
14
+ TransformerCrossBlock, convert_module_to_f16)
 
15
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
16
 
17
+ class OctreeProbabilityFixedlenDecoder(nn.Module):
18
+ def __init__(self, model_channels: int, cond_channels: int, num_blocks: int, num_heads: int,
19
+ mlp_ratio: float = 4.0):
20
+ super().__init__()
21
+ self.dtype = torch.float16
22
+ self.in_proj = nn.Linear(3, model_channels)
23
+ self.pos_embedder = PcdAbsolutePositionEmbedderv2(channels=model_channels, in_channels=3)
24
+ self.input_layer = nn.Linear(model_channels, model_channels)
25
+ self.l_embedder = SinusoidalEmbedder(model_channels, max_period=1024, two_pi=True)
26
+ self.adaLN_modulation = nn.Sequential(nn.SiLU(), nn.Linear(model_channels, 6 * model_channels, bias=True))
27
+ self.blocks = nn.ModuleList([
28
+ ModulatedTransformerCrossOnlyBlock(model_channels, ctx_channels=cond_channels, num_heads=num_heads,
29
+ mlp_ratio=mlp_ratio)
30
+ for _ in range(num_blocks)
31
+ ])
32
+ self.out_proj = nn.Linear(model_channels, 2 ** 3)
33
+ self.blocks.apply(convert_module_to_f16)
34
 
35
+ def forward(self, x: torch.Tensor, level: torch.Tensor, cond: torch.Tensor) -> torch.Tensor:
36
+ """x: cell centres [B, L, 3] in [0, 1]; level: the cells' octree resolution [B]; cond: latent [B, Lc, C].
37
+ Returns the logits [B, L, 8] of how each cell's points split among its 8 children."""
38
+ B, L, _ = x.shape
39
+ h = self.in_proj(x) + self.pos_embedder(x.view(-1, 3)).view(B, L, -1)
40
+ h = self.input_layer(h)
41
+ level_emb = self.adaLN_modulation(self.l_embedder(level.type(h.dtype))).type(self.dtype)
42
+ out = h.type(self.dtype)
43
+ context = cond.type(self.dtype)
44
+ for block in self.blocks:
45
+ out = block(out, level_emb, context)
46
+ out = out.type(h.dtype).type(cond.dtype)
47
+ return self.out_proj(F.layer_norm(out, out.shape[-1:]))
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
48
 
49
+ @staticmethod
50
+ @torch.no_grad()
51
+ def sample(model, cond: torch.Tensor, num_points: int, level: int) -> torch.Tensor:
52
+ """Place num_points anchor points [B, num_points, 3] in [0, 1]^3, descending the octree to ``level``."""
53
+ B = cond.shape[0]
54
+ device = cond.device
55
+ child_offset = torch.tensor([[i, j, k] for k in [0, 1] for j in [0, 1] for i in [0, 1]], dtype=torch.long,
56
+ device=device)
57
+ prev_coords_int = torch.zeros(B, 1, 3, dtype=torch.long, device=device)
58
+ prev_counts = torch.full((B, 1), num_points, dtype=torch.long, device=device)
59
+ batch_indices_range = torch.arange(B, device=device).unsqueeze(1)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
60
 
61
+ for l in range(1, level + 1):
62
+ res_p = 1 << (l - 1)
63
+ parent_coords_norm = (prev_coords_int.to(torch.float32) + 0.5) / res_p
64
+ res_tensor = torch.full((B,), 1 << l, dtype=torch.long, device=device)
65
+ pred_probs = torch.softmax(model(parent_coords_norm, res_tensor, cond), dim=-1) # [B, Np, 8]
66
+ sampled = systematic_sample(pred_probs, prev_counts).flatten(1, 2) # [B, Np*8]
67
+ child_coords_int = (prev_coords_int[:, :, None, :] * 2 + child_offset[None, None, :, :]).flatten(1, 2)
68
 
69
+ # keep the children that received points, packed to the front
70
+ mask = sampled > 0
71
+ max_valid = mask.sum(dim=1).max().item()
72
+ valid_scatter_indices = (mask.cumsum(dim=1) - 1)[mask]
73
+ valid_batch_indices = batch_indices_range.expand_as(mask)[mask]
74
+ prev_coords_int = torch.zeros(B, max_valid, 3, dtype=child_coords_int.dtype, device=device)
75
+ prev_coords_int[valid_batch_indices, valid_scatter_indices] = child_coords_int[mask]
76
+ prev_counts = torch.zeros(B, max_valid, dtype=sampled.dtype, device=device)
77
+ prev_counts[valid_batch_indices, valid_scatter_indices] = sampled[mask]
78
 
79
+ # each point lands at a uniformly random position inside its leaf cell
80
+ coords_int = torch.repeat_interleave(prev_coords_int.flatten(0, 1), prev_counts.flatten(0, 1),
81
+ dim=0).reshape(B, num_points, -1)
82
+ return (coords_int.to(torch.float32) + torch.rand_like(coords_int, dtype=torch.float32)) / (1 << level)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
83
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
84
 
85
+ @torch.no_grad()
86
+ def systematic_sample(probs: torch.Tensor, counts: torch.Tensor) -> torch.Tensor:
87
+ """Split counts[...] items among the P bins of probs[..., P] by systematic resampling -> [..., P] counts."""
88
+ batch_shape = counts.shape
89
+ B = counts.numel()
90
+ P = probs.size(-1)
91
+ device = probs.device
92
+ probs = probs.view(B, P)
93
+ counts = counts.view(B)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
94
 
95
+ # normalise, with a uniform fallback for zero-sum rows
96
+ probs = probs.to(torch.float32).clamp_min_(0)
97
+ row_sums = probs.sum(1, keepdim=True)
98
+ zero_mask = row_sums.eq(0)
99
+ probs = probs / row_sums.clamp_min_(1)
100
+ if zero_mask.any():
101
+ probs = probs.clone()
102
+ probs[zero_mask.expand_as(probs)] = 1.0 / P
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
103
 
104
+ counts = counts.to(device=device, dtype=torch.long)
105
+ out = torch.zeros(B, P, dtype=torch.long, device=device)
106
+ cdf = probs.cumsum(dim=1).clamp(max=1.0 - 1e-12)
107
+ unique_n, inv = counts.unique(sorted=False, return_inverse=True)
108
+ for index, n in enumerate(unique_n.tolist()):
109
+ if n == 0:
110
+ continue
111
+ rows = (inv == index).nonzero(as_tuple=False).squeeze(1)
112
+ r = rows.numel()
113
+ u0 = torch.rand(r, 1, device=device) / float(n) # random offset in [0, 1/n)
114
+ grid = torch.arange(n, device=device, dtype=torch.float32)[None, :] / float(n) # stratified points
115
+ us = (u0 + grid).clamp(max=1.0 - 1e-12)
116
+ idx = torch.searchsorted(cdf.index_select(0, rows), us).clamp_max(P - 1)
117
+ buf = torch.zeros(r, P, dtype=torch.float32, device=device)
118
+ buf.scatter_add_(1, idx, torch.ones_like(idx, dtype=buf.dtype))
119
+ out.index_copy_(0, rows, buf.to(torch.long))
120
+ return out.view(*batch_shape, P)
121
+
122
+
123
+ class GaussianFixedlenDecoder(nn.Module):
124
+ def __init__(self, in_channels: int, model_channels: int, cond_channels: int, num_blocks: int, num_heads: int,
125
+ representation_config: dict, mlp_ratio: float = 4.0):
126
+ super().__init__()
127
+ self.dtype = torch.float16
128
+ self.rep_config = representation_config
129
+ per_anchor = representation_config['num_gaussians']
130
+ # output channels per anchor: offset (3), colour (3), scale (3), rotation (4), opacity (1), offset scale (1)
131
+ self.layout = {
132
+ '_xyz': (3,),
133
+ '_features_dc': (1, 3),
134
+ '_scaling': (3,),
135
+ '_rotation': (4,),
136
+ '_opacity': (1,),
137
+ '_offset_scale': (1,),
138
+ }
139
+ start = 0
140
+ self.ranges = {}
141
+ for name, shape in self.layout.items():
142
+ size = per_anchor * int(torch.tensor(shape).prod())
143
+ self.ranges[name] = (start, start + size)
144
+ start += size
145
+
146
+ self.in_proj = nn.Linear(in_channels, model_channels)
147
+ self.pos_embedder = PcdAbsolutePositionEmbedderv2(channels=model_channels, in_channels=3)
148
+ self.input_layer = nn.Linear(model_channels, model_channels)
149
+ self.blocks = nn.ModuleList([
150
+ TransformerCrossBlock(model_channels, ctx_channels=cond_channels, num_heads=num_heads, mlp_ratio=mlp_ratio)
151
+ for _ in range(num_blocks)
152
+ ])
153
+ self.out_proj = nn.Linear(model_channels, start)
154
+ # fixed per-Gaussian offset pattern and the base offset scale, stored with the weights
155
+ self.register_buffer('points_offset_perturbation', torch.zeros(per_anchor, 3))
156
+ self.register_buffer('base_offset_scale', torch.zeros(()))
157
+ self.blocks.apply(convert_module_to_f16)
158
+
159
+ def forward(self, points: torch.Tensor, cond: torch.Tensor) -> torch.Tensor:
160
+ """points: anchors [B, L, 3]; cond: latent [B, Lc, C] -> per-anchor outputs [B, L, 32 x 15]"""
161
+ B, L, C = points.shape
162
+ h = self.in_proj(points) + self.pos_embedder(points.view(-1, C)).view(B, L, -1)
163
+ h = self.input_layer(h)
164
+ out = h.type(self.dtype)
165
+ context = cond.type(self.dtype)
166
+ for block in self.blocks:
167
+ out = block(out, context)
168
+ out = out.type(h.dtype).type(cond.dtype)
169
+ return self.out_proj(F.layer_norm(out, out.shape[-1:]))
170
 
171
+ def _slice(self, features, name):
172
+ start, end = self.ranges[name]
173
+ return features[:, :, start:end].reshape(features.shape[0], -1, self.rep_config['num_gaussians'],
174
+ *self.layout[name])
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
175
 
176
+ def to_representation(self, points: torch.Tensor, features: torch.Tensor) -> List[Gaussian]:
177
+ """Anchors [B, L, 3] and per-anchor outputs [B, L, D] -> one Gaussian object per batch element."""
178
+ config = self.rep_config
179
+ offset_scale = F.softplus(self._slice(features, '_offset_scale') + self.base_offset_scale)
180
+ offset = self._slice(features, '_xyz') * config['lr']['_xyz'] + self.points_offset_perturbation
181
+ offset = torch.tanh(offset) * 0.5 * config['perturbe_size'] * offset_scale
 
182
 
183
+ objects = []
184
+ for i in range(features.shape[0]):
185
+ raw = {name: (self._slice(features[i:i + 1], name)[0].flatten(0, 1) * config['lr'][name])
186
+ for name in ('_features_dc', '_scaling', '_rotation', '_opacity')}
187
+ objects.append(Gaussian(
188
+ raw_xyz=(offset[i] + points[i, :, None, :]).flatten(0, 1),
189
+ features_dc=raw['_features_dc'], raw_scaling=raw['_scaling'], raw_rotation=raw['_rotation'],
190
+ raw_opacity=raw['_opacity'], scaling_bias=config['scaling_bias'],
191
+ opacity_bias=config['opacity_bias'], minimum_kernel_size=config['3d_filter_kernel_size']))
192
+ return objects
idol/models/dit.py CHANGED
@@ -1,6 +1,6 @@
1
  # Adapted from https://github.com/huggingface/diffusers/blob/main/src/diffusers/models/transformers/transformer_wan.py
2
  # The original license is as follows:
3
- #
4
  # Copyright 2025 The Wan Team and The HuggingFace Team. All rights reserved.
5
  #
6
  # Licensed under the Apache License, Version 2.0 (the "License");
@@ -14,415 +14,109 @@
14
  # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
  # See the License for the specific language governing permissions and
16
  # limitations under the License.
17
-
18
- from typing import *
19
  import torch
20
  import torch.nn as nn
21
  import torch.nn.functional as F
22
- import numpy as np
23
  from tensordict import TensorDict
24
 
25
- from ..modules.mlp import MLP
26
- from ..modules.transformer.modulated import UnifiedTransformerBlock
27
- from ..modules.attention.modules import RePo3DRotaryEmbedding, Rotary3DPoseEmbedding
28
- from ..modules.utils import convert_module_to_f16, convert_module_to_f32
29
- from .transformer_base import PcdAbsolutePositionEmbedder
30
-
31
- class TimestepEmbedder(nn.Module):
32
- """
33
- Embeds scalar timesteps into vector representations.
34
- """
35
- def __init__(self, hidden_size, frequency_embedding_size=256):
36
- super().__init__()
37
- self.mlp = nn.Sequential(
38
- nn.Linear(frequency_embedding_size, hidden_size, bias=True),
39
- nn.SiLU(),
40
- nn.Linear(hidden_size, hidden_size, bias=True),
41
- )
42
- self.frequency_embedding_size = frequency_embedding_size
43
-
44
- @staticmethod
45
- def timestep_embedding(t, dim, max_period=10000):
46
- """
47
- Create sinusoidal timestep embeddings.
48
-
49
- Args:
50
- t: a 1-D Tensor of N indices, one per batch element.
51
- These may be fractional.
52
- dim: the dimension of the output.
53
- max_period: controls the minimum frequency of the embeddings.
54
-
55
- Returns:
56
- an (N, D) Tensor of positional embeddings.
57
- """
58
- # https://github.com/openai/glide-text2im/blob/main/glide_text2im/nn.py
59
- half = dim // 2
60
- freqs = torch.exp(
61
- -np.log(max_period) * torch.arange(start=0, end=half, dtype=torch.float32) / half
62
- ).to(device=t.device)
63
- args = t[:, None].float() * freqs[None]
64
- embedding = torch.cat([torch.cos(args), torch.sin(args)], dim=-1)
65
- if dim % 2:
66
- embedding = torch.cat([embedding, torch.zeros_like(embedding[:, :1])], dim=-1)
67
- return embedding
68
 
69
- def forward(self, t):
70
- t_freq = self.timestep_embedding(t, self.frequency_embedding_size)
71
- t_emb = self.mlp(t_freq)
72
- return t_emb
73
 
74
  class LatentSeqMMFlowModel(nn.Module):
75
- """
76
- Latent Sequence Multimodal Flow Model (Unified MMDiT).
77
- Adapts ZImageTransformer2DModel architecture for 3D-Image unification.
78
- MMDiT adapted from diffusers/models/transformers/transformer_z_image.py
79
- REPO adapted from https://github.com/SakanaAI/repo
80
- """
81
  def __init__(
82
  self,
83
  q_token_length: int,
84
  in_channels: int,
 
85
  model_channels: int,
86
  cond_channels: int,
87
- out_channels: int,
88
  num_blocks: int,
89
  num_refiner_blocks: int = 2,
90
- num_heads: Optional[int] = None,
91
- num_head_channels: Optional[int] = 64,
92
- cam_channels: Optional[int] = None,
93
- cond2_channels: Optional[int] = None,
94
  mlp_ratio: float = 4,
95
- pe_mode: Literal["3d_ot_rope", "3d_ot_repo", "3d_ot_ape", "repo", "learnable", None] = "3d_ot_ape",
96
- use_fp16: bool = False,
97
- use_checkpoint: bool = False,
98
- share_mod: bool = True,
99
- qk_rms_norm: bool = False,
100
- use_shift_table: bool = False,
101
  ):
102
  super().__init__()
103
  self.q_token_length = q_token_length
104
  self.in_channels = in_channels
105
  self.cam_channels = cam_channels
106
- self.model_channels = model_channels
107
- self.cond_channels = cond_channels
108
- self.cond2_channels = cond2_channels
109
- self.out_channels = out_channels
110
- self.num_blocks = num_blocks
111
- self.num_refiner_blocks = num_refiner_blocks
112
- self.num_heads = num_heads or model_channels // num_head_channels
113
- self.mlp_ratio = mlp_ratio
114
- self.pe_mode = pe_mode
115
- self.use_fp16 = use_fp16
116
- self.use_checkpoint = use_checkpoint
117
- self.share_mod = share_mod
118
- self.qk_rms_norm = qk_rms_norm
119
- self.dtype = torch.float16 if use_fp16 else torch.float32
120
- self.use_shift_table = use_shift_table
121
 
122
- self.t_embedder = TimestepEmbedder(model_channels)
123
- if share_mod:
124
- self.adaLN_modulation = nn.Sequential(
125
- nn.SiLU(),
126
- nn.Linear(model_channels, 6 * model_channels, bias=True)
127
- )
128
 
 
 
129
  self.input_layer = nn.Linear(in_channels, model_channels)
130
  self.cond_embedder = nn.Linear(cond_channels, model_channels)
131
- if cond2_channels is not None:
132
- self.cond_embedder2 = nn.Linear(cond2_channels, model_channels)
133
- else:
134
- self.cond_embedder2 = None
135
-
136
- # Positional Embeddings
137
- self.use_rope = (self.pe_mode in ["3d_ot_rope", "3d_ot_repo", "repo"])
138
- if pe_mode == "3d_ot_rope":
139
- sobol_seq = torch.quasirandom.SobolEngine(dimension=3, scramble=True, seed=123).draw(q_token_length)
140
- self.register_buffer("pos_pe", sobol_seq[None])
141
- self.pos_embedder = Rotary3DPoseEmbedding(num_head_channels)
142
- self.cond_pos_embedder = None
143
- elif pe_mode == "3d_ot_ape":
144
- sobol_seq = torch.quasirandom.SobolEngine(dimension=3, scramble=True, seed=123).draw(q_token_length)
145
- self.register_buffer("pos_pe", sobol_seq[None])
146
- self.pos_embedder = PcdAbsolutePositionEmbedder(model_channels)
147
- self.cond_pos_embedder = None
148
- elif pe_mode == "repo":
149
- self.pos_embedder = None
150
- self.cond_pos_embedder = None
151
- self.noise_repo_layers = nn.ModuleList([
152
- RePo3DRotaryEmbedding(model_channels, num_heads=self.num_heads, head_dim=num_head_channels)
153
- for _ in range(num_refiner_blocks)
154
- ])
155
- self.context_repo_layers = nn.ModuleList([
156
- RePo3DRotaryEmbedding(model_channels, num_heads=self.num_heads, head_dim=num_head_channels)
157
- for _ in range(num_refiner_blocks)
158
- ])
159
- self.repo_layers = nn.ModuleList([
160
- RePo3DRotaryEmbedding(model_channels, num_heads=self.num_heads, head_dim=num_head_channels)
161
- for _ in range(num_blocks)
162
- ])
163
- elif pe_mode == "3d_ot_repo":
164
- sobol_seq = torch.quasirandom.SobolEngine(dimension=3, scramble=True, seed=123).draw(q_token_length)
165
- self.register_buffer("pos_pe", sobol_seq[None])
166
- self.pos_embedder = PcdAbsolutePositionEmbedder(model_channels)
167
- self.cond_pos_embedder = None
168
- self.noise_repo_layers = nn.ModuleList([
169
- RePo3DRotaryEmbedding(model_channels, num_heads=self.num_heads, head_dim=num_head_channels)
170
- for _ in range(num_refiner_blocks)
171
- ])
172
- self.context_repo_layers = nn.ModuleList([
173
- RePo3DRotaryEmbedding(model_channels, num_heads=self.num_heads, head_dim=num_head_channels)
174
- for _ in range(num_refiner_blocks)
175
- ])
176
- self.repo_layers = nn.ModuleList([
177
- RePo3DRotaryEmbedding(model_channels, num_heads=self.num_heads, head_dim=num_head_channels)
178
- for _ in range(num_blocks)
179
- ])
180
- elif pe_mode == "learnable":
181
- # Not fully implemented adaptation for learnable
182
- self.pos_pe = None
183
- self.pos_embedder = None
184
- else:
185
- self.pos_pe = None
186
- self.pos_embedder = None
187
 
188
- # Refiners
189
- self.noise_refiner = nn.ModuleList([
190
- UnifiedTransformerBlock(
191
- model_channels,
192
- num_heads=self.num_heads,
193
- mlp_ratio=self.mlp_ratio,
194
- use_checkpoint=self.use_checkpoint,
195
- use_rope=self.use_rope,
196
- qk_rms_norm=self.qk_rms_norm,
197
- modulation=True,
198
- share_mod=self.share_mod,
199
- use_shift_table=self.use_shift_table,
200
- )
201
- for _ in range(num_refiner_blocks)
202
- ])
203
-
204
- self.context_refiner = nn.ModuleList([
205
- UnifiedTransformerBlock(
206
- model_channels,
207
- num_heads=self.num_heads,
208
- mlp_ratio=self.mlp_ratio,
209
- use_checkpoint=self.use_checkpoint,
210
- use_rope=self.use_rope,
211
- qk_rms_norm=self.qk_rms_norm,
212
- modulation=False,
213
- )
214
- for _ in range(num_refiner_blocks)
215
- ])
216
- if self.cam_channels is not None:
217
- self.cam_refiner = MLP(
218
- self.cam_channels,
219
- model_channels,
220
- model_channels,
221
- mlp_layer_num=num_refiner_blocks,
222
- )
223
-
224
- self.blocks = nn.ModuleList([
225
- UnifiedTransformerBlock(
226
- model_channels,
227
- num_heads=self.num_heads,
228
- mlp_ratio=self.mlp_ratio,
229
- use_checkpoint=self.use_checkpoint,
230
- use_rope=self.use_rope,
231
- qk_rms_norm=self.qk_rms_norm,
232
- modulation=True,
233
- share_mod=self.share_mod,
234
- use_shift_table=self.use_shift_table,
235
- )
236
- for _ in range(num_blocks)
237
- ])
238
 
239
- if self.use_shift_table:
240
- self.shift_table = nn.Parameter(torch.randn(1, 2, model_channels) / model_channels**0.5)
241
- else:
242
- self.shift_table = None
 
243
 
244
- self.out_layer = nn.Linear(model_channels, out_channels)
245
- if cam_channels is not None:
246
- self.cam_out_layer = nn.Linear(model_channels, cam_channels)
247
-
248
- self.initialize_weights()
249
- if use_fp16:
250
- self.convert_to_fp16()
251
 
252
- @property
253
- def device(self) -> torch.device:
254
- return next(self.parameters()).device
255
-
256
- def convert_to_fp16(self) -> None:
257
  self.blocks.apply(convert_module_to_f16)
258
  self.noise_refiner.apply(convert_module_to_f16)
259
  self.context_refiner.apply(convert_module_to_f16)
260
 
261
- def convert_to_fp32(self) -> None:
262
- self.blocks.apply(convert_module_to_f32)
263
- self.noise_refiner.apply(convert_module_to_f32)
264
- self.context_refiner.apply(convert_module_to_f32)
265
-
266
- def initialize_weights(self) -> None:
267
- def _basic_init(module):
268
- if isinstance(module, nn.Linear):
269
- torch.nn.init.xavier_uniform_(module.weight)
270
- if module.bias is not None:
271
- nn.init.constant_(module.bias, 0)
272
- self.apply(_basic_init)
273
-
274
- nn.init.normal_(self.t_embedder.mlp[0].weight, std=0.02)
275
- nn.init.normal_(self.t_embedder.mlp[2].weight, std=0.02)
276
-
277
- if self.share_mod:
278
- nn.init.constant_(self.adaLN_modulation[-1].weight, 0)
279
- nn.init.constant_(self.adaLN_modulation[-1].bias, 0)
280
- else:
281
- for block in self.blocks:
282
- nn.init.constant_(block.adaLN_modulation[-1].weight, 0)
283
- nn.init.constant_(block.adaLN_modulation[-1].bias, 0)
284
- for block in self.noise_refiner:
285
- nn.init.constant_(block.adaLN_modulation[-1].weight, 0)
286
- nn.init.constant_(block.adaLN_modulation[-1].bias, 0)
287
-
288
- if self.use_shift_table:
289
- for block in self.blocks:
290
- nn.init.constant_(block.shift_table, 0)
291
- for block in self.noise_refiner:
292
- nn.init.constant_(block.shift_table, 0)
293
- nn.init.constant_(self.shift_table, 0)
294
-
295
- if hasattr(self, 'repo_layers'):
296
- for block in self.noise_repo_layers:
297
- block.initialize_weights()
298
- for block in self.context_repo_layers:
299
- block.initialize_weights()
300
- for block in self.repo_layers:
301
- block.initialize_weights()
302
-
303
- if self.cond_embedder2 is not None:
304
- nn.init.constant_(self.cond_embedder2.weight, 0)
305
- nn.init.constant_(self.cond_embedder2.bias, 0)
306
-
307
- nn.init.constant_(self.out_layer.weight, 0)
308
- nn.init.constant_(self.out_layer.bias, 0)
309
-
310
- if self.cam_channels is not None:
311
- nn.init.constant_(self.cam_out_layer.weight, 0)
312
- nn.init.constant_(self.cam_out_layer.bias, 0)
313
-
314
- def forward(
315
- self,
316
- x: TensorDict,
317
- t: torch.Tensor,
318
- cond: TensorDict,
319
- points: Optional[torch.Tensor] = None,
320
- return_mid_features: bool = False,
321
- **kwargs,
322
- ) -> torch.Tensor:
323
  """
324
  Args:
325
- x: TensorDict with 'latent' [B, L_x, C_in] key, optional 'cam' [B, 1, C_cam] key
326
- cond: TensorDict with 'feature1' [B, L_c, C_cond1] key, optional 'feature2' [B, L_c, C_cond2] key
 
 
 
327
  """
328
-
329
- z = x['latent']
330
  h_x = self.input_layer(z).type(self.dtype)
331
- if self.cond_embedder2 is not None:
332
- h_cond = self.cond_embedder(cond['feature1']) + self.cond_embedder2(cond['feature2'])
333
- else:
334
- h_cond = self.cond_embedder(cond['feature1'])
335
  h_cond = h_cond.type(self.dtype)
336
-
337
- t_emb = self.t_embedder(t)
338
- if self.share_mod:
339
- t_mod = self.adaLN_modulation(t_emb)
340
- else:
341
- t_mod = t_emb
342
- t_mod = t_mod.type(self.dtype)
343
 
344
- rotary_emb = None
345
- rope_x = None
346
- rope_cond = None
347
 
348
- if self.pos_embedder is not None:
349
- if self.pe_mode == "3d_ot_rope":
350
- rope_x = self.pos_embedder(self.pos_pe) # [1, L, 1, D/2]
351
- rope_x = rope_x.expand(z.shape[0], -1, -1, -1)
352
-
353
- # cond RoPE (Identity)
354
- rope_cond = torch.ones(z.shape[0], h_cond.shape[1], 1, rope_x.shape[-1], device=z.device, dtype=rope_x.real.dtype)
355
- rope_cond = torch.polar(rope_cond, torch.zeros_like(rope_cond))
356
-
357
- parts = [rope_x, rope_cond]
358
- if self.cam_channels is not None:
359
- rope_cam = torch.ones(z.shape[0], x['camera'].shape[1], 1, rope_x.shape[-1], device=z.device, dtype=rope_x.real.dtype)
360
- rope_cam = torch.polar(rope_cam, torch.zeros_like(rope_cam))
361
- parts.append(rope_cam)
362
-
363
- rotary_emb = torch.cat(parts, dim=1)
364
 
365
- elif self.pe_mode in ["3d_ot_ape", "3d_ot_repo"]:
366
- h_x = h_x + self.pos_embedder(self.pos_pe).type(self.dtype)
367
-
368
- # Refine noise (z)
369
- for i, block in enumerate(self.noise_refiner):
370
- if "repo" in self.pe_mode:
371
- rope_x = self.noise_repo_layers[i](h_x)
372
- h_x = block(h_x, mod=t_mod, rotary_emb=rope_x)
373
-
374
- # Refine context (cond)
375
- # Note: We pass rope_cond (identity) if rope is used, though it might not affect much if it's identity
376
- for i, block in enumerate(self.context_refiner):
377
- if "repo" in self.pe_mode:
378
- rope_cond = self.context_repo_layers[i](h_cond)
379
- h_cond = block(h_cond, mod=None, rotary_emb=rope_cond)
380
-
381
- # Refine camera
382
- if self.cam_channels is not None:
383
- cam = x['camera']
384
- h_cam = self.cam_refiner(cam)
385
- h_cam = h_cam.type(self.dtype)
386
 
387
- # Concat
388
- h = torch.cat([h_x, h_cond], dim=1)
389
- if self.cam_channels is not None:
390
- h = torch.cat([h, h_cam], dim=1)
391
-
392
- mid_features = None
393
- mid_idx = len(self.blocks) // 2
394
- for i, block in enumerate(self.blocks):
395
- if "repo" in self.pe_mode:
396
- rotary_emb = self.repo_layers[i](h)
397
- h = block(h, mod=t_mod, rotary_emb=rotary_emb)
398
- if i == mid_idx:
399
- mid_features = h
400
-
401
- # Split
402
- h_x = h[:, :z.shape[1]]
403
- h_x = h_x.type(z.dtype)
404
  h_x = F.layer_norm(h_x, h_x.shape[-1:])
405
- # camera out
406
- if self.cam_channels is not None:
407
- h_cam = h[:, -cam.shape[1]:]
408
- h_cam = h_cam.type(cam.dtype)
409
- h_cam = F.layer_norm(h_cam, h_cam.shape[-1:])
410
-
411
- if self.use_shift_table:
412
- shift, scale = (self.shift_table + t_emb.unsqueeze(1)).chunk(2, dim=1)
413
- h_x = h_x * (1 + scale) + shift
414
- if self.cam_channels is not None:
415
- h_cam = h_cam * (1 + scale) + shift
416
-
417
- h_x = self.out_layer(h_x)
418
- if self.cam_channels is not None:
419
- h_cam = self.cam_out_layer(h_cam)
420
-
421
- out = TensorDict({
422
- 'latent': h_x,
423
- }, batch_size=x.batch_size)
424
- if self.cam_channels is not None:
425
- out['camera'] = h_cam
426
- if return_mid_features:
427
- return out, mid_features
428
  return out
 
1
  # Adapted from https://github.com/huggingface/diffusers/blob/main/src/diffusers/models/transformers/transformer_wan.py
2
  # The original license is as follows:
3
+ #
4
  # Copyright 2025 The Wan Team and The HuggingFace Team. All rights reserved.
5
  #
6
  # Licensed under the Apache License, Version 2.0 (the "License");
 
14
  # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
  # See the License for the specific language governing permissions and
16
  # limitations under the License.
17
+ """The denoiser: a rectified-flow transformer over the object latent (8192 tokens) and the input camera (1 token),
18
+ conditioned on the image tokens. Joint attention over [latent, image, camera] with learned rotary positions."""
19
  import torch
20
  import torch.nn as nn
21
  import torch.nn.functional as F
 
22
  from tensordict import TensorDict
23
 
24
+ from .layers import (MLP, PcdAbsolutePositionEmbedder, RePo3DRotaryEmbedding, SinusoidalEmbedder,
25
+ UnifiedTransformerBlock, convert_module_to_f16)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
26
 
 
 
 
 
27
 
28
  class LatentSeqMMFlowModel(nn.Module):
 
 
 
 
 
 
29
  def __init__(
30
  self,
31
  q_token_length: int,
32
  in_channels: int,
33
+ cam_channels: int,
34
  model_channels: int,
35
  cond_channels: int,
36
+ cond2_channels: int,
37
  num_blocks: int,
38
  num_refiner_blocks: int = 2,
39
+ num_heads: int = 16,
40
+ num_head_channels: int = 64,
 
 
41
  mlp_ratio: float = 4,
 
 
 
 
 
 
42
  ):
43
  super().__init__()
44
  self.q_token_length = q_token_length
45
  self.in_channels = in_channels
46
  self.cam_channels = cam_channels
47
+ self.dtype = torch.float16
48
+
49
+ def blocks(count, modulation=True):
50
+ return nn.ModuleList([UnifiedTransformerBlock(model_channels, num_heads=num_heads, mlp_ratio=mlp_ratio,
51
+ modulation=modulation) for _ in range(count)])
 
 
 
 
 
 
 
 
 
 
52
 
53
+ def rope_layers(count):
54
+ return nn.ModuleList([RePo3DRotaryEmbedding(model_channels, num_heads=num_heads,
55
+ head_dim=num_head_channels) for _ in range(count)])
 
 
 
56
 
57
+ self.t_embedder = SinusoidalEmbedder(model_channels)
58
+ self.adaLN_modulation = nn.Sequential(nn.SiLU(), nn.Linear(model_channels, 6 * model_channels, bias=True))
59
  self.input_layer = nn.Linear(in_channels, model_channels)
60
  self.cond_embedder = nn.Linear(cond_channels, model_channels)
61
+ self.cond_embedder2 = nn.Linear(cond2_channels, model_channels)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
62
 
63
+ # positions of the latent tokens (a fixed scrambled Sobol sequence, stored with the weights)
64
+ self.register_buffer("pos_pe", torch.zeros(1, q_token_length, 3))
65
+ self.pos_embedder = PcdAbsolutePositionEmbedder(model_channels)
66
+ self.noise_repo_layers = rope_layers(num_refiner_blocks)
67
+ self.context_repo_layers = rope_layers(num_refiner_blocks)
68
+ self.repo_layers = rope_layers(num_blocks)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
69
 
70
+ # each stream is refined on its own, then all tokens go through the joint blocks
71
+ self.noise_refiner = blocks(num_refiner_blocks)
72
+ self.context_refiner = blocks(num_refiner_blocks, modulation=False)
73
+ self.cam_refiner = MLP(cam_channels, model_channels, model_channels, mlp_layer_num=num_refiner_blocks)
74
+ self.blocks = blocks(num_blocks)
75
 
76
+ self.shift_table = nn.Parameter(torch.zeros(1, 2, model_channels))
77
+ self.out_layer = nn.Linear(model_channels, in_channels)
78
+ self.cam_out_layer = nn.Linear(model_channels, cam_channels)
 
 
 
 
79
 
 
 
 
 
 
80
  self.blocks.apply(convert_module_to_f16)
81
  self.noise_refiner.apply(convert_module_to_f16)
82
  self.context_refiner.apply(convert_module_to_f16)
83
 
84
+ def forward(self, x: TensorDict, t: torch.Tensor, cond: TensorDict) -> TensorDict:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
85
  """
86
  Args:
87
+ x: TensorDict with 'latent' [B, 8192, 16] and 'camera' [B, 1, 5]
88
+ t: [B] time in [0, 1000]
89
+ cond: TensorDict with 'feature1' [B, L_c, 1280] (DINOv3) and 'feature2' [B, L_c, 128] (FLUX.2 VAE)
90
+ Returns:
91
+ the velocity, a TensorDict shaped like x
92
  """
93
+ z, cam = x['latent'], x['camera']
 
94
  h_x = self.input_layer(z).type(self.dtype)
95
+ h_cond = self.cond_embedder(cond['feature1']) + self.cond_embedder2(cond['feature2'])
 
 
 
96
  h_cond = h_cond.type(self.dtype)
 
 
 
 
 
 
 
97
 
98
+ t_emb = self.t_embedder(t)
99
+ t_mod = self.adaLN_modulation(t_emb).type(self.dtype)
 
100
 
101
+ h_x = h_x + self.pos_embedder(self.pos_pe).type(self.dtype)
102
+ for repo, block in zip(self.noise_repo_layers, self.noise_refiner):
103
+ h_x = block(h_x, mod=t_mod, rotary_emb=repo(h_x))
104
+ for repo, block in zip(self.context_repo_layers, self.context_refiner):
105
+ h_cond = block(h_cond, mod=None, rotary_emb=repo(h_cond))
106
+ h_cam = self.cam_refiner(cam).type(self.dtype)
 
 
 
 
 
 
 
 
 
 
107
 
108
+ h = torch.cat([h_x, h_cond, h_cam], dim=1)
109
+ for repo, block in zip(self.repo_layers, self.blocks):
110
+ h = block(h, mod=t_mod, rotary_emb=repo(h))
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
111
 
112
+ h_x = h[:, :z.shape[1]].type(z.dtype)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
113
  h_x = F.layer_norm(h_x, h_x.shape[-1:])
114
+ h_cam = h[:, -cam.shape[1]:].type(cam.dtype)
115
+ h_cam = F.layer_norm(h_cam, h_cam.shape[-1:])
116
+ shift, scale = (self.shift_table + t_emb.unsqueeze(1)).chunk(2, dim=1)
117
+ h_x = h_x * (1 + scale) + shift
118
+ h_cam = h_cam * (1 + scale) + shift
119
+
120
+ out = TensorDict({'latent': self.out_layer(h_x)}, batch_size=x.batch_size)
121
+ out['camera'] = self.cam_out_layer(h_cam)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
122
  return out
idol/models/layers.py ADDED
@@ -0,0 +1,318 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Building blocks shared by the denoiser and the decoders: attention, positional embeddings, transformer blocks."""
2
+ import importlib.util
3
+ import os
4
+ from typing import Optional
5
+
6
+ import numpy as np
7
+ import torch
8
+ import torch.nn as nn
9
+ import torch.nn.functional as F
10
+
11
+ # flash-attn when it is installed, PyTorch SDPA otherwise (same result, flash-attn is faster).
12
+ # Override with ATTN_BACKEND=flash_attn|sdpa.
13
+ ATTN_BACKEND = os.environ.get('ATTN_BACKEND',
14
+ 'flash_attn' if importlib.util.find_spec('flash_attn') is not None else 'sdpa')
15
+ if ATTN_BACKEND not in ('flash_attn', 'sdpa'):
16
+ raise ValueError(f'Unknown attention backend: {ATTN_BACKEND}')
17
+ if ATTN_BACKEND == 'flash_attn':
18
+ import flash_attn
19
+
20
+
21
+ def scaled_dot_product_attention(*args) -> torch.Tensor:
22
+ """(qkv [N, L, 3, H, C]) or (q [N, L, H, C], k [N, S, H, C], v [N, S, H, C]) -> [N, L, H, C]."""
23
+ if ATTN_BACKEND == 'flash_attn':
24
+ if len(args) == 1:
25
+ return flash_attn.flash_attn_qkvpacked_func(args[0])
26
+ return flash_attn.flash_attn_func(*args)
27
+ q, k, v = args[0].unbind(dim=2) if len(args) == 1 else args
28
+ out = F.scaled_dot_product_attention(q.permute(0, 2, 1, 3), k.permute(0, 2, 1, 3), v.permute(0, 2, 1, 3))
29
+ return out.permute(0, 2, 1, 3)
30
+
31
+
32
+ def convert_module_to_f16(module):
33
+ """Store a linear layer's weights in float16 (the transformer trunks run in half precision)."""
34
+ if isinstance(module, nn.Linear):
35
+ for parameter in module.parameters():
36
+ parameter.data = parameter.data.half()
37
+
38
+
39
+ class LayerNorm32(nn.LayerNorm):
40
+ def forward(self, x: torch.Tensor) -> torch.Tensor:
41
+ return super().forward(x.float()).type(x.dtype)
42
+
43
+
44
+ class MultiHeadRMSNorm(nn.Module):
45
+ def __init__(self, dim: int, heads: int):
46
+ super().__init__()
47
+ self.scale = dim ** 0.5
48
+ self.gamma = nn.Parameter(torch.ones(heads, dim))
49
+
50
+ def forward(self, x: torch.Tensor) -> torch.Tensor:
51
+ return (F.normalize(x.float(), dim=-1) * self.gamma * self.scale).to(x.dtype)
52
+
53
+
54
+ class FeedForwardNet(nn.Module):
55
+ def __init__(self, channels: int, mlp_ratio: float = 4.0):
56
+ super().__init__()
57
+ self.mlp = nn.Sequential(
58
+ nn.Linear(channels, int(channels * mlp_ratio)),
59
+ nn.GELU(approximate="tanh"),
60
+ nn.Linear(int(channels * mlp_ratio), channels),
61
+ )
62
+
63
+ def forward(self, x: torch.Tensor) -> torch.Tensor:
64
+ return self.mlp(x)
65
+
66
+
67
+ class MLP(nn.Module):
68
+ def __init__(self, channels: int, inner_channels: int, channels_out: int, mlp_layer_num: int = 2):
69
+ super().__init__()
70
+ layers = []
71
+ for i in range(mlp_layer_num - 1):
72
+ layers.append(nn.Linear(channels if i == 0 else inner_channels, inner_channels))
73
+ layers.append(nn.GELU(approximate="tanh"))
74
+ layers.append(nn.Linear(inner_channels, channels_out))
75
+ self.mlp = nn.Sequential(*layers)
76
+
77
+ def forward(self, x: torch.Tensor) -> torch.Tensor:
78
+ return self.mlp(x)
79
+
80
+
81
+ # ---------------------------------------------------------------------------------------------------------------------
82
+ # Positional and scalar embeddings
83
+ # ---------------------------------------------------------------------------------------------------------------------
84
+
85
+ class PcdAbsolutePositionEmbedder(nn.Module):
86
+ """Sinusoidal embedding of 3D positions in [0, 1] (the denoiser's latent token positions)."""
87
+ def __init__(self, channels: int, in_channels: int = 3, max_res=16):
88
+ super().__init__()
89
+ self.channels = channels
90
+ self.in_channels = in_channels
91
+ self.freq_dim = channels // in_channels // 2
92
+ freqs_2exp = torch.arange(max_res, dtype=torch.float32)
93
+ res_dim = max(0, self.freq_dim - max_res)
94
+ freqs_res = torch.arange(res_dim, dtype=torch.float32) / res_dim * max_res
95
+ self.freqs = 2 ** torch.cat([freqs_2exp, freqs_res], dim=0)[:self.freq_dim]
96
+
97
+ def forward(self, x: torch.Tensor) -> torch.Tensor:
98
+ """x: (..., 3) positions -> (..., channels)"""
99
+ *dims, _ = x.shape
100
+ self.freqs = self.freqs.to(x.device)
101
+ out = torch.outer(x.reshape(-1), self.freqs) * 2 * torch.pi
102
+ embed = torch.cat([torch.sin(out), torch.cos(out)], dim=-1).reshape(*dims, -1)
103
+ if embed.shape[-1] < self.channels:
104
+ embed = torch.cat([embed, torch.zeros(*dims, self.channels - embed.shape[-1], device=embed.device)], dim=-1)
105
+ return embed
106
+
107
+
108
+ class PcdAbsolutePositionEmbedderv2(nn.Module):
109
+ """Sinusoidal embedding of 3D points in [0, 1] (the decoders' query points)."""
110
+ def __init__(self, channels: int, in_channels: int = 3, max_res: int = 10):
111
+ super().__init__()
112
+ self.channels = channels
113
+ self.freq_dim = channels // in_channels // 2
114
+ logs = torch.linspace(0.0, float(max_res), steps=self.freq_dim, dtype=torch.float32)
115
+ self.register_buffer("freqs", torch.pow(2.0, logs), persistent=False)
116
+
117
+ def forward(self, x: torch.Tensor) -> torch.Tensor:
118
+ """x: (N, 3) -> (N, channels)"""
119
+ N = x.shape[0]
120
+ freqs = self.freqs.to(x.device, dtype=x.dtype)
121
+ ang = x.unsqueeze(-1) * freqs * torch.pi
122
+ embed = torch.cat([torch.sin(ang), torch.cos(ang)], dim=-1).reshape(N, -1)
123
+ if embed.shape[1] < self.channels:
124
+ embed = torch.cat([embed, torch.zeros(N, self.channels - embed.shape[1], device=embed.device,
125
+ dtype=embed.dtype)], dim=-1)
126
+ return embed
127
+
128
+
129
+ class SinusoidalEmbedder(nn.Module):
130
+ """Embeds a scalar per batch element (diffusion time, octree level) into a vector."""
131
+ def __init__(self, hidden_size, frequency_embedding_size=256, max_period=10000, two_pi=False):
132
+ super().__init__()
133
+ self.mlp = nn.Sequential(
134
+ nn.Linear(frequency_embedding_size, hidden_size, bias=True),
135
+ nn.SiLU(),
136
+ nn.Linear(hidden_size, hidden_size, bias=True),
137
+ )
138
+ self.frequency_embedding_size = frequency_embedding_size
139
+ self.max_period = max_period
140
+ self.two_pi = two_pi
141
+
142
+ def forward(self, t):
143
+ half = self.frequency_embedding_size // 2
144
+ freqs = torch.exp(
145
+ -np.log(self.max_period) * torch.arange(start=0, end=half, dtype=torch.float32) / half
146
+ ).to(device=t.device)
147
+ args = t[:, None].float() * freqs[None]
148
+ if self.two_pi:
149
+ args = args * 2 * torch.pi
150
+ return self.mlp(torch.cat([torch.cos(args), torch.sin(args)], dim=-1))
151
+
152
+
153
+ def apply_rotary_emb(hidden_states: torch.Tensor, freqs: torch.Tensor) -> torch.Tensor:
154
+ """hidden_states [B, L, H, D] rotated by the complex frequencies freqs [B, L, H or 1, D/2]."""
155
+ x_rotated = torch.view_as_complex(hidden_states.float().reshape(*hidden_states.shape[:-1], -1, 2))
156
+ x_rotated = x_rotated * freqs
157
+ x_out = torch.view_as_real(x_rotated).reshape(*x_rotated.shape[:-1], -1)
158
+ return x_out.type_as(hidden_states)
159
+
160
+
161
+ def clamp_mul(x, f):
162
+ f_t = f.tanh()
163
+ return x * f_t + x.detach() * (f - f_t)
164
+
165
+
166
+ class RePo3DRotaryEmbedding(nn.Module):
167
+ """Learned rotary position: each token's features predict a 3D offset per head, turned into RoPE angles."""
168
+ def __init__(self, model_channels: int, num_heads: int, head_dim: int, repo_hidden_ratio: float = 0.125,
169
+ max_freq: float = 16.0):
170
+ super().__init__()
171
+ self.num_heads = num_heads
172
+ repo_hidden_size = int(model_channels * repo_hidden_ratio)
173
+ self.norm = LayerNorm32(model_channels)
174
+ self.gate_map = nn.Linear(model_channels, repo_hidden_size, bias=False)
175
+ self.content_map = nn.Linear(model_channels, repo_hidden_size, bias=False)
176
+ self.act = nn.SiLU()
177
+ self.final_map = nn.Linear(repo_hidden_size, 3 * num_heads, bias=False)
178
+ # head_dim split into 3 parts for x, y, z
179
+ dim_0 = dim_1 = 2 * (head_dim // 6)
180
+ dims = [dim_0, dim_1, head_dim - dim_0 - dim_1]
181
+ freqs = [torch.linspace(1.0, float(max_freq), steps=d // 2, dtype=torch.float32) for d in dims]
182
+ self.freqs_0 = nn.Parameter(freqs[0])
183
+ self.freqs_1 = nn.Parameter(freqs[1])
184
+ self.freqs_2 = nn.Parameter(freqs[2])
185
+
186
+ def forward(self, hidden_states: torch.Tensor) -> torch.Tensor:
187
+ """hidden_states [B, L, C] -> complex rotations [B, L, H, D/2]"""
188
+ h = self.norm(hidden_states.float())
189
+ out = self.final_map(self.act(self.gate_map(h)) * self.content_map(h))
190
+ B, L, _ = out.shape
191
+ delta_pos = out.reshape(B, L, self.num_heads, 3)
192
+ ang_0 = clamp_mul(delta_pos[..., 0].unsqueeze(-1), self.freqs_0) * torch.pi
193
+ ang_1 = clamp_mul(delta_pos[..., 1].unsqueeze(-1), self.freqs_1) * torch.pi
194
+ ang_2 = clamp_mul(delta_pos[..., 2].unsqueeze(-1), self.freqs_2) * torch.pi
195
+ ang = torch.cat([ang_0, ang_1, ang_2], dim=-1)
196
+ return torch.polar(torch.ones_like(ang), ang).type(torch.complex64)
197
+
198
+
199
+ # ---------------------------------------------------------------------------------------------------------------------
200
+ # Attention
201
+ # ---------------------------------------------------------------------------------------------------------------------
202
+
203
+ class MultiHeadAttention(nn.Module):
204
+ """Self or cross attention with RMS-normalised queries and keys (the decoders)."""
205
+ def __init__(self, channels: int, num_heads: int, ctx_channels: Optional[int] = None, cross: bool = False):
206
+ super().__init__()
207
+ self.num_heads = num_heads
208
+ self.cross = cross
209
+ head_dim = channels // num_heads
210
+ if cross:
211
+ self.to_q = nn.Linear(channels, channels)
212
+ self.to_kv = nn.Linear(ctx_channels, channels * 2)
213
+ else:
214
+ self.to_qkv = nn.Linear(channels, channels * 3)
215
+ self.q_rms_norm = MultiHeadRMSNorm(head_dim, num_heads)
216
+ self.k_rms_norm = MultiHeadRMSNorm(head_dim, num_heads)
217
+ self.to_out = nn.Linear(channels, channels)
218
+
219
+ def forward(self, x: torch.Tensor, context: Optional[torch.Tensor] = None) -> torch.Tensor:
220
+ B, L, C = x.shape
221
+ if self.cross:
222
+ q = self.q_rms_norm(self.to_q(x).reshape(B, L, self.num_heads, -1))
223
+ k, v = self.to_kv(context).reshape(B, context.shape[1], 2, self.num_heads, -1).unbind(dim=2)
224
+ h = scaled_dot_product_attention(q, self.k_rms_norm(k), v)
225
+ else:
226
+ q, k, v = self.to_qkv(x).reshape(B, L, 3, self.num_heads, -1).unbind(dim=2)
227
+ h = scaled_dot_product_attention(torch.stack([self.q_rms_norm(q), self.k_rms_norm(k), v], dim=2))
228
+ return self.to_out(h.reshape(B, L, -1))
229
+
230
+
231
+ class RopeMultiHeadAttention(nn.Module):
232
+ """Self attention with rotary embeddings and RMS-normalised queries and keys (the denoiser)."""
233
+ def __init__(self, channels: int, num_heads: int):
234
+ super().__init__()
235
+ self.num_heads = num_heads
236
+ self.head_dim = channels // num_heads
237
+ self.qkv = nn.Linear(channels, channels * 3)
238
+ self.q_norm = MultiHeadRMSNorm(self.head_dim, num_heads)
239
+ self.k_norm = MultiHeadRMSNorm(self.head_dim, num_heads)
240
+ self.out = nn.Linear(channels, channels)
241
+
242
+ def forward(self, x: torch.Tensor, rope_emb: torch.Tensor) -> torch.Tensor:
243
+ B, L, C = x.shape
244
+ q, k, v = self.qkv(x).reshape(B, L, 3, self.num_heads, self.head_dim).unbind(2)
245
+ q = self.q_norm(apply_rotary_emb(q, rope_emb))
246
+ k = self.k_norm(apply_rotary_emb(k, rope_emb))
247
+ return self.out(scaled_dot_product_attention(q, k, v).reshape(B, L, C))
248
+
249
+
250
+ # ---------------------------------------------------------------------------------------------------------------------
251
+ # Transformer blocks
252
+ # ---------------------------------------------------------------------------------------------------------------------
253
+
254
+ class UnifiedTransformerBlock(nn.Module):
255
+ """Denoiser block: RoPE self attention + FFN, modulated by the diffusion time through a shared adaLN plus
256
+ this block's own shift table (modulation=False for the condition refiner, which has plain LayerNorms)."""
257
+ def __init__(self, channels: int, num_heads: int, mlp_ratio: float = 4.0, modulation: bool = True):
258
+ super().__init__()
259
+ self.modulation = modulation
260
+ self.norm1 = LayerNorm32(channels, elementwise_affine=not modulation, eps=1e-6)
261
+ self.norm2 = LayerNorm32(channels, elementwise_affine=not modulation, eps=1e-6)
262
+ self.attn = RopeMultiHeadAttention(channels, num_heads=num_heads)
263
+ self.mlp = FeedForwardNet(channels, mlp_ratio=mlp_ratio)
264
+ if modulation:
265
+ self.shift_table = nn.Parameter(torch.zeros(1, 6 * channels))
266
+
267
+ def forward(self, x: torch.Tensor, mod: Optional[torch.Tensor], rotary_emb: torch.Tensor) -> torch.Tensor:
268
+ if not self.modulation:
269
+ x = x + self.attn(self.norm1(x), rope_emb=rotary_emb)
270
+ return x + self.mlp(self.norm2(x))
271
+ mod = mod + self.shift_table.type(mod.dtype)
272
+ shift_msa, scale_msa, gate_msa, shift_mlp, scale_mlp, gate_mlp = mod.chunk(6, dim=1)
273
+ h = self.norm1(x)
274
+ h = h * (1 + scale_msa.unsqueeze(1)) + shift_msa.unsqueeze(1)
275
+ h = self.attn(h, rope_emb=rotary_emb)
276
+ x = x + h * gate_msa.unsqueeze(1)
277
+ h = self.norm2(x)
278
+ h = h * (1 + scale_mlp.unsqueeze(1)) + shift_mlp.unsqueeze(1)
279
+ h = self.mlp(h)
280
+ return x + h * gate_mlp.unsqueeze(1)
281
+
282
+
283
+ class TransformerCrossBlock(nn.Module):
284
+ """Gaussian decoder block: self attention + cross attention to the latent + FFN."""
285
+ def __init__(self, channels: int, ctx_channels: int, num_heads: int, mlp_ratio: float = 4.0):
286
+ super().__init__()
287
+ self.norm1 = LayerNorm32(channels, elementwise_affine=False, eps=1e-6)
288
+ self.norm2 = LayerNorm32(channels, elementwise_affine=True, eps=1e-6)
289
+ self.norm3 = LayerNorm32(channels, elementwise_affine=False, eps=1e-6)
290
+ self.self_attn = MultiHeadAttention(channels, num_heads=num_heads)
291
+ self.cross_attn = MultiHeadAttention(channels, num_heads=num_heads, ctx_channels=ctx_channels, cross=True)
292
+ self.mlp = FeedForwardNet(channels, mlp_ratio=mlp_ratio)
293
+
294
+ def forward(self, x: torch.Tensor, context: torch.Tensor) -> torch.Tensor:
295
+ x = x + self.self_attn(self.norm1(x))
296
+ x = x + self.cross_attn(self.norm2(x), context)
297
+ return x + self.mlp(self.norm3(x))
298
+
299
+
300
+ class ModulatedTransformerCrossOnlyBlock(nn.Module):
301
+ """Octree decoder block: cross attention to the latent + FFN, modulated by the octree level (shared adaLN)."""
302
+ def __init__(self, channels: int, ctx_channels: int, num_heads: int, mlp_ratio: float = 4.0):
303
+ super().__init__()
304
+ self.norm1 = LayerNorm32(channels, elementwise_affine=False, eps=1e-6)
305
+ self.norm2 = LayerNorm32(channels, elementwise_affine=False, eps=1e-6)
306
+ self.cross_attn = MultiHeadAttention(channels, num_heads=num_heads, ctx_channels=ctx_channels, cross=True)
307
+ self.mlp = FeedForwardNet(channels, mlp_ratio=mlp_ratio)
308
+
309
+ def forward(self, x: torch.Tensor, mod: torch.Tensor, context: torch.Tensor) -> torch.Tensor:
310
+ shift_msa, scale_msa, gate_msa, shift_mlp, scale_mlp, gate_mlp = mod.chunk(6, dim=1)
311
+ h = self.norm1(x)
312
+ h = h * (1 + scale_msa.unsqueeze(1)) + shift_msa.unsqueeze(1)
313
+ h = self.cross_attn(h, context)
314
+ x = x + h * gate_msa.unsqueeze(1)
315
+ h = self.norm2(x)
316
+ h = h * (1 + scale_mlp.unsqueeze(1)) + shift_mlp.unsqueeze(1)
317
+ h = self.mlp(h)
318
+ return x + h * gate_mlp.unsqueeze(1)
idol/models/transformer_base.py DELETED
@@ -1,351 +0,0 @@
1
- from typing import *
2
- import torch
3
- import torch.nn as nn
4
- from ..modules.utils import convert_module_to_f16, convert_module_to_f32
5
- from ..modules.transformer import AbsolutePositionEmbedder, TransformerCrossBlock, TransformerBlock
6
- from ..modules.transformer.blocks import FeedForwardNet
7
- from ..modules.norm import LayerNorm32
8
- from ..modules.attention import MultiHeadAttention
9
- from ..modules.transformer.modulated import ModulatedTransformerBlock, ModulatedTransformerCrossBlock, ModulatedTransformerCrossOnlyBlock
10
- import numpy as np
11
-
12
- class PcdAbsolutePositionEmbedder(nn.Module):
13
- """
14
- Embeds spatial positions in [0,1] into vector representations.
15
- """
16
- def __init__(self, channels: int, in_channels: int = 3, max_res=16):
17
- super().__init__()
18
- self.channels = channels
19
- self.in_channels = in_channels
20
- self.freq_dim = channels // in_channels // 2
21
- self.max_res = max_res
22
- freqs_2exp = torch.arange(max_res, dtype=torch.float32)
23
- res_dim = max(0, self.freq_dim - max_res)
24
- freqs_res = torch.arange(res_dim, dtype=torch.float32) / res_dim * max_res
25
- self.freqs = torch.cat([freqs_2exp, freqs_res], dim=0)[:self.freq_dim]
26
- self.freqs = 2 ** self.freqs # [1, 2 ^ max_res]
27
-
28
- def _sin_cos_embedding(self, x: torch.Tensor) -> torch.Tensor:
29
- """
30
- Create sinusoidal position embeddings.
31
-
32
- Args:
33
- x: a 1-D Tensor of N indices (in [0,1]^3 range)
34
-
35
- Returns:
36
- an (N, D) Tensor of positional embeddings.
37
- """
38
- self.freqs = self.freqs.to(x.device)
39
- out = torch.outer(x, self.freqs) * 2 * torch.pi
40
- out = torch.cat([torch.sin(out), torch.cos(out)], dim=-1)
41
- return out
42
-
43
- def forward(self, x: torch.Tensor) -> torch.Tensor:
44
- """
45
- Args:
46
- x (torch.Tensor): (N, D) or (B, N, D) tensor of spatial positions
47
- """
48
- *dims, D = x.shape
49
- assert D == self.in_channels, "Input dimension must match number of input channels"
50
- embed = self._sin_cos_embedding(x.reshape(-1))
51
- embed = embed.reshape(*dims, -1)
52
- if embed.shape[-1] < self.channels:
53
- embed = torch.cat([embed, torch.zeros(*dims, self.channels - embed.shape[-1], device=embed.device)], dim=-1)
54
- return embed
55
-
56
-
57
- class PcdAbsolutePositionEmbedderv2(nn.Module):
58
- def __init__(self, channels: int, in_channels: int = 3, max_res: int = 10):
59
- super().__init__()
60
- self.channels = channels
61
- self.in_channels = in_channels
62
- self.freq_dim = channels // in_channels // 2
63
- logs = torch.linspace(0.0, float(max_res), steps=self.freq_dim, dtype=torch.float32)
64
- self.register_buffer("freqs", torch.pow(2.0, logs), persistent=False)
65
-
66
- def forward(self, x: torch.Tensor) -> torch.Tensor:
67
- N, D = x.shape
68
- assert D == self.in_channels, "Input dimension must match number of input channels"
69
- freqs = self.freqs.to(x.device, dtype=x.dtype)
70
- ang = x.unsqueeze(-1) * freqs * torch.pi
71
- embed = torch.cat([torch.sin(ang), torch.cos(ang)], dim=-1).reshape(N, -1)
72
- if embed.shape[1] < self.channels:
73
- embed = torch.cat([embed, torch.zeros(N, self.channels - embed.shape[1], device=embed.device, dtype=embed.dtype)], dim=-1)
74
- return embed
75
-
76
-
77
- class TransformerBase(nn.Module):
78
- """
79
- Transformer without output layers.
80
- Serve as the base class for encoder and decoder.
81
- """
82
- def __init__(
83
- self,
84
- in_channels: int,
85
- model_channels: int,
86
- cond_channels: Optional[int],
87
- num_blocks: int,
88
- num_heads: Optional[int] = None,
89
- num_head_channels: Optional[int] = 64,
90
- mlp_ratio: float = 4.0,
91
- use_fp16: bool = False,
92
- use_checkpoint: bool = False,
93
- qk_rms_norm: bool = True,
94
- qk_rms_norm_cross: bool = True,
95
- use_2_cross_block: bool = False,
96
- share_mod: bool = False,
97
- ):
98
- super().__init__()
99
- assert not use_2_cross_block or (cond_channels is not None), "use_2_cross_block requires cond_channels"
100
- self.model_channels = model_channels
101
- self.cond_channels = cond_channels
102
- self.num_blocks = num_blocks
103
- self.num_heads = num_heads or model_channels // num_head_channels
104
- self.mlp_ratio = mlp_ratio
105
- self.use_fp16 = use_fp16
106
- self.use_checkpoint = use_checkpoint
107
- self.qk_rms_norm = qk_rms_norm
108
- self.qk_rms_norm_cross = qk_rms_norm_cross
109
- self.share_mod = share_mod
110
- self.use_2_cross_block = use_2_cross_block
111
-
112
- self.dtype = torch.float16 if use_fp16 else torch.float32
113
-
114
- self.input_layer = nn.Linear(in_channels, model_channels)
115
- self.l_embedder = None
116
- if cond_channels is not None:
117
- self.blocks = nn.ModuleList([
118
- TransformerCrossBlock(
119
- model_channels,
120
- ctx_channels=cond_channels,
121
- num_heads=self.num_heads,
122
- mlp_ratio=self.mlp_ratio,
123
- use_checkpoint=self.use_checkpoint,
124
- qk_rms_norm=self.qk_rms_norm,
125
- qk_rms_norm_cross=self.qk_rms_norm_cross,
126
- use_2_cross_block=use_2_cross_block,
127
- )
128
- for _ in range(num_blocks)
129
- ])
130
- else:
131
- self.blocks = nn.ModuleList([
132
- TransformerBlock(
133
- model_channels,
134
- num_heads=self.num_heads,
135
- mlp_ratio=self.mlp_ratio,
136
- use_checkpoint=self.use_checkpoint,
137
- qk_rms_norm=self.qk_rms_norm,
138
- )
139
- for _ in range(num_blocks)
140
- ])
141
-
142
- @property
143
- def device(self) -> torch.device:
144
- """
145
- Return the device of the model.
146
- """
147
- return next(self.parameters()).device
148
-
149
- def convert_to_fp16(self) -> None:
150
- """
151
- Convert the torso of the model to float16.
152
- """
153
- self.blocks.apply(convert_module_to_f16)
154
-
155
- def convert_to_fp32(self) -> None:
156
- """
157
- Convert the torso of the model to float32.
158
- """
159
- self.blocks.apply(convert_module_to_f32)
160
-
161
- def initialize_weights(self) -> None:
162
- # Initialize transformer layers:
163
- def _basic_init(module):
164
- if isinstance(module, nn.Linear):
165
- torch.nn.init.xavier_uniform_(module.weight)
166
- if module.bias is not None:
167
- nn.init.constant_(module.bias, 0)
168
- self.apply(_basic_init)
169
-
170
-
171
- def forward(self, x: torch.Tensor, cond: Optional[torch.Tensor]=None, l: Optional[torch.Tensor]=None, cond2: Optional[torch.Tensor]=None, indices=None, context_indices=None, context2_indices=None) -> torch.Tensor:
172
- h = self.input_layer(x)
173
- h = h.type(self.dtype)
174
- cond = cond.type(self.dtype) if cond is not None else None
175
- cond2 = cond2.type(self.dtype) if cond2 is not None else None
176
- for block in self.blocks:
177
- if cond is None:
178
- h = block(h, indices=indices)
179
- else:
180
- h = block(h, cond, context2=cond2, indices=indices, context_indices=context_indices, context2_indices=context2_indices)
181
- h = h.type(x.dtype)
182
- return h
183
-
184
- class LevelEmbedder(nn.Module):
185
- """
186
- Embeds scalar timesteps into vector representations.
187
- """
188
- def __init__(self, hidden_size, frequency_embedding_size=256, max_period=1024):
189
- super().__init__()
190
- self.mlp = nn.Sequential(
191
- nn.Linear(frequency_embedding_size, hidden_size, bias=True),
192
- nn.SiLU(),
193
- nn.Linear(hidden_size, hidden_size, bias=True),
194
- )
195
- self.frequency_embedding_size = frequency_embedding_size
196
- self.max_period = max_period
197
-
198
- @staticmethod
199
- def level_embedding(t, dim, max_period=1024):
200
- """
201
- Create sinusoidal timestep embeddings.
202
-
203
- Args:
204
- t: a 1-D Tensor of N indices, one per batch element.
205
- These may be fractional.
206
- dim: the dimension of the output.
207
- max_period: controls the minimum frequency of the embeddings.
208
-
209
- Returns:
210
- an (N, D) Tensor of positional embeddings.
211
- """
212
- # https://github.com/openai/glide-text2im/blob/main/glide_text2im/nn.py
213
- half = dim // 2
214
- freqs = torch.exp(
215
- -np.log(max_period) * torch.arange(start=0, end=half, dtype=torch.float32) / half
216
- ).to(device=t.device)
217
- args = t[:, None].float() * freqs[None] * 2 * torch.pi
218
- embedding = torch.cat([torch.cos(args), torch.sin(args)], dim=-1)
219
- if dim % 2:
220
- embedding = torch.cat([embedding, torch.zeros_like(embedding[:, :1])], dim=-1)
221
- return embedding
222
-
223
- def forward(self, t):
224
- t_freq = self.level_embedding(t, self.frequency_embedding_size, self.max_period)
225
- t_emb = self.mlp(t_freq)
226
- return t_emb
227
-
228
- class ModulatedCrossOnlyTransformerBase(nn.Module):
229
- """
230
- Transformer without output layers.
231
- Serve as the base class for encoder and decoder.
232
- """
233
- def __init__(
234
- self,
235
- in_channels: int,
236
- model_channels: int,
237
- cond_channels: Optional[int],
238
- num_blocks: int,
239
- num_heads: Optional[int] = None,
240
- num_head_channels: Optional[int] = 64,
241
- mlp_ratio: float = 4.0,
242
- use_fp16: bool = False,
243
- use_checkpoint: bool = False,
244
- share_mod: bool = False,
245
- additional_level_embed: bool = False,
246
- qk_rms_norm_cross: bool = True,
247
- ):
248
- super().__init__()
249
- self.in_channels = in_channels
250
- self.model_channels = model_channels
251
- self.cond_channels = cond_channels
252
- self.num_blocks = num_blocks
253
- self.num_heads = num_heads or model_channels // num_head_channels
254
- self.mlp_ratio = mlp_ratio
255
- self.use_fp16 = use_fp16
256
- self.use_checkpoint = use_checkpoint
257
- self.share_mod = share_mod
258
- self.qk_rms_norm_cross = qk_rms_norm_cross
259
- self.dtype = torch.float16 if use_fp16 else torch.float32
260
-
261
- self.input_layer = nn.Linear(in_channels, model_channels)
262
- self.l_embedder = LevelEmbedder(model_channels)
263
- if additional_level_embed:
264
- self.l_embedder2 = LevelEmbedder(model_channels, max_period=100)
265
- else:
266
- self.l_embedder2 = None
267
- if share_mod:
268
- self.adaLN_modulation = nn.Sequential(
269
- nn.SiLU(),
270
- nn.Linear(model_channels, 6 * model_channels, bias=True)
271
- )
272
-
273
- if cond_channels is not None:
274
- self.blocks = nn.ModuleList([
275
- ModulatedTransformerCrossOnlyBlock(
276
- model_channels,
277
- ctx_channels=cond_channels,
278
- num_heads=self.num_heads,
279
- mlp_ratio=self.mlp_ratio,
280
- use_checkpoint=self.use_checkpoint,
281
- qk_rms_norm_cross=self.qk_rms_norm_cross,
282
- share_mod=self.share_mod
283
- )
284
- for _ in range(num_blocks)
285
- ])
286
- else:
287
- raise ValueError("ModulatedCrossOnlyTransformerBase requires cond_channels to be not None.")
288
-
289
- @property
290
- def device(self) -> torch.device:
291
- """
292
- Return the device of the model.
293
- """
294
- return next(self.parameters()).device
295
-
296
- def convert_to_fp16(self) -> None:
297
- """
298
- Convert the torso of the model to float16.
299
- """
300
- self.blocks.apply(convert_module_to_f16)
301
-
302
- def convert_to_fp32(self) -> None:
303
- """
304
- Convert the torso of the model to float32.
305
- """
306
- self.blocks.apply(convert_module_to_f32)
307
-
308
- def initialize_weights(self) -> None:
309
- # Initialize transformer layers:
310
- def _basic_init(module):
311
- if isinstance(module, nn.Linear):
312
- torch.nn.init.xavier_uniform_(module.weight)
313
- if module.bias is not None:
314
- nn.init.constant_(module.bias, 0)
315
- self.apply(_basic_init)
316
-
317
- # Initialize timestep embedding MLP:
318
- nn.init.normal_(self.l_embedder.mlp[0].weight, std=0.02)
319
- nn.init.normal_(self.l_embedder.mlp[2].weight, std=0.02)
320
-
321
- if self.l_embedder2 is not None:
322
- # zero projection init
323
- nn.init.normal_(self.l_embedder2.mlp[0].weight, std=0.02)
324
- nn.init.constant_(self.l_embedder2.mlp[2].weight, 0)
325
- nn.init.constant_(self.l_embedder2.mlp[2].bias, 0)
326
-
327
- # Zero-out adaLN modulation layers in DiT blocks:
328
- if self.share_mod:
329
- nn.init.constant_(self.adaLN_modulation[-1].weight, 0)
330
- nn.init.constant_(self.adaLN_modulation[-1].bias, 0)
331
- else:
332
- for block in self.blocks:
333
- nn.init.constant_(block.adaLN_modulation[-1].weight, 0)
334
- nn.init.constant_(block.adaLN_modulation[-1].bias, 0)
335
-
336
- def forward(self, x: torch.Tensor, l: torch.Tensor, cond: torch.Tensor, l2: Optional[torch.Tensor] = None) -> torch.Tensor:
337
- h = self.input_layer(x)
338
- l = l.type(h.dtype)
339
- l_emb = self.l_embedder(l)
340
- if self.l_embedder2 is not None and l2 is not None:
341
- l2 = l2.type(h.dtype)
342
- l_emb = l_emb + self.l_embedder2(l2)
343
- if self.share_mod:
344
- l_emb = self.adaLN_modulation(l_emb)
345
- l_emb = l_emb.type(self.dtype)
346
- h = h.type(self.dtype)
347
- cond = cond.type(self.dtype)
348
- for block in self.blocks:
349
- h = block(h, l_emb, cond)
350
- h = h.type(x.dtype)
351
- return h
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
idol/modules/__init__.py DELETED
File without changes
idol/modules/attention/__init__.py DELETED
@@ -1,14 +0,0 @@
1
- from typing import *
2
- import importlib.util
3
- import os
4
-
5
- # flash-attn when it is installed, PyTorch SDPA otherwise (same result, flash-attn is faster).
6
- # Override with ATTN_BACKEND=flash_attn|sdpa|naive.
7
- BACKEND = 'flash_attn' if importlib.util.find_spec('flash_attn') is not None else 'sdpa'
8
- BACKEND = os.environ.get('ATTN_BACKEND', BACKEND)
9
- if BACKEND not in ('flash_attn', 'sdpa', 'naive'):
10
- raise ValueError(f'Unknown attention backend: {BACKEND}')
11
-
12
-
13
- from .full_attn import *
14
- from .modules import *
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
idol/modules/attention/full_attn.py DELETED
@@ -1,127 +0,0 @@
1
- from typing import *
2
- import torch
3
- import math
4
- from torch.nn.functional import scaled_dot_product_attention as sdpa
5
- from . import BACKEND
6
-
7
- if BACKEND == 'flash_attn':
8
- import flash_attn
9
-
10
-
11
- __all__ = [
12
- 'scaled_dot_product_attention',
13
- ]
14
-
15
-
16
- def _naive_sdpa(q, k, v):
17
- """
18
- Naive implementation of scaled dot product attention.
19
- """
20
- q = q.permute(0, 2, 1, 3) # [N, H, L, C]
21
- k = k.permute(0, 2, 1, 3) # [N, H, L, C]
22
- v = v.permute(0, 2, 1, 3) # [N, H, L, C]
23
- scale_factor = 1 / math.sqrt(q.size(-1))
24
- attn_weight = q @ k.transpose(-2, -1) * scale_factor
25
- attn_weight = torch.softmax(attn_weight, dim=-1)
26
- out = attn_weight @ v
27
- out = out.permute(0, 2, 1, 3) # [N, L, H, C]
28
- return out
29
-
30
-
31
- @overload
32
- def scaled_dot_product_attention(qkv: torch.Tensor) -> torch.Tensor:
33
- """
34
- Apply scaled dot product attention.
35
-
36
- Args:
37
- qkv (torch.Tensor): A [N, L, 3, H, C] tensor containing Qs, Ks, and Vs.
38
- """
39
- ...
40
-
41
- @overload
42
- def scaled_dot_product_attention(q: torch.Tensor, kv: torch.Tensor) -> torch.Tensor:
43
- """
44
- Apply scaled dot product attention.
45
-
46
- Args:
47
- q (torch.Tensor): A [N, L, H, C] tensor containing Qs.
48
- kv (torch.Tensor): A [N, L, 2, H, C] tensor containing Ks and Vs.
49
- """
50
- ...
51
-
52
- @overload
53
- def scaled_dot_product_attention(q: torch.Tensor, k: torch.Tensor, v: torch.Tensor) -> torch.Tensor:
54
- """
55
- Apply scaled dot product attention.
56
-
57
- Args:
58
- q (torch.Tensor): A [N, L, H, Ci] tensor containing Qs.
59
- k (torch.Tensor): A [N, L, H, Ci] tensor containing Ks.
60
- v (torch.Tensor): A [N, L, H, Co] tensor containing Vs.
61
-
62
- Note:
63
- k and v are assumed to have the same coordinate map.
64
- """
65
- ...
66
-
67
- def scaled_dot_product_attention(*args, **kwargs):
68
- arg_names_dict = {
69
- 1: ['qkv'],
70
- 2: ['q', 'kv'],
71
- 3: ['q', 'k', 'v']
72
- }
73
- num_all_args = len(args) + len(kwargs)
74
- assert num_all_args in arg_names_dict, f"Invalid number of arguments, got {num_all_args}, expected 1, 2, or 3"
75
- for key in arg_names_dict[num_all_args][len(args):]:
76
- assert key in kwargs, f"Missing argument {key}"
77
-
78
- if num_all_args == 1:
79
- qkv = args[0] if len(args) > 0 else kwargs['qkv']
80
- assert len(qkv.shape) == 5 and qkv.shape[2] == 3, f"Invalid shape for qkv, got {qkv.shape}, expected [N, L, 3, H, C]"
81
- device = qkv.device
82
-
83
- elif num_all_args == 2:
84
- q = args[0] if len(args) > 0 else kwargs['q']
85
- kv = args[1] if len(args) > 1 else kwargs['kv']
86
- assert q.shape[0] == kv.shape[0], f"Batch size mismatch, got {q.shape[0]} and {kv.shape[0]}"
87
- assert len(q.shape) == 4, f"Invalid shape for q, got {q.shape}, expected [N, L, H, C]"
88
- assert len(kv.shape) == 5, f"Invalid shape for kv, got {kv.shape}, expected [N, L, 2, H, C]"
89
- device = q.device
90
-
91
- elif num_all_args == 3:
92
- q = args[0] if len(args) > 0 else kwargs['q']
93
- k = args[1] if len(args) > 1 else kwargs['k']
94
- v = args[2] if len(args) > 2 else kwargs['v']
95
- assert q.shape[0] == k.shape[0] == v.shape[0], f"Batch size mismatch, got {q.shape[0]}, {k.shape[0]}, and {v.shape[0]}"
96
- assert len(q.shape) == 4, f"Invalid shape for q, got {q.shape}, expected [N, L, H, Ci]"
97
- assert len(k.shape) == 4, f"Invalid shape for k, got {k.shape}, expected [N, L, H, Ci]"
98
- assert len(v.shape) == 4, f"Invalid shape for v, got {v.shape}, expected [N, L, H, Co]"
99
- device = q.device
100
-
101
- if BACKEND == 'flash_attn':
102
- if num_all_args == 1:
103
- out = flash_attn.flash_attn_qkvpacked_func(qkv)
104
- elif num_all_args == 2:
105
- out = flash_attn.flash_attn_kvpacked_func(q, kv)
106
- elif num_all_args == 3:
107
- out = flash_attn.flash_attn_func(q, k, v)
108
- elif BACKEND == 'sdpa':
109
- if num_all_args == 1:
110
- q, k, v = qkv.unbind(dim=2)
111
- elif num_all_args == 2:
112
- k, v = kv.unbind(dim=2)
113
- q = q.permute(0, 2, 1, 3) # [N, H, L, C]
114
- k = k.permute(0, 2, 1, 3) # [N, H, L, C]
115
- v = v.permute(0, 2, 1, 3) # [N, H, L, C]
116
- out = sdpa(q, k, v) # [N, H, L, C]
117
- out = out.permute(0, 2, 1, 3) # [N, L, H, C]
118
- elif BACKEND == 'naive':
119
- if num_all_args == 1:
120
- q, k, v = qkv.unbind(dim=2)
121
- elif num_all_args == 2:
122
- k, v = kv.unbind(dim=2)
123
- out = _naive_sdpa(q, k, v)
124
- else:
125
- raise ValueError(f"Unknown attention module: {BACKEND}")
126
-
127
- return out
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
idol/modules/attention/modules.py DELETED
@@ -1,338 +0,0 @@
1
- from typing import *
2
- import torch
3
- import torch.nn as nn
4
- import torch.nn.functional as F
5
- from .full_attn import scaled_dot_product_attention
6
- from ...modules.norm import LayerNorm32
7
-
8
- class MultiHeadRMSNorm(nn.Module):
9
- def __init__(self, dim: int, heads: int):
10
- super().__init__()
11
- self.scale = dim ** 0.5
12
- self.gamma = nn.Parameter(torch.ones(heads, dim))
13
-
14
- def forward(self, x: torch.Tensor) -> torch.Tensor:
15
- return (F.normalize(x.float(), dim = -1) * self.gamma * self.scale).to(x.dtype)
16
-
17
-
18
- class RotaryPositionEmbedder(nn.Module):
19
- def __init__(self, hidden_size: int, in_channels: int = 3):
20
- super().__init__()
21
- assert hidden_size % 2 == 0, "Hidden size must be divisible by 2"
22
- self.hidden_size = hidden_size
23
- self.in_channels = in_channels
24
- self.freq_dim = hidden_size // in_channels // 2
25
- self.freqs = torch.arange(self.freq_dim, dtype=torch.float32) / self.freq_dim
26
- self.freqs = 1.0 / (10000 ** self.freqs)
27
-
28
- def _get_phases(self, indices: torch.Tensor) -> torch.Tensor:
29
- self.freqs = self.freqs.to(indices.device)
30
- phases = torch.outer(indices, self.freqs)
31
- phases = torch.polar(torch.ones_like(phases), phases)
32
- return phases
33
-
34
- def _rotary_embedding(self, x: torch.Tensor, phases: torch.Tensor) -> torch.Tensor:
35
- x_complex = torch.view_as_complex(x.float().reshape(*x.shape[:-1], -1, 2))
36
- x_rotated = x_complex * phases
37
- x_embed = torch.view_as_real(x_rotated).reshape(*x_rotated.shape[:-1], -1).to(x.dtype)
38
- return x_embed
39
-
40
- def forward(self, q: torch.Tensor, k: torch.Tensor, indices: Optional[torch.Tensor] = None) -> Tuple[torch.Tensor, torch.Tensor]:
41
- if indices is None:
42
- indices = torch.arange(q.shape[-2], device=q.device)
43
- if len(q.shape) > 2:
44
- indices = indices.unsqueeze(0).expand(q.shape[:-2] + (-1,))
45
-
46
- phases = self._get_phases(indices.reshape(-1)).reshape(*indices.shape[:-1], -1)
47
- if phases.shape[-1] < self.hidden_size // 2:
48
- phases = torch.cat([phases, torch.polar(
49
- torch.ones(*phases.shape[:-1], self.hidden_size // 2 - phases.shape[1], device=phases.device),
50
- torch.zeros(*phases.shape[:-1], self.hidden_size // 2 - phases.shape[1], device=phases.device)
51
- )], dim=-1)
52
- q_embed = self._rotary_embedding(q, phases)
53
- k_embed = self._rotary_embedding(k, phases)
54
- return q_embed, k_embed
55
-
56
- def apply_rope(self, x: torch.Tensor, indices: Optional[torch.Tensor]) -> torch.Tensor:
57
- if indices is None:
58
- raise ValueError()
59
- phases = self._get_phases(indices.reshape(-1)).reshape(*indices.shape[:-1], -1)
60
- if phases.shape[-1] < self.hidden_size // 2:
61
- phases = torch.cat([phases, torch.polar(
62
- torch.ones(*phases.shape[:-1], self.hidden_size // 2 - phases.shape[-1], device=phases.device),
63
- torch.zeros(*phases.shape[:-1], self.hidden_size // 2 - phases.shape[-1], device=phases.device)
64
- )], dim=-1)
65
- return self._rotary_embedding(x, phases)
66
-
67
-
68
- class MultiHeadAttention(nn.Module):
69
- def __init__(
70
- self,
71
- channels: int,
72
- num_heads: int,
73
- ctx_channels: Optional[int]=None,
74
- type: Literal["self", "cross"] = "self",
75
- qkv_bias: bool = True,
76
- qk_rms_norm: bool = False,
77
- ):
78
- super().__init__()
79
- assert channels % num_heads == 0
80
- assert type in ["self", "cross"], f"Invalid attention type: {type}"
81
-
82
- self.channels = channels
83
- self.head_dim = channels // num_heads
84
- self.ctx_channels = ctx_channels if ctx_channels is not None else channels
85
- self.num_heads = num_heads
86
- self._type = type
87
- self.qk_rms_norm = qk_rms_norm
88
-
89
- if self._type == "self":
90
- self.to_qkv = nn.Linear(channels, channels * 3, bias=qkv_bias)
91
- else:
92
- self.to_q = nn.Linear(channels, channels, bias=qkv_bias)
93
- self.to_kv = nn.Linear(self.ctx_channels, channels * 2, bias=qkv_bias)
94
-
95
- if self.qk_rms_norm:
96
- self.q_rms_norm = MultiHeadRMSNorm(self.head_dim, num_heads)
97
- self.k_rms_norm = MultiHeadRMSNorm(self.head_dim, num_heads)
98
-
99
- self.to_out = nn.Linear(channels, channels)
100
-
101
- def forward(self, x: torch.Tensor, context: Optional[torch.Tensor] = None, indices: Optional[torch.Tensor] = None, context_indices: Optional[torch.Tensor] = None) -> torch.Tensor:
102
- B, L, C = x.shape
103
- if self._type == "self":
104
- qkv = self.to_qkv(x)
105
- qkv = qkv.reshape(B, L, 3, self.num_heads, -1)
106
- if self.qk_rms_norm:
107
- q, k, v = qkv.unbind(dim=2)
108
- q = self.q_rms_norm(q)
109
- k = self.k_rms_norm(k)
110
- qkv = torch.stack([q, k, v], dim=2)
111
- h = scaled_dot_product_attention(qkv)
112
- else:
113
- Lkv = context.shape[1]
114
- q = self.to_q(x)
115
- kv = self.to_kv(context)
116
- q = q.reshape(B, L, self.num_heads, -1)
117
- kv = kv.reshape(B, Lkv, 2, self.num_heads, -1)
118
- if self.qk_rms_norm:
119
- q = self.q_rms_norm(q)
120
- k, v = kv.unbind(dim=2)
121
- k = self.k_rms_norm(k)
122
- h = scaled_dot_product_attention(q, k, v)
123
- else:
124
- h = scaled_dot_product_attention(q, kv)
125
- h = h.reshape(B, L, -1)
126
- h = self.to_out(h)
127
- return h
128
-
129
-
130
-
131
- def apply_rotary_emb(hidden_states: torch.Tensor, freqs: torch.Tensor) -> torch.Tensor:
132
- """
133
- Apply rotary embeddings to input tensors using the given frequencies.
134
- Args:
135
- hidden_states: [B, L, H, D]
136
- freqs: [B, L, 1, D/2] complex tensor
137
- """
138
- # [B, L, H, D] -> [B, L, H, D/2, 2] -> [B, L, H, D/2] (complex)
139
- x_rotated = torch.view_as_complex(hidden_states.float().reshape(*hidden_states.shape[:-1], -1, 2))
140
-
141
- # [B, L, H, D/2] * [B, L, 1, D/2] (broadcast) -> [B, L, H, D/2]
142
- x_rotated = x_rotated * freqs
143
-
144
- # [B, L, H, D/2] -> [B, L, H, D/2, 2] -> [B, L, H, D]
145
- x_out = torch.view_as_real(x_rotated).reshape(*x_rotated.shape[:-1], -1)
146
- return x_out.type_as(hidden_states)
147
-
148
- def clamp_mul(x, f):
149
- f_t = f.tanh()
150
- return x * f_t + x.detach() * (f - f_t)
151
-
152
- class RePo3DRotaryEmbedding(nn.Module):
153
- def __init__(self, model_channels: int, num_heads: int, head_dim: int, repo_hidden_ratio: float = 0.125, max_freq: float = 16.0):
154
- super().__init__()
155
- self.num_heads = num_heads
156
- self.head_dim = head_dim
157
- repo_hidden_size = int(model_channels * repo_hidden_ratio)
158
-
159
- # Lightweight Residual Network
160
- self.norm = LayerNorm32(model_channels)
161
- self.gate_map = nn.Linear(model_channels, repo_hidden_size, bias=False)
162
- self.content_map = nn.Linear(model_channels, repo_hidden_size, bias=False)
163
- self.act = nn.SiLU()
164
- self.final_map = nn.Linear(repo_hidden_size, 3 * num_heads, bias=False)
165
-
166
- # Split head_dim into 3 parts for x, y, z (same as Rotary3DPoseEmbedding)
167
- self.dim_0 = 2 * (head_dim // 6)
168
- self.dim_1 = 2 * (head_dim // 6)
169
- self.dim_2 = head_dim - self.dim_0 - self.dim_1
170
- self.max_freq = max_freq
171
-
172
- dims = [self.dim_0, self.dim_1, self.dim_2]
173
- freqs_list = []
174
- for d in dims:
175
- freq_dim = d // 2
176
- freqs = torch.linspace(1.0, float(max_freq), steps=freq_dim, dtype=torch.float32)
177
- freqs_list.append(freqs)
178
-
179
- self.freqs_0 = nn.Parameter(freqs_list[0])
180
- self.freqs_1 = nn.Parameter(freqs_list[1])
181
- self.freqs_2 = nn.Parameter(freqs_list[2])
182
-
183
- def initialize_weights(self):
184
- # Initialize proj_out with small values
185
- nn.init.constant_(self.final_map.weight, 0)
186
-
187
- def forward(self, hidden_states: torch.Tensor) -> torch.Tensor:
188
- # hidden_states: [B, L, C]
189
- # base_rotary_emb: [1, L, 1, D/2] (or similar broadcastable shape)
190
- # pos: [B, L, 3] original 3D coordinates
191
- hidden_states = hidden_states.float()
192
- # Lightweight fusion logic
193
- h = self.norm(hidden_states)
194
- feat = self.act(self.gate_map(h)) * self.content_map(h)
195
- out = self.final_map(feat)
196
-
197
- # Reshape to [B, L, 3 * H] -> [B, L, H, 3]
198
- B, L, _ = out.shape
199
- delta_pos = out.reshape(B, L, self.num_heads, 3)
200
-
201
- # Compute angles for delta (rope like)
202
- # delta_pos[..., 0] is [B, L, H]
203
- # freqs_0 is [D0/2]
204
- # We want [B, L, H, D0/2]
205
- # delta_pos gradient scale clamping, avoid large freq gradient exploding
206
- # forward equivalent to delta_pos * self.freqs_I * torch.pi
207
- ang_0 = clamp_mul(delta_pos[..., 0].unsqueeze(-1), self.freqs_0) * torch.pi
208
- ang_1 = clamp_mul(delta_pos[..., 1].unsqueeze(-1), self.freqs_1) * torch.pi
209
- ang_2 = clamp_mul(delta_pos[..., 2].unsqueeze(-1), self.freqs_2) * torch.pi
210
-
211
- # Concatenate angles [B, L, H, D/2]
212
- ang = torch.cat([ang_0, ang_1, ang_2], dim=-1)
213
-
214
- # Complex rotation for delta
215
- delta_freqs_cis = torch.polar(torch.ones_like(ang), ang)
216
-
217
- # Apply residual rotation: base * delta
218
- # base_rotary_emb will broadcast to [B, L, H, D/2]
219
- return delta_freqs_cis.type(torch.complex64)
220
-
221
-
222
- class Rotary3DPoseEmbedding(nn.Module):
223
- def __init__(self, head_dim: int, max_res: int = 10):
224
- super().__init__()
225
- self.head_dim = head_dim
226
-
227
- # Split head_dim into 3 parts for x, y, z
228
- # Each part must be divisible by 2 for complex RoPE
229
- self.dim_0 = 2 * (head_dim // 6)
230
- self.dim_1 = 2 * (head_dim // 6)
231
- self.dim_2 = head_dim - self.dim_0 - self.dim_1
232
-
233
- dims = [self.dim_0, self.dim_1, self.dim_2]
234
- freqs_list = []
235
- for d in dims:
236
- freq_dim = d // 2
237
- logs = torch.linspace(0.0, float(max_res), steps=freq_dim, dtype=torch.float32)
238
- freqs = torch.pow(2.0, logs)
239
- freqs_list.append(freqs)
240
-
241
- self.register_buffer("freqs_0", freqs_list[0])
242
- self.register_buffer("freqs_1", freqs_list[1])
243
- self.register_buffer("freqs_2", freqs_list[2])
244
-
245
- def forward(self, pos: torch.Tensor) -> torch.Tensor:
246
- """
247
- Args:
248
- pos: [B, L, 3] tensor of spatial positions in [0, 1]
249
- Returns:
250
- rope_emb: [B, L, 1, D/2] complex tensor
251
- """
252
- # pos[..., 0]: [B, L]
253
- # freqs_0: [D0/2]
254
- # ang: [B, L, D0/2]
255
-
256
- ang_0 = torch.outer(pos[..., 0].flatten(), self.freqs_0).view(*pos.shape[:-1], -1) * torch.pi
257
- ang_1 = torch.outer(pos[..., 1].flatten(), self.freqs_1).view(*pos.shape[:-1], -1) * torch.pi
258
- ang_2 = torch.outer(pos[..., 2].flatten(), self.freqs_2).view(*pos.shape[:-1], -1) * torch.pi
259
-
260
- # Concatenate angles [B, L, D/2]
261
- ang = torch.cat([ang_0, ang_1, ang_2], dim=-1)
262
-
263
- # Convert to complex exponentials
264
- freqs_cis = torch.polar(torch.ones_like(ang), ang)
265
-
266
- # Add head dimension for broadcasting: [B, L, 1, D/2]
267
- return freqs_cis.unsqueeze(-2).type(torch.complex64)
268
-
269
-
270
- class RopeMultiHeadAttention(nn.Module):
271
- def __init__(
272
- self,
273
- channels: int,
274
- num_heads: int,
275
- ctx_channels: Optional[int]=None,
276
- type: Literal["self", "cross"] = "self",
277
- qkv_bias: bool = True,
278
- qk_rms_norm: bool = False,
279
- use_rope: bool = False,
280
- ):
281
- super().__init__()
282
- self.channels = channels
283
- self.num_heads = num_heads
284
- self.head_dim = channels // num_heads
285
- self.ctx_channels = ctx_channels if ctx_channels is not None else channels
286
- self._type = type
287
- self.qk_rms_norm = qk_rms_norm
288
- self.use_rope = use_rope
289
-
290
- assert channels % num_heads == 0
291
-
292
- if self._type == "self":
293
- self.qkv = nn.Linear(channels, channels * 3, bias=qkv_bias)
294
- else:
295
- self.q = nn.Linear(channels, channels, bias=qkv_bias)
296
- self.kv = nn.Linear(self.ctx_channels, channels * 2, bias=qkv_bias)
297
-
298
- if self.qk_rms_norm:
299
- self.q_norm = MultiHeadRMSNorm(self.head_dim, num_heads)
300
- self.k_norm = MultiHeadRMSNorm(self.head_dim, num_heads)
301
-
302
- self.out = nn.Linear(channels, channels)
303
-
304
- def forward(self, x: torch.Tensor, context: Optional[torch.Tensor] = None, rope_emb: Optional[torch.Tensor] = None) -> torch.Tensor:
305
- """
306
- Args:
307
- x: [B, L, C] query tensor
308
- context: [B, S, C] context tensor (for cross attention)
309
- rope_emb: [B, L, 1, D/2] precomputed complex RoPE embedding
310
- """
311
- B, L, C = x.shape
312
-
313
- if self._type == "self":
314
- # [B, L, 3, H, D]
315
- qkv = self.qkv(x).reshape(B, L, 3, self.num_heads, self.head_dim)
316
- q, k, v = qkv.unbind(2) # [B, L, H, D]
317
-
318
- if self.use_rope:
319
- q = apply_rotary_emb(q, rope_emb)
320
- k = apply_rotary_emb(k, rope_emb)
321
- else:
322
- # Cross attention
323
- q = self.q(x).reshape(B, L, self.num_heads, self.head_dim)
324
- if context is None:
325
- raise ValueError("Context must be provided for cross attention")
326
- kv = self.kv(context).reshape(B, context.shape[1], 2, self.num_heads, self.head_dim)
327
- k, v = kv.unbind(2)
328
- # No RoPE for cross attention per instructions
329
-
330
- if self.qk_rms_norm:
331
- q = self.q_norm(q)
332
- k = self.k_norm(k)
333
-
334
- h = scaled_dot_product_attention(q, k, v)
335
-
336
- # [B, L, H, D] -> [B, L, C]
337
- h = h.reshape(B, L, C)
338
- return self.out(h)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
idol/modules/mlp.py DELETED
@@ -1,16 +0,0 @@
1
- import torch
2
- import torch.nn as nn
3
- from typing import *
4
-
5
- class MLP(nn.Module):
6
- def __init__(self, channels: int, inner_channels: int, channels_out: Optional[int] = None, mlp_layer_num: int = 2):
7
- super().__init__()
8
- layers = []
9
- for i in range(mlp_layer_num - 1):
10
- layers.append(nn.Linear(channels if i == 0 else inner_channels, inner_channels))
11
- layers.append(nn.GELU(approximate="tanh"))
12
- layers.append(nn.Linear(inner_channels, channels if channels_out is None else channels_out))
13
- self.mlp = nn.Sequential(*layers)
14
-
15
- def forward(self, x: torch.Tensor) -> torch.Tensor:
16
- return self.mlp(x)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
idol/modules/norm.py DELETED
@@ -1,29 +0,0 @@
1
- import torch
2
- import torch.nn as nn
3
-
4
-
5
- class LayerNorm32(nn.LayerNorm):
6
- def forward(self, x: torch.Tensor) -> torch.Tensor:
7
- return super().forward(x.float()).type(x.dtype)
8
-
9
- class RMSNorm32(nn.RMSNorm):
10
- def forward(self, x: torch.Tensor) -> torch.Tensor:
11
- return super().forward(x.float()).type(x.dtype)
12
-
13
-
14
- class GroupNorm32(nn.GroupNorm):
15
- """
16
- A GroupNorm layer that converts to float32 before the forward pass.
17
- """
18
- def forward(self, x: torch.Tensor) -> torch.Tensor:
19
- return super().forward(x.float()).type(x.dtype)
20
-
21
-
22
- class ChannelLayerNorm32(LayerNorm32):
23
- def forward(self, x: torch.Tensor) -> torch.Tensor:
24
- DIM = x.dim()
25
- x = x.permute(0, *range(2, DIM), 1).contiguous()
26
- x = super().forward(x)
27
- x = x.permute(0, DIM-1, *range(1, DIM-1)).contiguous()
28
- return x
29
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
idol/modules/transformer/__init__.py DELETED
@@ -1,2 +0,0 @@
1
- from .blocks import *
2
- from .modulated import *
 
 
 
idol/modules/transformer/blocks.py DELETED
@@ -1,177 +0,0 @@
1
- from typing import *
2
- import torch
3
- import torch.nn as nn
4
- from ..attention import MultiHeadAttention
5
- from ..norm import LayerNorm32
6
-
7
-
8
- class AbsolutePositionEmbedder(nn.Module):
9
- """
10
- Embeds spatial positions into vector representations.
11
- """
12
- def __init__(self, channels: int, in_channels: int = 3):
13
- super().__init__()
14
- self.channels = channels
15
- self.in_channels = in_channels
16
- self.freq_dim = channels // in_channels // 2
17
- self.freqs = torch.arange(self.freq_dim, dtype=torch.float32) / self.freq_dim
18
- self.freqs = 1.0 / (10000 ** self.freqs)
19
-
20
- def _sin_cos_embedding(self, x: torch.Tensor) -> torch.Tensor:
21
- """
22
- Create sinusoidal position embeddings.
23
-
24
- Args:
25
- x: a 1-D Tensor of N indices
26
-
27
- Returns:
28
- an (N, D) Tensor of positional embeddings.
29
- """
30
- self.freqs = self.freqs.to(x.device)
31
- out = torch.outer(x, self.freqs)
32
- out = torch.cat([torch.sin(out), torch.cos(out)], dim=-1)
33
- return out
34
-
35
- def forward(self, x: torch.Tensor) -> torch.Tensor:
36
- """
37
- Args:
38
- x (torch.Tensor): (N, D) tensor of spatial positions
39
- """
40
- N, D = x.shape
41
- assert D == self.in_channels, "Input dimension must match number of input channels"
42
- embed = self._sin_cos_embedding(x.reshape(-1))
43
- embed = embed.reshape(N, -1)
44
- if embed.shape[1] < self.channels:
45
- embed = torch.cat([embed, torch.zeros(N, self.channels - embed.shape[1], device=embed.device)], dim=-1)
46
- return embed
47
-
48
-
49
- class FeedForwardNet(nn.Module):
50
- def __init__(self, channels: int, mlp_ratio: float = 4.0, channels_out: Optional[int] = None):
51
- super().__init__()
52
- self.mlp = nn.Sequential(
53
- nn.Linear(channels, int(channels * mlp_ratio)),
54
- nn.GELU(approximate="tanh"),
55
- nn.Linear(int(channels * mlp_ratio), channels if channels_out is None else channels_out),
56
- )
57
-
58
- def forward(self, x: torch.Tensor) -> torch.Tensor:
59
- return self.mlp(x)
60
-
61
- class TransformerBlock(nn.Module):
62
- """
63
- Transformer block (MSA + FFN).
64
- """
65
- def __init__(
66
- self,
67
- channels: int,
68
- num_heads: int,
69
- mlp_ratio: float = 4.0,
70
- use_checkpoint: bool = False,
71
- qk_rms_norm: bool = False,
72
- qkv_bias: bool = True,
73
- ):
74
- super().__init__()
75
- self.use_checkpoint = use_checkpoint
76
- self.norm1 = LayerNorm32(channels, elementwise_affine=False, eps=1e-6)
77
- self.norm2 = LayerNorm32(channels, elementwise_affine=False, eps=1e-6)
78
- self.attn = MultiHeadAttention(
79
- channels,
80
- num_heads=num_heads,
81
- qkv_bias=qkv_bias,
82
- qk_rms_norm=qk_rms_norm,
83
- )
84
- self.mlp = FeedForwardNet(
85
- channels,
86
- mlp_ratio=mlp_ratio,
87
- )
88
-
89
- def _forward(self, x: torch.Tensor, indices=None) -> torch.Tensor:
90
- h = self.norm1(x)
91
- h = self.attn(h, indices=indices)
92
- x = x + h
93
- h = self.norm2(x)
94
- h = self.mlp(h)
95
- x = x + h
96
- return x
97
-
98
- def forward(self, x: torch.Tensor, indices=None) -> torch.Tensor:
99
- if self.use_checkpoint:
100
- return torch.utils.checkpoint.checkpoint(self._forward, x, indices, use_reentrant=False)
101
- else:
102
- return self._forward(x, indices=indices)
103
-
104
-
105
- class TransformerCrossBlock(nn.Module):
106
- """
107
- Transformer cross-attention block (MSA + MCA + FFN).
108
- """
109
- def __init__(
110
- self,
111
- channels: int,
112
- ctx_channels: int,
113
- num_heads: int,
114
- mlp_ratio: float = 4.0,
115
- use_checkpoint: bool = False,
116
- qk_rms_norm: bool = False,
117
- qk_rms_norm_cross: bool = False,
118
- qkv_bias: bool = True,
119
- use_2_cross_block: bool = False,
120
- ):
121
- super().__init__()
122
- self.use_checkpoint = use_checkpoint
123
- self.use_2_cross_block = use_2_cross_block
124
- self.norm1 = LayerNorm32(channels, elementwise_affine=False, eps=1e-6)
125
- self.norm2 = LayerNorm32(channels, elementwise_affine=True, eps=1e-6)
126
- self.norm3 = LayerNorm32(channels, elementwise_affine=False, eps=1e-6)
127
- self.self_attn = MultiHeadAttention(
128
- channels,
129
- num_heads=num_heads,
130
- type="self",
131
- qkv_bias=qkv_bias,
132
- qk_rms_norm=qk_rms_norm,
133
- )
134
- self.cross_attn = MultiHeadAttention(
135
- channels,
136
- ctx_channels=ctx_channels,
137
- num_heads=num_heads,
138
- type="cross",
139
- qkv_bias=qkv_bias,
140
- qk_rms_norm=qk_rms_norm_cross,
141
- )
142
- if self.use_2_cross_block:
143
- self.norm2_1 = LayerNorm32(channels, elementwise_affine=True, eps=1e-6)
144
- self.cross_attn2 = MultiHeadAttention(
145
- channels,
146
- ctx_channels=ctx_channels,
147
- num_heads=num_heads,
148
- type="cross",
149
- qkv_bias=qkv_bias,
150
- qk_rms_norm=qk_rms_norm_cross,
151
- )
152
- self.mlp = FeedForwardNet(
153
- channels,
154
- mlp_ratio=mlp_ratio,
155
- )
156
-
157
- def _forward(self, x: torch.Tensor, context: torch.Tensor, context2=None, indices=None, context_indices=None, context2_indices=None) -> torch.Tensor:
158
- h = self.norm1(x)
159
- h = self.self_attn(h, indices=indices)
160
- x = x + h
161
- h = self.norm2(x)
162
- h = self.cross_attn(h, context, context_indices=context_indices)
163
- x = x + h
164
- if self.use_2_cross_block:
165
- h = self.norm2_1(x)
166
- h = self.cross_attn2(h, context2, context_indices=context2_indices)
167
- x = x + h
168
- h = self.norm3(x)
169
- h = self.mlp(h)
170
- x = x + h
171
- return x
172
-
173
- def forward(self, x: torch.Tensor, context: torch.Tensor, context2=None, indices=None, context_indices=None, context2_indices=None) -> torch.Tensor:
174
- if self.use_checkpoint:
175
- return torch.utils.checkpoint.checkpoint(self._forward, x, context, context2, indices, context_indices, context2_indices, use_reentrant=False)
176
- else:
177
- return self._forward(x, context, context2=context2, indices=indices, context_indices=context_indices, context2_indices=context2_indices)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
idol/modules/transformer/modulated.py DELETED
@@ -1,381 +0,0 @@
1
- from typing import *
2
- import torch
3
- import torch.nn as nn
4
-
5
- from ..attention.modules import RopeMultiHeadAttention
6
- from ..attention import MultiHeadAttention
7
- from ..norm import LayerNorm32, RMSNorm32
8
- from .blocks import FeedForwardNet
9
-
10
-
11
- class ModulatedTransformerBlock(nn.Module):
12
- """
13
- Transformer block (MSA + FFN) with adaptive layer norm conditioning.
14
- """
15
- def __init__(
16
- self,
17
- channels: int,
18
- num_heads: int,
19
- mlp_ratio: float = 4.0,
20
- use_checkpoint: bool = False,
21
- use_rope: bool = False,
22
- qk_rms_norm: bool = False,
23
- qkv_bias: bool = True,
24
- share_mod: bool = False,
25
- ):
26
- super().__init__()
27
- self.use_checkpoint = use_checkpoint
28
- self.share_mod = share_mod
29
- self.norm1 = LayerNorm32(channels, elementwise_affine=False, eps=1e-6)
30
- self.norm2 = LayerNorm32(channels, elementwise_affine=False, eps=1e-6)
31
- self.attn = MultiHeadAttention(
32
- channels,
33
- num_heads=num_heads,
34
- qkv_bias=qkv_bias,
35
- qk_rms_norm=qk_rms_norm,
36
- )
37
- self.mlp = FeedForwardNet(
38
- channels,
39
- mlp_ratio=mlp_ratio,
40
- )
41
- if not share_mod:
42
- self.adaLN_modulation = nn.Sequential(
43
- nn.SiLU(),
44
- nn.Linear(channels, 6 * channels, bias=True)
45
- )
46
-
47
- def _forward(self, x: torch.Tensor, mod: torch.Tensor) -> torch.Tensor:
48
- if self.share_mod:
49
- shift_msa, scale_msa, gate_msa, shift_mlp, scale_mlp, gate_mlp = mod.chunk(6, dim=1)
50
- else:
51
- shift_msa, scale_msa, gate_msa, shift_mlp, scale_mlp, gate_mlp = self.adaLN_modulation(mod).chunk(6, dim=1)
52
- h = self.norm1(x)
53
- h = h * (1 + scale_msa.unsqueeze(1)) + shift_msa.unsqueeze(1)
54
- h = self.attn(h)
55
- h = h * gate_msa.unsqueeze(1)
56
- x = x + h
57
- h = self.norm2(x)
58
- h = h * (1 + scale_mlp.unsqueeze(1)) + shift_mlp.unsqueeze(1)
59
- h = self.mlp(h)
60
- h = h * gate_mlp.unsqueeze(1)
61
- x = x + h
62
- return x
63
-
64
- def forward(self, x: torch.Tensor, mod: torch.Tensor) -> torch.Tensor:
65
- if self.use_checkpoint:
66
- return torch.utils.checkpoint.checkpoint(self._forward, x, mod, use_reentrant=False)
67
- else:
68
- return self._forward(x, mod)
69
-
70
-
71
- class ModulatedTransformerCrossBlock(nn.Module):
72
- """
73
- Transformer cross-attention block (MSA + MCA + FFN) with adaptive layer norm conditioning.
74
- """
75
- def __init__(
76
- self,
77
- channels: int,
78
- ctx_channels: int,
79
- num_heads: int,
80
- mlp_ratio: float = 4.0,
81
- use_checkpoint: bool = False,
82
- use_rope: bool = False,
83
- qk_rms_norm: bool = False,
84
- qk_rms_norm_cross: bool = False,
85
- qkv_bias: bool = True,
86
- share_mod: bool = False,
87
- use_shift_table: bool = False,
88
- ):
89
- super().__init__()
90
- self.use_checkpoint = use_checkpoint
91
- self.share_mod = share_mod
92
- self.norm1 = LayerNorm32(channels, elementwise_affine=False, eps=1e-6)
93
- self.norm2 = LayerNorm32(channels, elementwise_affine=True, eps=1e-6)
94
- self.norm3 = LayerNorm32(channels, elementwise_affine=False, eps=1e-6)
95
- self.self_attn = MultiHeadAttention(
96
- channels,
97
- num_heads=num_heads,
98
- type="self",
99
- qkv_bias=qkv_bias,
100
- qk_rms_norm=qk_rms_norm,
101
- )
102
- self.cross_attn = MultiHeadAttention(
103
- channels,
104
- ctx_channels=ctx_channels,
105
- num_heads=num_heads,
106
- type="cross",
107
- qkv_bias=qkv_bias,
108
- qk_rms_norm=qk_rms_norm_cross,
109
- )
110
- self.mlp = FeedForwardNet(
111
- channels,
112
- mlp_ratio=mlp_ratio,
113
- )
114
- if not share_mod:
115
- self.adaLN_modulation = nn.Sequential(
116
- nn.SiLU(),
117
- nn.Linear(channels, 6 * channels, bias=True)
118
- )
119
- if use_shift_table:
120
- self.shift_table = nn.Parameter(torch.randn(1, 6 * channels) / channels**0.5)
121
- else:
122
- self.shift_table = None
123
-
124
- def _forward(self, x: torch.Tensor, mod: torch.Tensor, context: torch.Tensor):
125
- if not self.share_mod:
126
- mod = self.adaLN_modulation(mod)
127
- if self.shift_table is not None:
128
- mod = mod + self.shift_table.type(mod.dtype)
129
- shift_msa, scale_msa, gate_msa, shift_mlp, scale_mlp, gate_mlp = mod.chunk(6, dim=1)
130
- h = self.norm1(x)
131
- h = h * (1 + scale_msa.unsqueeze(1)) + shift_msa.unsqueeze(1)
132
- h = self.self_attn(h)
133
- h = h * gate_msa.unsqueeze(1)
134
- x = x + h
135
- h = self.norm2(x)
136
- h = self.cross_attn(h, context)
137
- x = x + h
138
- h = self.norm3(x)
139
- h = h * (1 + scale_mlp.unsqueeze(1)) + shift_mlp.unsqueeze(1)
140
- h = self.mlp(h)
141
- h = h * gate_mlp.unsqueeze(1)
142
- x = x + h
143
- return x
144
-
145
- def forward(self, x: torch.Tensor, mod: torch.Tensor, context: torch.Tensor):
146
- if self.use_checkpoint:
147
- return torch.utils.checkpoint.checkpoint(self._forward, x, mod, context, use_reentrant=False)
148
- else:
149
- return self._forward(x, mod, context)
150
-
151
-
152
- class UnifiedTransformerBlock(nn.Module):
153
- """
154
- Transformer block (MSA + FFN) with optional adaptive layer norm conditioning and RoPE support.
155
- Used for Unified MMDiT.
156
- """
157
- def __init__(
158
- self,
159
- channels: int,
160
- num_heads: int,
161
- mlp_ratio: float = 4.0,
162
- use_checkpoint: bool = False,
163
- use_rope: bool = False,
164
- qk_rms_norm: bool = False,
165
- qkv_bias: bool = True,
166
- modulation: bool = True,
167
- share_mod: bool = False,
168
- use_shift_table: bool = False,
169
- ):
170
- super().__init__()
171
- self.use_checkpoint = use_checkpoint
172
- self.modulation = modulation
173
- self.share_mod = share_mod
174
-
175
- # If modulation is used, we usually disable elementwise_affine in LN because modulation handles it.
176
- self.norm1 = LayerNorm32(channels, elementwise_affine=not modulation, eps=1e-6)
177
- self.norm2 = LayerNorm32(channels, elementwise_affine=not modulation, eps=1e-6)
178
-
179
- self.attn = RopeMultiHeadAttention(
180
- channels,
181
- num_heads=num_heads,
182
- type="self",
183
- qkv_bias=qkv_bias,
184
- use_rope=use_rope,
185
- qk_rms_norm=qk_rms_norm,
186
- )
187
- self.mlp = FeedForwardNet(
188
- channels,
189
- mlp_ratio=mlp_ratio,
190
- )
191
-
192
- if modulation:
193
- if not share_mod:
194
- self.adaLN_modulation = nn.Sequential(
195
- nn.SiLU(),
196
- nn.Linear(channels, 6 * channels, bias=True)
197
- )
198
- if use_shift_table:
199
- self.shift_table = nn.Parameter(torch.randn(1, 6 * channels) / channels**0.5)
200
- else:
201
- self.shift_table = None
202
-
203
- def _forward(self, x: torch.Tensor, mod: Optional[torch.Tensor] = None, rotary_emb: Optional[torch.Tensor] = None):
204
- if self.modulation:
205
- assert mod is not None, "Modulation tensor must be provided when modulation is enabled"
206
- if not self.share_mod:
207
- mod = self.adaLN_modulation(mod)
208
- if hasattr(self, 'shift_table') and self.shift_table is not None:
209
- mod = mod + self.shift_table.type(mod.dtype)
210
- shift_msa, scale_msa, gate_msa, shift_mlp, scale_mlp, gate_mlp = mod.chunk(6, dim=1)
211
-
212
- h = self.norm1(x)
213
- h = h * (1 + scale_msa.unsqueeze(1)) + shift_msa.unsqueeze(1)
214
- h = self.attn(h, rope_emb=rotary_emb)
215
- h = h * gate_msa.unsqueeze(1)
216
- x = x + h
217
-
218
- h = self.norm2(x)
219
- h = h * (1 + scale_mlp.unsqueeze(1)) + shift_mlp.unsqueeze(1)
220
- h = self.mlp(h)
221
- h = h * gate_mlp.unsqueeze(1)
222
- x = x + h
223
- else:
224
- h = self.norm1(x)
225
- h = self.attn(h, rope_emb=rotary_emb)
226
- x = x + h
227
-
228
- h = self.norm2(x)
229
- h = self.mlp(h)
230
- x = x + h
231
-
232
- return x
233
-
234
- def forward(self, x: torch.Tensor, mod: Optional[torch.Tensor] = None, rotary_emb: Optional[torch.Tensor] = None):
235
- if self.use_checkpoint:
236
- return torch.utils.checkpoint.checkpoint(self._forward, x, mod, rotary_emb, use_reentrant=False)
237
- else:
238
- return self._forward(x, mod, rotary_emb)
239
-
240
-
241
- class ModulatedTransformerCrossBlockv2(nn.Module):
242
- """
243
- Transformer cross-attention block (MSA + MCA + FFN) with adaptive layer norm conditioning.
244
- """
245
- def __init__(
246
- self,
247
- channels: int,
248
- ctx_channels: int,
249
- num_heads: int,
250
- mlp_ratio: float = 4.0,
251
- use_checkpoint: bool = False,
252
- use_rope: bool = False,
253
- qk_rms_norm: bool = False,
254
- qk_rms_norm_cross: bool = False,
255
- qkv_bias: bool = True,
256
- share_mod: bool = False,
257
- use_shift_table: bool = False,
258
- ):
259
- super().__init__()
260
- self.use_checkpoint = use_checkpoint
261
- self.share_mod = share_mod
262
- self.norm1 = LayerNorm32(channels, elementwise_affine=False, eps=1e-6)
263
- self.norm2 = LayerNorm32(channels, elementwise_affine=True, eps=1e-6)
264
- self.norm3 = LayerNorm32(channels, elementwise_affine=False, eps=1e-6)
265
- self.self_attn = RopeMultiHeadAttention(
266
- channels,
267
- num_heads=num_heads,
268
- type="self",
269
- qkv_bias=qkv_bias,
270
- use_rope=use_rope,
271
- qk_rms_norm=qk_rms_norm,
272
- )
273
- self.cross_attn = RopeMultiHeadAttention(
274
- channels,
275
- ctx_channels=ctx_channels,
276
- num_heads=num_heads,
277
- type="cross",
278
- qkv_bias=qkv_bias,
279
- qk_rms_norm=qk_rms_norm_cross,
280
- )
281
- self.mlp = FeedForwardNet(
282
- channels,
283
- mlp_ratio=mlp_ratio,
284
- )
285
- if not share_mod:
286
- self.adaLN_modulation = nn.Sequential(
287
- nn.SiLU(),
288
- nn.Linear(channels, 6 * channels, bias=True)
289
- )
290
- if use_shift_table:
291
- self.shift_table = nn.Parameter(torch.randn(1, 6 * channels) / channels**0.5)
292
- else:
293
- self.shift_table = None
294
-
295
- def _forward(self, x: torch.Tensor, mod: torch.Tensor, context: torch.Tensor, rotary_emb: Optional[torch.Tensor] = None):
296
- if not self.share_mod:
297
- mod = self.adaLN_modulation(mod)
298
- if self.shift_table is not None:
299
- mod = mod + self.shift_table.type(mod.dtype)
300
- shift_msa, scale_msa, gate_msa, shift_mlp, scale_mlp, gate_mlp = mod.chunk(6, dim=1)
301
- h = self.norm1(x)
302
- h = h * (1 + scale_msa.unsqueeze(1)) + shift_msa.unsqueeze(1)
303
- h = self.self_attn(h, rope_emb=rotary_emb)
304
- h = h * gate_msa.unsqueeze(1)
305
- x = x + h
306
- h = self.norm2(x)
307
- h = self.cross_attn(h, context)
308
- x = x + h
309
- h = self.norm3(x)
310
- h = h * (1 + scale_mlp.unsqueeze(1)) + shift_mlp.unsqueeze(1)
311
- h = self.mlp(h)
312
- h = h * gate_mlp.unsqueeze(1)
313
- x = x + h
314
- return x
315
-
316
- def forward(self, x: torch.Tensor, mod: torch.Tensor, context: torch.Tensor, rotary_emb: Optional[torch.Tensor] = None):
317
- if self.use_checkpoint:
318
- return torch.utils.checkpoint.checkpoint(self._forward, x, mod, context, rotary_emb, use_reentrant=False)
319
- else:
320
- return self._forward(x, mod, context, rotary_emb)
321
-
322
- class ModulatedTransformerCrossOnlyBlock(nn.Module):
323
- """
324
- Transformer cross-attention block (MSA + MCA + FFN) with adaptive layer norm conditioning.
325
- """
326
- def __init__(
327
- self,
328
- channels: int,
329
- ctx_channels: int,
330
- num_heads: int,
331
- mlp_ratio: float = 4.0,
332
- use_checkpoint: bool = False,
333
- qk_rms_norm_cross: bool = True,
334
- qkv_bias: bool = True,
335
- share_mod: bool = False,
336
- ):
337
- super().__init__()
338
- self.use_checkpoint = use_checkpoint
339
- self.share_mod = share_mod
340
- self.norm1 = LayerNorm32(channels, elementwise_affine=False, eps=1e-6)
341
- self.norm2 = LayerNorm32(channels, elementwise_affine=False, eps=1e-6)
342
- self.cross_attn = MultiHeadAttention(
343
- channels,
344
- ctx_channels=ctx_channels,
345
- num_heads=num_heads,
346
- type="cross",
347
- qkv_bias=qkv_bias,
348
- qk_rms_norm=qk_rms_norm_cross,
349
- )
350
- self.mlp = FeedForwardNet(
351
- channels,
352
- mlp_ratio=mlp_ratio,
353
- )
354
- if not share_mod:
355
- self.adaLN_modulation = nn.Sequential(
356
- nn.SiLU(),
357
- nn.Linear(channels, 6 * channels, bias=True)
358
- )
359
-
360
- def _forward(self, x: torch.Tensor, mod: torch.Tensor, context: torch.Tensor):
361
- if self.share_mod:
362
- shift_msa, scale_msa, gate_msa, shift_mlp, scale_mlp, gate_mlp = mod.chunk(6, dim=1)
363
- else:
364
- shift_msa, scale_msa, gate_msa, shift_mlp, scale_mlp, gate_mlp = self.adaLN_modulation(mod).chunk(6, dim=1)
365
- h = self.norm1(x)
366
- h = h * (1 + scale_msa.unsqueeze(1)) + shift_msa.unsqueeze(1)
367
- h = self.cross_attn(h, context)
368
- h = h * gate_msa.unsqueeze(1)
369
- x = x + h
370
- h = self.norm2(x)
371
- h = h * (1 + scale_mlp.unsqueeze(1)) + shift_mlp.unsqueeze(1)
372
- h = self.mlp(h)
373
- h = h * gate_mlp.unsqueeze(1)
374
- x = x + h
375
- return x
376
-
377
- def forward(self, x: torch.Tensor, mod: torch.Tensor, context: torch.Tensor):
378
- if self.use_checkpoint:
379
- return torch.utils.checkpoint.checkpoint(self._forward, x, mod, context, use_reentrant=False)
380
- else:
381
- return self._forward(x, mod, context)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
idol/modules/utils.py DELETED
@@ -1,50 +0,0 @@
1
- import torch.nn as nn
2
-
3
- FP16_MODULES = (
4
- nn.Conv1d,
5
- nn.Conv2d,
6
- nn.Conv3d,
7
- nn.ConvTranspose1d,
8
- nn.ConvTranspose2d,
9
- nn.ConvTranspose3d,
10
- nn.Linear,
11
- )
12
-
13
- def convert_module_to_f16(l):
14
- """
15
- Convert primitive modules to float16.
16
- """
17
- if isinstance(l, FP16_MODULES):
18
- for p in l.parameters():
19
- p.data = p.data.half()
20
-
21
-
22
- def convert_module_to_f32(l):
23
- """
24
- Convert primitive modules to float32, undoing convert_module_to_f16().
25
- """
26
- if isinstance(l, FP16_MODULES):
27
- for p in l.parameters():
28
- p.data = p.data.float()
29
-
30
-
31
- def zero_module(module):
32
- """
33
- Zero out the parameters of a module and return it.
34
- """
35
- for p in module.parameters():
36
- p.detach().zero_()
37
- return module
38
-
39
-
40
- def scale_module(module, scale):
41
- """
42
- Scale the parameters of a module and return it.
43
- """
44
- for p in module.parameters():
45
- p.detach().mul_(scale)
46
- return module
47
-
48
-
49
- def modulate(x, shift, scale):
50
- return x * (1 + scale.unsqueeze(1)) + shift.unsqueeze(1)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
idol/pipeline.py CHANGED
@@ -2,7 +2,7 @@
2
  import json
3
  import os
4
  from dataclasses import dataclass
5
- from typing import Callable, Optional, Union
6
 
7
  import numpy as np
8
  import torch
@@ -15,7 +15,7 @@ from .conditioning import ImageEncoder
15
  from .gaussian import Gaussian
16
  from .models import BiRefNet, GaussianFixedlenDecoder, LatentSeqMMFlowModel, OctreeProbabilityFixedlenDecoder
17
  from .preprocess import CROP_MARGIN, preprocess_image
18
- from .sampler import FlowEulerCfgSampler
19
 
20
  DEFAULT_REPO = 'speridlabs/idol'
21
  WEIGHT_FILES = ['config.json', 'denoiser.safetensors', 'decoder.safetensors', 'decoder_gs.safetensors',
@@ -29,34 +29,25 @@ class IdolOutput:
29
  camera: dict # predicted input camera: yaw, pitch (radians), radius, fov (degrees)
30
 
31
 
32
- def _load(model, path):
33
- model.load_state_dict(load_file(path), strict=True)
34
- return model
35
-
36
-
37
  class IdolPipeline:
38
  def __init__(self, weights_dir: str, device: Optional[str] = None, dinov3_dtype: str = 'bf16'):
39
  self.device = torch.device(device or ('cuda' if torch.cuda.is_available() else 'cpu'))
40
  with open(os.path.join(weights_dir, 'config.json')) as f:
41
  self.config = json.load(f)
42
- config = self.config
43
 
44
- def weights(name):
45
- return os.path.join(weights_dir, f'{name}.safetensors')
 
46
 
47
- self.denoiser = _load(LatentSeqMMFlowModel(**config['denoiser']['args']), weights('denoiser'))
48
- self.decoder = _load(OctreeProbabilityFixedlenDecoder(**config['decoder']['args']), weights('decoder'))
49
- self.decoder_gs = _load(GaussianFixedlenDecoder(**config['decoder_gs']['args']), weights('decoder_gs'))
50
- self.background_remover = BiRefNet()
51
- self.background_remover.load_safetensors(weights('birefnet'))
52
- self.image_encoder = ImageEncoder(config['image_encoders']['dinov3'], config['image_encoders']['flux2_vae'],
53
- resolution=config['image_encoders']['resolution'],
54
- dinov3_dtype=dinov3_dtype, device=self.device)
55
- for model in (self.denoiser, self.decoder, self.decoder_gs):
56
- model.eval().requires_grad_(False).to(self.device)
57
- remover_dtype = torch.float16 if self.device.type == 'cuda' else torch.float32
58
- self.background_remover = self.background_remover.eval().to(self.device, remover_dtype)
59
- self.sampler = FlowEulerCfgSampler(sigma_min=config['sigma_min'])
60
 
61
  @classmethod
62
  def from_pretrained(cls, repo_or_dir: str = DEFAULT_REPO, **kwargs) -> 'IdolPipeline':
@@ -66,21 +57,14 @@ class IdolPipeline:
66
  from huggingface_hub import snapshot_download
67
  return cls(snapshot_download(repo_or_dir, allow_patterns=WEIGHT_FILES), **kwargs)
68
 
69
- def to(self, device) -> 'IdolPipeline':
70
- self.device = torch.device(device)
71
- for model in (self.denoiser, self.decoder, self.decoder_gs, self.background_remover):
72
- model.to(self.device)
73
- self.image_encoder.to(self.device)
74
- return self
75
-
76
  def preprocess(self, image: Union[str, Image.Image], mode: str = 'synthetic',
77
- crop_margin: Optional[float] = None, erode_radius: int = 1) -> Image.Image:
78
  """mode 'synthetic' (renders, generated images of one object: crop 1.2) or 'real' (photos: crop 1.7);
79
  crop_margin overrides the mode's value."""
80
  if isinstance(image, str):
81
  image = Image.open(image)
82
  margin = crop_margin if crop_margin is not None else CROP_MARGIN[mode]
83
- return preprocess_image(image, background_remover=self.background_remover, erode_radius=erode_radius,
84
  image_size=self.config['image_size'], crop_margin=margin)
85
 
86
  @torch.no_grad()
@@ -94,34 +78,23 @@ class IdolPipeline:
94
  rescale_t: float = 3.0,
95
  num_gaussians: int = 262144,
96
  crop_margin: Optional[float] = None,
97
- erode_radius: int = 1,
98
- preprocess: bool = True,
99
- callback: Optional[Callable[[int, int], None]] = None,
100
  verbose: bool = False,
101
  ) -> IdolOutput:
102
  """Generate the 3D Gaussians of the object in ``image``.
103
 
104
- mode / crop_margin / erode_radius: see ``preprocess``. preprocess=False feeds the image as given (it
105
- must already be a square, centred object on black).
106
- num_gaussians: 32,768 to 1,048,576, rounded down to a multiple of 32.
107
- callback(step_done, steps) is called after every sampling step."""
108
- if preprocess:
109
- prepared = self.preprocess(image, mode=mode, crop_margin=crop_margin, erode_radius=erode_radius)
110
- else:
111
- prepared = (Image.open(image) if isinstance(image, str) else image).convert('RGB')
112
  pixels = torch.from_numpy(np.array(prepared)).permute(2, 0, 1).float().div(255.0)[None].to(self.device)
113
 
114
  torch.manual_seed(seed)
115
- np.random.seed(seed)
116
  cond = self.image_encoder(pixels)
117
- neg_cond = cond.apply(torch.zeros_like)
118
  noise = TensorDict({
119
  'latent': torch.randn(1, self.denoiser.q_token_length, self.denoiser.in_channels, device=self.device),
120
  'camera': torch.randn(1, 1, self.denoiser.cam_channels, device=self.device),
121
  }, batch_size=1, device=self.device)
122
- sample = self.sampler.sample(self.denoiser, noise, cond=cond, neg_cond=neg_cond, steps=steps,
123
- rescale_t=rescale_t, cfg_strength=cfg_strength, verbose=verbose,
124
- callback=callback)
125
  gaussian = self.decode(sample['latent'], num_gaussians)
126
  return IdolOutput(gaussian=gaussian, preprocessed_image=prepared, camera=decode_camera(sample['camera'][0, 0]))
127
 
@@ -130,8 +103,7 @@ class IdolPipeline:
130
  """Latent [1, 8192, 16] -> Gaussians: the octree decoder places num_gaussians / 32 anchor points, the
131
  Gaussian decoder turns each anchor into 32 Gaussians."""
132
  per_anchor = self.decoder_gs.rep_config['num_gaussians']
133
- anchors = OctreeProbabilityFixedlenDecoder.sample(
134
- self.decoder, latent, num_points=max(1, int(num_gaussians) // per_anchor),
135
- level=self.config['max_voxel_level'], temperature=1.0, algo='systematic')
136
- features = self.decoder_gs(x=anchors, cond=latent)
137
- return self.decoder_gs.to_representation(anchors, features)[0]
 
2
  import json
3
  import os
4
  from dataclasses import dataclass
5
+ from typing import Optional, Union
6
 
7
  import numpy as np
8
  import torch
 
15
  from .gaussian import Gaussian
16
  from .models import BiRefNet, GaussianFixedlenDecoder, LatentSeqMMFlowModel, OctreeProbabilityFixedlenDecoder
17
  from .preprocess import CROP_MARGIN, preprocess_image
18
+ from .sampler import sample_flow
19
 
20
  DEFAULT_REPO = 'speridlabs/idol'
21
  WEIGHT_FILES = ['config.json', 'denoiser.safetensors', 'decoder.safetensors', 'decoder_gs.safetensors',
 
29
  camera: dict # predicted input camera: yaw, pitch (radians), radius, fov (degrees)
30
 
31
 
 
 
 
 
 
32
  class IdolPipeline:
33
  def __init__(self, weights_dir: str, device: Optional[str] = None, dinov3_dtype: str = 'bf16'):
34
  self.device = torch.device(device or ('cuda' if torch.cuda.is_available() else 'cpu'))
35
  with open(os.path.join(weights_dir, 'config.json')) as f:
36
  self.config = json.load(f)
 
37
 
38
+ def load(model, name):
39
+ model.load_state_dict(load_file(os.path.join(weights_dir, f'{name}.safetensors')), strict=True)
40
+ return model.eval().requires_grad_(False).to(self.device)
41
 
42
+ self.denoiser = load(LatentSeqMMFlowModel(**self.config['denoiser']['args']), 'denoiser')
43
+ self.decoder = load(OctreeProbabilityFixedlenDecoder(**self.config['decoder']['args']), 'decoder')
44
+ self.decoder_gs = load(GaussianFixedlenDecoder(**self.config['decoder_gs']['args']), 'decoder_gs')
45
+ self.background_remover = load(BiRefNet(), 'birefnet')
46
+ if self.device.type == 'cuda':
47
+ self.background_remover.half()
48
+ encoders = self.config['image_encoders']
49
+ self.image_encoder = ImageEncoder(encoders['dinov3'], encoders['flux2_vae'], dinov3_dtype=dinov3_dtype,
50
+ device=self.device)
 
 
 
 
51
 
52
  @classmethod
53
  def from_pretrained(cls, repo_or_dir: str = DEFAULT_REPO, **kwargs) -> 'IdolPipeline':
 
57
  from huggingface_hub import snapshot_download
58
  return cls(snapshot_download(repo_or_dir, allow_patterns=WEIGHT_FILES), **kwargs)
59
 
 
 
 
 
 
 
 
60
  def preprocess(self, image: Union[str, Image.Image], mode: str = 'synthetic',
61
+ crop_margin: Optional[float] = None) -> Image.Image:
62
  """mode 'synthetic' (renders, generated images of one object: crop 1.2) or 'real' (photos: crop 1.7);
63
  crop_margin overrides the mode's value."""
64
  if isinstance(image, str):
65
  image = Image.open(image)
66
  margin = crop_margin if crop_margin is not None else CROP_MARGIN[mode]
67
+ return preprocess_image(image, background_remover=self.background_remover,
68
  image_size=self.config['image_size'], crop_margin=margin)
69
 
70
  @torch.no_grad()
 
78
  rescale_t: float = 3.0,
79
  num_gaussians: int = 262144,
80
  crop_margin: Optional[float] = None,
 
 
 
81
  verbose: bool = False,
82
  ) -> IdolOutput:
83
  """Generate the 3D Gaussians of the object in ``image``.
84
 
85
+ mode / crop_margin: see ``preprocess``.
86
+ num_gaussians: 32,768 to 1,048,576, rounded down to a multiple of 32."""
87
+ prepared = self.preprocess(image, mode=mode, crop_margin=crop_margin)
 
 
 
 
 
88
  pixels = torch.from_numpy(np.array(prepared)).permute(2, 0, 1).float().div(255.0)[None].to(self.device)
89
 
90
  torch.manual_seed(seed)
 
91
  cond = self.image_encoder(pixels)
 
92
  noise = TensorDict({
93
  'latent': torch.randn(1, self.denoiser.q_token_length, self.denoiser.in_channels, device=self.device),
94
  'camera': torch.randn(1, 1, self.denoiser.cam_channels, device=self.device),
95
  }, batch_size=1, device=self.device)
96
+ sample = sample_flow(self.denoiser, noise, cond=cond, neg_cond=cond.apply(torch.zeros_like), steps=steps,
97
+ rescale_t=rescale_t, cfg_strength=cfg_strength, verbose=verbose)
 
98
  gaussian = self.decode(sample['latent'], num_gaussians)
99
  return IdolOutput(gaussian=gaussian, preprocessed_image=prepared, camera=decode_camera(sample['camera'][0, 0]))
100
 
 
103
  """Latent [1, 8192, 16] -> Gaussians: the octree decoder places num_gaussians / 32 anchor points, the
104
  Gaussian decoder turns each anchor into 32 Gaussians."""
105
  per_anchor = self.decoder_gs.rep_config['num_gaussians']
106
+ anchors = OctreeProbabilityFixedlenDecoder.sample(self.decoder, latent,
107
+ num_points=max(1, int(num_gaussians) // per_anchor),
108
+ level=self.config['max_voxel_level'])
109
+ return self.decoder_gs.to_representation(anchors, self.decoder_gs(anchors, latent))[0]
 
idol/preprocess.py CHANGED
@@ -11,22 +11,19 @@ def has_real_alpha(image: Image.Image) -> bool:
11
  return image.mode == 'RGBA' and np.array(image.getchannel(3), dtype=np.int32).min() < 255
12
 
13
 
14
- def preprocess_image(image: Image.Image, background_remover=None, erode_radius: int = 1, image_size: int = 1024,
15
  crop_margin: float = 1.2) -> Image.Image:
16
  """Return the RGB image the encoders see.
17
 
18
  Resize so the short side is ``image_size``; if the image has no real alpha, ``background_remover`` (BiRefNet)
19
- predicts one; erode the alpha by ``erode_radius`` px so no background-coloured fringe survives; crop a square of
20
  side ``crop_margin`` x the object's bounding box; resize to ``image_size``; composite on black."""
21
  width, height = image.size
22
  scale = image_size / min(width, height)
23
  image = image.resize((max(1, int(round(width * scale))), max(1, int(round(height * scale)))), Image.LANCZOS)
24
  if not has_real_alpha(image):
25
- if background_remover is None:
26
- raise ValueError('the image has no alpha channel: pass a background remover or an RGBA cutout')
27
  image = background_remover.remove_background(image.convert('RGB'))
28
- if erode_radius > 0:
29
- image.putalpha(image.getchannel(3).filter(ImageFilter.MinFilter(2 * erode_radius + 1)))
30
  alpha = np.array(image.getchannel(3))
31
  ys, xs = np.nonzero(alpha)
32
  if len(xs) == 0:
 
11
  return image.mode == 'RGBA' and np.array(image.getchannel(3), dtype=np.int32).min() < 255
12
 
13
 
14
+ def preprocess_image(image: Image.Image, background_remover, image_size: int = 1024,
15
  crop_margin: float = 1.2) -> Image.Image:
16
  """Return the RGB image the encoders see.
17
 
18
  Resize so the short side is ``image_size``; if the image has no real alpha, ``background_remover`` (BiRefNet)
19
+ predicts one; erode the alpha by 1 px so no background-coloured fringe survives; crop a square of
20
  side ``crop_margin`` x the object's bounding box; resize to ``image_size``; composite on black."""
21
  width, height = image.size
22
  scale = image_size / min(width, height)
23
  image = image.resize((max(1, int(round(width * scale))), max(1, int(round(height * scale)))), Image.LANCZOS)
24
  if not has_real_alpha(image):
 
 
25
  image = background_remover.remove_background(image.convert('RGB'))
26
+ image.putalpha(image.getchannel(3).filter(ImageFilter.MinFilter(3)))
 
27
  alpha = np.array(image.getchannel(3))
28
  ys, xs = np.nonzero(alpha)
29
  if len(xs) == 0:
idol/sampler.py CHANGED
@@ -1,38 +1,28 @@
1
  """Euler sampler for the rectified-flow denoiser, with classifier-free guidance."""
2
- from typing import Callable, Optional
3
-
4
  import numpy as np
5
  import torch
6
  from tqdm import tqdm
7
 
8
 
9
- class FlowEulerCfgSampler:
10
- def __init__(self, sigma_min: float):
11
- self.sigma_min = sigma_min
 
12
 
13
- def _velocity(self, model, x_t, t: float, cond):
14
- t = torch.tensor([1000 * t] * x_t.shape[0], device=x_t.device, dtype=torch.float32)
15
- return model(x_t, t, cond)
16
 
17
- @torch.no_grad()
18
- def sample(self, model, noise, cond, neg_cond, steps: int = 50, rescale_t: float = 3.0,
19
- cfg_strength: float = 3.0, verbose: bool = True,
20
- callback: Optional[Callable[[int, int], None]] = None):
21
- """Integrate from noise (t = 1) to data (t = 0) in ``steps`` Euler steps.
22
 
23
- rescale_t shifts the time grid towards t = 1 (more steps at high noise).
24
- cfg_strength: guided velocity = (1 + w) * v(cond) - w * v(neg_cond).
25
- callback(step_done, steps) is called after every step."""
26
- sample = noise
27
- t_seq = np.linspace(1, 0, steps + 1)
28
- t_seq = rescale_t * t_seq / (1 + (rescale_t - 1) * t_seq)
29
- for i in tqdm(range(steps), desc='Sampling', disable=not verbose):
30
- t, t_prev = t_seq[i], t_seq[i + 1]
31
- velocity = self._velocity(model, sample, t, cond)
32
- if cfg_strength > 0:
33
- negative_velocity = self._velocity(model, sample, t, neg_cond)
34
- velocity = (1 + cfg_strength) * velocity - cfg_strength * negative_velocity
35
- sample = sample - velocity * (t - t_prev)
36
- if callback is not None:
37
- callback(i + 1, steps)
38
- return sample
 
1
  """Euler sampler for the rectified-flow denoiser, with classifier-free guidance."""
 
 
2
  import numpy as np
3
  import torch
4
  from tqdm import tqdm
5
 
6
 
7
+ @torch.no_grad()
8
+ def sample_flow(model, noise, cond, neg_cond, steps: int = 50, rescale_t: float = 3.0, cfg_strength: float = 3.0,
9
+ verbose: bool = True):
10
+ """Integrate from noise (t = 1) to data (t = 0) in ``steps`` Euler steps.
11
 
12
+ rescale_t shifts the time grid towards t = 1 (more steps at high noise).
13
+ cfg_strength: guided velocity = (1 + w) * v(cond) - w * v(neg_cond)."""
 
14
 
15
+ def velocity(sample, t, condition):
16
+ return model(sample, torch.tensor([1000 * t] * sample.shape[0], device=sample.device, dtype=torch.float32),
17
+ condition)
 
 
18
 
19
+ sample = noise
20
+ t_seq = np.linspace(1, 0, steps + 1)
21
+ t_seq = rescale_t * t_seq / (1 + (rescale_t - 1) * t_seq)
22
+ for i in tqdm(range(steps), desc='Sampling', disable=not verbose):
23
+ t, t_prev = t_seq[i], t_seq[i + 1]
24
+ v = velocity(sample, t, cond)
25
+ if cfg_strength > 0:
26
+ v = (1 + cfg_strength) * v - cfg_strength * velocity(sample, t, neg_cond)
27
+ sample = sample - v * (t - t_prev)
28
+ return sample
 
 
 
 
 
 
idol/sampling_utils.py DELETED
@@ -1,150 +0,0 @@
1
- import numpy as np
2
- import torch
3
- from typing import *
4
-
5
- PRIMES = [2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53]
6
-
7
- def radical_inverse(base, n):
8
- val = 0
9
- inv_base = 1.0 / base
10
- inv_base_n = inv_base
11
- while n > 0:
12
- digit = n % base
13
- val += digit * inv_base_n
14
- n //= base
15
- inv_base_n *= inv_base
16
- return val
17
-
18
- def halton_sequence(dim, n):
19
- return [radical_inverse(PRIMES[dim], n) for dim in range(dim)]
20
-
21
- def hammersley_sequence(dim, n, num_samples):
22
- return [n / num_samples] + halton_sequence(dim - 1, n)
23
-
24
- @torch.no_grad()
25
- def sample_probs(
26
- probs: torch.Tensor,
27
- counts: torch.Tensor,
28
- algo: Literal["iid", "residual", "systematic"] = "systematic",
29
- group_rows: bool = False,
30
- ) -> torch.Tensor:
31
- """
32
- probs : [..., P] nonnegative; rows need not be normalized
33
- counts : [...] nonnegative ints
34
- returns: [..., P] long tensor of counts per bin
35
- handle properly for conunts = 0 case
36
- """
37
- batch_shape = counts.shape
38
- B = counts.numel()
39
- P = probs.size(-1)
40
- assert B * P == probs.numel() and probs.dim() - counts.dim() == 1
41
- device = probs.device
42
- probs = probs.view(B, P)
43
- counts = counts.view(B)
44
-
45
- # Normalize with uniform fallback for zero-sum rows
46
- probs = probs.to(torch.float32).clamp_min_(0)
47
- row_sums = probs.sum(1, keepdim=True)
48
- zero_mask = row_sums.eq(0)
49
- probs = probs / row_sums.clamp_min_(1)
50
- if zero_mask.any():
51
- probs = probs.clone()
52
- probs[zero_mask.expand_as(probs)] = 1.0 / P
53
-
54
- counts = counts.to(device=device, dtype=torch.long)
55
- out = torch.zeros(B, P, dtype=torch.long, device=device)
56
-
57
- if algo == "iid":
58
- # Pure multinomial IID, batched by unique k
59
- unique_k, inv = counts.unique(sorted=False, return_inverse=True)
60
- for i, k in enumerate(unique_k.tolist()):
61
- if k == 0:
62
- continue
63
- rows = (inv == i).nonzero(as_tuple=False).squeeze(1) # indices for this k
64
- samples = torch.multinomial(
65
- probs.index_select(0, rows), num_samples=int(k), replacement=True
66
- )
67
- buf = torch.zeros(rows.numel(), P, dtype=torch.float32, device=device)
68
- buf.scatter_add_(1, samples, torch.ones_like(samples, dtype=buf.dtype))
69
- out.index_copy_(0, rows, buf.to(torch.long))
70
-
71
- elif algo in ("residual", "owen_scrambled_residual"):
72
- # Step 1: deterministic floor allocation (vectorized)
73
- expected = probs * counts.unsqueeze(1).to(torch.float32) # [B, P]
74
- base = torch.floor(expected) # [B, P]
75
- out += base.to(torch.long)
76
-
77
- # Step 2: compute residuals
78
- residual_mass = expected - base # [B, P]
79
- m = counts - base.sum(1).to(torch.long) # [B]
80
-
81
- # Guard: skip rows with no residuals
82
- active_mask = m > 0
83
- if active_mask.any():
84
- rows = active_mask.nonzero(as_tuple=False).squeeze(1)
85
- m_active = m[rows]
86
- res_probs = residual_mass[rows]
87
- res_probs /= res_probs.sum(1, keepdim=True) # normalize residuals
88
-
89
- if algo == "residual":
90
- # --- Batched multinomial ---
91
- unique_m, invm = m_active.unique(sorted=False, return_inverse=True)
92
- for j, mm in enumerate(unique_m.tolist()):
93
- rows_m = (invm == j).nonzero(as_tuple=False).squeeze(1) # indices for this m
94
- rsel = rows[rows_m]
95
- if mm == 0:
96
- continue
97
- samples = torch.multinomial(
98
- res_probs.index_select(0, rows_m),
99
- num_samples=int(mm),
100
- replacement=True,
101
- )
102
- buf = torch.zeros(rows_m.numel(), P, dtype=torch.float32, device=device)
103
- buf.scatter_add_(1, samples, torch.ones_like(samples, dtype=buf.dtype))
104
- out.index_copy_(0, rsel, out[rsel] + buf.to(torch.long))
105
- else:
106
- raise NotImplementedError("Owen-scrambled residual not implemented yet")
107
- assert out.sum() == counts.sum(), f"sampling count mismatch, input probs max: {probs.max().item()}, counts: {counts.sum().item()}, output sum: {out.sum().item()}"
108
-
109
- elif algo == "systematic":
110
- cdf = probs.cumsum(dim=1).clamp(max=1.0 - 1e-12)
111
- if group_rows:
112
- # Stable grouping retains both ascending count order (torch.unique's
113
- # existing order) and original row order within each group. Therefore
114
- # torch.rand sees exactly the same sequence of call shapes and rows.
115
- # One metadata transfer replaces one synchronizing nonzero per group.
116
- sorted_counts, row_order = torch.sort(counts, stable=True)
117
- unique_n, group_sizes = torch.unique_consecutive(sorted_counts, return_counts=True)
118
- group_metadata = torch.stack((unique_n, group_sizes), dim=1).tolist()
119
- row_ranges = []
120
- row_offset = 0
121
- for count, group_size in group_metadata:
122
- row_ranges.append((count, row_order[row_offset:row_offset + group_size]))
123
- row_offset += group_size
124
- else:
125
- unique_n, inv = counts.unique(sorted=False, return_inverse=True)
126
- row_ranges = ((count, (inv == index).nonzero(as_tuple=False).squeeze(1))
127
- for index, count in enumerate(unique_n.tolist()) if count != 0)
128
- for n, rows in row_ranges:
129
- if n == 0:
130
- continue
131
- r = rows.numel()
132
-
133
- # random offset per row in [0,1/n)
134
- U0 = torch.rand(r, 1, device=device) / float(n) # [r,1]
135
- # stratified points
136
- grid = torch.arange(n, device=device, dtype=torch.float32)[None, :] / float(n) # [1,n]
137
- us = (U0 + grid).clamp(max=1.0 - 1e-12) # [r,n]
138
-
139
- cdf_rows = cdf.index_select(0, rows) # [r,P]
140
- idx = torch.searchsorted(cdf_rows, us).clamp_max(probs.size(1) - 1) # [r,n]
141
-
142
- buf = torch.zeros(r, P, dtype=torch.float32, device=device)
143
- buf.scatter_add_(1, idx, torch.ones_like(idx, dtype=buf.dtype))
144
- out.index_copy_(0, rows, buf.to(torch.long))
145
-
146
- else:
147
- raise ValueError(f"Unknown algo {algo}")
148
-
149
- out = out.view(*batch_shape, P)
150
- return out