refactor(modeling): self-consistent block/function names; drop unused attrs

#2
by gkalstn0 - opened
Files changed (1) hide show
  1. modeling_motifvae.py +45 -54
modeling_motifvae.py CHANGED
@@ -33,28 +33,28 @@ from diffusers.utils import BaseOutput
33
  # ---------------------------------------------------------------------------
34
  #
35
  # The config encodes residual / mid block choices as strings (e.g.
36
- # ``"ResnetBlock2D"``). ``resolve_str_to_obj`` maps those strings to the
37
  # classes defined in this module. ``"Identity"`` maps to ``nn.Identity`` so a
38
  # mid stage can be made attention-free while keeping the surrounding key
39
  # layout intact.
40
 
41
- _STR_TO_OBJ_OVERRIDES = {"Identity": nn.Identity}
42
 
43
 
44
- def resolve_str_to_obj(name: str):
45
  """Resolve a block-type string from the config to its class."""
46
- if name in _STR_TO_OBJ_OVERRIDES:
47
- return _STR_TO_OBJ_OVERRIDES[name]
48
  try:
49
  return globals()[name]
50
  except KeyError as exc:
51
  raise AttributeError(
52
- f"resolve_str_to_obj: unknown class name {name!r} "
53
  f"(not defined in this module)"
54
  ) from exc
55
 
56
 
57
- class VideoBaseAE(ModelMixin, ConfigMixin):
58
  """Base class wiring diffusers' ``ModelMixin`` + ``ConfigMixin`` together."""
59
 
60
  config_name = "config.json"
@@ -68,7 +68,7 @@ class VideoBaseAE(ModelMixin, ConfigMixin):
68
  # ---------------------------------------------------------------------------
69
 
70
 
71
- def video_to_image(func):
72
  """Wrap a 2D forward so it also accepts 5D ``(B, C, T, H, W)`` tensors.
73
 
74
  Frames are folded into the batch dimension, processed independently, then
@@ -88,12 +88,12 @@ def video_to_image(func):
88
  return wrapper
89
 
90
 
91
- def nonlinearity(x: torch.Tensor) -> torch.Tensor:
92
  """SiLU / swish activation used throughout the network."""
93
  return x * torch.sigmoid(x)
94
 
95
 
96
- def cast_tuple(value, length: int = 1):
97
  """Broadcast a scalar to a length-``length`` tuple; pass tuples through."""
98
  return value if isinstance(value, (tuple, list)) else ((value,) * length)
99
 
@@ -134,7 +134,7 @@ class Conv2d(nn.Conv2d):
134
  dtype,
135
  )
136
 
137
- @video_to_image
138
  def forward(self, x):
139
  return super().forward(x)
140
 
@@ -156,12 +156,12 @@ class CausalConv3d(nn.Module):
156
  **kwargs,
157
  ) -> None:
158
  super().__init__()
159
- self.kernel_size = cast_tuple(kernel_size, 3)
160
  self.time_kernel_size = self.kernel_size[0]
161
  self.chan_in = chan_in
162
  self.chan_out = chan_out
163
- stride = cast_tuple(kwargs.pop("stride", 1), 3)
164
- padding = list(cast_tuple(kwargs.pop("padding", 0), 3)) # (T, H, W)
165
  self.stride = stride
166
  self.padding = padding
167
  self.conv = nn.Conv3d(
@@ -203,7 +203,7 @@ class LayerNorm(nn.Module):
203
  return x
204
 
205
 
206
- def Normalize(in_channels, num_groups=32, norm_type="groupnorm"):
207
  """Build the normalisation layer selected by ``norm_type``."""
208
  if norm_type == "groupnorm":
209
  return torch.nn.GroupNorm(
@@ -317,7 +317,7 @@ class Upsample(nn.Module):
317
  r = x.repeat_interleave(out_c // in_c, dim=1)
318
  return F.interpolate(r, scale_factor=2.0, mode="nearest")
319
 
320
- @video_to_image
321
  def forward(self, x):
322
  h = self.shuffle(self.conv(x))
323
  if self.residual:
@@ -337,13 +337,13 @@ class Downsample(nn.Module):
337
  in_channels, out_channels, kernel_size=3, stride=2, padding=0
338
  )
339
 
340
- @video_to_image
341
  def forward(self, x):
342
  x = F.pad(x, (0, 1, 0, 1), mode="constant", value=0)
343
  return self.conv(x)
344
 
345
 
346
- class Spatial2xTime2x3DDownsample(nn.Module):
347
  """Joint 2x spatial + 2x temporal downsample via a stride-2 causal conv."""
348
 
349
  def __init__(self, in_channels, out_channels):
@@ -357,7 +357,7 @@ class Spatial2xTime2x3DDownsample(nn.Module):
357
  return self.conv(x)
358
 
359
 
360
- class Spatial2xTime2x3DUpsample(nn.Module):
361
  """Joint spatial + temporal 2x upsample.
