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 guide_positions(&self, gf: usize, frame_offset: i64) -> Vec<Vec<f64>> {
142 let causal = |v: f64| (v + 1.0 - SCALE_TIME as f64).max(0.0);
143 let off = frame_offset as f64;
144 let mut out = Vec::with_capacity(gf * self.tokens_per_frame());
145 for f in 0..gf {
146 let t0 = (causal((f * SCALE_TIME) as f64) + off) / self.fps;
147 let t1 = (causal(((f + 1) * SCALE_TIME) as f64) + off) / self.fps;
148 let t = (t0 + t1) / 2.0;
149 for h in 0..self.lh {
150 let y = ((h * SCALE_SPACE) as f64 + ((h + 1) * SCALE_SPACE) as f64) / 2.0;
151 for w in 0..self.lw {
152 let x = ((w * SCALE_SPACE) as f64 + ((w + 1) * SCALE_SPACE) as f64) / 2.0;
153 out.push(vec![t, y, x]);
154 }
155 }
156 }
157 out
158 }
159
160 pub fn keyframes_mask(&self) -> Vec<f32> {
161 let mut m = vec![0f32; self.video_tokens()];
162 for v in m.iter_mut().take(self.tokens_per_frame()) {
163 *v = 1.0;
164 }
165 m
166 }
167}
168
169pub struct Stage {
171 pub sigmas: Vec<f32>,
172 pub ancestral: bool,
173}
174
175impl Stage {
176 pub fn stage1() -> Stage {
177 Stage { sigmas: STAGE1_SIGMAS.to_vec(), ancestral: true }
178 }
179 pub fn stage2() -> Stage {
180 Stage { sigmas: STAGE2_SIGMAS.to_vec(), ancestral: false }
181 }
182
183 pub fn stage1_steps(steps: usize) -> Stage {
192 let base = &STAGE1_SIGMAS;
193 let n = steps.max(1);
194 if n + 1 == base.len() {
195 return Stage::stage1();
196 }
197 let last = base.len() - 1;
198 let sigmas: Vec<f32> = (0..=n)
199 .map(|i| {
200 let t = i as f64 * last as f64 / n as f64;
201 let lo = (t.floor() as usize).min(last);
202 let hi = (lo + 1).min(last);
203 let f = (t - lo as f64) as f32;
204 base[lo] + (base[hi] - base[lo]) * f
205 })
206 .collect();
207 Stage { sigmas, ancestral: true }
208 }
209
210 pub fn stage2_steps(steps: usize) -> Stage {
212 let base = &STAGE2_SIGMAS;
213 let n = steps.max(1);
214 if n + 1 == base.len() {
215 return Stage::stage2();
216 }
217 let last = base.len() - 1;
218 let sigmas: Vec<f32> = (0..=n)
219 .map(|i| {
220 let t = i as f64 * last as f64 / n as f64;
221 let lo = (t.floor() as usize).min(last);
222 let hi = (lo + 1).min(last);
223 let f = (t - lo as f64) as f32;
224 base[lo] + (base[hi] - base[lo]) * f
225 })
226 .collect();
227 Stage { sigmas, ancestral: false }
228 }
229
230 pub fn from_strength(strength: f32) -> Stage {
237 let s0 = strength.clamp(0.02, 1.0);
238 let mut sigmas = vec![s0];
243 sigmas.extend(STAGE1_SIGMAS.iter().copied().filter(|&s| s < s0 && s > 0.0));
244 sigmas.push(0.0);
245 Stage { sigmas, ancestral: true }
246 }
247}
248
249fn euler_step(x: &mut [f32], denoised: &[f32], sigma: f32, sigma_next: f32, eta: f32, noise: Option<&[f32]>) {
254 if sigma_next == 0.0 {
255 x.copy_from_slice(denoised);
256 return;
257 }
258 let down_ratio = 1.0 + (sigma_next / sigma - 1.0) * eta;
259 let sigma_down = sigma_next * down_ratio;
260 let r = sigma_down / sigma;
261 for (v, &d) in x.iter_mut().zip(denoised) {
262 *v = r * *v + (1.0 - r) * d;
263 }
264 if eta > 0.0 {
265 let alpha_next = 1.0 - sigma_next;
266 let alpha_down = 1.0 - sigma_down;
267 let coeff = (sigma_next * sigma_next
268 - sigma_down * sigma_down * alpha_next * alpha_next / (alpha_down * alpha_down))
269 .max(0.0)
270 .sqrt();
271 let scale = alpha_next / alpha_down;
272 let n = noise.expect("ancestral step needs noise");
273 for (v, &e) in x.iter_mut().zip(n) {
274 *v = scale * *v + e * coeff;
275 }
276 }
277}
278
279pub struct Latents {
281 pub video: Vec<f32>,
282 pub audio: Vec<f32>,
283}
284
285#[derive(Clone, Default)]
291pub struct Conditioning {
292 pub video_mask: Vec<f32>,
293 pub video_clean: Vec<f32>,
294 pub audio_mask: Vec<f32>,
295 pub audio_clean: Vec<f32>,
296 pub refs: Option<RefTokens>,
300}
301
302#[derive(Clone, Default)]
305pub struct RefTokens {
306 pub latent: Vec<f32>,
307 pub positions: Vec<Vec<f64>>,
308 pub count: usize,
309}
310
311impl Conditioning {
312 pub fn video_prefix(geo: &Geometry, clean: &[f32], frames: usize) -> Conditioning {
314 let per = geo.tokens_per_frame();
315 let mut mask = vec![1f32; geo.video_tokens()];
316 let mut full = vec![0f32; geo.video_tokens() * 128];
317 let n = (frames * per).min(geo.video_tokens());
318 for (t, m) in mask.iter_mut().enumerate().take(n) {
319 *m = 0.0;
320 full[t * 128..(t + 1) * 128].copy_from_slice(&clean[t * 128..(t + 1) * 128]);
321 }
322 Conditioning { video_mask: mask, video_clean: full, ..Default::default() }
323 }
324
325 pub fn video_all(geo: &Geometry, clean: &[f32]) -> Conditioning {
328 Conditioning {
329 video_mask: vec![0f32; geo.video_tokens()],
330 video_clean: clean.to_vec(),
331 ..Default::default()
332 }
333 }
334
335 pub fn with_references(mut self, refs: RefTokens) -> Conditioning {
339 self.refs = Some(refs);
340 self
341 }
342
343 pub fn with_audio_all(mut self, geo: &Geometry, clean: &[f32]) -> Conditioning {
346 self.audio_mask = vec![0f32; geo.af];
347 self.audio_clean = clean.to_vec();
348 self
349 }
350}
351
352pub type Progress<'a> = &'a mut dyn FnMut(usize, usize, f64);
354
355#[allow(clippy::too_many_arguments)]
356pub fn run_stage(
357 dit: &LtxDit,
358 geo: &Geometry,
359 stage: &Stage,
360 video_ctx: &[f32],
361 audio_ctx: &[f32],
362 ctx_len: usize,
363 init: Option<Latents>,
364 rng: &mut Rng,
365 pool: Option<&Pool>,
366 progress: Progress<'_>,
367) -> Latents {
368 run_stage_cond(dit, geo, stage, video_ctx, audio_ctx, ctx_len, init, None, rng, pool, progress)
369}
370
371#[allow(clippy::too_many_arguments)]
372pub fn run_stage_cond(
373 dit: &LtxDit,
374 geo: &Geometry,
375 stage: &Stage,
376 video_ctx: &[f32],
377 audio_ctx: &[f32],
378 ctx_len: usize,
379 init: Option<Latents>,
380 cond: Option<&Conditioning>,
381 rng: &mut Rng,
382 pool: Option<&Pool>,
383 progress: Progress<'_>,
384) -> Latents {
385 let vt = geo.video_tokens();
386 let at = geo.af;
387 let vch = 128usize;
388 let ach = 128usize;
389 let s0 = stage.sigmas[0];
390
391 let mut v = vec![0f32; vt * vch];
395 let mut a = vec![0f32; at * ach];
396 rng.fill_normal(&mut v);
397 rng.fill_normal(&mut a);
398 if let Some(prev) = init {
399 for (x, &p) in v.iter_mut().zip(&prev.video) {
400 *x = p + (*x - p) * s0;
401 }
402 for (x, &p) in a.iter_mut().zip(&prev.audio) {
403 *x = p + (*x - p) * s0;
404 }
405 }
406
407 let vmask: Vec<f32> = cond
410 .map(|c| c.video_mask.clone())
411 .filter(|m| m.len() == vt)
412 .unwrap_or_else(|| vec![1f32; vt]);
413 let amask: Vec<f32> = cond
414 .map(|c| c.audio_mask.clone())
415 .filter(|m| m.len() == at)
416 .unwrap_or_else(|| vec![1f32; at]);
417 let vclean: Vec<f32> = cond
418 .map(|c| c.video_clean.clone())
419 .filter(|c| c.len() == v.len())
420 .unwrap_or_else(|| vec![0f32; v.len()]);
421 let aclean: Vec<f32> = cond
422 .map(|c| c.audio_clean.clone())
423 .filter(|c| c.len() == a.len())
424 .unwrap_or_else(|| vec![0f32; a.len()]);
425 let blend = |x: &mut [f32], clean: &[f32], mask: &[f32], ch: usize| {
426 for (t, &m) in mask.iter().enumerate() {
427 if m >= 1.0 {
428 continue;
429 }
430 for d in 0..ch {
431 let i = t * ch + d;
432 x[i] = clean[i] + (x[i] - clean[i]) * m;
433 }
434 }
435 };
436 blend(&mut v, &vclean, &vmask, vch);
437 blend(&mut a, &aclean, &amask, ach);
438
439 let mut vpos = geo.video_positions();
440 let apos = geo.audio_positions();
441 let mut kf = geo.keyframes_mask();
442
443 let clip_tokens = vt;
450 let mut vt = vt;
451 let mut vmask = vmask;
452 let mut vclean = vclean;
453 let mut v = v;
454 if let Some(r) = cond.and_then(|c| c.refs.as_ref()) {
455 if r.count > 0 && r.latent.len() == r.count * vch && r.positions.len() == r.count {
456 v.extend_from_slice(&r.latent);
457 vclean.extend_from_slice(&r.latent);
458 vmask.extend(std::iter::repeat_n(0f32, r.count));
459 kf.extend(std::iter::repeat_n(0f32, r.count));
460 vpos.extend(r.positions.iter().cloned());
461 vt += r.count;
462 tracing::info!(
463 "reference conditioning: {} tokens beside {clip_tokens} of clip",
464 r.count
465 );
466 } else if r.count > 0 {
467 tracing::warn!(
468 "reference conditioning ignored: {} tokens, {} latents, {} positions",
469 r.count,
470 r.latent.len(),
471 r.positions.len()
472 );
473 }
474 }
475 let v_frozen_src: Vec<f32> = vmask[..clip_tokens].to_vec();
480 let steps = stage.sigmas.len() - 1;
481 let eta = if stage.ancestral { 1.0 } else { 0.0 };
482
483 let v_frozen = v_frozen_src.iter().all(|&m| m == 0.0);
489 let a_frozen = amask.iter().all(|&m| m == 0.0);
490
491 for i in 0..steps {
492 let t0 = std::time::Instant::now();
493 let sigma = stage.sigmas[i];
494 let sigma_next = stage.sigmas[i + 1];
495 let vin = StreamInput {
496 latent: v.clone(),
497 tokens: vt,
498 timesteps: vmask.iter().map(|m| sigma * m).collect(),
499 positions: vpos.clone(),
500 context: video_ctx.to_vec(),
501 ctx_len,
502 context_mask: Vec::new(),
503 keyframes: kf.clone(),
504 sigma: if v_frozen { 0.0 } else { sigma },
505 };
506 let ain = StreamInput {
507 latent: a.clone(),
508 tokens: at,
509 timesteps: amask.iter().map(|m| sigma * m).collect(),
510 positions: apos.clone(),
511 context: audio_ctx.to_vec(),
512 ctx_len,
513 context_mask: Vec::new(),
514 keyframes: Vec::new(),
515 sigma: if a_frozen { 0.0 } else { sigma },
516 };
517 let (vv, av) = dit.forward(&vin, &ain, pool);
518 let mut vd: Vec<f32> = v
522 .iter()
523 .zip(&vv)
524 .enumerate()
525 .map(|(i, (&x, &g))| x - g * sigma * vmask[i / vch])
526 .collect();
527 let mut ad: Vec<f32> = a
528 .iter()
529 .zip(&av)
530 .enumerate()
531 .map(|(i, (&x, &g))| x - g * sigma * amask[i / ach])
532 .collect();
533 blend(&mut vd, &vclean, &vmask, vch);
534 blend(&mut ad, &aclean, &amask, ach);
535 let (vn, an) = if eta > 0.0 && sigma_next > 0.0 {
536 let mut vn = vec![0f32; v.len()];
537 let mut an = vec![0f32; a.len()];
538 rng.fill_normal(&mut vn);
539 rng.fill_normal(&mut an);
540 (Some(vn), Some(an))
541 } else {
542 (None, None)
543 };
544 euler_step(&mut v, &vd, sigma, sigma_next, eta, vn.as_deref());
545 euler_step(&mut a, &ad, sigma, sigma_next, eta, an.as_deref());
546 blend(&mut v, &vclean, &vmask, vch);
547 blend(&mut a, &aclean, &amask, ach);
548 progress(i + 1, steps, t0.elapsed().as_secs_f64());
549 }
550 v.truncate(clip_tokens * vch);
551 Latents { video: v, audio: a }
552}
553
554pub fn unpatchify_video(tokens: &[f32], geo: &Geometry) -> Vec<f32> {
556 let (lf, lh, lw) = (geo.lf, geo.lh, geo.lw);
557 let c = 128usize;
558 let mut out = vec![0f32; c * lf * lh * lw];
559 for f in 0..lf {
560 for h in 0..lh {
561 for w in 0..lw {
562 let t = (f * lh + h) * lw + w;
563 for ch in 0..c {
564 out[((ch * lf + f) * lh + h) * lw + w] = tokens[t * c + ch];
565 }
566 }
567 }
568 }
569 out
570}
571
572pub fn unpatchify_audio(tokens: &[f32], frames: usize) -> Vec<f32> {
575 let (c, mel) = (8usize, 16usize);
576 let mut out = vec![0f32; c * frames * mel];
577 for t in 0..frames {
578 for ch in 0..c {
579 for m in 0..mel {
580 out[(ch * frames + t) * mel + m] = tokens[t * c * mel + ch * mel + m];
581 }
582 }
583 }
584 out
585}
586
587pub fn patchify_video(vol: &[f32], geo: &Geometry) -> Vec<f32> {
590 let (lf, lh, lw) = (geo.lf, geo.lh, geo.lw);
591 let c = 128usize;
592 let mut out = vec![0f32; c * lf * lh * lw];
593 for f in 0..lf {
594 for h in 0..lh {
595 for w in 0..lw {
596 let t = (f * lh + h) * lw + w;
597 for ch in 0..c {
598 out[t * c + ch] = vol[((ch * lf + f) * lh + h) * lw + w];
599 }
600 }
601 }
602 }
603 out
604}