Cccccz commited on
Commit
0360ac1
Β·
verified Β·
1 Parent(s): db459ec

Upload demo utility source

Browse files
demo_utils/constant.py ADDED
@@ -0,0 +1,41 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ import torch
3
+
4
+
5
+ ZERO_VAE_CACHE = [
6
+ torch.zeros(1, 16, 2, 60, 104),
7
+ torch.zeros(1, 384, 2, 60, 104),
8
+ torch.zeros(1, 384, 2, 60, 104),
9
+ torch.zeros(1, 384, 2, 60, 104),
10
+ torch.zeros(1, 384, 2, 60, 104),
11
+ torch.zeros(1, 384, 2, 60, 104),
12
+ torch.zeros(1, 384, 2, 60, 104),
13
+ torch.zeros(1, 384, 2, 60, 104),
14
+ torch.zeros(1, 384, 2, 60, 104),
15
+ torch.zeros(1, 384, 2, 60, 104),
16
+ torch.zeros(1, 384, 2, 60, 104),
17
+ torch.zeros(1, 384, 2, 60, 104),
18
+ torch.zeros(1, 192, 2, 120, 208),
19
+ torch.zeros(1, 384, 2, 120, 208),
20
+ torch.zeros(1, 384, 2, 120, 208),
21
+ torch.zeros(1, 384, 2, 120, 208),
22
+ torch.zeros(1, 384, 2, 120, 208),
23
+ torch.zeros(1, 384, 2, 120, 208),
24
+ torch.zeros(1, 384, 2, 120, 208),
25
+ torch.zeros(1, 192, 2, 240, 416),
26
+ torch.zeros(1, 192, 2, 240, 416),
27
+ torch.zeros(1, 192, 2, 240, 416),
28
+ torch.zeros(1, 192, 2, 240, 416),
29
+ torch.zeros(1, 192, 2, 240, 416),
30
+ torch.zeros(1, 192, 2, 240, 416),
31
+ torch.zeros(1, 96, 2, 480, 832),
32
+ torch.zeros(1, 96, 2, 480, 832),
33
+ torch.zeros(1, 96, 2, 480, 832),
34
+ torch.zeros(1, 96, 2, 480, 832),
35
+ torch.zeros(1, 96, 2, 480, 832),
36
+ torch.zeros(1, 96, 2, 480, 832),
37
+ torch.zeros(1, 96, 2, 480, 832)
38
+ ]
39
+
40
+ feat_names = [f"vae_cache_{i}" for i in range(len(ZERO_VAE_CACHE))]
41
+ ALL_INPUTS_NAMES = ["z", "use_cache"] + feat_names
demo_utils/memory.py ADDED
@@ -0,0 +1,135 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copied from https://github.com/lllyasviel/FramePack/tree/main/demo_utils
2
+ # Apache-2.0 License
3
+ # By lllyasviel
4
+
5
+ import torch
6
+
7
+
8
+ cpu = torch.device('cpu')
9
+ gpu = torch.device(f'cuda:{torch.cuda.current_device()}')
10
+ gpu_complete_modules = []
11
+
12
+
13
+ class DynamicSwapInstaller:
14
+ @staticmethod
15
+ def _install_module(module: torch.nn.Module, **kwargs):
16
+ original_class = module.__class__
17
+ module.__dict__['forge_backup_original_class'] = original_class
18
+
19
+ def hacked_get_attr(self, name: str):
20
+ if '_parameters' in self.__dict__:
21
+ _parameters = self.__dict__['_parameters']
22
+ if name in _parameters:
23
+ p = _parameters[name]
24
+ if p is None:
25
+ return None
26
+ if p.__class__ == torch.nn.Parameter:
27
+ return torch.nn.Parameter(p.to(**kwargs), requires_grad=p.requires_grad)
28
+ else:
29
+ return p.to(**kwargs)
30
+ if '_buffers' in self.__dict__:
31
+ _buffers = self.__dict__['_buffers']
32
+ if name in _buffers:
33
+ return _buffers[name].to(**kwargs)
34
+ return super(original_class, self).__getattr__(name)
35
+
36
+ module.__class__ = type('DynamicSwap_' + original_class.__name__, (original_class,), {
37
+ '__getattr__': hacked_get_attr,
38
+ })
39
+
40
+ return
41
+
42
+ @staticmethod
43
+ def _uninstall_module(module: torch.nn.Module):
44
+ if 'forge_backup_original_class' in module.__dict__:
45
+ module.__class__ = module.__dict__.pop('forge_backup_original_class')
46
+ return
47
+
48
+ @staticmethod
49
+ def install_model(model: torch.nn.Module, **kwargs):
50
+ for m in model.modules():
51
+ DynamicSwapInstaller._install_module(m, **kwargs)
52
+ return
53
+
54
+ @staticmethod
55
+ def uninstall_model(model: torch.nn.Module):
56
+ for m in model.modules():
57
+ DynamicSwapInstaller._uninstall_module(m)
58
+ return
59
+
60
+
61
+ def fake_diffusers_current_device(model: torch.nn.Module, target_device: torch.device):
62
+ if hasattr(model, 'scale_shift_table'):
63
+ model.scale_shift_table.data = model.scale_shift_table.data.to(target_device)
64
+ return
65
+
66
+ for k, p in model.named_modules():
67
+ if hasattr(p, 'weight'):
68
+ p.to(target_device)
69
+ return
70
+
71
+
72
+ def get_cuda_free_memory_gb(device=None):
73
+ if device is None:
74
+ device = gpu
75
+
76
+ memory_stats = torch.cuda.memory_stats(device)
77
+ bytes_active = memory_stats['active_bytes.all.current']
78
+ bytes_reserved = memory_stats['reserved_bytes.all.current']
79
+ bytes_free_cuda, _ = torch.cuda.mem_get_info(device)
80
+ bytes_inactive_reserved = bytes_reserved - bytes_active
81
+ bytes_total_available = bytes_free_cuda + bytes_inactive_reserved
82
+ return bytes_total_available / (1024 ** 3)
83
+
84
+
85
+ def move_model_to_device_with_memory_preservation(model, target_device, preserved_memory_gb=0):
86
+ print(f'Moving {model.__class__.__name__} to {target_device} with preserved memory: {preserved_memory_gb} GB')
87
+
88
+ for m in model.modules():
89
+ if get_cuda_free_memory_gb(target_device) <= preserved_memory_gb:
90
+ torch.cuda.empty_cache()
91
+ return
92
+
93
+ if hasattr(m, 'weight'):
94
+ m.to(device=target_device)
95
+
96
+ model.to(device=target_device)
97
+ torch.cuda.empty_cache()
98
+ return
99
+
100
+
101
+ def offload_model_from_device_for_memory_preservation(model, target_device, preserved_memory_gb=0):
102
+ print(f'Offloading {model.__class__.__name__} from {target_device} to preserve memory: {preserved_memory_gb} GB')
103
+
104
+ for m in model.modules():
105
+ if get_cuda_free_memory_gb(target_device) >= preserved_memory_gb:
106
+ torch.cuda.empty_cache()
107
+ return
108
+
109
+ if hasattr(m, 'weight'):
110
+ m.to(device=cpu)
111
+
112
+ model.to(device=cpu)
113
+ torch.cuda.empty_cache()
114
+ return
115
+
116
+
117
+ def unload_complete_models(*args):
118
+ for m in gpu_complete_modules + list(args):
119
+ m.to(device=cpu)
120
+ print(f'Unloaded {m.__class__.__name__} as complete.')
121
+
122
+ gpu_complete_modules.clear()
123
+ torch.cuda.empty_cache()
124
+ return
125
+
126
+
127
+ def load_model_as_complete(model, target_device, unload=True):
128
+ if unload:
129
+ unload_complete_models()
130
+
131
+ model.to(device=target_device)
132
+ print(f'Loaded {model.__class__.__name__} to {target_device} as complete.')
133
+
134
+ gpu_complete_modules.append(model)
135
+ return
demo_utils/taehv.py ADDED
@@ -0,0 +1,313 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """
3
+ Tiny AutoEncoder for Hunyuan Video
4
+ (DNN for encoding / decoding videos to Hunyuan Video's latent space)
5
+ """
6
+ import torch
7
+ import torch.nn as nn
8
+ import torch.nn.functional as F
9
+ from tqdm.auto import tqdm
10
+ from collections import namedtuple
11
+
12
+ DecoderResult = namedtuple("DecoderResult", ("frame", "memory"))
13
+ TWorkItem = namedtuple("TWorkItem", ("input_tensor", "block_index"))
14
+
15
+
16
+ def conv(n_in, n_out, **kwargs):
17
+ return nn.Conv2d(n_in, n_out, 3, padding=1, **kwargs)
18
+
19
+
20
+ class Clamp(nn.Module):
21
+ def forward(self, x):
22
+ return torch.tanh(x / 3) * 3
23
+
24
+
25
+ class MemBlock(nn.Module):
26
+ def __init__(self, n_in, n_out):
27
+ super().__init__()
28
+ self.conv = nn.Sequential(conv(n_in * 2, n_out), nn.ReLU(inplace=True),
29
+ conv(n_out, n_out), nn.ReLU(inplace=True), conv(n_out, n_out))
30
+ self.skip = nn.Conv2d(n_in, n_out, 1, bias=False) if n_in != n_out else nn.Identity()
31
+ self.act = nn.ReLU(inplace=True)
32
+
33
+ def forward(self, x, past):
34
+ return self.act(self.conv(torch.cat([x, past], 1)) + self.skip(x))
35
+
36
+
37
+ class TPool(nn.Module):
38
+ def __init__(self, n_f, stride):
39
+ super().__init__()
40
+ self.stride = stride
41
+ self.conv = nn.Conv2d(n_f * stride, n_f, 1, bias=False)
42
+
43
+ def forward(self, x):
44
+ _NT, C, H, W = x.shape
45
+ return self.conv(x.reshape(-1, self.stride * C, H, W))
46
+
47
+
48
+ class TGrow(nn.Module):
49
+ def __init__(self, n_f, stride):
50
+ super().__init__()
51
+ self.stride = stride
52
+ self.conv = nn.Conv2d(n_f, n_f * stride, 1, bias=False)
53
+
54
+ def forward(self, x):
55
+ _NT, C, H, W = x.shape
56
+ x = self.conv(x)
57
+ return x.reshape(-1, C, H, W)
58
+
59
+
60
+ def apply_model_with_memblocks(model, x, parallel, show_progress_bar):
61
+ """
62
+ Apply a sequential model with memblocks to the given input.
63
+ Args:
64
+ - model: nn.Sequential of blocks to apply
65
+ - x: input data, of dimensions NTCHW
66
+ - parallel: if True, parallelize over timesteps (fast but uses O(T) memory)
67
+ if False, each timestep will be processed sequentially (slow but uses O(1) memory)
68
+ - show_progress_bar: if True, enables tqdm progressbar display
69
+
70
+ Returns NTCHW tensor of output data.
71
+ """
72
+ assert x.ndim == 5, f"TAEHV operates on NTCHW tensors, but got {x.ndim}-dim tensor"
73
+ N, T, C, H, W = x.shape
74
+ if parallel:
75
+ x = x.reshape(N * T, C, H, W)
76
+ # parallel over input timesteps, iterate over blocks
77
+ for b in tqdm(model, disable=not show_progress_bar):
78
+ if isinstance(b, MemBlock):
79
+ NT, C, H, W = x.shape
80
+ T = NT // N
81
+ _x = x.reshape(N, T, C, H, W)
82
+ mem = F.pad(_x, (0, 0, 0, 0, 0, 0, 1, 0), value=0)[:, :T].reshape(x.shape)
83
+ x = b(x, mem)
84
+ else:
85
+ x = b(x)
86
+ NT, C, H, W = x.shape
87
+ T = NT // N
88
+ x = x.view(N, T, C, H, W)
89
+ else:
90
+ # TODO(oboerbohan): at least on macos this still gradually uses more memory during decode...
91
+ # need to fix :(
92
+ out = []
93
+ # iterate over input timesteps and also iterate over blocks.
94
+ # because of the cursed TPool/TGrow blocks, this is not a nested loop,
95
+ # it's actually a ***graph traversal*** problem! so let's make a queue
96
+ work_queue = [TWorkItem(xt, 0) for t, xt in enumerate(x.reshape(N, T * C, H, W).chunk(T, dim=1))]
97
+ # in addition to manually managing our queue, we also need to manually manage our progressbar.
98
+ # we'll update it for every source node that we consume.
99
+ progress_bar = tqdm(range(T), disable=not show_progress_bar)
100
+ # we'll also need a separate addressable memory per node as well
101
+ mem = [None] * len(model)
102
+ while work_queue:
103
+ xt, i = work_queue.pop(0)
104
+ if i == 0:
105
+ # new source node consumed
106
+ progress_bar.update(1)
107
+ if i == len(model):
108
+ # reached end of the graph, append result to output list
109
+ out.append(xt)
110
+ else:
111
+ # fetch the block to process
112
+ b = model[i]
113
+ if isinstance(b, MemBlock):
114
+ # mem blocks are simple since we're visiting the graph in causal order
115
+ if mem[i] is None:
116
+ xt_new = b(xt, xt * 0)
117
+ mem[i] = xt
118
+ else:
119
+ xt_new = b(xt, mem[i])
120
+ mem[i].copy_(xt) # inplace might reduce mysterious pytorch memory allocations? doesn't help though
121
+ # add successor to work queue
122
+ work_queue.insert(0, TWorkItem(xt_new, i + 1))
123
+ elif isinstance(b, TPool):
124
+ # pool blocks are miserable
125
+ if mem[i] is None:
126
+ mem[i] = [] # pool memory is itself a queue of inputs to pool
127
+ mem[i].append(xt)
128
+ if len(mem[i]) > b.stride:
129
+ # pool mem is in invalid state, we should have pooled before this
130
+ raise ValueError("???")
131
+ elif len(mem[i]) < b.stride:
132
+ # pool mem is not yet full, go back to processing the work queue
133
+ pass
134
+ else:
135
+ # pool mem is ready, run the pool block
136
+ N, C, H, W = xt.shape
137
+ xt = b(torch.cat(mem[i], 1).view(N * b.stride, C, H, W))
138
+ # reset the pool mem
139
+ mem[i] = []
140
+ # add successor to work queue
141
+ work_queue.insert(0, TWorkItem(xt, i + 1))
142
+ elif isinstance(b, TGrow):
143
+ xt = b(xt)
144
+ NT, C, H, W = xt.shape
145
+ # each tgrow has multiple successor nodes
146
+ for xt_next in reversed(xt.view(N, b.stride * C, H, W).chunk(b.stride, 1)):
147
+ # add successor to work queue
148
+ work_queue.insert(0, TWorkItem(xt_next, i + 1))
149
+ else:
150
+ # normal block with no funny business
151
+ xt = b(xt)
152
+ # add successor to work queue
153
+ work_queue.insert(0, TWorkItem(xt, i + 1))
154
+ progress_bar.close()
155
+ x = torch.stack(out, 1)
156
+ return x
157
+
158
+
159
+ class TAEHV(nn.Module):
160
+ latent_channels = 16
161
+ image_channels = 3
162
+
163
+ def __init__(self, checkpoint_path="taehv.pth", decoder_time_upscale=(True, True), decoder_space_upscale=(True, True, True)):
164
+ """Initialize pretrained TAEHV from the given checkpoint.
165
+
166
+ Arg:
167
+ checkpoint_path: path to weight file to load. taehv.pth for Hunyuan, taew2_1.pth for Wan 2.1.
168
+ decoder_time_upscale: whether temporal upsampling is enabled for each block. upsampling can be disabled for a cheaper preview.
169
+ decoder_space_upscale: whether spatial upsampling is enabled for each block. upsampling can be disabled for a cheaper preview.
170
+ """
171
+ super().__init__()
172
+ self.encoder = nn.Sequential(
173
+ conv(TAEHV.image_channels, 64), nn.ReLU(inplace=True),
174
+ TPool(64, 2), conv(64, 64, stride=2, bias=False), MemBlock(64, 64), MemBlock(64, 64), MemBlock(64, 64),
175
+ TPool(64, 2), conv(64, 64, stride=2, bias=False), MemBlock(64, 64), MemBlock(64, 64), MemBlock(64, 64),
176
+ TPool(64, 1), conv(64, 64, stride=2, bias=False), MemBlock(64, 64), MemBlock(64, 64), MemBlock(64, 64),
177
+ conv(64, TAEHV.latent_channels),
178
+ )
179
+ n_f = [256, 128, 64, 64]
180
+ self.frames_to_trim = 2**sum(decoder_time_upscale) - 1
181
+ self.decoder = nn.Sequential(
182
+ Clamp(), conv(TAEHV.latent_channels, n_f[0]), nn.ReLU(inplace=True),
183
+ MemBlock(n_f[0], n_f[0]), MemBlock(n_f[0], n_f[0]), MemBlock(n_f[0], n_f[0]), nn.Upsample(
184
+ scale_factor=2 if decoder_space_upscale[0] else 1), TGrow(n_f[0], 1), conv(n_f[0], n_f[1], bias=False),
185
+ MemBlock(n_f[1], n_f[1]), MemBlock(n_f[1], n_f[1]), MemBlock(n_f[1], n_f[1]), nn.Upsample(
186
+ scale_factor=2 if decoder_space_upscale[1] else 1), TGrow(n_f[1], 2 if decoder_time_upscale[0] else 1), conv(n_f[1], n_f[2], bias=False),
187
+ MemBlock(n_f[2], n_f[2]), MemBlock(n_f[2], n_f[2]), MemBlock(n_f[2], n_f[2]), nn.Upsample(
188
+ scale_factor=2 if decoder_space_upscale[2] else 1), TGrow(n_f[2], 2 if decoder_time_upscale[1] else 1), conv(n_f[2], n_f[3], bias=False),
189
+ nn.ReLU(inplace=True), conv(n_f[3], TAEHV.image_channels),
190
+ )
191
+ if checkpoint_path is not None:
192
+ self.load_state_dict(self.patch_tgrow_layers(torch.load(
193
+ checkpoint_path, map_location="cpu", weights_only=True)))
194
+
195
+ def patch_tgrow_layers(self, sd):
196
+ """Patch TGrow layers to use a smaller kernel if needed.
197
+
198
+ Args:
199
+ sd: state dict to patch
200
+ """
201
+ new_sd = self.state_dict()
202
+ for i, layer in enumerate(self.decoder):
203
+ if isinstance(layer, TGrow):
204
+ key = f"decoder.{i}.conv.weight"
205
+ if sd[key].shape[0] > new_sd[key].shape[0]:
206
+ # take the last-timestep output channels
207
+ sd[key] = sd[key][-new_sd[key].shape[0]:]
208
+ return sd
209
+
210
+ def encode_video(self, x, parallel=True, show_progress_bar=True):
211
+ """Encode a sequence of frames.
212
+
213
+ Args:
214
+ x: input NTCHW RGB (C=3) tensor with values in [0, 1].
215
+ parallel: if True, all frames will be processed at once.
216
+ (this is faster but may require more memory).
217
+ if False, frames will be processed sequentially.
218
+ Returns NTCHW latent tensor with ~Gaussian values.
219
+ """
220
+ return apply_model_with_memblocks(self.encoder, x, parallel, show_progress_bar)
221
+
222
+ def decode_video(self, x, parallel=True, show_progress_bar=False):
223
+ """Decode a sequence of frames.
224
+
225
+ Args:
226
+ x: input NTCHW latent (C=12) tensor with ~Gaussian values.
227
+ parallel: if True, all frames will be processed at once.
228
+ (this is faster but may require more memory).
229
+ if False, frames will be processed sequentially.
230
+ Returns NTCHW RGB tensor with ~[0, 1] values.
231
+ """
232
+ x = apply_model_with_memblocks(self.decoder, x, parallel, show_progress_bar)
233
+ # return x[:, self.frames_to_trim:]
234
+ return x
235
+
236
+ def forward(self, x):
237
+ return self.c(x)
238
+
239
+
240
+ @torch.no_grad()
241
+ def main():
242
+ """Run TAEHV roundtrip reconstruction on the given video paths."""
243
+ import os
244
+ import sys
245
+ import cv2 # no highly esteemed deed is commemorated here
246
+
247
+ class VideoTensorReader:
248
+ def __init__(self, video_file_path):
249
+ self.cap = cv2.VideoCapture(video_file_path)
250
+ assert self.cap.isOpened(), f"Could not load {video_file_path}"
251
+ self.fps = self.cap.get(cv2.CAP_PROP_FPS)
252
+
253
+ def __iter__(self):
254
+ return self
255
+
256
+ def __next__(self):
257
+ ret, frame = self.cap.read()
258
+ if not ret:
259
+ self.cap.release()
260
+ raise StopIteration # End of video or error
261
+ return torch.from_numpy(cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)).permute(2, 0, 1) # BGR HWC -> RGB CHW
262
+
263
+ class VideoTensorWriter:
264
+ def __init__(self, video_file_path, width_height, fps=30):
265
+ self.writer = cv2.VideoWriter(video_file_path, cv2.VideoWriter_fourcc(*'mp4v'), fps, width_height)
266
+ assert self.writer.isOpened(), f"Could not create writer for {video_file_path}"
267
+
268
+ def write(self, frame_tensor):
269
+ assert frame_tensor.ndim == 3 and frame_tensor.shape[0] == 3, f"{frame_tensor.shape}??"
270
+ self.writer.write(cv2.cvtColor(frame_tensor.permute(1, 2, 0).numpy(),
271
+ cv2.COLOR_RGB2BGR)) # RGB CHW -> BGR HWC
272
+
273
+ def __del__(self):
274
+ if hasattr(self, 'writer'):
275
+ self.writer.release()
276
+
277
+ dev = torch.device("cuda" if torch.cuda.is_available() else "mps" if torch.backends.mps.is_available() else "cpu")
278
+ dtype = torch.float16
279
+ checkpoint_path = os.getenv("TAEHV_CHECKPOINT_PATH", "taehv.pth")
280
+ checkpoint_name = os.path.splitext(os.path.basename(checkpoint_path))[0]
281
+ print(
282
+ f"Using device \033[31m{dev}\033[0m, dtype \033[32m{dtype}\033[0m, checkpoint \033[34m{checkpoint_name}\033[0m ({checkpoint_path})")
283
+ taehv = TAEHV(checkpoint_path=checkpoint_path).to(dev, dtype)
284
+ for video_path in sys.argv[1:]:
285
+ print(f"Processing {video_path}...")
286
+ video_in = VideoTensorReader(video_path)
287
+ video = torch.stack(list(video_in), 0)[None]
288
+ vid_dev = video.to(dev, dtype).div_(255.0)
289
+ # convert to device tensor
290
+ if video.numel() < 100_000_000:
291
+ print(f" {video_path} seems small enough, will process all frames in parallel")
292
+ # convert to device tensor
293
+ vid_enc = taehv.encode_video(vid_dev)
294
+ print(f" Encoded {video_path} -> {vid_enc.shape}. Decoding...")
295
+ vid_dec = taehv.decode_video(vid_enc)
296
+ print(f" Decoded {video_path} -> {vid_dec.shape}")
297
+ else:
298
+ print(f" {video_path} seems large, will process each frame sequentially")
299
+ # convert to device tensor
300
+ vid_enc = taehv.encode_video(vid_dev, parallel=False)
301
+ print(f" Encoded {video_path} -> {vid_enc.shape}. Decoding...")
302
+ vid_dec = taehv.decode_video(vid_enc, parallel=False)
303
+ print(f" Decoded {video_path} -> {vid_dec.shape}")
304
+ video_out_path = video_path + f".reconstructed_by_{checkpoint_name}.mp4"
305
+ video_out = VideoTensorWriter(
306
+ video_out_path, (vid_dec.shape[-1], vid_dec.shape[-2]), fps=int(round(video_in.fps)))
307
+ for frame in vid_dec.clamp_(0, 1).mul_(255).round_().byte().cpu()[0]:
308
+ video_out.write(frame)
309
+ print(f" Saved to {video_out_path}")
310
+
311
+
312
+ if __name__ == "__main__":
313
+ main()
demo_utils/utils.py ADDED
@@ -0,0 +1,616 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copied from https://github.com/lllyasviel/FramePack/tree/main/demo_utils
2
+ # Apache-2.0 License
3
+ # By lllyasviel
4
+
5
+ import os
6
+ import cv2
7
+ import json
8
+ import random
9
+ import glob
10
+ import torch
11
+ import einops
12
+ import numpy as np
13
+ import datetime
14
+ import torchvision
15
+
16
+ from PIL import Image
17
+
18
+
19
+ def min_resize(x, m):
20
+ if x.shape[0] < x.shape[1]:
21
+ s0 = m
22
+ s1 = int(float(m) / float(x.shape[0]) * float(x.shape[1]))
23
+ else:
24
+ s0 = int(float(m) / float(x.shape[1]) * float(x.shape[0]))
25
+ s1 = m
26
+ new_max = max(s1, s0)
27
+ raw_max = max(x.shape[0], x.shape[1])
28
+ if new_max < raw_max:
29
+ interpolation = cv2.INTER_AREA
30
+ else:
31
+ interpolation = cv2.INTER_LANCZOS4
32
+ y = cv2.resize(x, (s1, s0), interpolation=interpolation)
33
+ return y
34
+
35
+
36
+ def d_resize(x, y):
37
+ H, W, C = y.shape
38
+ new_min = min(H, W)
39
+ raw_min = min(x.shape[0], x.shape[1])
40
+ if new_min < raw_min:
41
+ interpolation = cv2.INTER_AREA
42
+ else:
43
+ interpolation = cv2.INTER_LANCZOS4
44
+ y = cv2.resize(x, (W, H), interpolation=interpolation)
45
+ return y
46
+
47
+
48
+ def resize_and_center_crop(image, target_width, target_height):
49
+ if target_height == image.shape[0] and target_width == image.shape[1]:
50
+ return image
51
+
52
+ pil_image = Image.fromarray(image)
53
+ original_width, original_height = pil_image.size
54
+ scale_factor = max(target_width / original_width, target_height / original_height)
55
+ resized_width = int(round(original_width * scale_factor))
56
+ resized_height = int(round(original_height * scale_factor))
57
+ resized_image = pil_image.resize((resized_width, resized_height), Image.LANCZOS)
58
+ left = (resized_width - target_width) / 2
59
+ top = (resized_height - target_height) / 2
60
+ right = (resized_width + target_width) / 2
61
+ bottom = (resized_height + target_height) / 2
62
+ cropped_image = resized_image.crop((left, top, right, bottom))
63
+ return np.array(cropped_image)
64
+
65
+
66
+ def resize_and_center_crop_pytorch(image, target_width, target_height):
67
+ B, C, H, W = image.shape
68
+
69
+ if H == target_height and W == target_width:
70
+ return image
71
+
72
+ scale_factor = max(target_width / W, target_height / H)
73
+ resized_width = int(round(W * scale_factor))
74
+ resized_height = int(round(H * scale_factor))
75
+
76
+ resized = torch.nn.functional.interpolate(image, size=(resized_height, resized_width), mode='bilinear', align_corners=False)
77
+
78
+ top = (resized_height - target_height) // 2
79
+ left = (resized_width - target_width) // 2
80
+ cropped = resized[:, :, top:top + target_height, left:left + target_width]
81
+
82
+ return cropped
83
+
84
+
85
+ def resize_without_crop(image, target_width, target_height):
86
+ if target_height == image.shape[0] and target_width == image.shape[1]:
87
+ return image
88
+
89
+ pil_image = Image.fromarray(image)
90
+ resized_image = pil_image.resize((target_width, target_height), Image.LANCZOS)
91
+ return np.array(resized_image)
92
+
93
+
94
+ def just_crop(image, w, h):
95
+ if h == image.shape[0] and w == image.shape[1]:
96
+ return image
97
+
98
+ original_height, original_width = image.shape[:2]
99
+ k = min(original_height / h, original_width / w)
100
+ new_width = int(round(w * k))
101
+ new_height = int(round(h * k))
102
+ x_start = (original_width - new_width) // 2
103
+ y_start = (original_height - new_height) // 2
104
+ cropped_image = image[y_start:y_start + new_height, x_start:x_start + new_width]
105
+ return cropped_image
106
+
107
+
108
+ def write_to_json(data, file_path):
109
+ temp_file_path = file_path + ".tmp"
110
+ with open(temp_file_path, 'wt', encoding='utf-8') as temp_file:
111
+ json.dump(data, temp_file, indent=4)
112
+ os.replace(temp_file_path, file_path)
113
+ return
114
+
115
+
116
+ def read_from_json(file_path):
117
+ with open(file_path, 'rt', encoding='utf-8') as file:
118
+ data = json.load(file)
119
+ return data
120
+
121
+
122
+ def get_active_parameters(m):
123
+ return {k: v for k, v in m.named_parameters() if v.requires_grad}
124
+
125
+
126
+ def cast_training_params(m, dtype=torch.float32):
127
+ result = {}
128
+ for n, param in m.named_parameters():
129
+ if param.requires_grad:
130
+ param.data = param.to(dtype)
131
+ result[n] = param
132
+ return result
133
+
134
+
135
+ def separate_lora_AB(parameters, B_patterns=None):
136
+ parameters_normal = {}
137
+ parameters_B = {}
138
+
139
+ if B_patterns is None:
140
+ B_patterns = ['.lora_B.', '__zero__']
141
+
142
+ for k, v in parameters.items():
143
+ if any(B_pattern in k for B_pattern in B_patterns):
144
+ parameters_B[k] = v
145
+ else:
146
+ parameters_normal[k] = v
147
+
148
+ return parameters_normal, parameters_B
149
+
150
+
151
+ def set_attr_recursive(obj, attr, value):
152
+ attrs = attr.split(".")
153
+ for name in attrs[:-1]:
154
+ obj = getattr(obj, name)
155
+ setattr(obj, attrs[-1], value)
156
+ return
157
+
158
+
159
+ def print_tensor_list_size(tensors):
160
+ total_size = 0
161
+ total_elements = 0
162
+
163
+ if isinstance(tensors, dict):
164
+ tensors = tensors.values()
165
+
166
+ for tensor in tensors:
167
+ total_size += tensor.nelement() * tensor.element_size()
168
+ total_elements += tensor.nelement()
169
+
170
+ total_size_MB = total_size / (1024 ** 2)
171
+ total_elements_B = total_elements / 1e9
172
+
173
+ print(f"Total number of tensors: {len(tensors)}")
174
+ print(f"Total size of tensors: {total_size_MB:.2f} MB")
175
+ print(f"Total number of parameters: {total_elements_B:.3f} billion")
176
+ return
177
+
178
+
179
+ @torch.no_grad()
180
+ def batch_mixture(a, b=None, probability_a=0.5, mask_a=None):
181
+ batch_size = a.size(0)
182
+
183
+ if b is None:
184
+ b = torch.zeros_like(a)
185
+
186
+ if mask_a is None:
187
+ mask_a = torch.rand(batch_size) < probability_a
188
+
189
+ mask_a = mask_a.to(a.device)
190
+ mask_a = mask_a.reshape((batch_size,) + (1,) * (a.dim() - 1))
191
+ result = torch.where(mask_a, a, b)
192
+ return result
193
+
194
+
195
+ @torch.no_grad()
196
+ def zero_module(module):
197
+ for p in module.parameters():
198
+ p.detach().zero_()
199
+ return module
200
+
201
+
202
+ @torch.no_grad()
203
+ def supress_lower_channels(m, k, alpha=0.01):
204
+ data = m.weight.data.clone()
205
+
206
+ assert int(data.shape[1]) >= k
207
+
208
+ data[:, :k] = data[:, :k] * alpha
209
+ m.weight.data = data.contiguous().clone()
210
+ return m
211
+
212
+
213
+ def freeze_module(m):
214
+ if not hasattr(m, '_forward_inside_frozen_module'):
215
+ m._forward_inside_frozen_module = m.forward
216
+ m.requires_grad_(False)
217
+ m.forward = torch.no_grad()(m.forward)
218
+ return m
219
+
220
+
221
+ def get_latest_safetensors(folder_path):
222
+ safetensors_files = glob.glob(os.path.join(folder_path, '*.safetensors'))
223
+
224
+ if not safetensors_files:
225
+ raise ValueError('No file to resume!')
226
+
227
+ latest_file = max(safetensors_files, key=os.path.getmtime)
228
+ latest_file = os.path.abspath(os.path.realpath(latest_file))
229
+ return latest_file
230
+
231
+
232
+ def generate_random_prompt_from_tags(tags_str, min_length=3, max_length=32):
233
+ tags = tags_str.split(', ')
234
+ tags = random.sample(tags, k=min(random.randint(min_length, max_length), len(tags)))
235
+ prompt = ', '.join(tags)
236
+ return prompt
237
+
238
+
239
+ def interpolate_numbers(a, b, n, round_to_int=False, gamma=1.0):
240
+ numbers = a + (b - a) * (np.linspace(0, 1, n) ** gamma)
241
+ if round_to_int:
242
+ numbers = np.round(numbers).astype(int)
243
+ return numbers.tolist()
244
+
245
+
246
+ def uniform_random_by_intervals(inclusive, exclusive, n, round_to_int=False):
247
+ edges = np.linspace(0, 1, n + 1)
248
+ points = np.random.uniform(edges[:-1], edges[1:])
249
+ numbers = inclusive + (exclusive - inclusive) * points
250
+ if round_to_int:
251
+ numbers = np.round(numbers).astype(int)
252
+ return numbers.tolist()
253
+
254
+
255
+ def soft_append_bcthw(history, current, overlap=0):
256
+ if overlap <= 0:
257
+ return torch.cat([history, current], dim=2)
258
+
259
+ assert history.shape[2] >= overlap, f"History length ({history.shape[2]}) must be >= overlap ({overlap})"
260
+ assert current.shape[2] >= overlap, f"Current length ({current.shape[2]}) must be >= overlap ({overlap})"
261
+
262
+ weights = torch.linspace(1, 0, overlap, dtype=history.dtype, device=history.device).view(1, 1, -1, 1, 1)
263
+ blended = weights * history[:, :, -overlap:] + (1 - weights) * current[:, :, :overlap]
264
+ output = torch.cat([history[:, :, :-overlap], blended, current[:, :, overlap:]], dim=2)
265
+
266
+ return output.to(history)
267
+
268
+
269
+ def save_bcthw_as_mp4(x, output_filename, fps=10, crf=0):
270
+ b, c, t, h, w = x.shape
271
+
272
+ per_row = b
273
+ for p in [6, 5, 4, 3, 2]:
274
+ if b % p == 0:
275
+ per_row = p
276
+ break
277
+
278
+ os.makedirs(os.path.dirname(os.path.abspath(os.path.realpath(output_filename))), exist_ok=True)
279
+ x = torch.clamp(x.float(), -1., 1.) * 127.5 + 127.5
280
+ x = x.detach().cpu().to(torch.uint8)
281
+ x = einops.rearrange(x, '(m n) c t h w -> t (m h) (n w) c', n=per_row)
282
+ torchvision.io.write_video(output_filename, x, fps=fps, video_codec='libx264', options={'crf': str(int(crf))})
283
+ return x
284
+
285
+
286
+ def save_bcthw_as_png(x, output_filename):
287
+ os.makedirs(os.path.dirname(os.path.abspath(os.path.realpath(output_filename))), exist_ok=True)
288
+ x = torch.clamp(x.float(), -1., 1.) * 127.5 + 127.5
289
+ x = x.detach().cpu().to(torch.uint8)
290
+ x = einops.rearrange(x, 'b c t h w -> c (b h) (t w)')
291
+ torchvision.io.write_png(x, output_filename)
292
+ return output_filename
293
+
294
+
295
+ def save_bchw_as_png(x, output_filename):
296
+ os.makedirs(os.path.dirname(os.path.abspath(os.path.realpath(output_filename))), exist_ok=True)
297
+ x = torch.clamp(x.float(), -1., 1.) * 127.5 + 127.5
298
+ x = x.detach().cpu().to(torch.uint8)
299
+ x = einops.rearrange(x, 'b c h w -> c h (b w)')
300
+ torchvision.io.write_png(x, output_filename)
301
+ return output_filename
302
+
303
+
304
+ def add_tensors_with_padding(tensor1, tensor2):
305
+ if tensor1.shape == tensor2.shape:
306
+ return tensor1 + tensor2
307
+
308
+ shape1 = tensor1.shape
309
+ shape2 = tensor2.shape
310
+
311
+ new_shape = tuple(max(s1, s2) for s1, s2 in zip(shape1, shape2))
312
+
313
+ padded_tensor1 = torch.zeros(new_shape)
314
+ padded_tensor2 = torch.zeros(new_shape)
315
+
316
+ padded_tensor1[tuple(slice(0, s) for s in shape1)] = tensor1
317
+ padded_tensor2[tuple(slice(0, s) for s in shape2)] = tensor2
318
+
319
+ result = padded_tensor1 + padded_tensor2
320
+ return result
321
+
322
+
323
+ def print_free_mem():
324
+ torch.cuda.empty_cache()
325
+ free_mem, total_mem = torch.cuda.mem_get_info(0)
326
+ free_mem_mb = free_mem / (1024 ** 2)
327
+ total_mem_mb = total_mem / (1024 ** 2)
328
+ print(f"Free memory: {free_mem_mb:.2f} MB")
329
+ print(f"Total memory: {total_mem_mb:.2f} MB")
330
+ return
331
+
332
+
333
+ def print_gpu_parameters(device, state_dict, log_count=1):
334
+ summary = {"device": device, "keys_count": len(state_dict)}
335
+
336
+ logged_params = {}
337
+ for i, (key, tensor) in enumerate(state_dict.items()):
338
+ if i >= log_count:
339
+ break
340
+ logged_params[key] = tensor.flatten()[:3].tolist()
341
+
342
+ summary["params"] = logged_params
343
+
344
+ print(str(summary))
345
+ return
346
+
347
+
348
+ def visualize_txt_as_img(width, height, text, font_path='font/DejaVuSans.ttf', size=18):
349
+ from PIL import Image, ImageDraw, ImageFont
350
+
351
+ txt = Image.new("RGB", (width, height), color="white")
352
+ draw = ImageDraw.Draw(txt)
353
+ font = ImageFont.truetype(font_path, size=size)
354
+
355
+ if text == '':
356
+ return np.array(txt)
357
+
358
+ # Split text into lines that fit within the image width
359
+ lines = []
360
+ words = text.split()
361
+ current_line = words[0]
362
+
363
+ for word in words[1:]:
364
+ line_with_word = f"{current_line} {word}"
365
+ if draw.textbbox((0, 0), line_with_word, font=font)[2] <= width:
366
+ current_line = line_with_word
367
+ else:
368
+ lines.append(current_line)
369
+ current_line = word
370
+
371
+ lines.append(current_line)
372
+
373
+ # Draw the text line by line
374
+ y = 0
375
+ line_height = draw.textbbox((0, 0), "A", font=font)[3]
376
+
377
+ for line in lines:
378
+ if y + line_height > height:
379
+ break # stop drawing if the next line will be outside the image
380
+ draw.text((0, y), line, fill="black", font=font)
381
+ y += line_height
382
+
383
+ return np.array(txt)
384
+
385
+
386
+ def blue_mark(x):
387
+ x = x.copy()
388
+ c = x[:, :, 2]
389
+ b = cv2.blur(c, (9, 9))
390
+ x[:, :, 2] = ((c - b) * 16.0 + b).clip(-1, 1)
391
+ return x
392
+
393
+
394
+ def green_mark(x):
395
+ x = x.copy()
396
+ x[:, :, 2] = -1
397
+ x[:, :, 0] = -1
398
+ return x
399
+
400
+
401
+ def frame_mark(x):
402
+ x = x.copy()
403
+ x[:64] = -1
404
+ x[-64:] = -1
405
+ x[:, :8] = 1
406
+ x[:, -8:] = 1
407
+ return x
408
+
409
+
410
+ @torch.inference_mode()
411
+ def pytorch2numpy(imgs):
412
+ results = []
413
+ for x in imgs:
414
+ y = x.movedim(0, -1)
415
+ y = y * 127.5 + 127.5
416
+ y = y.detach().float().cpu().numpy().clip(0, 255).astype(np.uint8)
417
+ results.append(y)
418
+ return results
419
+
420
+
421
+ @torch.inference_mode()
422
+ def numpy2pytorch(imgs):
423
+ h = torch.from_numpy(np.stack(imgs, axis=0)).float() / 127.5 - 1.0
424
+ h = h.movedim(-1, 1)
425
+ return h
426
+
427
+
428
+ @torch.no_grad()
429
+ def duplicate_prefix_to_suffix(x, count, zero_out=False):
430
+ if zero_out:
431
+ return torch.cat([x, torch.zeros_like(x[:count])], dim=0)
432
+ else:
433
+ return torch.cat([x, x[:count]], dim=0)
434
+
435
+
436
+ def weighted_mse(a, b, weight):
437
+ return torch.mean(weight.float() * (a.float() - b.float()) ** 2)
438
+
439
+
440
+ def clamped_linear_interpolation(x, x_min, y_min, x_max, y_max, sigma=1.0):
441
+ x = (x - x_min) / (x_max - x_min)
442
+ x = max(0.0, min(x, 1.0))
443
+ x = x ** sigma
444
+ return y_min + x * (y_max - y_min)
445
+
446
+
447
+ def expand_to_dims(x, target_dims):
448
+ return x.view(*x.shape, *([1] * max(0, target_dims - x.dim())))
449
+
450
+
451
+ def repeat_to_batch_size(tensor: torch.Tensor, batch_size: int):
452
+ if tensor is None:
453
+ return None
454
+
455
+ first_dim = tensor.shape[0]
456
+
457
+ if first_dim == batch_size:
458
+ return tensor
459
+
460
+ if batch_size % first_dim != 0:
461
+ raise ValueError(f"Cannot evenly repeat first dim {first_dim} to match batch_size {batch_size}.")
462
+
463
+ repeat_times = batch_size // first_dim
464
+
465
+ return tensor.repeat(repeat_times, *[1] * (tensor.dim() - 1))
466
+
467
+
468
+ def dim5(x):
469
+ return expand_to_dims(x, 5)
470
+
471
+
472
+ def dim4(x):
473
+ return expand_to_dims(x, 4)
474
+
475
+
476
+ def dim3(x):
477
+ return expand_to_dims(x, 3)
478
+
479
+
480
+ def crop_or_pad_yield_mask(x, length):
481
+ B, F, C = x.shape
482
+ device = x.device
483
+ dtype = x.dtype
484
+
485
+ if F < length:
486
+ y = torch.zeros((B, length, C), dtype=dtype, device=device)
487
+ mask = torch.zeros((B, length), dtype=torch.bool, device=device)
488
+ y[:, :F, :] = x
489
+ mask[:, :F] = True
490
+ return y, mask
491
+
492
+ return x[:, :length, :], torch.ones((B, length), dtype=torch.bool, device=device)
493
+
494
+
495
+ def extend_dim(x, dim, minimal_length, zero_pad=False):
496
+ original_length = int(x.shape[dim])
497
+
498
+ if original_length >= minimal_length:
499
+ return x
500
+
501
+ if zero_pad:
502
+ padding_shape = list(x.shape)
503
+ padding_shape[dim] = minimal_length - original_length
504
+ padding = torch.zeros(padding_shape, dtype=x.dtype, device=x.device)
505
+ else:
506
+ idx = (slice(None),) * dim + (slice(-1, None),) + (slice(None),) * (len(x.shape) - dim - 1)
507
+ last_element = x[idx]
508
+ padding = last_element.repeat_interleave(minimal_length - original_length, dim=dim)
509
+
510
+ return torch.cat([x, padding], dim=dim)
511
+
512
+
513
+ def lazy_positional_encoding(t, repeats=None):
514
+ if not isinstance(t, list):
515
+ t = [t]
516
+
517
+ from diffusers.models.embeddings import get_timestep_embedding
518
+
519
+ te = torch.tensor(t)
520
+ te = get_timestep_embedding(timesteps=te, embedding_dim=256, flip_sin_to_cos=True, downscale_freq_shift=0.0, scale=1.0)
521
+
522
+ if repeats is None:
523
+ return te
524
+
525
+ te = te[:, None, :].expand(-1, repeats, -1)
526
+
527
+ return te
528
+
529
+
530
+ def state_dict_offset_merge(A, B, C=None):
531
+ result = {}
532
+ keys = A.keys()
533
+
534
+ for key in keys:
535
+ A_value = A[key]
536
+ B_value = B[key].to(A_value)
537
+
538
+ if C is None:
539
+ result[key] = A_value + B_value
540
+ else:
541
+ C_value = C[key].to(A_value)
542
+ result[key] = A_value + B_value - C_value
543
+
544
+ return result
545
+
546
+
547
+ def state_dict_weighted_merge(state_dicts, weights):
548
+ if len(state_dicts) != len(weights):
549
+ raise ValueError("Number of state dictionaries must match number of weights")
550
+
551
+ if not state_dicts:
552
+ return {}
553
+
554
+ total_weight = sum(weights)
555
+
556
+ if total_weight == 0:
557
+ raise ValueError("Sum of weights cannot be zero")
558
+
559
+ normalized_weights = [w / total_weight for w in weights]
560
+
561
+ keys = state_dicts[0].keys()
562
+ result = {}
563
+
564
+ for key in keys:
565
+ result[key] = state_dicts[0][key] * normalized_weights[0]
566
+
567
+ for i in range(1, len(state_dicts)):
568
+ state_dict_value = state_dicts[i][key].to(result[key])
569
+ result[key] += state_dict_value * normalized_weights[i]
570
+
571
+ return result
572
+
573
+
574
+ def group_files_by_folder(all_files):
575
+ grouped_files = {}
576
+
577
+ for file in all_files:
578
+ folder_name = os.path.basename(os.path.dirname(file))
579
+ if folder_name not in grouped_files:
580
+ grouped_files[folder_name] = []
581
+ grouped_files[folder_name].append(file)
582
+
583
+ list_of_lists = list(grouped_files.values())
584
+ return list_of_lists
585
+
586
+
587
+ def generate_timestamp():
588
+ now = datetime.datetime.now()
589
+ timestamp = now.strftime('%y%m%d_%H%M%S')
590
+ milliseconds = f"{int(now.microsecond / 1000):03d}"
591
+ random_number = random.randint(0, 9999)
592
+ return f"{timestamp}_{milliseconds}_{random_number}"
593
+
594
+
595
+ def write_PIL_image_with_png_info(image, metadata, path):
596
+ from PIL.PngImagePlugin import PngInfo
597
+
598
+ png_info = PngInfo()
599
+ for key, value in metadata.items():
600
+ png_info.add_text(key, value)
601
+
602
+ image.save(path, "PNG", pnginfo=png_info)
603
+ return image
604
+
605
+
606
+ def torch_safe_save(content, path):
607
+ torch.save(content, path + '_tmp')
608
+ os.replace(path + '_tmp', path)
609
+ return path
610
+
611
+
612
+ def move_optimizer_to_device(optimizer, device):
613
+ for state in optimizer.state.values():
614
+ for k, v in state.items():
615
+ if isinstance(v, torch.Tensor):
616
+ state[k] = v.to(device)
demo_utils/vae.py ADDED
@@ -0,0 +1,390 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from typing import List
2
+ from einops import rearrange
3
+ import tensorrt as trt
4
+ import torch
5
+ import torch.nn as nn
6
+
7
+ from demo_utils.constant import ALL_INPUTS_NAMES, ZERO_VAE_CACHE
8
+ from wan.modules.vae import AttentionBlock, CausalConv3d, RMS_norm, Upsample
9
+
10
+ CACHE_T = 2
11
+
12
+
13
+ class ResidualBlock(nn.Module):
14
+
15
+ def __init__(self, in_dim, out_dim, dropout=0.0):
16
+ super().__init__()
17
+ self.in_dim = in_dim
18
+ self.out_dim = out_dim
19
+
20
+ # layers
21
+ self.residual = nn.Sequential(
22
+ RMS_norm(in_dim, images=False), nn.SiLU(),
23
+ CausalConv3d(in_dim, out_dim, 3, padding=1),
24
+ RMS_norm(out_dim, images=False), nn.SiLU(), nn.Dropout(dropout),
25
+ CausalConv3d(out_dim, out_dim, 3, padding=1))
26
+ self.shortcut = CausalConv3d(in_dim, out_dim, 1) \
27
+ if in_dim != out_dim else nn.Identity()
28
+
29
+ def forward(self, x, feat_cache_1, feat_cache_2):
30
+ h = self.shortcut(x)
31
+ feat_cache = feat_cache_1
32
+ out_feat_cache = []
33
+ for layer in self.residual:
34
+ if isinstance(layer, CausalConv3d):
35
+ cache_x = x[:, :, -CACHE_T:, :, :].clone()
36
+ if cache_x.shape[2] < 2 and feat_cache is not None:
37
+ # cache last frame of last two chunk
38
+ cache_x = torch.cat([
39
+ feat_cache[:, :, -1, :, :].unsqueeze(2).to(
40
+ cache_x.device), cache_x
41
+ ],
42
+ dim=2)
43
+ x = layer(x, feat_cache)
44
+ out_feat_cache.append(cache_x)
45
+ feat_cache = feat_cache_2
46
+ else:
47
+ x = layer(x)
48
+ return x + h, *out_feat_cache
49
+
50
+
51
+ class Resample(nn.Module):
52
+
53
+ def __init__(self, dim, mode):
54
+ assert mode in ('none', 'upsample2d', 'upsample3d')
55
+ super().__init__()
56
+ self.dim = dim
57
+ self.mode = mode
58
+
59
+ # layers
60
+ if mode == 'upsample2d':
61
+ self.resample = nn.Sequential(
62
+ Upsample(scale_factor=(2., 2.), mode='nearest'),
63
+ nn.Conv2d(dim, dim // 2, 3, padding=1))
64
+ elif mode == 'upsample3d':
65
+ self.resample = nn.Sequential(
66
+ Upsample(scale_factor=(2., 2.), mode='nearest'),
67
+ nn.Conv2d(dim, dim // 2, 3, padding=1))
68
+ self.time_conv = CausalConv3d(
69
+ dim, dim * 2, (3, 1, 1), padding=(1, 0, 0))
70
+ else:
71
+ self.resample = nn.Identity()
72
+
73
+ def forward(self, x, is_first_frame, feat_cache):
74
+ if self.mode == 'upsample3d':
75
+ b, c, t, h, w = x.size()
76
+ # x, out_feat_cache = torch.cond(
77
+ # is_first_frame,
78
+ # lambda: (torch.cat([torch.zeros_like(x), x], dim=2), feat_cache.clone()),
79
+ # lambda: self.temporal_conv(x, feat_cache),
80
+ # )
81
+ # x, out_feat_cache = torch.cond(
82
+ # is_first_frame,
83
+ # lambda: (torch.cat([torch.zeros_like(x), x], dim=2), feat_cache.clone()),
84
+ # lambda: self.temporal_conv(x, feat_cache),
85
+ # )
86
+ x, out_feat_cache = self.temporal_conv(x, is_first_frame, feat_cache)
87
+ out_feat_cache = torch.cond(
88
+ is_first_frame,
89
+ lambda: feat_cache.clone().contiguous(),
90
+ lambda: out_feat_cache.clone().contiguous(),
91
+ )
92
+ # if is_first_frame:
93
+ # x = torch.cat([torch.zeros_like(x), x], dim=2)
94
+ # out_feat_cache = feat_cache.clone()
95
+ # else:
96
+ # x, out_feat_cache = self.temporal_conv(x, feat_cache)
97
+ else:
98
+ out_feat_cache = None
99
+ t = x.shape[2]
100
+ x = rearrange(x, 'b c t h w -> (b t) c h w')
101
+ x = self.resample(x)
102
+ x = rearrange(x, '(b t) c h w -> b c t h w', t=t)
103
+ return x, out_feat_cache
104
+
105
+ def temporal_conv(self, x, is_first_frame, feat_cache):
106
+ b, c, t, h, w = x.size()
107
+ cache_x = x[:, :, -CACHE_T:, :, :].clone()
108
+ if cache_x.shape[2] < 2 and feat_cache is not None:
109
+ cache_x = torch.cat([
110
+ torch.zeros_like(cache_x),
111
+ cache_x
112
+ ], dim=2)
113
+ x = torch.cond(
114
+ is_first_frame,
115
+ lambda: torch.cat([torch.zeros_like(x), x], dim=1).contiguous(),
116
+ lambda: self.time_conv(x, feat_cache).contiguous(),
117
+ )
118
+ # x = self.time_conv(x, feat_cache)
119
+ out_feat_cache = cache_x
120
+
121
+ x = x.reshape(b, 2, c, t, h, w)
122
+ x = torch.stack((x[:, 0, :, :, :, :], x[:, 1, :, :, :, :]),
123
+ 3)
124
+ x = x.reshape(b, c, t * 2, h, w)
125
+ return x.contiguous(), out_feat_cache.contiguous()
126
+
127
+ def init_weight(self, conv):
128
+ conv_weight = conv.weight
129
+ nn.init.zeros_(conv_weight)
130
+ c1, c2, t, h, w = conv_weight.size()
131
+ one_matrix = torch.eye(c1, c2)
132
+ init_matrix = one_matrix
133
+ nn.init.zeros_(conv_weight)
134
+ # conv_weight.data[:,:,-1,1,1] = init_matrix * 0.5
135
+ conv_weight.data[:, :, 1, 0, 0] = init_matrix # * 0.5
136
+ conv.weight.data.copy_(conv_weight)
137
+ nn.init.zeros_(conv.bias.data)
138
+
139
+ def init_weight2(self, conv):
140
+ conv_weight = conv.weight.data
141
+ nn.init.zeros_(conv_weight)
142
+ c1, c2, t, h, w = conv_weight.size()
143
+ init_matrix = torch.eye(c1 // 2, c2)
144
+ # init_matrix = repeat(init_matrix, 'o ... -> (o 2) ...').permute(1,0,2).contiguous().reshape(c1,c2)
145
+ conv_weight[:c1 // 2, :, -1, 0, 0] = init_matrix
146
+ conv_weight[c1 // 2:, :, -1, 0, 0] = init_matrix
147
+ conv.weight.data.copy_(conv_weight)
148
+ nn.init.zeros_(conv.bias.data)
149
+
150
+
151
+ class VAEDecoderWrapperSingle(nn.Module):
152
+ def __init__(self):
153
+ super().__init__()
154
+ self.decoder = VAEDecoder3d()
155
+ mean = [
156
+ -0.7571, -0.7089, -0.9113, 0.1075, -0.1745, 0.9653, -0.1517, 1.5508,
157
+ 0.4134, -0.0715, 0.5517, -0.3632, -0.1922, -0.9497, 0.2503, -0.2921
158
+ ]
159
+ std = [
160
+ 2.8184, 1.4541, 2.3275, 2.6558, 1.2196, 1.7708, 2.6052, 2.0743,
161
+ 3.2687, 2.1526, 2.8652, 1.5579, 1.6382, 1.1253, 2.8251, 1.9160
162
+ ]
163
+ self.mean = torch.tensor(mean, dtype=torch.float32)
164
+ self.std = torch.tensor(std, dtype=torch.float32)
165
+ self.z_dim = 16
166
+ self.conv2 = CausalConv3d(self.z_dim, self.z_dim, 1)
167
+
168
+ def forward(
169
+ self,
170
+ z: torch.Tensor,
171
+ is_first_frame: torch.Tensor,
172
+ *feat_cache: List[torch.Tensor]
173
+ ):
174
+ # from [batch_size, num_frames, num_channels, height, width]
175
+ # to [batch_size, num_channels, num_frames, height, width]
176
+ z = z.permute(0, 2, 1, 3, 4)
177
+ assert z.shape[2] == 1
178
+ feat_cache = list(feat_cache)
179
+ is_first_frame = is_first_frame.bool()
180
+
181
+ device, dtype = z.device, z.dtype
182
+ scale = [self.mean.to(device=device, dtype=dtype),
183
+ 1.0 / self.std.to(device=device, dtype=dtype)]
184
+
185
+ if isinstance(scale[0], torch.Tensor):
186
+ z = z / scale[1].view(1, self.z_dim, 1, 1, 1) + scale[0].view(
187
+ 1, self.z_dim, 1, 1, 1)
188
+ else:
189
+ z = z / scale[1] + scale[0]
190
+ x = self.conv2(z)
191
+ out, feat_cache = self.decoder(x, is_first_frame, feat_cache=feat_cache)
192
+ out = out.clamp_(-1, 1)
193
+ # from [batch_size, num_channels, num_frames, height, width]
194
+ # to [batch_size, num_frames, num_channels, height, width]
195
+ out = out.permute(0, 2, 1, 3, 4)
196
+ return out, feat_cache
197
+
198
+
199
+ class VAEDecoder3d(nn.Module):
200
+ def __init__(self,
201
+ dim=96,
202
+ z_dim=16,
203
+ dim_mult=[1, 2, 4, 4],
204
+ num_res_blocks=2,
205
+ attn_scales=[],
206
+ temperal_upsample=[True, True, False],
207
+ dropout=0.0):
208
+ super().__init__()
209
+ self.dim = dim
210
+ self.z_dim = z_dim
211
+ self.dim_mult = dim_mult
212
+ self.num_res_blocks = num_res_blocks
213
+ self.attn_scales = attn_scales
214
+ self.temperal_upsample = temperal_upsample
215
+ self.cache_t = 2
216
+ self.decoder_conv_num = 32
217
+
218
+ # dimensions
219
+ dims = [dim * u for u in [dim_mult[-1]] + dim_mult[::-1]]
220
+ scale = 1.0 / 2**(len(dim_mult) - 2)
221
+
222
+ # init block
223
+ self.conv1 = CausalConv3d(z_dim, dims[0], 3, padding=1)
224
+
225
+ # middle blocks
226
+ self.middle = nn.Sequential(
227
+ ResidualBlock(dims[0], dims[0], dropout), AttentionBlock(dims[0]),
228
+ ResidualBlock(dims[0], dims[0], dropout))
229
+
230
+ # upsample blocks
231
+ upsamples = []
232
+ for i, (in_dim, out_dim) in enumerate(zip(dims[:-1], dims[1:])):
233
+ # residual (+attention) blocks
234
+ if i == 1 or i == 2 or i == 3:
235
+ in_dim = in_dim // 2
236
+ for _ in range(num_res_blocks + 1):
237
+ upsamples.append(ResidualBlock(in_dim, out_dim, dropout))
238
+ if scale in attn_scales:
239
+ upsamples.append(AttentionBlock(out_dim))
240
+ in_dim = out_dim
241
+
242
+ # upsample block
243
+ if i != len(dim_mult) - 1:
244
+ mode = 'upsample3d' if temperal_upsample[i] else 'upsample2d'
245
+ upsamples.append(Resample(out_dim, mode=mode))
246
+ scale *= 2.0
247
+ self.upsamples = nn.Sequential(*upsamples)
248
+
249
+ # output blocks
250
+ self.head = nn.Sequential(
251
+ RMS_norm(out_dim, images=False), nn.SiLU(),
252
+ CausalConv3d(out_dim, 3, 3, padding=1))
253
+
254
+ def forward(
255
+ self,
256
+ x: torch.Tensor,
257
+ is_first_frame: torch.Tensor,
258
+ feat_cache: List[torch.Tensor]
259
+ ):
260
+ idx = 0
261
+ out_feat_cache = []
262
+
263
+ # conv1
264
+ cache_x = x[:, :, -self.cache_t:, :, :].clone()
265
+ if cache_x.shape[2] < 2 and feat_cache[idx] is not None:
266
+ # cache last frame of last two chunk
267
+ cache_x = torch.cat([
268
+ feat_cache[idx][:, :, -1, :, :].unsqueeze(2).to(
269
+ cache_x.device), cache_x
270
+ ],
271
+ dim=2)
272
+ x = self.conv1(x, feat_cache[idx])
273
+ out_feat_cache.append(cache_x)
274
+ idx += 1
275
+
276
+ # middle
277
+ for layer in self.middle:
278
+ if isinstance(layer, ResidualBlock) and feat_cache is not None:
279
+ x, out_feat_cache_1, out_feat_cache_2 = layer(x, feat_cache[idx], feat_cache[idx + 1])
280
+ idx += 2
281
+ out_feat_cache.append(out_feat_cache_1)
282
+ out_feat_cache.append(out_feat_cache_2)
283
+ else:
284
+ x = layer(x)
285
+
286
+ # upsamples
287
+ for layer in self.upsamples:
288
+ if isinstance(layer, Resample):
289
+ x, cache_x = layer(x, is_first_frame, feat_cache[idx])
290
+ if cache_x is not None:
291
+ out_feat_cache.append(cache_x)
292
+ idx += 1
293
+ else:
294
+ x, out_feat_cache_1, out_feat_cache_2 = layer(x, feat_cache[idx], feat_cache[idx + 1])
295
+ idx += 2
296
+ out_feat_cache.append(out_feat_cache_1)
297
+ out_feat_cache.append(out_feat_cache_2)
298
+
299
+ # head
300
+ for layer in self.head:
301
+ if isinstance(layer, CausalConv3d) and feat_cache is not None:
302
+ cache_x = x[:, :, -self.cache_t:, :, :].clone()
303
+ if cache_x.shape[2] < 2 and feat_cache[idx] is not None:
304
+ # cache last frame of last two chunk
305
+ cache_x = torch.cat([
306
+ feat_cache[idx][:, :, -1, :, :].unsqueeze(2).to(
307
+ cache_x.device), cache_x
308
+ ],
309
+ dim=2)
310
+ x = layer(x, feat_cache[idx])
311
+ out_feat_cache.append(cache_x)
312
+ idx += 1
313
+ else:
314
+ x = layer(x)
315
+ return x, out_feat_cache
316
+
317
+
318
+ class VAETRTWrapper():
319
+ def __init__(self):
320
+ TRT_LOGGER = trt.Logger(trt.Logger.WARNING)
321
+ with open("checkpoints/vae_decoder_int8.trt", "rb") as f, trt.Runtime(TRT_LOGGER) as rt:
322
+ self.engine: trt.ICudaEngine = rt.deserialize_cuda_engine(f.read())
323
+
324
+ self.context: trt.IExecutionContext = self.engine.create_execution_context()
325
+ self.stream = torch.cuda.current_stream().cuda_stream
326
+
327
+ # ──────────────────────────────
328
+ # 2️⃣ Feed the engine with tensors
329
+ # (name-based API in TRT β‰₯10)
330
+ # ──────────────────────────────
331
+ self.dtype_map = {
332
+ trt.float32: torch.float32,
333
+ trt.float16: torch.float16,
334
+ trt.int8: torch.int8,
335
+ trt.int32: torch.int32,
336
+ }
337
+ test_input = torch.zeros(1, 16, 1, 60, 104).cuda().half()
338
+ is_first_frame = torch.tensor(1.0).cuda().half()
339
+ test_cache_inputs = [c.cuda().half() for c in ZERO_VAE_CACHE]
340
+ test_inputs = [test_input, is_first_frame] + test_cache_inputs
341
+
342
+ # keep references so buffers stay alive
343
+ self.device_buffers, self.outputs = {}, []
344
+
345
+ # ---- inputs ----
346
+ for i, name in enumerate(ALL_INPUTS_NAMES):
347
+ tensor, scale = test_inputs[i], 1 / 127
348
+ tensor = self.quantize_if_needed(tensor, self.engine.get_tensor_dtype(name), scale)
349
+
350
+ # dynamic shapes
351
+ if -1 in self.engine.get_tensor_shape(name):
352
+ # new API :contentReference[oaicite:0]{index=0}
353
+ self.context.set_input_shape(name, tuple(tensor.shape))
354
+
355
+ # replaces bindings[] :contentReference[oaicite:1]{index=1}
356
+ self.context.set_tensor_address(name, int(tensor.data_ptr()))
357
+ self.device_buffers[name] = tensor # keep pointer alive
358
+
359
+ # ---- (after all input shapes are known) infer output shapes ----
360
+ # propagates shapes :contentReference[oaicite:2]{index=2}
361
+ self.context.infer_shapes()
362
+
363
+ for i in range(self.engine.num_io_tensors):
364
+ name = self.engine.get_tensor_name(i)
365
+ # replaces binding_is_input :contentReference[oaicite:3]{index=3}
366
+ if self.engine.get_tensor_mode(name) == trt.TensorIOMode.OUTPUT:
367
+ shape = tuple(self.context.get_tensor_shape(name))
368
+ dtype = self.dtype_map[self.engine.get_tensor_dtype(name)]
369
+ out = torch.empty(shape, dtype=dtype, device="cuda").contiguous()
370
+
371
+ self.context.set_tensor_address(name, int(out.data_ptr()))
372
+ self.outputs.append(out)
373
+ self.device_buffers[name] = out
374
+
375
+ # helper to quant-convert on the fly
376
+ def quantize_if_needed(self, t, expected_dtype, scale):
377
+ if expected_dtype == trt.int8 and t.dtype != torch.int8:
378
+ t = torch.clamp((t / scale).round(), -128, 127).to(torch.int8).contiguous()
379
+ return t # keep pointer alive
380
+
381
+ def forward(self, *test_inputs):
382
+ for i, name in enumerate(ALL_INPUTS_NAMES):
383
+ tensor, scale = test_inputs[i], 1 / 127
384
+ tensor = self.quantize_if_needed(tensor, self.engine.get_tensor_dtype(name), scale)
385
+ self.context.set_tensor_address(name, int(tensor.data_ptr()))
386
+ self.device_buffers[name] = tensor
387
+
388
+ self.context.execute_async_v3(stream_handle=self.stream)
389
+ torch.cuda.current_stream().synchronize()
390
+ return self.outputs
demo_utils/vae_block3.py ADDED
@@ -0,0 +1,291 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from typing import List
2
+ from einops import rearrange
3
+ import torch
4
+ import torch.nn as nn
5
+
6
+ from wan.modules.vae import AttentionBlock, CausalConv3d, RMS_norm, ResidualBlock, Upsample
7
+
8
+
9
+ class Resample(nn.Module):
10
+
11
+ def __init__(self, dim, mode):
12
+ assert mode in ('none', 'upsample2d', 'upsample3d', 'downsample2d',
13
+ 'downsample3d')
14
+ super().__init__()
15
+ self.dim = dim
16
+ self.mode = mode
17
+ self.cache_t = 2
18
+
19
+ # layers
20
+ if mode == 'upsample2d':
21
+ self.resample = nn.Sequential(
22
+ Upsample(scale_factor=(2., 2.), mode='nearest'),
23
+ nn.Conv2d(dim, dim // 2, 3, padding=1))
24
+ elif mode == 'upsample3d':
25
+ self.resample = nn.Sequential(
26
+ Upsample(scale_factor=(2., 2.), mode='nearest'),
27
+ nn.Conv2d(dim, dim // 2, 3, padding=1))
28
+ self.time_conv = CausalConv3d(
29
+ dim, dim * 2, (3, 1, 1), padding=(1, 0, 0))
30
+
31
+ elif mode == 'downsample2d':
32
+ self.resample = nn.Sequential(
33
+ nn.ZeroPad2d((0, 1, 0, 1)),
34
+ nn.Conv2d(dim, dim, 3, stride=(2, 2)))
35
+ elif mode == 'downsample3d':
36
+ self.resample = nn.Sequential(
37
+ nn.ZeroPad2d((0, 1, 0, 1)),
38
+ nn.Conv2d(dim, dim, 3, stride=(2, 2)))
39
+ self.time_conv = CausalConv3d(
40
+ dim, dim, (3, 1, 1), stride=(2, 1, 1), padding=(0, 0, 0))
41
+
42
+ else:
43
+ self.resample = nn.Identity()
44
+
45
+ def forward(self, x, feat_cache=None, feat_idx=[0]):
46
+ b, c, t, h, w = x.size()
47
+ if self.mode == 'upsample3d':
48
+ if feat_cache is not None:
49
+ idx = feat_idx[0]
50
+ if feat_cache[idx] is None:
51
+ feat_cache[idx] = 'Rep'
52
+ feat_idx[0] += 1
53
+ else:
54
+
55
+ cache_x = x[:, :, -self.cache_t:, :, :].clone()
56
+ if cache_x.shape[2] < 2 and feat_cache[
57
+ idx] is not None and feat_cache[idx] != 'Rep':
58
+ # cache last frame of last two chunk
59
+ cache_x = torch.cat([
60
+ feat_cache[idx][:, :, -1, :, :].unsqueeze(2).to(
61
+ cache_x.device), cache_x
62
+ ],
63
+ dim=2)
64
+ if cache_x.shape[2] < 2 and feat_cache[
65
+ idx] is not None and feat_cache[idx] == 'Rep':
66
+ cache_x = torch.cat([
67
+ torch.zeros_like(cache_x).to(cache_x.device),
68
+ cache_x
69
+ ],
70
+ dim=2)
71
+ if feat_cache[idx] == 'Rep':
72
+ x = self.time_conv(x)
73
+ else:
74
+ x = self.time_conv(x, feat_cache[idx])
75
+ feat_cache[idx] = cache_x
76
+ feat_idx[0] += 1
77
+
78
+ x = x.reshape(b, 2, c, t, h, w)
79
+ x = torch.stack((x[:, 0, :, :, :, :], x[:, 1, :, :, :, :]),
80
+ 3)
81
+ x = x.reshape(b, c, t * 2, h, w)
82
+ t = x.shape[2]
83
+ x = rearrange(x, 'b c t h w -> (b t) c h w')
84
+ x = self.resample(x)
85
+ x = rearrange(x, '(b t) c h w -> b c t h w', t=t)
86
+
87
+ if self.mode == 'downsample3d':
88
+ if feat_cache is not None:
89
+ idx = feat_idx[0]
90
+ if feat_cache[idx] is None:
91
+ feat_cache[idx] = x.clone()
92
+ feat_idx[0] += 1
93
+ else:
94
+
95
+ cache_x = x[:, :, -1:, :, :].clone()
96
+ # if cache_x.shape[2] < 2 and feat_cache[idx] is not None and feat_cache[idx]!='Rep':
97
+ # # cache last frame of last two chunk
98
+ # cache_x = torch.cat([feat_cache[idx][:, :, -1, :, :].unsqueeze(2).to(cache_x.device), cache_x], dim=2)
99
+
100
+ x = self.time_conv(
101
+ torch.cat([feat_cache[idx][:, :, -1:, :, :], x], 2))
102
+ feat_cache[idx] = cache_x
103
+ feat_idx[0] += 1
104
+ return x
105
+
106
+ def init_weight(self, conv):
107
+ conv_weight = conv.weight
108
+ nn.init.zeros_(conv_weight)
109
+ c1, c2, t, h, w = conv_weight.size()
110
+ one_matrix = torch.eye(c1, c2)
111
+ init_matrix = one_matrix
112
+ nn.init.zeros_(conv_weight)
113
+ # conv_weight.data[:,:,-1,1,1] = init_matrix * 0.5
114
+ conv_weight.data[:, :, 1, 0, 0] = init_matrix # * 0.5
115
+ conv.weight.data.copy_(conv_weight)
116
+ nn.init.zeros_(conv.bias.data)
117
+
118
+ def init_weight2(self, conv):
119
+ conv_weight = conv.weight.data
120
+ nn.init.zeros_(conv_weight)
121
+ c1, c2, t, h, w = conv_weight.size()
122
+ init_matrix = torch.eye(c1 // 2, c2)
123
+ # init_matrix = repeat(init_matrix, 'o ... -> (o 2) ...').permute(1,0,2).contiguous().reshape(c1,c2)
124
+ conv_weight[:c1 // 2, :, -1, 0, 0] = init_matrix
125
+ conv_weight[c1 // 2:, :, -1, 0, 0] = init_matrix
126
+ conv.weight.data.copy_(conv_weight)
127
+ nn.init.zeros_(conv.bias.data)
128
+
129
+
130
+ class VAEDecoderWrapper(nn.Module):
131
+ def __init__(self):
132
+ super().__init__()
133
+ self.decoder = VAEDecoder3d()
134
+ mean = [
135
+ -0.7571, -0.7089, -0.9113, 0.1075, -0.1745, 0.9653, -0.1517, 1.5508,
136
+ 0.4134, -0.0715, 0.5517, -0.3632, -0.1922, -0.9497, 0.2503, -0.2921
137
+ ]
138
+ std = [
139
+ 2.8184, 1.4541, 2.3275, 2.6558, 1.2196, 1.7708, 2.6052, 2.0743,
140
+ 3.2687, 2.1526, 2.8652, 1.5579, 1.6382, 1.1253, 2.8251, 1.9160
141
+ ]
142
+ self.mean = torch.tensor(mean, dtype=torch.float32)
143
+ self.std = torch.tensor(std, dtype=torch.float32)
144
+ self.z_dim = 16
145
+ self.conv2 = CausalConv3d(self.z_dim, self.z_dim, 1)
146
+
147
+ def forward(
148
+ self,
149
+ z: torch.Tensor,
150
+ *feat_cache: List[torch.Tensor]
151
+ ):
152
+ # from [batch_size, num_frames, num_channels, height, width]
153
+ # to [batch_size, num_channels, num_frames, height, width]
154
+ z = z.permute(0, 2, 1, 3, 4)
155
+ feat_cache = list(feat_cache)
156
+ print("Length of feat_cache: ", len(feat_cache))
157
+
158
+ device, dtype = z.device, z.dtype
159
+ scale = [self.mean.to(device=device, dtype=dtype),
160
+ 1.0 / self.std.to(device=device, dtype=dtype)]
161
+
162
+ if isinstance(scale[0], torch.Tensor):
163
+ z = z / scale[1].view(1, self.z_dim, 1, 1, 1) + scale[0].view(
164
+ 1, self.z_dim, 1, 1, 1)
165
+ else:
166
+ z = z / scale[1] + scale[0]
167
+ iter_ = z.shape[2]
168
+ x = self.conv2(z)
169
+ for i in range(iter_):
170
+ if i == 0:
171
+ out, feat_cache = self.decoder(
172
+ x[:, :, i:i + 1, :, :],
173
+ feat_cache=feat_cache)
174
+ else:
175
+ out_, feat_cache = self.decoder(
176
+ x[:, :, i:i + 1, :, :],
177
+ feat_cache=feat_cache)
178
+ out = torch.cat([out, out_], 2)
179
+
180
+ out = out.float().clamp_(-1, 1)
181
+ # from [batch_size, num_channels, num_frames, height, width]
182
+ # to [batch_size, num_frames, num_channels, height, width]
183
+ out = out.permute(0, 2, 1, 3, 4)
184
+ return out, feat_cache
185
+
186
+
187
+ class VAEDecoder3d(nn.Module):
188
+ def __init__(self,
189
+ dim=96,
190
+ z_dim=16,
191
+ dim_mult=[1, 2, 4, 4],
192
+ num_res_blocks=2,
193
+ attn_scales=[],
194
+ temperal_upsample=[True, True, False],
195
+ dropout=0.0):
196
+ super().__init__()
197
+ self.dim = dim
198
+ self.z_dim = z_dim
199
+ self.dim_mult = dim_mult
200
+ self.num_res_blocks = num_res_blocks
201
+ self.attn_scales = attn_scales
202
+ self.temperal_upsample = temperal_upsample
203
+ self.cache_t = 2
204
+ self.decoder_conv_num = 32
205
+
206
+ # dimensions
207
+ dims = [dim * u for u in [dim_mult[-1]] + dim_mult[::-1]]
208
+ scale = 1.0 / 2**(len(dim_mult) - 2)
209
+
210
+ # init block
211
+ self.conv1 = CausalConv3d(z_dim, dims[0], 3, padding=1)
212
+
213
+ # middle blocks
214
+ self.middle = nn.Sequential(
215
+ ResidualBlock(dims[0], dims[0], dropout), AttentionBlock(dims[0]),
216
+ ResidualBlock(dims[0], dims[0], dropout))
217
+
218
+ # upsample blocks
219
+ upsamples = []
220
+ for i, (in_dim, out_dim) in enumerate(zip(dims[:-1], dims[1:])):
221
+ # residual (+attention) blocks
222
+ if i == 1 or i == 2 or i == 3:
223
+ in_dim = in_dim // 2
224
+ for _ in range(num_res_blocks + 1):
225
+ upsamples.append(ResidualBlock(in_dim, out_dim, dropout))
226
+ if scale in attn_scales:
227
+ upsamples.append(AttentionBlock(out_dim))
228
+ in_dim = out_dim
229
+
230
+ # upsample block
231
+ if i != len(dim_mult) - 1:
232
+ mode = 'upsample3d' if temperal_upsample[i] else 'upsample2d'
233
+ upsamples.append(Resample(out_dim, mode=mode))
234
+ scale *= 2.0
235
+ self.upsamples = nn.Sequential(*upsamples)
236
+
237
+ # output blocks
238
+ self.head = nn.Sequential(
239
+ RMS_norm(out_dim, images=False), nn.SiLU(),
240
+ CausalConv3d(out_dim, 3, 3, padding=1))
241
+
242
+ def forward(
243
+ self,
244
+ x: torch.Tensor,
245
+ feat_cache: List[torch.Tensor]
246
+ ):
247
+ feat_idx = [0]
248
+
249
+ # conv1
250
+ idx = feat_idx[0]
251
+ cache_x = x[:, :, -self.cache_t:, :, :].clone()
252
+ if cache_x.shape[2] < 2 and feat_cache[idx] is not None:
253
+ # cache last frame of last two chunk
254
+ cache_x = torch.cat([
255
+ feat_cache[idx][:, :, -1, :, :].unsqueeze(2).to(
256
+ cache_x.device), cache_x
257
+ ],
258
+ dim=2)
259
+ x = self.conv1(x, feat_cache[idx])
260
+ feat_cache[idx] = cache_x
261
+ feat_idx[0] += 1
262
+
263
+ # middle
264
+ for layer in self.middle:
265
+ if isinstance(layer, ResidualBlock) and feat_cache is not None:
266
+ x = layer(x, feat_cache, feat_idx)
267
+ else:
268
+ x = layer(x)
269
+
270
+ # upsamples
271
+ for layer in self.upsamples:
272
+ x = layer(x, feat_cache, feat_idx)
273
+
274
+ # head
275
+ for layer in self.head:
276
+ if isinstance(layer, CausalConv3d) and feat_cache is not None:
277
+ idx = feat_idx[0]
278
+ cache_x = x[:, :, -self.cache_t:, :, :].clone()
279
+ if cache_x.shape[2] < 2 and feat_cache[idx] is not None:
280
+ # cache last frame of last two chunk
281
+ cache_x = torch.cat([
282
+ feat_cache[idx][:, :, -1, :, :].unsqueeze(2).to(
283
+ cache_x.device), cache_x
284
+ ],
285
+ dim=2)
286
+ x = layer(x, feat_cache[idx])
287
+ feat_cache[idx] = cache_x
288
+ feat_idx[0] += 1
289
+ else:
290
+ x = layer(x)
291
+ return x, feat_cache
demo_utils/vae_torch2trt.py ADDED
@@ -0,0 +1,308 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # ---- INT8 (optional) ----
2
+ from demo_utils.vae import (
3
+ VAEDecoderWrapperSingle, # main nn.Module
4
+ ZERO_VAE_CACHE # helper constants shipped with your code base
5
+ )
6
+ import pycuda.driver as cuda # ← add
7
+ import pycuda.autoinit # noqa
8
+
9
+ import sys
10
+ from pathlib import Path
11
+
12
+ import torch
13
+ import tensorrt as trt
14
+
15
+ from utils.dataset import ShardingLMDBDataset
16
+
17
+ data_path = "/mnt/localssd/wanx_14B_shift-3.0_cfg-5.0_lmdb_oneshard"
18
+ dataset = ShardingLMDBDataset(data_path, max_pair=int(1e8))
19
+ dataloader = torch.utils.data.DataLoader(
20
+ dataset,
21
+ batch_size=1,
22
+ num_workers=0
23
+ )
24
+
25
+ # ─────────────────────────────────────────────────────────
26
+ # 1️⃣ Bring the PyTorch model into scope
27
+ # (all code you pasted lives in `vae_decoder.py`)
28
+ # ─────────────────────────────────────────────────────────
29
+
30
+ # --- dummy tensors (exact shapes you posted) ---
31
+ dummy_input = torch.randn(1, 1, 16, 60, 104).half().cuda()
32
+ is_first_frame = torch.tensor([1.0], device="cuda", dtype=torch.float16)
33
+ dummy_cache_input = [
34
+ torch.randn(*s.shape).half().cuda() if isinstance(s, torch.Tensor) else s
35
+ for s in ZERO_VAE_CACHE # keep exactly the same ordering
36
+ ]
37
+ inputs = [dummy_input, is_first_frame, *dummy_cache_input]
38
+
39
+ # ─────────────────────────────────────────────────────────
40
+ # 2️⃣ Export β†’ ONNX
41
+ # ─────────────────────────────────────────────────────────
42
+ model = VAEDecoderWrapperSingle().half().cuda().eval()
43
+
44
+ vae_state_dict = torch.load('wan_models/Wan2.1-T2V-1.3B/Wan2.1_VAE.pth', map_location="cpu")
45
+ decoder_state_dict = {}
46
+ for key, value in vae_state_dict.items():
47
+ if 'decoder.' in key or 'conv2' in key:
48
+ decoder_state_dict[key] = value
49
+ model.load_state_dict(decoder_state_dict)
50
+ model = model.half().cuda().eval() # only batch dim dynamic
51
+
52
+ onnx_path = Path("vae_decoder.onnx")
53
+ feat_names = [f"vae_cache_{i}" for i in range(len(dummy_cache_input))]
54
+ all_inputs_names = ["z", "use_cache"] + feat_names
55
+
56
+ with torch.inference_mode():
57
+ torch.onnx.export(
58
+ model,
59
+ tuple(inputs), # must be a tuple
60
+ onnx_path.as_posix(),
61
+ input_names=all_inputs_names,
62
+ output_names=["rgb_out", "cache_out"],
63
+ opset_version=17,
64
+ do_constant_folding=True,
65
+ dynamo=True
66
+ )
67
+ print(f"βœ… ONNX graph saved to {onnx_path.resolve()}")
68
+
69
+ # (Optional) quick sanity-check with ONNX-Runtime
70
+ try:
71
+ import onnxruntime as ort
72
+ sess = ort.InferenceSession(onnx_path.as_posix(),
73
+ providers=["CUDAExecutionProvider"])
74
+ ort_inputs = {n: t.cpu().numpy() for n, t in zip(all_inputs_names, inputs)}
75
+ _ = sess.run(None, ort_inputs)
76
+ print("βœ… ONNX graph is executable")
77
+ except Exception as e:
78
+ print("⚠️ ONNX check failed:", e)
79
+
80
+ # ─────────────────────────────────────────────────────────
81
+ # 3️⃣ Build the TensorRT engine
82
+ # ─────────────────────────────────────────────────────────
83
+ TRT_LOGGER = trt.Logger(trt.Logger.WARNING)
84
+ builder = trt.Builder(TRT_LOGGER)
85
+ network = builder.create_network(
86
+ 1 << int(trt.NetworkDefinitionCreationFlag.EXPLICIT_BATCH))
87
+ parser = trt.OnnxParser(network, TRT_LOGGER)
88
+
89
+ with open(onnx_path, "rb") as f:
90
+ if not parser.parse(f.read()):
91
+ for i in range(parser.num_errors):
92
+ print(parser.get_error(i))
93
+ sys.exit("❌ ONNX β†’ TRT parsing failed")
94
+
95
+ config = builder.create_builder_config()
96
+
97
+
98
+ def set_workspace(config, bytes_):
99
+ """Version-agnostic workspace limit."""
100
+ if hasattr(config, "max_workspace_size"): # TRT 8 / 9
101
+ config.max_workspace_size = bytes_
102
+ else: # TRT 10+
103
+ config.set_memory_pool_limit(trt.MemoryPoolType.WORKSPACE, bytes_)
104
+
105
+
106
+ # …
107
+ config = builder.create_builder_config()
108
+ set_workspace(config, 4 << 30) # 4 GB
109
+ # 4 GB
110
+
111
+ if builder.platform_has_fast_fp16:
112
+ config.set_flag(trt.BuilderFlag.FP16)
113
+
114
+ # ---- INT8 (optional) ----
115
+ # provide a calibrator if you need an INT8 engine; comment this
116
+ # block if you only care about FP16.
117
+ # ─────────────────────────────────────────────────────────
118
+ # helper: version-agnostic workspace limit
119
+ # ─────────────────────────────────────────────────────────
120
+
121
+
122
+ def set_workspace(config: trt.IBuilderConfig, bytes_: int = 4 << 30):
123
+ """
124
+ TRT < 10.x β†’ config.max_workspace_size
125
+ TRT β‰₯ 10.x β†’ config.set_memory_pool_limit(...)
126
+ """
127
+ if hasattr(config, "max_workspace_size"): # TRT 8 / 9
128
+ config.max_workspace_size = bytes_
129
+ else: # TRT 10+
130
+ config.set_memory_pool_limit(trt.MemoryPoolType.WORKSPACE,
131
+ bytes_)
132
+
133
+ # ─────────────────────────────────────────────────────────
134
+ # (optional) INT-8 calibrator
135
+ # ─────────────────────────────────────────────────────────
136
+ # β€Ό Only keep this block if you really need INT-8 β€Ό # gracefully skip if PyCUDA not present
137
+
138
+
139
+ class VAECalibrator(trt.IInt8EntropyCalibrator2):
140
+ def __init__(self, loader, cache="calibration.cache", max_batches=10):
141
+ super().__init__()
142
+ self.loader = iter(loader)
143
+ self.batch_size = loader.batch_size or 1
144
+ self.max_batches = max_batches
145
+ self.count = 0
146
+ self.cache_file = cache
147
+ self.stream = cuda.Stream()
148
+ self.dev_ptrs = {}
149
+
150
+ # --- TRT 10 needs BOTH spellings ---
151
+ def get_batch_size(self):
152
+ return self.batch_size
153
+
154
+ def getBatchSize(self):
155
+ return self.batch_size
156
+
157
+ def get_batch(self, names):
158
+ if self.count >= self.max_batches:
159
+ return None
160
+
161
+ # Randomly sample a number from 1 to 10
162
+ import random
163
+ vae_idx = random.randint(0, 10)
164
+ data = next(self.loader)
165
+
166
+ latent = data['ode_latent'][0][:, :1]
167
+ is_first_frame = torch.tensor([1.0], device="cuda", dtype=torch.float16)
168
+ feat_cache = ZERO_VAE_CACHE
169
+ for i in range(vae_idx):
170
+ inputs = [latent, is_first_frame, *feat_cache]
171
+ with torch.inference_mode():
172
+ outputs = model(*inputs)
173
+ latent = data['ode_latent'][0][:, i + 1:i + 2]
174
+ is_first_frame = torch.tensor([0.0], device="cuda", dtype=torch.float16)
175
+ feat_cache = outputs[1:]
176
+
177
+ # -------- ensure context is current --------
178
+ z_np = latent.cpu().numpy().astype('float32')
179
+
180
+ ptrs = [] # list[int] – one entry per name
181
+ for name in names: # <-- match TRT's binding order
182
+ if name == "z":
183
+ arr = z_np
184
+ elif name == "use_cache":
185
+ arr = is_first_frame.cpu().numpy().astype('float32')
186
+ else:
187
+ idx = int(name.split('_')[-1]) # "vae_cache_17" -> 17
188
+ arr = feat_cache[idx].cpu().numpy().astype('float32')
189
+
190
+ if name not in self.dev_ptrs:
191
+ self.dev_ptrs[name] = cuda.mem_alloc(arr.nbytes)
192
+
193
+ cuda.memcpy_htod_async(self.dev_ptrs[name], arr, self.stream)
194
+ ptrs.append(int(self.dev_ptrs[name])) # ***int() is required***
195
+
196
+ self.stream.synchronize()
197
+ self.count += 1
198
+ print(f"Calibration batch {self.count}/{self.max_batches}")
199
+ return ptrs
200
+
201
+ # --- calibration-cache helpers (both spellings) ---
202
+ def read_calibration_cache(self):
203
+ try:
204
+ with open(self.cache_file, "rb") as f:
205
+ return f.read()
206
+ except FileNotFoundError:
207
+ return None
208
+
209
+ def readCalibrationCache(self):
210
+ return self.read_calibration_cache()
211
+
212
+ def write_calibration_cache(self, cache):
213
+ with open(self.cache_file, "wb") as f:
214
+ f.write(cache)
215
+
216
+ def writeCalibrationCache(self, cache):
217
+ self.write_calibration_cache(cache)
218
+
219
+
220
+ # ─────────────────────────────────────────────────────────
221
+ # Builder-config + optimisation profile
222
+ # ─────────────────────────────────────────────────────────
223
+ config = builder.create_builder_config()
224
+ set_workspace(config, 4 << 30) # 4 GB
225
+
226
+ # β–Ί enable FP16 if possible
227
+ if builder.platform_has_fast_fp16:
228
+ config.set_flag(trt.BuilderFlag.FP16)
229
+
230
+ # β–Ί enable INT-8 (delete this block if you don’t need it)
231
+ if cuda is not None:
232
+ config.set_flag(trt.BuilderFlag.INT8)
233
+ # supply any representative batch you like – here we reuse the latent z
234
+ calib = VAECalibrator(dataloader)
235
+ # TRT-10 renamed the setter:
236
+ if hasattr(config, "set_int8_calibrator"): # TRT 10+
237
+ config.set_int8_calibrator(calib)
238
+ else: # TRT ≀ 9
239
+ config.int8_calibrator = calib
240
+
241
+ # ---- optimisation profile ----
242
+ profile = builder.create_optimization_profile()
243
+ profile.set_shape(all_inputs_names[0], # latent z
244
+ min=(1, 1, 16, 60, 104),
245
+ opt=(1, 1, 16, 60, 104),
246
+ max=(1, 1, 16, 60, 104))
247
+ profile.set_shape("use_cache", # scalar flag
248
+ min=(1,), opt=(1,), max=(1,))
249
+ for name, tensor in zip(all_inputs_names[2:], dummy_cache_input):
250
+ profile.set_shape(name, tensor.shape, tensor.shape, tensor.shape)
251
+
252
+ config.add_optimization_profile(profile)
253
+
254
+ # ─────────────────────────────────────────────────────────
255
+ # Build the engine (API changed in TRT-10)
256
+ # ─────────────────────────────────────────────────────────
257
+ print("βš™οΈ Building engine … (can take a minute)")
258
+
259
+ if hasattr(builder, "build_serialized_network"): # TRT 10+
260
+ serialized_engine = builder.build_serialized_network(network, config)
261
+ assert serialized_engine is not None, "build_serialized_network() failed"
262
+ plan_path = Path("checkpoints/vae_decoder_int8.trt")
263
+ plan_path.write_bytes(serialized_engine)
264
+ engine_bytes = serialized_engine # keep for smoke-test
265
+ else: # TRT ≀ 9
266
+ engine = builder.build_engine(network, config)
267
+ assert engine is not None, "build_engine() returned None"
268
+ plan_path = Path("checkpoints/vae_decoder_int8.trt")
269
+ plan_path.write_bytes(engine.serialize())
270
+ engine_bytes = engine.serialize()
271
+
272
+ print(f"βœ… TensorRT engine written to {plan_path.resolve()}")
273
+
274
+ # ─────────────────────────────────────────────────────────
275
+ # 4️⃣ Quick smoke-test with the brand-new engine
276
+ # ─────────────────────────────────────────────────────────
277
+ with trt.Runtime(TRT_LOGGER) as rt:
278
+ engine = rt.deserialize_cuda_engine(engine_bytes)
279
+ context = engine.create_execution_context()
280
+ stream = torch.cuda.current_stream().cuda_stream
281
+
282
+ # pre-allocate device buffers once
283
+ device_buffers, outputs = {}, []
284
+ dtype_map = {trt.float32: torch.float32,
285
+ trt.float16: torch.float16,
286
+ trt.int8: torch.int8,
287
+ trt.int32: torch.int32}
288
+
289
+ for name, tensor in zip(all_inputs_names, inputs):
290
+ if -1 in engine.get_tensor_shape(name): # dynamic input
291
+ context.set_input_shape(name, tensor.shape)
292
+ context.set_tensor_address(name, int(tensor.data_ptr()))
293
+ device_buffers[name] = tensor
294
+
295
+ context.infer_shapes() # propagate β‡’ outputs
296
+ for i in range(engine.num_io_tensors):
297
+ name = engine.get_tensor_name(i)
298
+ if engine.get_tensor_mode(name) == trt.TensorIOMode.OUTPUT:
299
+ shape = tuple(context.get_tensor_shape(name))
300
+ dtype = dtype_map[engine.get_tensor_dtype(name)]
301
+ out = torch.empty(shape, dtype=dtype, device="cuda")
302
+ context.set_tensor_address(name, int(out.data_ptr()))
303
+ outputs.append(out)
304
+ print(f"output {name} shape: {shape}")
305
+
306
+ context.execute_async_v3(stream_handle=stream)
307
+ torch.cuda.current_stream().synchronize()
308
+ print("βœ… TRT execution OK – first output shape:", outputs[0].shape)