返回 JoyAI-Echo
memory_bidirectional_pipeline.py
根目录 / echo_longvideo / ltx-distillation / src / ltx_distillation / inference / memory_bidirectional_pipeline.py
1 """
2 Bidirectional pipelines for memory-conditioned DMD.
3 """
4
5 from __future__ import annotations
6
7 from typing import Any, Callable, Dict, Optional, Tuple
8
9 import torch
10 import torch.nn as nn
11
12
13 class BidirectionalMemoryVideoTrajectoryPipeline:
14 """
15 Few-step backward simulation for video-only memory-conditioned DMD.
16 """
17
18 def __init__(
19 self,
20 generator: nn.Module,
21 add_noise_fn,
22 denoising_sigmas: torch.Tensor,
23 memory_downscale_factor: int = 1,
24 audio_latent_clamp: float = 0.0,
25 ) -> None:
26 self.generator = generator
27 self.add_noise_fn = add_noise_fn
28 self.denoising_sigmas = denoising_sigmas
29 self.memory_downscale_factor = int(memory_downscale_factor)
30 self.audio_latent_clamp = float(audio_latent_clamp)
31
32 @torch.no_grad()
33 def inference_with_trajectory(
34 self,
35 video_noise: torch.Tensor,
36 conditional_dict: Dict[str, Any],
37 memory_video: torch.Tensor,
38 ) -> torch.Tensor:
39 batch_size = video_noise.shape[0]
40 num_frames = video_noise.shape[1]
41 device = video_noise.device
42 dtype = video_noise.dtype
43 memory_video = memory_video.to(device=device, dtype=dtype)
44
45 trajectory = [video_noise]
46 noisy_video = video_noise
47
48 for idx, sigma in enumerate(self.denoising_sigmas[:-1]):
49 video_sigma = sigma * torch.ones([batch_size, num_frames], device=device, dtype=dtype)
50 pred_video, _ = self.generator(
51 noisy_image_or_video=noisy_video,
52 conditional_dict=conditional_dict,
53 timestep=video_sigma,
54 noisy_audio=None,
55 audio_timestep=None,
56 memory_video=memory_video,
57 memory_downscale_factor=self.memory_downscale_factor,
58 )
59 pred_video = pred_video.to(dtype=dtype)
60
61 next_sigma = self.denoising_sigmas[idx + 1]
62 if next_sigma > 0:
63 fresh_noise = torch.randn_like(video_noise)
64 next_video_sigma = next_sigma * torch.ones([batch_size, num_frames], device=device, dtype=dtype)
65 noisy_video = self.add_noise_fn(
66 pred_video.flatten(0, 1),
67 fresh_noise.flatten(0, 1),
68 next_video_sigma.flatten(0, 1),
69 ).unflatten(0, (batch_size, num_frames)).to(dtype=dtype)
70 else:
71 noisy_video = pred_video
72
73 trajectory.append(noisy_video)
74
75 return torch.stack(trajectory, dim=1)
76
77
78 class BidirectionalMemoryVideoInferencePipeline:
79 """
80 Few-step inference pipeline for video-only memory-conditioned DMD.
81 """
82
83 def __init__(
84 self,
85 generator: nn.Module,
86 add_noise_fn,
87 denoising_sigmas: torch.Tensor,
88 memory_downscale_factor: int = 1,
89 trace_fn: Optional[Callable[[Dict[str, Any]], None]] = None,
90 ) -> None:
91 self.generator = generator
92 self.add_noise_fn = add_noise_fn
93 self.denoising_sigmas = denoising_sigmas
94 self.memory_downscale_factor = int(memory_downscale_factor)
95 self.trace_fn = trace_fn
96
97 def _emit_trace(
98 self,
99 event: str,
100 tensor: torch.Tensor,
101 *,
102 sigma_idx: Optional[int] = None,
103 sigma: Optional[torch.Tensor] = None,
104 ) -> None:
105 if self.trace_fn is None:
106 return
107 values = tensor.detach().float()
108 if values.numel() == 0:
109 stats = {
110 "mean": 0.0,
111 "std": 0.0,
112 "min": 0.0,
113 "max": 0.0,
114 "absmax": 0.0,
115 "nonzero_frac": 0.0,
116 }
117 else:
118 stats = {
119 "mean": values.mean().item(),
120 "std": values.std(unbiased=False).item() if values.numel() > 1 else 0.0,
121 "min": values.min().item(),
122 "max": values.max().item(),
123 "absmax": values.abs().max().item(),
124 "nonzero_frac": values.ne(0).float().mean().item(),
125 }
126 payload: Dict[str, Any] = {
127 "phase": "bootstrap",
128 "event": event,
129 "shape": list(tensor.shape),
130 **stats,
131 }
132 if sigma_idx is not None:
133 payload["sigma_idx"] = int(sigma_idx)
134 if sigma is not None:
135 payload["sigma"] = float(sigma.detach().float().item())
136 self.trace_fn(payload)
137
138 @torch.no_grad()
139 def generate(
140 self,
141 video_shape: Tuple[int, ...],
142 conditional_dict: Dict[str, Any],
143 memory_video: torch.Tensor,
144 seed: Optional[int] = None,
145 ) -> torch.Tensor:
146 batch_size = video_shape[0]
147 num_frames = video_shape[1]
148
149 if seed is not None:
150 torch.manual_seed(seed)
151
152 device = next(self.generator.parameters()).device
153 dtype = next(self.generator.parameters()).dtype
154
155 video = torch.randn(video_shape, device=device, dtype=dtype)
156 memory_video = memory_video.to(device=device, dtype=dtype)
157 self._emit_trace("initial_noise", video)
158
159 for idx, sigma in enumerate(self.denoising_sigmas[:-1]):
160 video_sigma = sigma * torch.ones([batch_size, num_frames], device=device, dtype=dtype)
161
162 pred_video, _ = self.generator(
163 noisy_image_or_video=video,
164 conditional_dict=conditional_dict,
165 timestep=video_sigma,
166 noisy_audio=None,
167 audio_timestep=None,
168 memory_video=memory_video,
169 memory_downscale_factor=self.memory_downscale_factor,
170 )
171 pred_video = pred_video.to(dtype=dtype)
172 self._emit_trace("pred_x0", pred_video, sigma_idx=idx, sigma=sigma)
173
174 next_sigma = self.denoising_sigmas[idx + 1]
175 if next_sigma > 0:
176 fresh_noise = torch.randn_like(video)
177 next_video_sigma = next_sigma * torch.ones([batch_size, num_frames], device=device, dtype=dtype)
178 video = self.add_noise_fn(
179 pred_video.flatten(0, 1),
180 fresh_noise.flatten(0, 1),
181 next_video_sigma.flatten(0, 1),
182 ).unflatten(0, (batch_size, num_frames)).to(dtype=dtype)
183 else:
184 video = pred_video
185 self._emit_trace("updated_video", video, sigma_idx=idx + 1, sigma=next_sigma)
186
187 return video
188
189
190 class BidirectionalMemoryAVTrajectoryPipeline:
191 """
192 Few-step backward simulation for video-memory-conditioned AV DMD.
193 """
194
195 def __init__(
196 self,
197 generator: nn.Module,
198 add_noise_fn,
199 denoising_sigmas: torch.Tensor,
200 memory_downscale_factor: int = 1,
201 audio_latent_clamp: float = 0.0,
202 ) -> None:
203 self.generator = generator
204 self.add_noise_fn = add_noise_fn
205 self.denoising_sigmas = denoising_sigmas
206 self.memory_downscale_factor = int(memory_downscale_factor)
207 self.audio_latent_clamp = float(audio_latent_clamp)
208
209 @torch.no_grad()
210 def inference_with_trajectory(
211 self,
212 video_noise: torch.Tensor,
213 audio_noise: torch.Tensor,
214 conditional_dict: Dict[str, Any],
215 memory_video: torch.Tensor,
216 memory_audio: Optional[torch.Tensor] = None,
217 memory_audio_timestep: Optional[torch.Tensor] = None,
218 memory_audio_segment_lengths: tuple[tuple[int, ...], ...] | None = None,
219 memory_position_mode: str = "reference",
220 memory_position_offset: float = 0.0,
221 memory_position_slot_stride: float = 50.0,
222 ) -> Tuple[torch.Tensor, torch.Tensor]:
223 batch_size = video_noise.shape[0]
224 num_video_frames = video_noise.shape[1]
225 num_audio_frames = audio_noise.shape[1]
226 device = video_noise.device
227 dtype = video_noise.dtype
228 memory_video = memory_video.to(device=device, dtype=dtype)
229 if memory_audio is not None:
230 memory_audio = memory_audio.to(device=device, dtype=dtype)
231 if memory_audio_timestep is not None:
232 memory_audio_timestep = memory_audio_timestep.to(device=device, dtype=dtype)
233
234 video_trajectory = [video_noise]
235 audio_trajectory = [audio_noise]
236 noisy_video = video_noise
237 noisy_audio = audio_noise
238 memory_audio_kwargs = (
239 {
240 "memory_audio": memory_audio,
241 "memory_audio_timestep": memory_audio_timestep,
242 "memory_audio_segment_lengths": memory_audio_segment_lengths,
243 }
244 if memory_audio is not None or memory_audio_timestep is not None
245 else {}
246 )
247
248 for idx, sigma in enumerate(self.denoising_sigmas[:-1]):
249 video_sigma = sigma * torch.ones([batch_size, num_video_frames], device=device, dtype=dtype)
250 audio_sigma = sigma * torch.ones([batch_size, num_audio_frames], device=device, dtype=dtype)
251
252 pred_video, pred_audio = self.generator(
253 noisy_image_or_video=noisy_video,
254 conditional_dict=conditional_dict,
255 timestep=video_sigma,
256 noisy_audio=noisy_audio,
257 audio_timestep=audio_sigma,
258 memory_video=memory_video,
259 memory_downscale_factor=self.memory_downscale_factor,
260 memory_position_mode=memory_position_mode,
261 memory_position_offset=memory_position_offset,
262 memory_position_slot_stride=memory_position_slot_stride,
263 **memory_audio_kwargs,
264 )
265 pred_video = pred_video.to(dtype=dtype)
266 pred_audio = pred_audio.to(dtype=dtype)
267 if self.audio_latent_clamp > 0:
268 pred_audio = pred_audio.clamp(-self.audio_latent_clamp, self.audio_latent_clamp)
269
270 next_sigma = self.denoising_sigmas[idx + 1]
271 if next_sigma > 0:
272 fresh_noise_video = torch.randn_like(video_noise)
273 fresh_noise_audio = torch.randn_like(audio_noise)
274 next_video_sigma = next_sigma * torch.ones([batch_size, num_video_frames], device=device, dtype=dtype)
275 next_audio_sigma = next_sigma * torch.ones([batch_size, num_audio_frames], device=device, dtype=dtype)
276 noisy_video = self.add_noise_fn(
277 pred_video.flatten(0, 1),
278 fresh_noise_video.flatten(0, 1),
279 next_video_sigma.flatten(0, 1),
280 ).unflatten(0, (batch_size, num_video_frames)).to(dtype=dtype)
281 noisy_audio = self.add_noise_fn(
282 pred_audio,
283 fresh_noise_audio,
284 next_audio_sigma,
285 ).to(dtype=dtype)
286 else:
287 noisy_video = pred_video
288 noisy_audio = pred_audio
289
290 video_trajectory.append(noisy_video)
291 audio_trajectory.append(noisy_audio)
292
293 return torch.stack(video_trajectory, dim=1), torch.stack(audio_trajectory, dim=1)
294
295
296 class BidirectionalMemoryAVInferencePipeline:
297 """
298 Few-step inference pipeline for video-memory-conditioned AV generation.
299 """
300
301 def __init__(
302 self,
303 generator: nn.Module,
304 add_noise_fn,
305 denoising_sigmas: torch.Tensor,
306 memory_downscale_factor: int = 1,
307 ) -> None:
308 self.generator = generator
309 self.add_noise_fn = add_noise_fn
310 self.denoising_sigmas = denoising_sigmas
311 self.memory_downscale_factor = int(memory_downscale_factor)
312
313 @torch.no_grad()
314 def generate(
315 self,
316 video_shape: Tuple[int, ...],
317 audio_shape: Tuple[int, ...],
318 conditional_dict: Dict[str, Any],
319 memory_video: Optional[torch.Tensor] = None,
320 memory_audio: Optional[torch.Tensor] = None,
321 memory_audio_timestep: Optional[torch.Tensor] = None,
322 memory_audio_segment_lengths: tuple[tuple[int, ...], ...] | None = None,
323 memory_position_mode: str = "reference",
324 memory_position_offset: float = 0.0,
325 memory_position_slot_stride: float = 50.0,
326 seed: Optional[int] = None,
327 first_frame_latent: Optional[torch.Tensor] = None,
328 ) -> Tuple[torch.Tensor, torch.Tensor]:
329 batch_size = video_shape[0]
330 num_video_frames = video_shape[1]
331 num_audio_frames = audio_shape[1]
332
333 if seed is not None:
334 torch.manual_seed(seed)
335
336 device = next(self.generator.parameters()).device
337 dtype = next(self.generator.parameters()).dtype
338
339 video = torch.randn(video_shape, device=device, dtype=dtype)
340 audio = torch.randn(audio_shape, device=device, dtype=dtype)
341 if first_frame_latent is not None:
342 if first_frame_latent.shape[1] != 1:
343 raise ValueError(
344 "first_frame_latent must contain one frame, got "
345 f"{tuple(first_frame_latent.shape)}"
346 )
347 if tuple(first_frame_latent.shape[2:]) != tuple(video_shape[2:]):
348 raise ValueError(
349 "first-frame latent shape does not match requested output: "
350 f"{tuple(first_frame_latent.shape)} vs {tuple(video_shape)}"
351 )
352 first_frame_latent = first_frame_latent.to(device=device, dtype=dtype)
353 video[:, :1] = first_frame_latent
354 if memory_video is not None:
355 memory_video = memory_video.to(device=device, dtype=dtype)
356 if memory_audio is not None:
357 memory_audio = memory_audio.to(device=device, dtype=dtype)
358 if memory_audio_timestep is not None:
359 memory_audio_timestep = memory_audio_timestep.to(device=device, dtype=dtype)
360 memory_audio_kwargs = (
361 {
362 "memory_audio": memory_audio,
363 "memory_audio_timestep": memory_audio_timestep,
364 "memory_audio_segment_lengths": memory_audio_segment_lengths,
365 }
366 if memory_audio is not None or memory_audio_timestep is not None
367 else {}
368 )
369
370 for idx, sigma in enumerate(self.denoising_sigmas[:-1]):
371 video_sigma = sigma * torch.ones([batch_size, num_video_frames], device=device, dtype=dtype)
372 if first_frame_latent is not None:
373 video_sigma[:, 0] = 0
374 audio_sigma = sigma * torch.ones([batch_size, num_audio_frames], device=device, dtype=dtype)
375
376 generator_kwargs: Dict[str, Any] = {}
377 if memory_video is not None:
378 generator_kwargs = {
379 "memory_video": memory_video,
380 "memory_downscale_factor": self.memory_downscale_factor,
381 "memory_position_mode": memory_position_mode,
382 "memory_position_offset": memory_position_offset,
383 "memory_position_slot_stride": memory_position_slot_stride,
384 **memory_audio_kwargs,
385 }
386 pred_video, pred_audio = self.generator(
387 noisy_image_or_video=video,
388 conditional_dict=conditional_dict,
389 timestep=video_sigma,
390 noisy_audio=audio,
391 audio_timestep=audio_sigma,
392 **generator_kwargs,
393 )
394 pred_video = pred_video.to(dtype=dtype)
395 pred_audio = pred_audio.to(dtype=dtype)
396 if first_frame_latent is not None:
397 pred_video[:, :1] = first_frame_latent
398
399 next_sigma = self.denoising_sigmas[idx + 1]
400 if next_sigma > 0:
401 fresh_noise_video = torch.randn_like(video)
402 fresh_noise_audio = torch.randn_like(audio)
403 next_video_sigma = next_sigma * torch.ones([batch_size, num_video_frames], device=device, dtype=dtype)
404 next_audio_sigma = next_sigma * torch.ones([batch_size, num_audio_frames], device=device, dtype=dtype)
405 video = self.add_noise_fn(
406 pred_video.flatten(0, 1),
407 fresh_noise_video.flatten(0, 1),
408 next_video_sigma.flatten(0, 1),
409 ).unflatten(0, (batch_size, num_video_frames)).to(dtype=dtype)
410 audio = self.add_noise_fn(pred_audio, fresh_noise_audio, next_audio_sigma).to(dtype=dtype)
411 if first_frame_latent is not None:
412 video[:, :1] = first_frame_latent
413 else:
414 video = pred_video
415 audio = pred_audio
416
417 if first_frame_latent is not None:
418 video[:, :1] = first_frame_latent
419 return video, audio
420
421
422 class BidirectionalR2VInferencePipeline(BidirectionalMemoryAVInferencePipeline):
423 """Unified T2V, memory-R2V, first-frame I2V, and combined R2V pipeline."""
424
424 lines PYTHON