1use crate::ltxdit::{LtxDit, StreamInput};
17use crate::pool::Pool;
18
19pub const SCALE_TIME: usize = 8;
21pub const SCALE_SPACE: usize = 32;
22pub const AUDIO_LATENTS_PER_SEC: f64 = 25.0;
24const AUDIO_HOP: f64 = 160.0;
25const AUDIO_RATE: f64 = 16000.0;
26const AUDIO_DOWNSAMPLE: f64 = 4.0;
27
28pub const STAGE1_SIGMAS: [f32; 9] =
30 [1.0, 0.99375, 0.9875, 0.98125, 0.975, 0.909375, 0.725, 0.421875, 0.0];
31pub const STAGE2_SIGMAS: [f32; 4] = [0.909375, 0.725, 0.421875, 0.0];
32
33pub struct Rng(u64);
36
37impl Rng {
38 pub fn new(seed: u64) -> Rng {
39 Rng(seed.wrapping_mul(0x9E37_79B9_7F4A_7C15) | 1)
40 }
41 fn next_u64(&mut self) -> u64 {
42 self.0 = self.0.wrapping_add(0x9E37_79B9_7F4A_7C15);
44 let mut z = self.0;
45 z = (z ^ (z >> 30)).wrapping_mul(0xBF58_476D_1CE4_E5B9);
46 z = (z ^ (z >> 27)).wrapping_mul(0x94D0_49BB_1331_11EB);
47 z ^ (z >> 31)
48 }
49 fn unit(&mut self) -> f64 {
50 (self.next_u64() >> 11) as f64 / (1u64 << 53) as f64
51 }
52 pub fn normal(&mut self) -> f32 {
54 let u1 = self.unit().max(1e-12);
55 let u2 = self.unit();
56 ((-2.0 * u1.ln()).sqrt() * (2.0 * std::f64::consts::PI * u2).cos()) as f32
57 }
58 pub fn fill_normal(&mut self, dst: &mut [f32]) {
59 for v in dst.iter_mut() {
60 *v = self.normal();
61 }
62 }
63}
64
65#[derive(Clone, Copy, Debug)]
67pub struct Geometry {
68 pub frames: usize,
69 pub height: usize,
70 pub width: usize,
71 pub fps: f64,
72 pub lf: usize,
74 pub lh: usize,
75 pub lw: usize,
76 pub af: usize,
78}
79
80impl Geometry {
81 pub fn new(frames: usize, height: usize, width: usize, fps: f64) -> Geometry {
82 let lf = (frames - 1) / SCALE_TIME + 1;
83 let duration = frames as f64 / fps;
84 Geometry {
85 frames,
86 height,
87 width,
88 fps,
89 lf,
90 lh: height / SCALE_SPACE,
91 lw: width / SCALE_SPACE,
92 af: (duration * AUDIO_LATENTS_PER_SEC).round() as usize,
93 }
94 }
95
96 pub fn video_tokens(&self) -> usize {
97 self.lf * self.lh * self.lw
98 }
99
100 pub fn tokens_per_frame(&self) -> usize {
101 self.lh * self.lw
102 }
103
104 pub fn video_positions(&self) -> Vec<Vec<f64>> {
107 let causal = |v: f64| (v + 1.0 - SCALE_TIME as f64).max(0.0);
108 let mut out = Vec::with_capacity(self.video_tokens());
109 for f in 0..self.lf {
110 let t0 = causal((f * SCALE_TIME) as f64) / self.fps;
111 let t1 = causal(((f + 1) * SCALE_TIME) as f64) / self.fps;
112 let t = (t0 + t1) / 2.0;
113 for h in 0..self.lh {
114 let y = ((h * SCALE_SPACE) as f64 + ((h + 1) * SCALE_SPACE) as f64) / 2.0;
115 for w in 0..self.lw {
116 let x = ((w * SCALE_SPACE) as f64 + ((w + 1) * SCALE_SPACE) as f64) / 2.0;
117 out.push(vec![t, y, x]);
118 }
119 }
120 }
121 out
122 }
123
124 pub fn audio_positions(&self) -> Vec<Vec<f64>> {
126 let sec = |i: usize| {
127 let mel = (i as f64 * AUDIO_DOWNSAMPLE + 1.0 - AUDIO_DOWNSAMPLE).max(0.0);
128 mel * AUDIO_HOP / AUDIO_RATE
129 };
130 (0..self.af).map(|i| vec![(sec(i) + sec(i + 1)) / 2.0]).collect()
131 }
132
133 pub fn keyframes_mask(&self) -> Vec<f32> {
136 let mut m = vec![0f32; self.video_tokens()];
137 for v in m.iter_mut().take(self.tokens_per_frame()) {
138 *v = 1.0;
139 }
140 m
141 }
142}
143
144pub struct Stage {
146 pub sigmas: Vec<f32>,
147 pub ancestral: bool,
148}
149
150impl Stage {
151 pub fn stage1() -> Stage {
152 Stage { sigmas: STAGE1_SIGMAS.to_vec(), ancestral: true }
153 }
154 pub fn stage2() -> Stage {
155 Stage { sigmas: STAGE2_SIGMAS.to_vec(), ancestral: false }
156 }
157
158 pub fn from_strength(strength: f32) -> Stage {
165 let s0 = strength.clamp(0.02, 1.0);
166 let mut sigmas = vec![s0];
171 sigmas.extend(STAGE1_SIGMAS.iter().copied().filter(|&s| s < s0 && s > 0.0));
172 sigmas.push(0.0);
173 Stage { sigmas, ancestral: true }
174 }
175}
176
177fn euler_step(x: &mut [f32], denoised: &[f32], sigma: f32, sigma_next: f32, eta: f32, noise: Option<&[f32]>) {
182 if sigma_next == 0.0 {
183 x.copy_from_slice(denoised);
184 return;
185 }
186 let down_ratio = 1.0 + (sigma_next / sigma - 1.0) * eta;
187 let sigma_down = sigma_next * down_ratio;
188 let r = sigma_down / sigma;
189 for (v, &d) in x.iter_mut().zip(denoised) {
190 *v = r * *v + (1.0 - r) * d;
191 }
192 if eta > 0.0 {
193 let alpha_next = 1.0 - sigma_next;
194 let alpha_down = 1.0 - sigma_down;
195 let coeff = (sigma_next * sigma_next
196 - sigma_down * sigma_down * alpha_next * alpha_next / (alpha_down * alpha_down))
197 .max(0.0)
198 .sqrt();
199 let scale = alpha_next / alpha_down;
200 let n = noise.expect("ancestral step needs noise");
201 for (v, &e) in x.iter_mut().zip(n) {
202 *v = scale * *v + e * coeff;
203 }
204 }
205}
206
207pub struct Latents {
209 pub video: Vec<f32>,
210 pub audio: Vec<f32>,
211}
212
213#[derive(Clone, Default)]
219pub struct Conditioning {
220 pub video_mask: Vec<f32>,
221 pub video_clean: Vec<f32>,
222 pub audio_mask: Vec<f32>,
223 pub audio_clean: Vec<f32>,
224}
225
226impl Conditioning {
227 pub fn video_prefix(geo: &Geometry, clean: &[f32], frames: usize) -> Conditioning {
229 let per = geo.tokens_per_frame();
230 let mut mask = vec![1f32; geo.video_tokens()];
231 let mut full = vec![0f32; geo.video_tokens() * 128];
232 let n = (frames * per).min(geo.video_tokens());
233 for (t, m) in mask.iter_mut().enumerate().take(n) {
234 *m = 0.0;
235 full[t * 128..(t + 1) * 128].copy_from_slice(&clean[t * 128..(t + 1) * 128]);
236 }
237 Conditioning { video_mask: mask, video_clean: full, ..Default::default() }
238 }
239
240 pub fn video_all(geo: &Geometry, clean: &[f32]) -> Conditioning {
243 Conditioning {
244 video_mask: vec![0f32; geo.video_tokens()],
245 video_clean: clean.to_vec(),
246 ..Default::default()
247 }
248 }
249
250 pub fn with_audio_all(mut self, geo: &Geometry, clean: &[f32]) -> Conditioning {
253 self.audio_mask = vec![0f32; geo.af];
254 self.audio_clean = clean.to_vec();
255 self
256 }
257}
258
259pub type Progress<'a> = &'a mut dyn FnMut(usize, usize, f64);
261
262#[allow(clippy::too_many_arguments)]
263pub fn run_stage(
264 dit: &LtxDit,
265 geo: &Geometry,
266 stage: &Stage,
267 video_ctx: &[f32],
268 audio_ctx: &[f32],
269 ctx_len: usize,
270 init: Option<Latents>,
271 rng: &mut Rng,
272 pool: Option<&Pool>,
273 progress: Progress<'_>,
274) -> Latents {
275 run_stage_cond(dit, geo, stage, video_ctx, audio_ctx, ctx_len, init, None, rng, pool, progress)
276}
277
278#[allow(clippy::too_many_arguments)]
279pub fn run_stage_cond(
280 dit: &LtxDit,
281 geo: &Geometry,
282 stage: &Stage,
283 video_ctx: &[f32],
284 audio_ctx: &[f32],
285 ctx_len: usize,
286 init: Option<Latents>,
287 cond: Option<&Conditioning>,
288 rng: &mut Rng,
289 pool: Option<&Pool>,
290 progress: Progress<'_>,
291) -> Latents {
292 let vt = geo.video_tokens();
293 let at = geo.af;
294 let vch = 128usize;
295 let ach = 128usize;
296 let s0 = stage.sigmas[0];
297
298 let mut v = vec![0f32; vt * vch];
302 let mut a = vec![0f32; at * ach];
303 rng.fill_normal(&mut v);
304 rng.fill_normal(&mut a);
305 if let Some(prev) = init {
306 for (x, &p) in v.iter_mut().zip(&prev.video) {
307 *x = p + (*x - p) * s0;
308 }
309 for (x, &p) in a.iter_mut().zip(&prev.audio) {
310 *x = p + (*x - p) * s0;
311 }
312 }
313
314 let vmask: Vec<f32> = cond
317 .map(|c| c.video_mask.clone())
318 .filter(|m| m.len() == vt)
319 .unwrap_or_else(|| vec![1f32; vt]);
320 let amask: Vec<f32> = cond
321 .map(|c| c.audio_mask.clone())
322 .filter(|m| m.len() == at)
323 .unwrap_or_else(|| vec![1f32; at]);
324 let vclean: Vec<f32> = cond
325 .map(|c| c.video_clean.clone())
326 .filter(|c| c.len() == v.len())
327 .unwrap_or_else(|| vec![0f32; v.len()]);
328 let aclean: Vec<f32> = cond
329 .map(|c| c.audio_clean.clone())
330 .filter(|c| c.len() == a.len())
331 .unwrap_or_else(|| vec![0f32; a.len()]);
332 let blend = |x: &mut [f32], clean: &[f32], mask: &[f32], ch: usize| {
333 for (t, &m) in mask.iter().enumerate() {
334 if m >= 1.0 {
335 continue;
336 }
337 for d in 0..ch {
338 let i = t * ch + d;
339 x[i] = clean[i] + (x[i] - clean[i]) * m;
340 }
341 }
342 };
343 blend(&mut v, &vclean, &vmask, vch);
344 blend(&mut a, &aclean, &amask, ach);
345
346 let vpos = geo.video_positions();
347 let apos = geo.audio_positions();
348 let kf = geo.keyframes_mask();
349 let steps = stage.sigmas.len() - 1;
350 let eta = if stage.ancestral { 1.0 } else { 0.0 };
351
352 let v_frozen = vmask.iter().all(|&m| m == 0.0);
358 let a_frozen = amask.iter().all(|&m| m == 0.0);
359
360 for i in 0..steps {
361 let t0 = std::time::Instant::now();
362 let sigma = stage.sigmas[i];
363 let sigma_next = stage.sigmas[i + 1];
364 let vin = StreamInput {
365 latent: v.clone(),
366 tokens: vt,
367 timesteps: vmask.iter().map(|m| sigma * m).collect(),
368 positions: vpos.clone(),
369 context: video_ctx.to_vec(),
370 ctx_len,
371 context_mask: Vec::new(),
372 keyframes: kf.clone(),
373 sigma: if v_frozen { 0.0 } else { sigma },
374 };
375 let ain = StreamInput {
376 latent: a.clone(),
377 tokens: at,
378 timesteps: amask.iter().map(|m| sigma * m).collect(),
379 positions: apos.clone(),
380 context: audio_ctx.to_vec(),
381 ctx_len,
382 context_mask: Vec::new(),
383 keyframes: Vec::new(),
384 sigma: if a_frozen { 0.0 } else { sigma },
385 };
386 let (vv, av) = dit.forward(&vin, &ain, pool);
387 let mut vd: Vec<f32> = v
391 .iter()
392 .zip(&vv)
393 .enumerate()
394 .map(|(i, (&x, &g))| x - g * sigma * vmask[i / vch])
395 .collect();
396 let mut ad: Vec<f32> = a
397 .iter()
398 .zip(&av)
399 .enumerate()
400 .map(|(i, (&x, &g))| x - g * sigma * amask[i / ach])
401 .collect();
402 blend(&mut vd, &vclean, &vmask, vch);
403 blend(&mut ad, &aclean, &amask, ach);
404 let (vn, an) = if eta > 0.0 && sigma_next > 0.0 {
405 let mut vn = vec![0f32; v.len()];
406 let mut an = vec![0f32; a.len()];
407 rng.fill_normal(&mut vn);
408 rng.fill_normal(&mut an);
409 (Some(vn), Some(an))
410 } else {
411 (None, None)
412 };
413 euler_step(&mut v, &vd, sigma, sigma_next, eta, vn.as_deref());
414 euler_step(&mut a, &ad, sigma, sigma_next, eta, an.as_deref());
415 blend(&mut v, &vclean, &vmask, vch);
416 blend(&mut a, &aclean, &amask, ach);
417 progress(i + 1, steps, t0.elapsed().as_secs_f64());
418 }
419 Latents { video: v, audio: a }
420}
421
422pub fn unpatchify_video(tokens: &[f32], geo: &Geometry) -> Vec<f32> {
424 let (lf, lh, lw) = (geo.lf, geo.lh, geo.lw);
425 let c = 128usize;
426 let mut out = vec![0f32; c * lf * lh * lw];
427 for f in 0..lf {
428 for h in 0..lh {
429 for w in 0..lw {
430 let t = (f * lh + h) * lw + w;
431 for ch in 0..c {
432 out[((ch * lf + f) * lh + h) * lw + w] = tokens[t * c + ch];
433 }
434 }
435 }
436 }
437 out
438}
439
440pub fn unpatchify_audio(tokens: &[f32], frames: usize) -> Vec<f32> {
443 let (c, mel) = (8usize, 16usize);
444 let mut out = vec![0f32; c * frames * mel];
445 for t in 0..frames {
446 for ch in 0..c {
447 for m in 0..mel {
448 out[(ch * frames + t) * mel + m] = tokens[t * c * mel + ch * mel + m];
449 }
450 }
451 }
452 out
453}
454
455pub fn patchify_video(vol: &[f32], geo: &Geometry) -> Vec<f32> {
458 let (lf, lh, lw) = (geo.lf, geo.lh, geo.lw);
459 let c = 128usize;
460 let mut out = vec![0f32; c * lf * lh * lw];
461 for f in 0..lf {
462 for h in 0..lh {
463 for w in 0..lw {
464 let t = (f * lh + h) * lw + w;
465 for ch in 0..c {
466 out[t * c + ch] = vol[((ch * lf + f) * lh + h) * lw + w];
467 }
468 }
469 }
470 }
471 out
472}