ffgs 0.1.0.dev0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (63) hide show
  1. ffgs/__init__.py +39 -0
  2. ffgs/dynamic.py +239 -0
  3. ffgs/geometry.py +418 -0
  4. ffgs/image.py +204 -0
  5. ffgs/models/__init__.py +2 -0
  6. ffgs/models/anysplat/LICENSE +1120 -0
  7. ffgs/models/anysplat/__init__.py +49 -0
  8. ffgs/models/anysplat/encoder.py +372 -0
  9. ffgs/models/anysplat/evaluation.py +98 -0
  10. ffgs/models/anysplat/gaussian_adapter.py +96 -0
  11. ffgs/models/anysplat/gs_head.py +166 -0
  12. ffgs/models/anysplat/modeling.py +95 -0
  13. ffgs/models/anysplat/processing.py +249 -0
  14. ffgs/models/anysplat/vggt/__init__.py +0 -0
  15. ffgs/models/anysplat/vggt/heads/__init__.py +0 -0
  16. ffgs/models/anysplat/vggt/heads/camera_head.py +181 -0
  17. ffgs/models/anysplat/vggt/heads/dpt_head.py +612 -0
  18. ffgs/models/anysplat/vggt/heads/head_act.py +134 -0
  19. ffgs/models/anysplat/vggt/heads/utils.py +126 -0
  20. ffgs/models/anysplat/vggt/layers/__init__.py +28 -0
  21. ffgs/models/anysplat/vggt/layers/attention.py +92 -0
  22. ffgs/models/anysplat/vggt/layers/block.py +157 -0
  23. ffgs/models/anysplat/vggt/layers/drop_path.py +42 -0
  24. ffgs/models/anysplat/vggt/layers/layer_scale.py +32 -0
  25. ffgs/models/anysplat/vggt/layers/mlp.py +45 -0
  26. ffgs/models/anysplat/vggt/layers/patch_embed.py +106 -0
  27. ffgs/models/anysplat/vggt/layers/rope.py +211 -0
  28. ffgs/models/anysplat/vggt/layers/swiglu_ffn.py +77 -0
  29. ffgs/models/anysplat/vggt/layers/vision_transformer.py +463 -0
  30. ffgs/models/anysplat/vggt/models/__init__.py +0 -0
  31. ffgs/models/anysplat/vggt/models/aggregator.py +398 -0
  32. ffgs/models/anysplat/vggt/utils/__init__.py +0 -0
  33. ffgs/models/anysplat/vggt/utils/geometry.py +230 -0
  34. ffgs/models/anysplat/vggt/utils/pose_enc.py +144 -0
  35. ffgs/models/anysplat/vggt/utils/rotation.py +148 -0
  36. ffgs/models/tokengs/__init__.py +43 -0
  37. ffgs/models/tokengs/activations.py +135 -0
  38. ffgs/models/tokengs/attention.py +189 -0
  39. ffgs/models/tokengs/enc_dec.py +323 -0
  40. ffgs/models/tokengs/modeling.py +173 -0
  41. ffgs/models/tokengs/processing.py +339 -0
  42. ffgs/pipeline.py +341 -0
  43. ffgs/processor.py +236 -0
  44. ffgs/registry.py +132 -0
  45. ffgs/render.py +111 -0
  46. ffgs/types.py +291 -0
  47. ffgs/zoo/__init__.py +234 -0
  48. ffgs/zoo/anysplat/default.json +34 -0
  49. ffgs/zoo/tokengs/dl3dv-2v.json +29 -0
  50. ffgs/zoo/tokengs/dl3dv-4v.json +29 -0
  51. ffgs/zoo/tokengs/dl3dv-6v.json +29 -0
  52. ffgs/zoo/tokengs/dl3dv-base.json +29 -0
  53. ffgs/zoo/tokengs/dl3dv-latent-2v-lpips.json +48 -0
  54. ffgs/zoo/tokengs/dl3dv-latent-2v-ssim.json +48 -0
  55. ffgs/zoo/tokengs/dl3dv-latent-4v-lpips.json +48 -0
  56. ffgs/zoo/tokengs/dl3dv-latent-4v-ssim.json +48 -0
  57. ffgs/zoo/tokengs/dl3dv-latent-6v-lpips.json +48 -0
  58. ffgs/zoo/tokengs/dl3dv-latent-6v-ssim.json +48 -0
  59. ffgs-0.1.0.dev0.dist-info/METADATA +320 -0
  60. ffgs-0.1.0.dev0.dist-info/RECORD +63 -0
  61. ffgs-0.1.0.dev0.dist-info/WHEEL +4 -0
  62. ffgs-0.1.0.dev0.dist-info/licenses/LICENSE +237 -0
  63. ffgs-0.1.0.dev0.dist-info/licenses/src/ffgs/models/anysplat/LICENSE +1120 -0