362
 
363
  The temporal axis is stretched with ``F.interpolate`` (cheap and smooth);
@@ -469,11 +469,11 @@ class ResnetBlock2D(nn.Module):
469
  self.out_channels = in_channels if out_channels is None else out_channels
470
  self.use_conv_shortcut = conv_shortcut
471
 
472
- self.norm1 = Normalize(in_channels, norm_type=norm_type)
473
  self.conv1 = torch.nn.Conv2d(
474
  in_channels, out_channels, kernel_size=3, stride=1, padding=1
475
  )
476
- self.norm2 = Normalize(out_channels, norm_type=norm_type)
477
  self.dropout = torch.nn.Dropout(dropout)
478
  self.conv2 = torch.nn.Conv2d(
479
  out_channels, out_channels, kernel_size=3, stride=1, padding=1
@@ -488,13 +488,13 @@ class ResnetBlock2D(nn.Module):
488
  in_channels, out_channels, kernel_size=1, stride=1, padding=0
489
  )
490
 
491
- @video_to_image
492
  def forward(self, x):
493
  h = self.norm1(x)
494
- h = nonlinearity(h)
495
  h = self.conv1(h)
496
  h = self.norm2(h)
497
- h = nonlinearity(h)
498
  h = self.dropout(h)
499
  h = self.conv2(h)
500
  if self.in_channels != self.out_channels:
@@ -522,9 +522,9 @@ class ResnetBlock3D(nn.Module):
522
  self.out_channels = in_channels if out_channels is None else out_channels
523
  self.use_conv_shortcut = conv_shortcut
524
 
525
- self.norm1 = Normalize(in_channels, norm_type=norm_type)
526
  self.conv1 = CausalConv3d(in_channels, out_channels, 3, padding=1)
527
- self.norm2 = Normalize(out_channels, norm_type=norm_type)
528
  self.dropout = torch.nn.Dropout(dropout)
529
  self.conv2 = CausalConv3d(out_channels, out_channels, 3, padding=1)
530
  if self.in_channels != self.out_channels:
@@ -535,10 +535,10 @@ class ResnetBlock3D(nn.Module):
535
 
536
  def forward(self, x):
537
  h = self.norm1(x)
538
- h = nonlinearity(h)
539
  h = self.conv1(h)
540
  h = self.norm2(h)
541
- h = nonlinearity(h)
542
  h = self.dropout(h)
543
  h = self.conv2(h)
544
  if self.in_channels != self.out_channels:
@@ -559,7 +559,7 @@ class ResnetBlock3D(nn.Module):
559
 
560
 
561
  @dataclass
562
- class AutoencoderKLOutput(BaseOutput):
563
  latent_dist: "DeterministicLatent"
564
  extra_output: Optional[tuple] = None
565
 
