Ref2VA/video_vae/vae_cnn.py
8.6 KB · 305 lines · python Raw
1 # SPDX-License-Identifier: Apache-2.0
2 # 3D causal CNN encoder for the MiniMax H3 visual VAE (inference-only bundle).
3 import os
4 import torch.nn as nn
5 import torch.nn.functional as F
6
7 from .attention import maybe_checkpoint
8 from .conv import SpatialParallelConv3d
9 from .norm import get_spatial_norm_3d
10 from .parallel import get_parallel_state, exchange_strides
11 from .norm import get_group_norm_3d
12
13
14
15
16
17
18
19
20
21
22 # ============================================================================
23 # 3D CNN Components
24 # ============================================================================
25
26
27 def norm_silu(x, norm, cond=None):
28 if cond is None:
29 return F.silu(norm(x))
30 else:
31 return F.silu(norm(x, cond))
32
33
34 class Downsample3D(nn.Module):
35 def __init__(
36 self,
37 in_channels,
38 out_channels,
39 time_stride=1,
40 space_stride=2,
41 padding_mode="zeros",
42 padding_mode_t=None,
43 causal=True,
44 ):
45 super().__init__()
46 self.time_stride = time_stride
47 self.space_stride = space_stride
48
49 assert time_stride in [1, 2]
50 assert space_stride in [1, 2, 3]
51
52 self.conv = SpatialParallelConv3d(
53 in_channels,
54 out_channels,
55 kernel_size=3,
56 padding=(1, 0, 0),
57 stride=(time_stride, space_stride, space_stride),
58 padding_mode=padding_mode,
59 padding_mode_t=padding_mode_t,
60 causal=causal,
61 )
62 self.causal = self.conv.causal
63 self.pad_mode = self.conv.pad_mode
64
65 def forward(self, x):
66 if self.space_stride == 2:
67 if getattr(self.conv, "spatial_parallel", False):
68 state = get_parallel_state()
69 x = exchange_strides(
70 x,
71 self.pad_mode,
72 state["sp_rank"],
73 state["sp_size"],
74 state["sp_process_group"],
75 self.conv.chunk_dim,
76 )
77 else:
78 pad = (0, 1, 0, 1, 0, 0)
79 x = F.pad(x, pad, mode=self.pad_mode)
80 return self.conv(x)
81
82
83 class ResnetBlock3D(nn.Module):
84 def __init__(
85 self,
86 in_channels,
87 out_channels=None,
88 zq_ch=None,
89 padding_mode="zeros",
90 padding_mode_t=None,
91 causal=True,
92 use_t_isolated_gn=False,
93 ):
94 super().__init__()
95 self.in_channels = in_channels
96 out_channels = in_channels if out_channels is None else out_channels
97 self.out_channels = out_channels
98
99 self.use_fused_norm = (
100 os.environ.get("MINIMAX_H3_USE_FUSED_NORM", "false").lower() == "true"
101 )
102
103 if zq_ch is None:
104 self.norm1 = get_group_norm_3d(in_channels, use_t_isolated_gn=use_t_isolated_gn)
105 self.norm2 = get_group_norm_3d(out_channels, use_t_isolated_gn=use_t_isolated_gn)
106 else:
107 self.norm1 = get_spatial_norm_3d(
108 in_channels,
109 zq_ch,
110 padding_mode=padding_mode,
111 padding_mode_t=padding_mode_t,
112 causal=causal,
113 use_t_isolated_gn=use_t_isolated_gn,
114 )
115 self.norm2 = get_spatial_norm_3d(
116 out_channels,
117 zq_ch,
118 padding_mode=padding_mode,
119 padding_mode_t=padding_mode_t,
120 causal=causal,
121 use_t_isolated_gn=use_t_isolated_gn,
122 )
123
124 self.conv1 = SpatialParallelConv3d(
125 in_channels,
126 out_channels,
127 kernel_size=3,
128 padding=1,
129 padding_mode=padding_mode,
130 padding_mode_t=padding_mode_t,
131 causal=causal,
132 )
133
134 self.conv2 = SpatialParallelConv3d(
135 out_channels,
136 out_channels,
137 kernel_size=3,
138 padding=1,
139 padding_mode=padding_mode,
140 padding_mode_t=padding_mode_t,
141 causal=causal,
142 )
143
144 if self.in_channels != self.out_channels:
145 self.nin_shortcut = SpatialParallelConv3d(
146 in_channels,
147 out_channels,
148 kernel_size=1,
149 padding_mode=padding_mode,
150 padding_mode_t=padding_mode_t,
151 causal=causal,
152 )
153
154 def forward(self, x, zq=None):
155 h = x
156
157 if self.use_fused_norm:
158 h = self.norm1(h, zq)
159 else:
160 h = norm_silu(h, self.norm1, zq)
161
162 h = self.conv1(h)
163
164 if self.use_fused_norm:
165 h = self.norm2(h, zq)
166 else:
167 h = norm_silu(h, self.norm2, zq)
168
169 h = self.conv2(h)
170
171 if self.in_channels != self.out_channels:
172 x = self.nin_shortcut(x)
173
174 return x + h
175
176
177 class EncoderFCN3D(nn.Module):
178 def __init__(
179 self,
180 ch,
181 ch_mult,
182 space_down,
183 time_down,
184 num_res_blocks,
185 in_channels,
186 z_channels,
187 double_z=False,
188 zq_ch=None,
189 padding_mode="zeros",
190 padding_mode_t=None,
191 causal=True,
192 use_t_isolated_gn=False,
193 ):
194 super().__init__()
195 self.ch = ch
196 self.num_levels = len(ch_mult)
197
198 if isinstance(num_res_blocks, int):
199 self.num_res_blocks = [num_res_blocks] * self.num_levels
200 else:
201 self.num_res_blocks = num_res_blocks
202
203 self.space_down_factors = space_down
204 self.time_down_factors = time_down
205 self.in_channels = in_channels
206
207 self.use_fused_norm = (
208 os.environ.get("MINIMAX_H3_USE_FUSED_NORM", "false").lower() == "true"
209 )
210
211 block_mid = [ch * ch_mult[i] for i in range(self.num_levels)]
212 block_in = [block_mid[0]] + block_mid[:-1]
213 block_out = block_mid
214
215 conv_kwargs = dict(
216 padding_mode=padding_mode,
217 padding_mode_t=padding_mode_t,
218 causal=causal,
219 )
220
221 self.conv_in = SpatialParallelConv3d(
222 in_channels, block_in[0], kernel_size=3, padding=1, **conv_kwargs
223 )
224
225 self.down = nn.ModuleList()
226 for i_level in range(self.num_levels):
227 down = nn.Module()
228
229 down.block = nn.ModuleList()
230 for i in range(self.num_res_blocks[i_level]):
231 down.block.append(
232 ResnetBlock3D(
233 in_channels=block_in[i_level] if i == 0 else block_mid[i_level],
234 out_channels=block_mid[i_level],
235 zq_ch=zq_ch,
236 use_t_isolated_gn=use_t_isolated_gn,
237 **conv_kwargs,
238 )
239 )
240
241 if space_down[i_level] * time_down[i_level] > 1:
242 down.downsample = Downsample3D(
243 block_mid[i_level],
244 block_out[i_level],
245 time_stride=time_down[i_level],
246 space_stride=space_down[i_level],
247 **conv_kwargs,
248 )
249 else:
250 if block_out[i_level] != block_mid[i_level]:
251 down.downsample = SpatialParallelConv3d(
252 block_mid[i_level],
253 block_out[i_level],
254 kernel_size=1,
255 **conv_kwargs,
256 )
257
258 self.down.append(down)
259
260 if zq_ch is None:
261 self.norm_out = get_group_norm_3d(
262 block_out[-1], use_t_isolated_gn=use_t_isolated_gn
263 )
264 else:
265 self.norm_out = get_spatial_norm_3d(
266 block_out[-1],
267 zq_ch,
268 use_t_isolated_gn=use_t_isolated_gn,
269 **conv_kwargs,
270 )
271
272 self.conv_out = SpatialParallelConv3d(
273 block_out[-1],
274 2 * z_channels if double_z else z_channels,
275 kernel_size=3,
276 padding=1,
277 **conv_kwargs,
278 )
279
280 self.gradient_checkpointing = False
281
282 def _set_gradient_checkpointing(self, module, value=False):
283 if hasattr(module, "gradient_checkpointing"):
284 module.gradient_checkpointing = value
285
286 def forward(self, x, zq=None):
287 h = self.conv_in(x)
288 for i_level in range(self.num_levels):
289 for i_block in range(self.num_res_blocks[i_level]):
290 h = maybe_checkpoint(self, self.down[i_level].block[i_block], h, zq)
291 if hasattr(self.down[i_level], "downsample"):
292 h = self.down[i_level].downsample(h)
293
294 if self.use_fused_norm:
295 h = self.norm_out(h, zq)
296 else:
297 h = norm_silu(h, self.norm_out, zq)
298
299 h = self.conv_out(h)
300 return h
301
302
303
304
305