@@ -0,0 +1,612 @@
1
+ # Copyright (c) Meta Platforms, Inc. and affiliates.
2
+ # All rights reserved.
3
+ #
4
+ # This source code is licensed under the license found in the
5
+ # LICENSE file in the root directory of this source tree.
6
+ #
7
+ # Modified by the libgs contributors, 2026, from
8
+ # src/model/encoder/vggt/heads/dpt_head.py of https://github.com/InternRobotics/AnySplat
9
+ # (5f5e208): formatted with black, unused imports removed.
10
+ # "The license found in the LICENSE file" above is the one VGGT's code
11
+ # had when AnySplat vendored it (2025-06-30): CC BY-NC 4.0, non-commercial
12
+ # (facebookresearch/vggt c6bf698); see LICENSE (this directory).
13
+
14
+
15
+ # Inspired by https://github.com/DepthAnything/Depth-Anything-V2
16
+
17
+
18
+ from typing import List, Tuple, Union
19
+
20
+ import torch
21
+ import torch.nn as nn
22
+
23
+ from .head_act import activate_head
24
+ from .utils import create_uv_grid, position_grid_to_embed
25
+
26
+
27
+ class DPTHead(nn.Module):
28
+ """
29
+ DPT Head for dense prediction tasks.
30
+
31
+ This implementation follows the architecture described in "Vision Transformers for Dense Prediction"
32
+ (https://arxiv.org/abs/2103.13413). The DPT head processes features from a vision transformer
33
+ backbone and produces dense predictions by fusing multi-scale features.
34
+
35
+ Args:
36
+ dim_in (int): Input dimension (channels).
37
+ patch_size (int, optional): Patch size. Default is 14.
38
+ output_dim (int, optional): Number of output channels. Default is 4.
39
+ activation (str, optional): Activation type. Default is "inv_log".
40
+ conf_activation (str, optional): Confidence activation type. Default is "expp1".
41
+ features (int, optional): Feature channels for intermediate representations. Default is 256.
42
+ out_channels (List[int], optional): Output channels for each intermediate layer.
43
+ intermediate_layer_idx (List[int], optional): Indices of layers from aggregated tokens used for DPT.
44
+ pos_embed (bool, optional): Whether to use positional embedding. Default is True.
45
+ feature_only (bool, optional): If True, return features only without the last several layers and activation head. Default is False.
46
+ down_ratio (int, optional): Downscaling factor for the output resolution. Default is 1.
47
+ """
48
+
49
+ def __init__(
50
+ self,
51
+ dim_in: int,
52
+ patch_size: int = 14,
53
+ output_dim: int = 4,
54
+ activation: str = "inv_log",
55
+ conf_activation: str = "expp1",
56
+ features: int = 256,
57
+ out_channels: List[int] = [256, 512, 1024, 1024],
58
+ intermediate_layer_idx: List[int] = [4, 11, 17, 23],
59
+ pos_embed: bool = True,
60
+ feature_only: bool = False,
61
+ down_ratio: int = 1,
62
+ ) -> None:
63
+ super(DPTHead, self).__init__()
64
+ self.patch_size = patch_size
65
+ self.activation = activation
66
+ self.conf_activation = conf_activation
67
+ self.pos_embed = pos_embed
68
+ self.feature_only = feature_only
69
+ self.down_ratio = down_ratio
70
+ self.intermediate_layer_idx = intermediate_layer_idx
71
+
72
+ self.norm = nn.LayerNorm(dim_in)
73
+
74
+ # Projection layers for each output channel from tokens.
75
+ self.projects = nn.ModuleList(
76
+ [
77
+ nn.Conv2d(
78
+ in_channels=dim_in,
79
+ out_channels=oc,
80
+ kernel_size=1,
81
+ stride=1,
82
+ padding=0,
83
+ )
84
+ for oc in out_channels
85
+ ]
86
+ )
87
+
88
+ # Resize layers for upsampling feature maps.
89
+ self.resize_layers = nn.ModuleList(
90
+ [
91
+ nn.ConvTranspose2d(
92
+ in_channels=out_channels[0],
93
+ out_channels=out_channels[0],
94
+ kernel_size=4,
95
+ stride=4,
96
+ padding=0,
97
+ ),
98
+ nn.ConvTranspose2d(
99
+ in_channels=out_channels[1],
100
+ out_channels=out_channels[1],
101
+ kernel_size=2,
102
+ stride=2,
103
+ padding=0,
104
+ ),
105
+ nn.Identity(),
106
+ nn.Conv2d(
107
+ in_channels=out_channels[3],
108
+ out_channels=out_channels[3],
109
+ kernel_size=3,
110
+ stride=2,
111
+ padding=1,
112
+ ),
113
+ ]
114
+ )
115
+
116
+ self.scratch = _make_scratch(
117
+ out_channels,
118
+ features,
119
+ expand=False,
120
+ )
121
+
122
+ # Attach additional modules to scratch.
123
+ self.scratch.stem_transpose = None
124
+ self.scratch.refinenet1 = _make_fusion_block(features)
125
+ self.scratch.refinenet2 = _make_fusion_block(features)
126
+ self.scratch.refinenet3 = _make_fusion_block(features)
127
+ self.scratch.refinenet4 = _make_fusion_block(features, has_residual=False)
128
+
129
+ head_features_1 = features
130
+ head_features_2 = 32
131
+
132
+ if feature_only:
133
+ self.scratch.output_conv1 = nn.Conv2d(
134
+ head_features_1, head_features_1, kernel_size=3, stride=1, padding=1
135
+ )
136
+ else:
137
+ self.scratch.output_conv1 = nn.Conv2d(
138
+ head_features_1,
139
+ head_features_1 // 2,
140
+ kernel_size=3,
141
+ stride=1,
142
+ padding=1,
143
+ )
144
+ conv2_in_channels = head_features_1 // 2
145
+
146
+ self.scratch.output_conv2 = nn.Sequential(
147
+ nn.Conv2d(
148
+ conv2_in_channels,
149
+ head_features_2,
150
+ kernel_size=3,
151
+ stride=1,
152
+ padding=1,
153
+ ),
154
+ nn.ReLU(inplace=True),
155
+ nn.Conv2d(
156
+ head_features_2, output_dim, kernel_size=1, stride=1, padding=0
157
+ ),
158
+ )
159
+
160
+ def forward(
161
+ self,
162
+ aggregated_tokens_list: List[torch.Tensor],
163
+ images: torch.Tensor,
164
+ patch_start_idx: int,
165
+ frames_chunk_size: int = 8,
166
+ ) -> Union[torch.Tensor, Tuple[torch.Tensor, torch.Tensor]]:
167
+ """
168
+ Forward pass through the DPT head, supports processing by chunking frames.
169
+ Args:
170
+ aggregated_tokens_list (List[Tensor]): List of token tensors from different transformer layers.
171
+ images (Tensor): Input images with shape [B, S, 3, H, W], in range [0, 1].
172
+ patch_start_idx (int): Starting index for patch tokens in the token sequence.
173
+ Used to separate patch tokens from other tokens (e.g., camera or register tokens).
174
+ frames_chunk_size (int, optional): Number of frames to process in each chunk.
175
+ If None or larger than S, all frames are processed at once. Default: 8.
176
+
177
+ Returns:
178
+ Tensor or Tuple[Tensor, Tensor]:
179
+ - If feature_only=True: Feature maps with shape [B, S, C, H, W]
180
+ - Otherwise: Tuple of (predictions, confidence) both with shape [B, S, 1, H, W]
181
+ """
182
+ B, S, _, H, W = images.shape
183
+
184
+ # If frames_chunk_size is not specified or greater than S, process all frames at once
185
+ if frames_chunk_size is None or frames_chunk_size >= S:
186
+ return self._forward_impl(aggregated_tokens_list, images, patch_start_idx)
187
+
188
+ # Otherwise, process frames in chunks to manage memory usage
189
+ assert frames_chunk_size > 0
190
+
191
+ # Process frames in batches
192
+ all_preds = []
193
+ all_conf = []
194
+
195
+ for frames_start_idx in range(0, S, frames_chunk_size):
196
+ frames_end_idx = min(frames_start_idx + frames_chunk_size, S)
197
+
198
+ # Process batch of frames
199
+ if self.feature_only:
200
+ chunk_output = self._forward_impl(
201
+ aggregated_tokens_list,
202
+ images,
203
+ patch_start_idx,
204
+ frames_start_idx,
205
+ frames_end_idx,
206
+ )
207
+ all_preds.append(chunk_output)
208
+ else:
209
+ chunk_preds, chunk_conf = self._forward_impl(
210
+ aggregated_tokens_list,
211
+ images,
212
+ patch_start_idx,
213
+ frames_start_idx,
214
+ frames_end_idx,
215
+ )
216
+ all_preds.append(chunk_preds)
217
+ all_conf.append(chunk_conf)
218
+
219
+ # Concatenate results along the sequence dimension
220
+ if self.feature_only:
221
+ return torch.cat(all_preds, dim=1)
222
+ else:
223
+ return torch.cat(all_preds, dim=1), torch.cat(all_conf, dim=1)
224
+
225
+ def _forward_impl(
226
+ self,
227
+ aggregated_tokens_list: List[torch.Tensor],
228
+ images: torch.Tensor,
229
+ patch_start_idx: int,
230
+ frames_start_idx: int = None,
231
+ frames_end_idx: int = None,
232
+ ) -> Union[torch.Tensor, Tuple[torch.Tensor, torch.Tensor]]:
233
+ """
234
+ Implementation of the forward pass through the DPT head.
235
+
236
+ This method processes a specific chunk of frames from the sequence.
237
+
238
+ Args:
239
+ aggregated_tokens_list (List[Tensor]): List of token tensors from different transformer layers.
240
+ images (Tensor): Input images with shape [B, S, 3, H, W].
241
+ patch_start_idx (int): Starting index for patch tokens.
242
+ frames_start_idx (int, optional): Starting index for frames to process.
243
+ frames_end_idx (int, optional): Ending index for frames to process.
244
+
245
+ Returns:
246
+ Tensor or Tuple[Tensor, Tensor]: Feature maps or (predictions, confidence).
247
+ """
248
+ if frames_start_idx is not None and frames_end_idx is not None:
249
+ images = images[:, frames_start_idx:frames_end_idx]
250
+
251
+ B, S, _, H, W = images.shape
252
+
253
+ patch_h, patch_w = H // self.patch_size, W // self.patch_size
254
+
255
+ out = []
256
+ dpt_idx = 0
257
+
258
+ for layer_idx in self.intermediate_layer_idx:
259
+ # x = aggregated_tokens_list[layer_idx][:, :, patch_start_idx:]
260
+ if len(aggregated_tokens_list) > 10:
261
+ x = aggregated_tokens_list[layer_idx][:, :, patch_start_idx:]
262
+ else:
263
+ list_idx = self.intermediate_layer_idx.index(layer_idx)
264
+ x = aggregated_tokens_list[list_idx][:, :, patch_start_idx:]
265
+
266
+ # Select frames if processing a chunk
267
+ if frames_start_idx is not None and frames_end_idx is not None:
268
+ x = x[:, frames_start_idx:frames_end_idx].contiguous()
269
+
270
+ x = x.view(B * S, -1, x.shape[-1])
271
+ x = self.norm(x)
272
+
273
+ x = x.permute(0, 2, 1).reshape((x.shape[0], x.shape[-1], patch_h, patch_w))
274
+
275
+ x = self.projects[dpt_idx](x)
276
+ if self.pos_embed:
277
+ x = self._apply_pos_embed(x, W, H)
278
+
279
+ x = self.resize_layers[dpt_idx](x)
280
+
281
+ out.append(x)
282
+ dpt_idx += 1
283
+
284
+ # Fuse features from multiple layers.
285
+ out = self.scratch_forward(out)
286
+ # Interpolate fused output to match target image resolution.
287
+ out = custom_interpolate(
288
+ out,
289
+ (
290
+ int(patch_h * self.patch_size / self.down_ratio),
291
+ int(patch_w * self.patch_size / self.down_ratio),
292
+ ),
293
+ mode="bilinear",
294
+ align_corners=True,
295
+ )
296
+
297
+ if self.pos_embed:
298
+ out = self._apply_pos_embed(out, W, H)
299
+
300
+ if self.feature_only:
301
+ return out.view(B, S, *out.shape[1:])
302
+
303
+ out = self.scratch.output_conv2(out)
304
+ preds, conf = activate_head(
305
+ out, activation=self.activation, conf_activation=self.conf_activation
306
+ )
307
+
308
+ preds = preds.view(B, S, *preds.shape[1:])
309
+ conf = conf.view(B, S, *conf.shape[1:])
310
+ return preds, conf
311
+
312
+ def _apply_pos_embed(
313
+ self, x: torch.Tensor, W: int, H: int, ratio: float = 0.1
314
+ ) -> torch.Tensor:
315
+ """
316
+ Apply positional embedding to tensor x.
317
+ """
318
+ patch_w = x.shape[-1]
319
+ patch_h = x.shape[-2]
320
+ pos_embed = create_uv_grid(
321
+ patch_w, patch_h, aspect_ratio=W / H, dtype=x.dtype, device=x.device
322
+ )
323
+ pos_embed = position_grid_to_embed(pos_embed, x.shape[1])
324
+ pos_embed = pos_embed * ratio
325
+ pos_embed = pos_embed.permute(2, 0, 1)[None].expand(x.shape[0], -1, -1, -1)
326
+ return x + pos_embed
327
+
328
+ def scratch_forward(self, features: List[torch.Tensor]) -> torch.Tensor:
329
+ """
330
+ Forward pass through the fusion blocks.
331
+
332
+ Args:
333
+ features (List[Tensor]): List of feature maps from different layers.
334
+
335
+ Returns:
336
+ Tensor: Fused feature map.
337
+ """
338
+ layer_1, layer_2, layer_3, layer_4 = features
339
+
340
+ layer_1_rn = self.scratch.layer1_rn(layer_1)
341
+ layer_2_rn = self.scratch.layer2_rn(layer_2)
342
+ layer_3_rn = self.scratch.layer3_rn(layer_3)
343
+ layer_4_rn = self.scratch.layer4_rn(layer_4)
344
+
345
+ out = self.scratch.refinenet4(layer_4_rn, size=layer_3_rn.shape[2:])
346
+ del layer_4_rn, layer_4
347
+
348
+ out = self.scratch.refinenet3(out, layer_3_rn, size=layer_2_rn.shape[2:])
349
+ del layer_3_rn, layer_3
350
+
351
+ out = self.scratch.refinenet2(out, layer_2_rn, size=layer_1_rn.shape[2:])
352
+ del layer_2_rn, layer_2
353
+
354
+ out = self.scratch.refinenet1(out, layer_1_rn)
355
+ del layer_1_rn, layer_1
356
+
357
+ out = self.scratch.output_conv1(out)
358
+ return out
359
+
360
+
361
+ ################################################################################
362
+ # Modules
363
+ ################################################################################
364
+
365
+
366
+ def _make_fusion_block(
367
+ features: int, size: int = None, has_residual: bool = True, groups: int = 1
368
+ ) -> nn.Module:
369
+ return FeatureFusionBlock(
370
+ features,
371
+ nn.ReLU(inplace=True),
372
+ deconv=False,
373
+ bn=False,
374
+ expand=False,
375
+ align_corners=True,
376
+ size=size,
377
+ has_residual=has_residual,
378
+ groups=groups,
379
+ )
380
+
381
+
382
+ def _make_scratch(
383
+ in_shape: List[int], out_shape: int, groups: int = 1, expand: bool = False
384
+ ) -> nn.Module:
385
+ scratch = nn.Module()
386
+ out_shape1 = out_shape
387
+ out_shape2 = out_shape
388
+ out_shape3 = out_shape
389
+ if len(in_shape) >= 4:
390
+ out_shape4 = out_shape
391
+
392
+ if expand:
393
+ out_shape1 = out_shape
394
+ out_shape2 = out_shape * 2
395
+ out_shape3 = out_shape * 4
396
+ if len(in_shape) >= 4:
397
+ out_shape4 = out_shape * 8
398
+
399
+ scratch.layer1_rn = nn.Conv2d(
400
+ in_shape[0],
401
+ out_shape1,
402
+ kernel_size=3,
403
+ stride=1,
404
+ padding=1,
405
+ bias=False,
406
+ groups=groups,
407
+ )
408
+ scratch.layer2_rn = nn.Conv2d(
409
+ in_shape[1],
410
+ out_shape2,
411
+ kernel_size=3,
412
+ stride=1,
413
+ padding=1,
414
+ bias=False,
415
+ groups=groups,
416
+ )
417
+ scratch.layer3_rn = nn.Conv2d(
418
+ in_shape[2],
419
+ out_shape3,
420
+ kernel_size=3,
421
+ stride=1,
422
+ padding=1,
423
+ bias=False,
424
+ groups=groups,
425
+ )
426
+ if len(in_shape) >= 4:
427
+ scratch.layer4_rn = nn.Conv2d(
428
+ in_shape[3],
429
+ out_shape4,
430
+ kernel_size=3,
431
+ stride=1,
432
+ padding=1,
433
+ bias=False,
434
+ groups=groups,
435
+ )
436
+ return scratch
437
+
438
+
439
+ class ResidualConvUnit(nn.Module):
440
+ """Residual convolution module."""
441
+
442
+ def __init__(self, features, activation, bn, groups=1):
443
+ """Init.
444
+
445
+ Args:
446
+ features (int): number of features
447
+ """
448
+ super().__init__()
449
+
450
+ self.bn = bn
451
+ self.groups = groups
452
+ self.conv1 = nn.Conv2d(
453
+ features,
454
+ features,
455
+ kernel_size=3,
456
+ stride=1,
457
+ padding=1,
458
+ bias=True,
459
+ groups=self.groups,
460
+ )
461
+ self.conv2 = nn.Conv2d(
462
+ features,
463
+ features,
464
+ kernel_size=3,
465
+ stride=1,
466
+ padding=1,
467
+ bias=True,
468
+ groups=self.groups,
469
+ )
470
+
471
+ self.norm1 = None
472
+ self.norm2 = None
473
+
474
+ self.activation = activation
475
+ self.skip_add = nn.quantized.FloatFunctional()
476
+
477
+ def forward(self, x):
478
+ """Forward pass.
479
+
480
+ Args:
481
+ x (tensor): input
482
+
483
+ Returns:
484
+ tensor: output
485
+ """
486
+
487
+ out = self.activation(x)
488
+ out = self.conv1(out)
489
+ if self.norm1 is not None:
490
+ out = self.norm1(out)
491
+
492
+ out = self.activation(out)
493
+ out = self.conv2(out)
494
+ if self.norm2 is not None:
495
+ out = self.norm2(out)
496
+
497
+ return self.skip_add.add(out, x)
498
+
499
+
500
+ class FeatureFusionBlock(nn.Module):
501
+ """Feature fusion block."""
502
+
503
+ def __init__(
504
+ self,
505
+ features,
506
+ activation,
507
+ deconv=False,
508
+ bn=False,
509
+ expand=False,
510
+ align_corners=True,
511
+ size=None,
512
+ has_residual=True,
513
+ groups=1,
514
+ ):
515
+ """Init.
516
+
517
+ Args:
518
+ features (int): number of features
519
+ """
520
+ super(FeatureFusionBlock, self).__init__()
521
+
522
+ self.deconv = deconv
523
+ self.align_corners = align_corners
524
+ self.groups = groups
525
+ self.expand = expand
526
+ out_features = features
527
+ if self.expand == True: # noqa: E712 (upstream)
528
+ out_features = features // 2
529
+
530
+ self.out_conv = nn.Conv2d(
531
+ features,
532
+ out_features,
533
+ kernel_size=1,
534
+ stride=1,
535
+ padding=0,
536
+ bias=True,
537
+ groups=self.groups,
538
+ )
539
+
540
+ if has_residual:
541
+ self.resConfUnit1 = ResidualConvUnit(
542
+ features, activation, bn, groups=self.groups
543
+ )
544
+
545
+ self.has_residual = has_residual
546
+ self.resConfUnit2 = ResidualConvUnit(
547
+ features, activation, bn, groups=self.groups
548
+ )
549
+
550
+ self.skip_add = nn.quantized.FloatFunctional()
551
+ self.size = size
552
+
553
+ def forward(self, *xs, size=None):
554
+ """Forward pass.
555
+
556
+ Returns:
557
+ tensor: output
558
+ """
559
+ output = xs[0]
560
+
561
+ if self.has_residual:
562
+ res = self.resConfUnit1(xs[1])
563
+ output = self.skip_add.add(output, res)
564
+
565
+ output = self.resConfUnit2(output)
566
+
567
+ if (size is None) and (self.size is None):
568
+ modifier = {"scale_factor": 2}
569
+ elif size is None:
570
+ modifier = {"size": self.size}
571
+ else:
572
+ modifier = {"size": size}
573
+
574
+ output = custom_interpolate(
575
+ output, **modifier, mode="bilinear", align_corners=self.align_corners
576
+ )
577
+ output = self.out_conv(output)
578
+
579
+ return output
580
+
581
+
582
+ def custom_interpolate(
583
+ x: torch.Tensor,
584
+ size: Tuple[int, int] = None,
585
+ scale_factor: float = None,
586
+ mode: str = "bilinear",
587
+ align_corners: bool = True,
588
+ ) -> torch.Tensor:
589
+ """
590
+ Custom interpolate to avoid INT_MAX issues in nn.functional.interpolate.
591
+ """
592
+ if size is None:
593
+ size = (int(x.shape[-2] * scale_factor), int(x.shape[-1] * scale_factor))
594
+
595
+ INT_MAX = 1610612736
596
+
597
+ input_elements = size[0] * size[1] * x.shape[0] * x.shape[1]
598
+
599
+ if input_elements > INT_MAX:
600
+ chunks = torch.chunk(x, chunks=(input_elements // INT_MAX) + 1, dim=0)
601
+ interpolated_chunks = [
602
+ nn.functional.interpolate(
603
+ chunk, size=size, mode=mode, align_corners=align_corners
604
+ )
605
+ for chunk in chunks
606
+ ]
607
+ x = torch.cat(interpolated_chunks, dim=0)
608
+ return x.contiguous()
609
+ else:
610
+ return nn.functional.interpolate(
611
+ x, size=size, mode=mode, align_corners=align_corners
612
+ )