@@ -621,7 +621,7 @@ def build_mid_layer(
621
  """
622
  if layer_type is None or layer_type == "Identity":
623
  return nn.Identity()
624
- return resolve_str_to_obj(layer_type)(
625
  in_channels=channels,
626
  out_channels=channels,
627
  dropout=dropout,
@@ -632,7 +632,7 @@ def build_mid_layer(
632
  def pad_time_to_even(x: torch.Tensor) -> torch.Tensor:
633
  """Prepend a repeated leading frame so the temporal axis becomes even.
634
 
635
- This matches the temporal arithmetic of ``Spatial2xTime2x3DDownsample``:
636
  folding the time axis by 2 needs an even length, and pre-pending (rather
637
  than appending) the repeated frame keeps the fold causal.
638
  """
@@ -686,7 +686,7 @@ class MotifDownBlock(nn.Module):
686
  )
687
 
688
  if down_type == "thw":
689
- self.down = Spatial2xTime2x3DDownsample(
690
  in_channels=in_channels, out_channels=in_channels
691
  )
692
  elif down_type == "hw":
@@ -761,7 +761,7 @@ class MotifUpBlock(nn.Module):
761
  """Decoder up stage: residual stack, spatial/temporal upsample, residual.
762
 
763
  ``upsample_residual=True`` forwards the parameter-free identity skip into
764
- the upsample module (see :class:`Upsample` / :class:`Spatial2xTime2x3DUpsample`).
765
  """
766
 
767
  def __init__(
@@ -796,7 +796,7 @@ class MotifUpBlock(nn.Module):
796
  )
797
 
798
  if up_type == "thw":
799
- self.up = Spatial2xTime2x3DUpsample(
800
  in_channels=in_channels,
801
  out_channels=in_channels,
802
  t_interpolation=t_interpolation,
@@ -829,7 +829,7 @@ class MotifUpBlock(nn.Module):
829
  # ---------------------------------------------------------------------------
830
 
831
 
832
- class MotifEncoder(VideoBaseAE):
833
  """Deterministic 3D causal encoder.
834
 
835
  For a ``(B, 3, T, H, W)`` clip the stem is a stride-1 conv; five down
@@ -884,7 +884,7 @@ class MotifEncoder(VideoBaseAE):
884
  out_channels=base_channels[idx + 1],
885
  num_res_blocks=num_resblocks,
886
  down_type=down_type,
887
- res_block=resolve_str_to_obj(down_res_type),
888
  dropout=dropout,
889
  norm_type=norm_type,
890
  )
@@ -897,7 +897,7 @@ class MotifEncoder(VideoBaseAE):
897
  ]
898
  )
899
 
900
- self.norm_out = Normalize(base_channels[-1], norm_type=norm_type)
901
  # conv_out emits latent_dim channels directly (no mean/log-var split).
902
  if self.input_type == "video":
903
  self.conv_out = CausalConv3d(
@@ -914,11 +914,11 @@ class MotifEncoder(VideoBaseAE):
914
  h = down_block(h)
915
  h = self.mid(h)
916
  h = self.norm_out(h)
917
- h = nonlinearity(h)
918
  return self.conv_out(h)
919
 
920
 
921
- class MotifDecoder(VideoBaseAE):
922
  """Asymmetric decoder mirroring the encoder's stage layout.
923
 
924
  The decoder is intentionally wider than the encoder
@@ -991,7 +991,7 @@ class MotifDecoder(VideoBaseAE):
991
  out_channels=base_channels[idx - 1],
992
  num_res_blocks=num_resblocks,
993
  up_type=up_type,
994
- res_block=resolve_str_to_obj(up_res_type),
995
  t_interpolation=t_interpolation,
996
  dropout=dropout,
997
  norm_type=norm_type,
@@ -999,7 +999,7 @@ class MotifDecoder(VideoBaseAE):
999
  )
1000
  )
1001
 
1002
- self.norm_out = Normalize(base_channels[0], norm_type=norm_type)
1003
  self.conv_out = Conv2d(base_channels[0], 3, kernel_size=3, stride=1, padding=1)
1004
 
1005
  def forward(self, z: torch.Tensor):
@@ -1008,7 +1008,7 @@ class MotifDecoder(VideoBaseAE):
1008
  for up_block in self.up_blocks:
1009
  h = up_block(h)
1010
  h = self.norm_out(h)
1011
- h = nonlinearity(h)
1012
  return self.conv_out(h)
1013
 
1014
 
@@ -1017,7 +1017,7 @@ class MotifDecoder(VideoBaseAE):
1017
  # ---------------------------------------------------------------------------
1018
 
1019
 
1020
- class MotifVAE(VideoBaseAE):
1021
  """3D causal video VAE with a 128-channel deterministic latent.
1022
 
1023
  Compression is 4x temporal and 32x spatial. The encoder (width 96) uses a
@@ -1056,13 +1056,9 @@ class MotifVAE(VideoBaseAE):
1056
  upsample_residual: bool = False,
1057
  ) -> None:
