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,
31];
32pub const STAGE2_SIGMAS: [f32; 4] = [0.909375, 0.725, 0.421875, 0.0];
33
34pub struct Rng(u64);
37
38impl Rng {
39 pub fn new(seed: u64) -> Rng {
40 Rng(seed.wrapping_mul(0x9E37_79B9_7F4A_7C15) | 1)
41 }
42 fn next_u64(&mut self) -> u64 {
43 self.0 = self.0.wrapping_add(0x9E37_79B9_7F4A_7C15);
45 let mut z = self.0;
46 z = (z ^ (z >> 30)).wrapping_mul(0xBF58_476D_1CE4_E5B9);
47 z = (z ^ (z >> 27)).wrapping_mul(0x94D0_49BB_1331_11EB);
48 z ^ (z >> 31)
49 }
50 fn unit(&mut self) -> f64 {
51 (self.next_u64() >> 11) as f64 / (1u64 << 53) as f64
52 }
53 pub fn normal(&mut self) -> f32 {
55 let u1 = self.unit().max(1e-12);
56 let u2 = self.unit();
57 ((-2.0 * u1.ln()).sqrt() * (2.0 * std::f64::consts::PI * u2).cos()) as f32
58 }
59 pub fn fill_normal(&mut self, dst: &mut [f32]) {
60 for v in dst.iter_mut() {
61 *v = self.normal();
62 }
63 }
64}
65
66#[derive(Clone, Copy, Debug)]
68pub struct Geometry {
69 pub frames: usize,
70 pub height: usize,
71 pub width: usize,
72 pub fps: f64,
73 pub lf: usize,
75 pub lh: usize,
76 pub lw: usize,
77 pub af: usize,
79}
80
81impl Geometry {
82 pub fn new(frames: usize, height: usize, width: usize, fps: f64) -> Geometry {
83 let lf = (frames - 1) / SCALE_TIME + 1;
84 let duration = frames as f64 / fps;
85 Geometry {
86 frames,
87 height,
88 width,
89 fps,
90 lf,
91 lh: height / SCALE_SPACE,
92 lw: width / SCALE_SPACE,
93 af: (duration * AUDIO_LATENTS_PER_SEC).round() as usize,
94 }
95 }
96
97 pub fn video_tokens(&self) -> usize {
98 self.lf * self.lh * self.lw
99 }
100
101 pub fn tokens_per_frame(&self) -> usize {
102 self.lh * self.lw
103 }
104
105 pub fn video_positions(&self) -> Vec<Vec<f64>> {
108 let causal = |v: f64| (v + 1.0 - SCALE_TIME as f64).max(0.0);
109 let mut out = Vec::with_capacity(self.video_tokens());
110 for f in 0..self.lf {
111 let t0 = causal((f * SCALE_TIME) as f64) / self.fps;
112 let t1 = causal(((f + 1) * SCALE_TIME) as f64) / self.fps;
113 let t = (t0 + t1) / 2.0;
114 for h in 0..self.lh {
115 let y = ((h * SCALE_SPACE) as f64 + ((h + 1) * SCALE_SPACE) as f64) / 2.0;
116 for w in 0..self.lw {
117 let x = ((w * SCALE_SPACE) as f64 + ((w + 1) * SCALE_SPACE) as f64) / 2.0;
118 out.push(vec![t, y, x]);
119 }
120 }
121 }
122 out
123 }
124
125 pub fn audio_positions(&self) -> Vec<Vec<f64>> {
127 let sec = |i: usize| {
128 let mel = (i as f64 * AUDIO_DOWNSAMPLE + 1.0 - AUDIO_DOWNSAMPLE).max(0.0);
129 mel * AUDIO_HOP / AUDIO_RATE
130 };
131 (0..self.af)
132 .map(|i| vec![(sec(i) + sec(i + 1)) / 2.0])
133 .collect()
134 }
135
136 pub fn guide_positions(&self, gf: usize, frame_offset: i64) -> Vec<Vec<f64>> {
145 let causal = |v: f64| (v + 1.0 - SCALE_TIME as f64).max(0.0);
146 let off = frame_offset as f64;
147 let mut out = Vec::with_capacity(gf * self.tokens_per_frame());
148 for f in 0..gf {
149 let t0 = (causal((f * SCALE_TIME) as f64) + off) / self.fps;
150 let t1 = (causal(((f + 1) * SCALE_TIME) as f64) + off) / self.fps;
151 let t = (t0 + t1) / 2.0;
152 for h in 0..self.lh {
153 let y = ((h * SCALE_SPACE) as f64 + ((h + 1) * SCALE_SPACE) as f64) / 2.0;
154 for w in 0..self.lw {
155 let x = ((w * SCALE_SPACE) as f64 + ((w + 1) * SCALE_SPACE) as f64) / 2.0;
156 out.push(vec![t, y, x]);
157 }
158 }
159 }
160 out
161 }
162
163 pub fn keyframes_mask(&self) -> Vec<f32> {
164 let mut m = vec![0f32; self.video_tokens()];
165 for v in m.iter_mut().take(self.tokens_per_frame()) {
166 *v = 1.0;
167 }
168 m
169 }
170}
171
172pub struct Stage {
174 pub sigmas: Vec<f32>,
175 pub ancestral: bool,
176}
177
178impl Stage {
179 pub fn stage1() -> Stage {
180 Stage {
181 sigmas: STAGE1_SIGMAS.to_vec(),
182 ancestral: true,
183 }
184 }
185 pub fn stage2() -> Stage {
186 Stage {
187 sigmas: STAGE2_SIGMAS.to_vec(),
188 ancestral: false,
189 }
190 }
191
192 pub fn stage1_steps(steps: usize) -> Stage {
201 let base = &STAGE1_SIGMAS;
202 let n = steps.max(1);
203 if n + 1 == base.len() {
204 return Stage::stage1();
205 }
206 let last = base.len() - 1;
207 let sigmas: Vec<f32> = (0..=n)
208 .map(|i| {
209 let t = i as f64 * last as f64 / n as f64;
210 let lo = (t.floor() as usize).min(last);
211 let hi = (lo + 1).min(last);
212 let f = (t - lo as f64) as f32;
213 base[lo] + (base[hi] - base[lo]) * f
214 })
215 .collect();
216 Stage {
217 sigmas,
218 ancestral: true,
219 }
220 }
221
222 pub fn stage2_steps(steps: usize) -> Stage {
224 let base = &STAGE2_SIGMAS;
225 let n = steps.max(1);
226 if n + 1 == base.len() {
227 return Stage::stage2();
228 }
229 let last = base.len() - 1;
230 let sigmas: Vec<f32> = (0..=n)
231 .map(|i| {
232 let t = i as f64 * last as f64 / n as f64;
233 let lo = (t.floor() as usize).min(last);
234 let hi = (lo + 1).min(last);
235 let f = (t - lo as f64) as f32;
236 base[lo] + (base[hi] - base[lo]) * f
237 })
238 .collect();
239 Stage {
240 sigmas,
241 ancestral: false,
242 }
243 }
244
245 pub fn from_strength(strength: f32) -> Stage {
252 let s0 = strength.clamp(0.02, 1.0);
253 let mut sigmas = vec![s0];
258 sigmas.extend(STAGE1_SIGMAS.iter().copied().filter(|&s| s < s0 && s > 0.0));
259 sigmas.push(0.0);
260 Stage {
261 sigmas,
262 ancestral: true,
263 }
264 }
265}
266
267fn euler_step(
272 x: &mut [f32],
273 denoised: &[f32],
274 sigma: f32,
275 sigma_next: f32,
276 eta: f32,
277 noise: Option<&[f32]>,
278) {
279 if sigma_next == 0.0 {
280 x.copy_from_slice(denoised);
281 return;
282 }
283 let down_ratio = 1.0 + (sigma_next / sigma - 1.0) * eta;
284 let sigma_down = sigma_next * down_ratio;
285 let r = sigma_down / sigma;
286 for (v, &d) in x.iter_mut().zip(denoised) {
287 *v = r * *v + (1.0 - r) * d;
288 }
289 if eta > 0.0 {
290 let alpha_next = 1.0 - sigma_next;
291 let alpha_down = 1.0 - sigma_down;
292 let coeff = (sigma_next * sigma_next
293 - sigma_down * sigma_down * alpha_next * alpha_next / (alpha_down * alpha_down))
294 .max(0.0)
295 .sqrt();
296 let scale = alpha_next / alpha_down;
297 let n = noise.expect("ancestral step needs noise");
298 for (v, &e) in x.iter_mut().zip(n) {
299 *v = scale * *v + e * coeff;
300 }
301 }
302}
303
304pub struct Latents {
306 pub video: Vec<f32>,
307 pub audio: Vec<f32>,
308}
309
310#[derive(Clone, Default)]
316pub struct Conditioning {
317 pub video_mask: Vec<f32>,
318 pub video_clean: Vec<f32>,
319 pub audio_mask: Vec<f32>,
320 pub audio_clean: Vec<f32>,
321 pub refs: Option<RefTokens>,
325}
326
327#[derive(Clone, Default)]
330pub struct RefTokens {
331 pub latent: Vec<f32>,
332 pub positions: Vec<Vec<f64>>,
333 pub count: usize,
334}
335
336impl Conditioning {
337 pub fn video_prefix(geo: &Geometry, clean: &[f32], frames: usize) -> Conditioning {
339 let per = geo.tokens_per_frame();
340 let mut mask = vec![1f32; geo.video_tokens()];
341 let mut full = vec![0f32; geo.video_tokens() * 128];
342 let n = (frames * per).min(geo.video_tokens());
343 for (t, m) in mask.iter_mut().enumerate().take(n) {
344 *m = 0.0;
345 full[t * 128..(t + 1) * 128].copy_from_slice(&clean[t * 128..(t + 1) * 128]);
346 }
347 Conditioning {
348 video_mask: mask,
349 video_clean: full,
350 ..Default::default()
351 }
352 }
353
354 pub fn video_all(geo: &Geometry, clean: &[f32]) -> Conditioning {
357 Conditioning {
358 video_mask: vec![0f32; geo.video_tokens()],
359 video_clean: clean.to_vec(),
360 ..Default::default()
361 }
362 }
363
364 pub fn with_references(mut self, refs: RefTokens) -> Conditioning {
368 self.refs = Some(refs);
369 self
370 }
371
372 pub fn with_audio_all(mut self, geo: &Geometry, clean: &[f32]) -> Conditioning {
375 self.audio_mask = vec![0f32; geo.af];
376 self.audio_clean = clean.to_vec();
377 self
378 }
379}
380
381pub type Progress<'a> = &'a mut dyn FnMut(usize, usize, f64);
383
384#[allow(clippy::too_many_arguments)]
385pub fn run_stage(
386 dit: &LtxDit,
387 geo: &Geometry,
388 stage: &Stage,
389 video_ctx: &[f32],
390 audio_ctx: &[f32],
391 ctx_len: usize,
392 init: Option<Latents>,
393 rng: &mut Rng,
394 pool: Option<&Pool>,
395 progress: Progress<'_>,
396) -> Latents {
397 run_stage_cond(
398 dit, geo, stage, video_ctx, audio_ctx, ctx_len, init, None, rng, pool, progress,
399 )
400}
401
402#[allow(clippy::too_many_arguments)]
403pub fn run_stage_cond(
404 dit: &LtxDit,
405 geo: &Geometry,
406 stage: &Stage,
407 video_ctx: &[f32],
408 audio_ctx: &[f32],
409 ctx_len: usize,
410 init: Option<Latents>,
411 cond: Option<&Conditioning>,
412 rng: &mut Rng,
413 pool: Option<&Pool>,
414 progress: Progress<'_>,
415) -> Latents {
416 let vt = geo.video_tokens();
417 let at = geo.af;
418 let vch = 128usize;
419 let ach = 128usize;
420 let s0 = stage.sigmas[0];
421
422 let mut v = vec![0f32; vt * vch];
426 let mut a = vec![0f32; at * ach];
427 rng.fill_normal(&mut v);
428 rng.fill_normal(&mut a);
429 if let Some(prev) = init {
430 for (x, &p) in v.iter_mut().zip(&prev.video) {
431 *x = p + (*x - p) * s0;
432 }
433 for (x, &p) in a.iter_mut().zip(&prev.audio) {
434 *x = p + (*x - p) * s0;
435 }
436 }
437
438 let vmask: Vec<f32> = cond
441 .map(|c| c.video_mask.clone())
442 .filter(|m| m.len() == vt)
443 .unwrap_or_else(|| vec![1f32; vt]);
444 let amask: Vec<f32> = cond
445 .map(|c| c.audio_mask.clone())
446 .filter(|m| m.len() == at)
447 .unwrap_or_else(|| vec![1f32; at]);
448 let vclean: Vec<f32> = cond
449 .map(|c| c.video_clean.clone())
450 .filter(|c| c.len() == v.len())
451 .unwrap_or_else(|| vec![0f32; v.len()]);
452 let aclean: Vec<f32> = cond
453 .map(|c| c.audio_clean.clone())
454 .filter(|c| c.len() == a.len())
455 .unwrap_or_else(|| vec![0f32; a.len()]);
456 let blend = |x: &mut [f32], clean: &[f32], mask: &[f32], ch: usize| {
457 for (t, &m) in mask.iter().enumerate() {
458 if m >= 1.0 {
459 continue;
460 }
461 for d in 0..ch {
462 let i = t * ch + d;
463 x[i] = clean[i] + (x[i] - clean[i]) * m;
464 }
465 }
466 };
467 blend(&mut v, &vclean, &vmask, vch);
468 blend(&mut a, &aclean, &amask, ach);
469
470 let mut vpos = geo.video_positions();
471 let apos = geo.audio_positions();
472 let mut kf = geo.keyframes_mask();
473
474 let clip_tokens = vt;
481 let mut vt = vt;
482 let mut vmask = vmask;
483 let mut vclean = vclean;
484 let mut v = v;
485 if let Some(r) = cond.and_then(|c| c.refs.as_ref()) {
486 if r.count > 0 && r.latent.len() == r.count * vch && r.positions.len() == r.count {
487 v.extend_from_slice(&r.latent);
488 vclean.extend_from_slice(&r.latent);
489 vmask.extend(std::iter::repeat_n(0f32, r.count));
490 kf.extend(std::iter::repeat_n(0f32, r.count));
491 vpos.extend(r.positions.iter().cloned());
492 vt += r.count;
493 tracing::info!(
494 "reference conditioning: {} tokens beside {clip_tokens} of clip",
495 r.count
496 );
497 } else if r.count > 0 {
498 tracing::warn!(
499 "reference conditioning ignored: {} tokens, {} latents, {} positions",
500 r.count,
501 r.latent.len(),
502 r.positions.len()
503 );
504 }
505 }
506 let v_frozen_src: Vec<f32> = vmask[..clip_tokens].to_vec();
511 let steps = stage.sigmas.len() - 1;
512 let eta = if stage.ancestral { 1.0 } else { 0.0 };
513
514 let v_frozen = v_frozen_src.iter().all(|&m| m == 0.0);
520 let a_frozen = amask.iter().all(|&m| m == 0.0);
521
522 for i in 0..steps {
523 let t0 = std::time::Instant::now();
524 let sigma = stage.sigmas[i];
525 let sigma_next = stage.sigmas[i + 1];
526 let vin = StreamInput {
527 latent: v.clone(),
528 tokens: vt,
529 timesteps: vmask.iter().map(|m| sigma * m).collect(),
530 positions: vpos.clone(),
531 context: video_ctx.to_vec(),
532 ctx_len,
533 context_mask: Vec::new(),
534 keyframes: kf.clone(),
535 sigma: if v_frozen { 0.0 } else { sigma },
536 };
537 let ain = StreamInput {
538 latent: a.clone(),
539 tokens: at,
540 timesteps: amask.iter().map(|m| sigma * m).collect(),
541 positions: apos.clone(),
542 context: audio_ctx.to_vec(),
543 ctx_len,
544 context_mask: Vec::new(),
545 keyframes: Vec::new(),
546 sigma: if a_frozen { 0.0 } else { sigma },
547 };
548 let (vv, av) = dit.forward(&vin, &ain, pool);
549 let mut vd: Vec<f32> = v
553 .iter()
554 .zip(&vv)
555 .enumerate()
556 .map(|(i, (&x, &g))| x - g * sigma * vmask[i / vch])
557 .collect();
558 let mut ad: Vec<f32> = a
559 .iter()
560 .zip(&av)
561 .enumerate()
562 .map(|(i, (&x, &g))| x - g * sigma * amask[i / ach])
563 .collect();
564 blend(&mut vd, &vclean, &vmask, vch);
565 blend(&mut ad, &aclean, &amask, ach);
566 let (vn, an) = if eta > 0.0 && sigma_next > 0.0 {
567 let mut vn = vec![0f32; v.len()];
568 let mut an = vec![0f32; a.len()];
569 rng.fill_normal(&mut vn);
570 rng.fill_normal(&mut an);
571 (Some(vn), Some(an))
572 } else {
573 (None, None)
574 };
575 euler_step(&mut v, &vd, sigma, sigma_next, eta, vn.as_deref());
576 euler_step(&mut a, &ad, sigma, sigma_next, eta, an.as_deref());
577 blend(&mut v, &vclean, &vmask, vch);
578 blend(&mut a, &aclean, &amask, ach);
579 progress(i + 1, steps, t0.elapsed().as_secs_f64());
580 }
581 v.truncate(clip_tokens * vch);
582 Latents { video: v, audio: a }
583}
584
585pub fn unpatchify_video(tokens: &[f32], geo: &Geometry) -> Vec<f32> {
587 let (lf, lh, lw) = (geo.lf, geo.lh, geo.lw);
588 let c = 128usize;
589 let mut out = vec![0f32; c * lf * lh * lw];
590 for f in 0..lf {
591 for h in 0..lh {
592 for w in 0..lw {
593 let t = (f * lh + h) * lw + w;
594 for ch in 0..c {
595 out[((ch * lf + f) * lh + h) * lw + w] = tokens[t * c + ch];
596 }
597 }
598 }
599 }
600 out
601}
602
603pub fn unpatchify_audio(tokens: &[f32], frames: usize) -> Vec<f32> {
606 let (c, mel) = (8usize, 16usize);
607 let mut out = vec![0f32; c * frames * mel];
608 for t in 0..frames {
609 for ch in 0..c {
610 for m in 0..mel {
611 out[(ch * frames + t) * mel + m] = tokens[t * c * mel + ch * mel + m];
612 }
613 }
614 }
615 out
616}
617
618pub fn patchify_video(vol: &[f32], geo: &Geometry) -> Vec<f32> {
621 let (lf, lh, lw) = (geo.lf, geo.lh, geo.lw);
622 let c = 128usize;
623 let mut out = vec![0f32; c * lf * lh * lw];
624 for f in 0..lf {
625 for h in 0..lh {
626 for w in 0..lw {
627 let t = (f * lh + h) * lw + w;
628 for ch in 0..c {
629 out[t * c + ch] = vol[((ch * lf + f) * lh + h) * lw + w];
630 }
631 }
632 }
633 }
634 out
635}