1058
  super().__init__()
1059
- self.use_tiling = False
1060
- self.t_chunk_enc = 16
1061
- self.t_chunk_dec = 4
1062
- self.use_quant_layer = False
1063
 
1064
  if scale is None:
1065
- scale = [0.18215] * latent_dim
1066
  if shift is None:
1067
  shift = [0.0] * latent_dim
1068
  if decoder_base_channels is None:
@@ -1109,7 +1105,7 @@ class MotifVAE(VideoBaseAE):
1109
  # posterior API over the latent.
1110
  h = self.encoder(x)
1111
  posterior = DeterministicLatent(h)
1112
- return AutoencoderKLOutput(latent_dist=posterior, extra_output=None)
1113
 
1114
  def decode(self, z, **kwargs):
1115
  dec = self.decoder(z)
@@ -1125,8 +1121,3 @@ class MotifVAE(VideoBaseAE):
1125
  sampled_latent=z,
1126
  extra_output=None,
1127
  )
1128
-
1129
- def get_last_layer(self):
1130
- if hasattr(self.decoder.conv_out, "conv"):
1131
- return self.decoder.conv_out.conv.weight
1132
- return self.decoder.conv_out.weight
 
33
  # ---------------------------------------------------------------------------
34
  #
35
  # The config encodes residual / mid block choices as strings (e.g.
36
+ # ``"ResnetBlock2D"``). ``block_from_name`` maps those strings to the
37
  # classes defined in this module. ``"Identity"`` maps to ``nn.Identity`` so a
38
  # mid stage can be made attention-free while keeping the surrounding key
39
  # layout intact.
40
 
41
+ _NAME_OVERRIDES = {"Identity": nn.Identity}
42
 
43
 
44
+ def block_from_name(name: str):
45
  """Resolve a block-type string from the config to its class."""
46
+ if name in _NAME_OVERRIDES:
47
+ return _NAME_OVERRIDES[name]
48
  try:
49
  return globals()[name]
50
  except KeyError as exc:
51
  raise AttributeError(
52
+ f"block_from_name: unknown class name {name!r} "
53
  f"(not defined in this module)"
54
  ) from exc
55
 
56
 
57
+ class MotifVAEBase(ModelMixin, ConfigMixin):
58
  """Base class wiring diffusers' ``ModelMixin`` + ``ConfigMixin`` together."""
59
 
60
  config_name = "config.json"
 
68
  # ---------------------------------------------------------------------------
69
 
70
 
71
+ def per_frame_2d(func):
72
  """Wrap a 2D forward so it also accepts 5D ``(B, C, T, H, W)`` tensors.
73
 
74
  Frames are folded into the batch dimension, processed independently, then
 
88
  return wrapper
89
 
90
 
91
+ def silu(x: torch.Tensor) -> torch.Tensor:
92
  """SiLU / swish activation used throughout the network."""
93
  return x * torch.sigmoid(x)
94
 
95
 
96
+ def _as_tuple(value, length: int = 1):
97
  """Broadcast a scalar to a length-``length`` tuple; pass tuples through."""
98
  return value if isinstance(value, (tuple, list)) else ((value,) * length)
99
 
 
134
  dtype,
135
  )
136
 
137
+ @per_frame_2d
138
  def forward(self, x):
139
  return super().forward(x)
140
 
 
156
  **kwargs,
157
  ) -> None:
158
  super().__init__()
159
+ self.kernel_size = _as_tuple(kernel_size, 3)
160
  self.time_kernel_size = self.kernel_size[0]
161
  self.chan_in = chan_in
162
  self.chan_out = chan_out
163
+ stride = _as_tuple(kwargs.pop("stride", 1), 3)
164
+ padding = list(_as_tuple(kwargs.pop("padding", 0), 3)) # (T, H, W)
165
  self.stride = stride
166
  self.padding = padding
167
  self.conv = nn.Conv3d(
 
203
  return x
204
 
205
 
206
+ def make_norm(in_channels, num_groups=32, norm_type="groupnorm"):
207
  """Build the normalisation layer selected by ``norm_type``."""
208
  if norm_type == "groupnorm":
209
  return torch.nn.GroupNorm(
 
317
  r = x.repeat_interleave(out_c // in_c, dim=1)
318
  return F.interpolate(r, scale_factor=2.0, mode="nearest")
319
 
320
+ @per_frame_2d
321
  def forward(self, x):
322
  h = self.shuffle(self.conv(x))
323
  if self.residual:
 
337
  in_channels, out_channels, kernel_size=3, stride=2, padding=0
338
  )
339
 
340
+ @per_frame_2d
341
  def forward(self, x):
342
  x = F.pad(x, (0, 1, 0, 1), mode="constant", value=0)
343
  return self.conv(x)
344
 
345
 
346
+ class STDownsample(nn.Module):
347
  """Joint 2x spatial + 2x temporal downsample via a stride-2 causal conv."""
348
 
349
  def __init__(self, in_channels, out_channels):
 
357
  return self.conv(x)
358
 
359
 
360
+ class STUpsample(nn.Module):
361
  """Joint spatial + temporal 2x upsample.
362
 
363
  The temporal axis is stretched with ``F.interpolate`` (cheap and smooth);
 
469
  self.out_channels = in_channels if out_channels is None else out_channels
470
  self.use_conv_shortcut = conv_shortcut
471
 
472
+ self.norm1 = make_norm(in_channels, norm_type=norm_type)
473
  self.conv1 = torch.nn.Conv2d(
474
  in_channels, out_channels, kernel_size=3, stride=1, padding=1
475
  )
476
+ self.norm2 = make_norm(out_channels, norm_type=norm_type)
477
  self.dropout = torch.nn.Dropout(dropout)
478
  self.conv2 = torch.nn.Conv2d(
479
  out_channels, out_channels, kernel_size=3, stride=1, padding=1
 
488
  in_channels, out_channels, kernel_size=1, stride=1, padding=0
489
  )
490
 
491
+ @per_frame_2d
492
  def forward(self, x):
493
  h = self.norm1(x)
494
+ h = silu(h)
495
  h = self.conv1(h)
496
  h = self.norm2(h)
497
+ h = silu(h)
498
  h = self.dropout(h)
499
  h = self.conv2(h)
500
  if self.in_channels != self.out_channels:
 
522
  self.out_channels = in_channels if out_channels is None else out_channels
523
  self.use_conv_shortcut = conv_shortcut
524
 
525
+ self.norm1 = make_norm(in_channels, norm_type=norm_type)
526
  self.conv1 = CausalConv3d(in_channels, out_channels, 3, padding=1)
527
+ self.norm2 = make_norm(out_channels, norm_type=norm_type)
528
  self.dropout = torch.nn.Dropout(dropout)
529
  self.conv2 = CausalConv3d(out_channels, out_channels, 3, padding=1)
530
  if self.in_channels != self.out_channels:
 
535
 
536
  def forward(self, x):
537
  h = self.norm1(x)
538
+ h = silu(h)
539
  h = self.conv1(h)
540
  h = self.norm2(h)
541
+ h = silu(h)
542
  h = self.dropout(h)
543
  h = self.conv2(h)
544
  if self.in_channels != self.out_channels:
 
559
 
560
 
561
  @dataclass
562
+ class MotifEncoderOutput(BaseOutput):
563
  latent_dist: "DeterministicLatent"
564
  extra_output: Optional[tuple] = None
565
 
 
621
  """
622
  if layer_type is None or layer_type == "Identity":
623
  return nn.Identity()
624
+ return block_from_name(layer_type)(
625
  in_channels=channels,
626
  out_channels=channels,
627
  dropout=dropout,
 
632
  def pad_time_to_even(x: torch.Tensor) -> torch.Tensor:
633
  """Prepend a repeated leading frame so the temporal axis becomes even.
634
 
635
+ This matches the temporal arithmetic of ``STDownsample``:
636
  folding the time axis by 2 needs an even length, and pre-pending (rather
637
  than appending) the repeated frame keeps the fold causal.
638
  """
 
686
  )
687
 
688
  if down_type == "thw":
689
+ self.down = STDownsample(
690
  in_channels=in_channels, out_channels=in_channels
691
  )
692
  elif down_type == "hw":
 
761
  """Decoder up stage: residual stack, spatial/temporal upsample, residual.
762
 
763
  ``upsample_residual=True`` forwards the parameter-free identity skip into
764
+ the upsample module (see :class:`Upsample` / :class:`STUpsample`).
765
  """
766
 
767
  def __init__(
 
796
  )
797
 
798
  if up_type == "thw":
799
+ self.up = STUpsample(
800
  in_channels=in_channels,
801
  out_channels=in_channels,
802
  t_interpolation=t_interpolation,
 
829
  # ---------------------------------------------------------------------------
830
 
831
 
832
+ class MotifEncoder(MotifVAEBase):
833
  """Deterministic 3D causal encoder.
834
 
835
  For a ``(B, 3, T, H, W)`` clip the stem is a stride-1 conv; five down
 
884
  out_channels=base_channels[idx + 1],
885
  num_res_blocks=num_resblocks,
886
  down_type=down_type,
887
+ res_block=block_from_name(down_res_type),
888
  dropout=dropout,
889
  norm_type=norm_type,
890
  )
 
897
  ]
898
  )
899
 
900
+ self.norm_out = make_norm(base_channels[-1], norm_type=norm_type)
901
  # conv_out emits latent_dim channels directly (no mean/log-var split).
902
  if self.input_type == "video":
903
  self.conv_out = CausalConv3d(
 
914
  h = down_block(h)
915
  h = self.mid(h)
916
  h = self.norm_out(h)
917
+ h = silu(h)
918
  return self.conv_out(h)
919
 
920
 
921
+ class MotifDecoder(MotifVAEBase):
922
  """Asymmetric decoder mirroring the encoder's stage layout.
923
 
924
  The decoder is intentionally wider than the encoder
 
991
  out_channels=base_channels[idx - 1],
992
  num_res_blocks=num_resblocks,
993
  up_type=up_type,
994
+ res_block=block_from_name(up_res_type),
995
  t_interpolation=t_interpolation,
996
  dropout=dropout,
997
  norm_type=norm_type,
 
999
  )
1000
  )
1001
 
1002
+ self.norm_out = make_norm(base_channels[0], norm_type=norm_type)
1003
  self.conv_out = Conv2d(base_channels[0], 3, kernel_size=3, stride=1, padding=1)
1004
 
1005
  def forward(self, z: torch.Tensor):
 
1008
  for up_block in self.up_blocks:
1009
  h = up_block(h)
1010
  h = self.norm_out(h)
1011
+ h = silu(h)
1012
  return self.conv_out(h)
1013
 
1014
 
 
1017
  # ---------------------------------------------------------------------------
1018
 
1019
 
1020
+ class MotifVAE(MotifVAEBase):
1021
  """3D causal video VAE with a 128-channel deterministic latent.
1022
 
1023
  Compression is 4x temporal and 32x spatial. The encoder (width 96) uses a
 
1056
  upsample_residual: bool = False,
1057
  ) -> None:
1058
  super().__init__()
 
 
 
 
1059
 
1060
  if scale is None:
1061
+ scale = [1.0] * latent_dim
1062
  if shift is None:
1063
  shift = [0.0] * latent_dim
1064
  if decoder_base_channels is None:
 
1105
  # posterior API over the latent.
1106
  h = self.encoder(x)
1107
  posterior = DeterministicLatent(h)
1108
+ return MotifEncoderOutput(latent_dist=posterior, extra_output=None)
1109
 
1110
  def decode(self, z, **kwargs):
1111
  dec = self.decoder(z)
 
1121
  sampled_latent=z,
1122
  extra_output=None,
1123
  )