1use crate::ltxdit::{Attn, Lin, Rope, gelu_tanh, rms_plain, rows};
26use crate::pool::Pool;
27use crate::qtensor::QTensor;
28use cortiq_core::CmfModel;
29use std::sync::Arc;
30
31const EPS: f64 = 1e-6;
32
33fn rms(x: &[f32], w: &[f32], dst: &mut [f32]) {
36 let ss = x.iter().map(|&v| (v as f64) * (v as f64)).sum::<f64>() / x.len() as f64;
37 let inv = 1.0 / (ss + EPS).sqrt();
38 for ((d, &v), &g) in dst.iter_mut().zip(x).zip(w) {
39 *d = (v as f64 * inv) as f32 * g;
40 }
41}
42
43fn rms_nw(x: &mut [f32]) {
45 let ss = x.iter().map(|&v| (v as f64) * (v as f64)).sum::<f64>() / x.len() as f64;
46 let inv = 1.0 / (ss + EPS).sqrt();
47 for v in x.iter_mut() {
48 *v = (*v as f64 * inv) as f32;
49 }
50}
51
52struct Rot {
55 cos: Vec<f32>,
56 sin: Vec<f32>,
57 half: usize,
58}
59
60impl Rot {
61 fn build(seq: usize, head_dim: usize, base: f64, rotary: f64) -> Rot {
66 let half = head_dim / 2;
67 let rope_angles = (rotary * head_dim as f64 / 2.0) as usize;
68 let inv: Vec<f64> = (0..half)
69 .map(|j| {
70 if j < rope_angles {
71 1.0 / base.powf((2 * j) as f64 / head_dim as f64)
72 } else {
73 0.0
74 }
75 })
76 .collect();
77 let mut cos = vec![0f32; seq * half];
78 let mut sin = vec![0f32; seq * half];
79 for p in 0..seq {
80 for (j, &f) in inv.iter().enumerate() {
81 let a = p as f64 * f;
82 cos[p * half + j] = a.cos() as f32;
83 sin[p * half + j] = a.sin() as f32;
84 }
85 }
86 Rot { cos, sin, half }
87 }
88
89 fn apply(&self, t: usize, row: &mut [f32]) {
90 let h = self.half;
91 let (c, s) = (&self.cos[t * h..(t + 1) * h], &self.sin[t * h..(t + 1) * h]);
92 for i in 0..h {
93 let (a, b) = (row[i], row[i + h]);
94 row[i] = a * c[i] - b * s[i];
95 row[i + h] = b * c[i] + a * s[i];
96 }
97 }
98}
99
100struct GemmaLayer {
101 q: Lin,
102 k: Lin,
103 v: Option<Lin>,
104 o: Lin,
105 q_norm: Vec<f32>,
106 k_norm: Vec<f32>,
107 in_norm: Vec<f32>,
108 post_attn_norm: Vec<f32>,
109 pre_ff_norm: Vec<f32>,
110 post_ff_norm: Vec<f32>,
111 gate: Lin,
112 up: Lin,
113 down: Lin,
114 scalar: f32,
115 head_dim: usize,
116 q_heads: usize,
117 kv_heads: usize,
118 sliding: bool,
119}
120
121pub struct LtxTextEncoder {
123 embed: QTensor,
124 layers: Vec<GemmaLayer>,
125 norm: Vec<f32>,
126 video_agg: Lin,
127 audio_agg: Lin,
128 v_conn: Connector,
129 a_conn: Connector,
130 hidden: usize,
131 embed_scale: f32,
132 pub max_len: usize,
133 pub bos: u32,
134 pub pad: u32,
135}
136
137struct Connector {
138 blocks: Vec<(Attn, Lin, Lin)>,
139 registers: Vec<f32>,
140 dim: usize,
141 heads: usize,
142 dh: usize,
143 max_pos: f64,
144}
145
146fn vecf(model: &Arc<CmfModel>, name: &str) -> Result<Vec<f32>, String> {
147 crate::dit::cmf_f32(model, name)
148}
149
150impl LtxTextEncoder {
151 pub fn from_cmf(model: &Arc<CmfModel>) -> Result<LtxTextEncoder, String> {
152 let cfg_bytes = ["te.gemma_config_json", "ltx.gemma_config_json"]
153 .iter()
154 .find_map(|n| model.tensor(n).map(|e| model.entry_bytes(e)));
155 let cfg: serde_json::Value = match cfg_bytes {
156 Some(b) => serde_json::from_slice(b).map_err(|e| format!("gemma config: {e}"))?,
157 None => serde_json::Value::Null,
158 };
159 let tc = cfg.get("text_config").cloned().unwrap_or(serde_json::Value::Null);
160 let g = |k: &str, d: f64| tc.get(k).and_then(|v| v.as_f64()).unwrap_or(d);
161 let hidden = g("hidden_size", 3840.0) as usize;
162 let n_layers = g("num_hidden_layers", 48.0) as usize;
163 let head_dim = g("head_dim", 256.0) as usize;
164 let global_head_dim = g("global_head_dim", 512.0) as usize;
165 let types: Vec<String> = tc
166 .get("layer_types")
167 .and_then(|v| v.as_array())
168 .map(|a| a.iter().map(|s| s.as_str().unwrap_or("sliding_attention").to_string()).collect())
169 .unwrap_or_else(|| {
170 (0..n_layers)
172 .map(|i| {
173 if i % 6 == 5 { "full_attention".into() } else { "sliding_attention".into() }
174 })
175 .collect()
176 });
177
178 let mut layers = Vec::with_capacity(n_layers);
179 for i in 0..n_layers {
180 let p = format!("te.model.layers.{i}");
181 let sliding = types[i] != "full_attention";
182 let hd = if sliding { head_dim } else { global_head_dim };
183 let q = Lin::load(model, &format!("{p}.self_attn.q_proj"), false)?;
184 let k = Lin::load(model, &format!("{p}.self_attn.k_proj"), false)?;
185 let v = match model.tensor(&format!("{p}.self_attn.v_proj.weight")) {
186 Some(_) => Some(Lin::load(model, &format!("{p}.self_attn.v_proj"), false)?),
187 None => None,
188 };
189 let q_rows = model
190 .tensor(&format!("{p}.self_attn.q_proj.weight"))
191 .ok_or_else(|| format!("missing {p}.self_attn.q_proj.weight"))?
192 .shape[0];
193 let k_rows = model
194 .tensor(&format!("{p}.self_attn.k_proj.weight"))
195 .ok_or("missing k_proj")?
196 .shape[0];
197 layers.push(GemmaLayer {
198 q,
199 k,
200 v,
201 o: Lin::load(model, &format!("{p}.self_attn.o_proj"), false)?,
202 q_norm: vecf(model, &format!("{p}.self_attn.q_norm.weight"))?,
203 k_norm: vecf(model, &format!("{p}.self_attn.k_norm.weight"))?,
204 in_norm: vecf(model, &format!("{p}.input_layernorm.weight"))?,
205 post_attn_norm: vecf(model, &format!("{p}.post_attention_layernorm.weight"))?,
206 pre_ff_norm: vecf(model, &format!("{p}.pre_feedforward_layernorm.weight"))?,
207 post_ff_norm: vecf(model, &format!("{p}.post_feedforward_layernorm.weight"))?,
208 gate: Lin::load(model, &format!("{p}.mlp.gate_proj"), false)?,
209 up: Lin::load(model, &format!("{p}.mlp.up_proj"), false)?,
210 down: Lin::load(model, &format!("{p}.mlp.down_proj"), false)?,
211 scalar: vecf(model, &format!("{p}.layer_scalar"))?[0],
212 head_dim: hd,
213 q_heads: q_rows / hd,
214 kv_heads: k_rows / hd,
215 sliding,
216 });
217 }
218
219 let conn = |prefix: &str, dim: usize, heads: usize, dh: usize| -> Result<Connector, String> {
220 let mut blocks = Vec::new();
221 let mut i = 0usize;
222 while model
223 .tensor(&format!("{prefix}.transformer_1d_blocks.{i}.attn1.to_q.weight"))
224 .is_some()
225 {
226 let p = format!("{prefix}.transformer_1d_blocks.{i}");
227 blocks.push((
228 Attn::load(model, &format!("{p}.attn1"), heads, dh)?,
229 Lin::load(model, &format!("{p}.ff.net.0.proj"), true)?,
230 Lin::load(model, &format!("{p}.ff.net.2"), true)?,
231 ));
232 i += 1;
233 }
234 Ok(Connector {
235 blocks,
236 registers: vecf(model, &format!("{prefix}.learnable_registers"))?,
237 dim,
238 heads,
239 dh,
240 max_pos: 4096.0,
241 })
242 };
243
244 Ok(LtxTextEncoder {
245 embed: QTensor::from_model(model, "te.model.embed_tokens.weight")?,
246 layers,
247 norm: vecf(model, "te.model.norm.weight")?,
248 video_agg: Lin::load(model, "te.text_embedding_projection.video_aggregate_embed", true)?,
249 audio_agg: Lin::load(model, "te.text_embedding_projection.audio_aggregate_embed", true)?,
250 v_conn: conn("dit.video_embeddings_connector", 4096, 32, 128)?,
251 a_conn: conn("dit.audio_embeddings_connector", 2048, 32, 64)?,
252 hidden,
253 embed_scale: (hidden as f64).sqrt() as f32,
254 max_len: 1024,
255 bos: tc.get("bos_token_id").and_then(|v| v.as_u64()).unwrap_or(2) as u32,
256 pad: tc.get("pad_token_id").and_then(|v| v.as_u64()).unwrap_or(0) as u32,
257 })
258 }
259
260 pub fn pad_ids(&self, ids: &[u32]) -> (Vec<u32>, Vec<f32>) {
263 let mut body = Vec::with_capacity(self.max_len);
264 if ids.first() != Some(&self.bos) {
265 body.push(self.bos);
266 }
267 body.extend_from_slice(ids);
268 body.truncate(self.max_len);
269 let padlen = self.max_len - body.len();
270 let mut out = vec![self.pad; padlen];
271 out.extend_from_slice(&body);
272 let mut mask = vec![0f32; padlen];
273 mask.extend(std::iter::repeat_n(1f32, body.len()));
274 (out, mask)
275 }
276
277 pub fn hidden_states(&self, ids: &[u32], mask: &[f32], pool: Option<&Pool>) -> Vec<Vec<f32>> {
280 let t = ids.len();
281 let d = self.hidden;
282 let mut x = vec![0f32; t * d];
283 for (i, &id) in ids.iter().enumerate() {
284 self.embed.row_f32(id as usize, &mut x[i * d..(i + 1) * d]);
285 for v in x[i * d..(i + 1) * d].iter_mut() {
286 *v *= self.embed_scale;
287 }
288 }
289 let mut out = vec![x.clone()];
290 let rot_slide = Rot::build(t, self.layers[0].head_dim, 10000.0, 1.0);
292 let full = self.layers.iter().find(|l| !l.sliding);
293 let rot_full = full.map(|l| Rot::build(t, l.head_dim, 1_000_000.0, 0.25));
294
295 for layer in &self.layers {
296 let rot = if layer.sliding { &rot_slide } else { rot_full.as_ref().unwrap() };
297 let mut h = vec![0f32; t * d];
298 for i in 0..t {
299 rms(&x[i * d..(i + 1) * d], &layer.in_norm, &mut h[i * d..(i + 1) * d]);
300 }
301 let attn = self.attention(layer, &h, t, mask, rot, pool);
302 for i in 0..t {
303 let mut n = vec![0f32; d];
304 rms(&attn[i * d..(i + 1) * d], &layer.post_attn_norm, &mut n);
305 for (v, &a) in x[i * d..(i + 1) * d].iter_mut().zip(&n) {
306 *v += a;
307 }
308 }
309 let mut h2 = vec![0f32; t * d];
310 for i in 0..t {
311 rms(&x[i * d..(i + 1) * d], &layer.pre_ff_norm, &mut h2[i * d..(i + 1) * d]);
312 }
313 let mut g = layer.gate.apply(&h2, t, pool);
314 let u = layer.up.apply(&h2, t, pool);
315 for (a, &b) in g.iter_mut().zip(&u) {
316 *a = gelu_tanh(*a) * b;
317 }
318 let ff = layer.down.apply(&g, t, pool);
319 for i in 0..t {
320 let mut n = vec![0f32; d];
321 rms(&ff[i * d..(i + 1) * d], &layer.post_ff_norm, &mut n);
322 for (v, &a) in x[i * d..(i + 1) * d].iter_mut().zip(&n) {
323 *v += a;
324 }
325 }
326 for v in x.iter_mut() {
327 *v *= layer.scalar;
328 }
329 out.push(x.clone());
330 }
331 if let Some(last) = out.last_mut() {
337 let mut n = vec![0f32; t * d];
338 for i in 0..t {
339 rms(&last[i * d..(i + 1) * d], &self.norm, &mut n[i * d..(i + 1) * d]);
340 }
341 *last = n;
342 }
343 out
344 }
345
346 fn attention(
347 &self,
348 l: &GemmaLayer,
349 h: &[f32],
350 t: usize,
351 mask: &[f32],
352 rot: &Rot,
353 pool: Option<&Pool>,
354 ) -> Vec<f32> {
355 let hd = l.head_dim;
356 let qi = l.q_heads * hd;
357 let ki = l.kv_heads * hd;
358 let mut q = l.q.apply(h, t, pool);
359 let mut k = l.k.apply(h, t, pool);
360 let mut v = match &l.v {
361 Some(p) => p.apply(h, t, pool),
362 None => k.clone(),
363 };
364 for i in 0..t {
365 for hh in 0..l.q_heads {
366 let r = &mut q[i * qi + hh * hd..i * qi + (hh + 1) * hd];
367 let mut n = vec![0f32; hd];
368 rms(r, &l.q_norm, &mut n);
369 r.copy_from_slice(&n);
370 rot.apply(i, r);
371 }
372 for hh in 0..l.kv_heads {
373 let r = &mut k[i * ki + hh * hd..i * ki + (hh + 1) * hd];
374 let mut n = vec![0f32; hd];
375 rms(r, &l.k_norm, &mut n);
376 r.copy_from_slice(&n);
377 rot.apply(i, r);
378 rms_nw(&mut v[i * ki + hh * hd..i * ki + (hh + 1) * hd]);
379 }
380 }
381 let window = if l.sliding { 1024usize } else { usize::MAX };
385 let mut bias = vec![0f32; t * t];
386 for i in 0..t {
387 for j in 0..t {
388 let blocked =
389 j > i || (window != usize::MAX && i - j >= window) || mask[j] == 0.0;
390 bias[i * t + j] = if blocked { f32::NEG_INFINITY } else { 0.0 };
391 }
392 }
393 let dead: Vec<bool> = (0..t).map(|i| mask[i] == 0.0).collect();
399 let mut out = vec![0f32; t * qi];
400 let mut qh = vec![0f32; t * hd];
401 let mut kh = vec![0f32; t * hd];
402 let mut vh = vec![0f32; t * hd];
403 let mut sc = vec![0f32; t * t];
404 let mut oh = vec![0f32; t * hd];
405 for hh in 0..l.q_heads {
406 let kv = hh * l.kv_heads / l.q_heads;
407 for i in 0..t {
408 qh[i * hd..(i + 1) * hd].copy_from_slice(&q[i * qi + hh * hd..][..hd]);
409 kh[i * hd..(i + 1) * hd].copy_from_slice(&k[i * ki + kv * hd..][..hd]);
410 vh[i * hd..(i + 1) * hd].copy_from_slice(&v[i * ki + kv * hd..][..hd]);
411 }
412 crate::fcd_ops::gemm_nt(&qh, &kh, &mut sc, t, hd, t, pool);
413 let sp = crate::ltxdit::Shared(sc.as_mut_ptr());
414 rows(pool, t, &|s, e| {
415 let r = unsafe { sp.at(s * t, (e - s) * t) };
416 for (row, i) in r.chunks_exact_mut(t).zip(s..e) {
417 if dead[i] {
418 row.iter_mut().for_each(|x| *x = 0.0);
419 continue;
420 }
421 for (x, b) in row.iter_mut().zip(&bias[i * t..(i + 1) * t]) {
422 *x += *b;
423 }
424 crate::ltxdit::softmax(row);
425 }
426 });
427 oh.iter_mut().for_each(|x| *x = 0.0);
428 crate::fcd_ops::gemm_dx(&sc, &vh, &mut oh, t, hd, t, pool);
429 for i in 0..t {
430 out[i * qi + hh * hd..i * qi + (hh + 1) * hd]
431 .copy_from_slice(&oh[i * hd..(i + 1) * hd]);
432 }
433 }
434 l.o.apply(&out, t, pool)
435 }
436
437 pub fn encode_ids(
439 &self,
440 ids: &[u32],
441 mask: &[f32],
442 pool: Option<&Pool>,
443 ) -> (Vec<f32>, Vec<f32>, usize) {
444 let hs = self.hidden_states(ids, mask, pool);
445 let (t, d, l) = (ids.len(), self.hidden, hs.len());
446 let mut feats = vec![0f32; t * d * l];
449 for (li, layer) in hs.iter().enumerate() {
450 for i in 0..t {
451 let row = &layer[i * d..(i + 1) * d];
452 let var = row.iter().map(|&v| (v as f64) * (v as f64)).sum::<f64>() / d as f64;
453 let inv = 1.0 / (var + 1e-6).sqrt();
454 let keep = mask[i] != 0.0;
455 for j in 0..d {
456 feats[i * d * l + j * l + li] = if keep { (row[j] as f64 * inv) as f32 } else { 0.0 };
457 }
458 }
459 }
460 let project = |agg: &Lin, out_dim: usize| -> Vec<f32> {
465 let scale = ((out_dim as f64) / (d as f64)).sqrt() as f32;
466 let scaled: Vec<f32> = feats.iter().map(|&v| v * scale).collect();
467 crate::gpu::cpu_scope(|| agg.apply(&scaled, t, pool))
468 };
469 let vfeat = project(&self.video_agg, 4096);
470 let afeat = project(&self.audio_agg, 2048);
471 let order: Vec<usize> = (0..t)
473 .filter(|&i| mask[i] != 0.0)
474 .chain((0..t).filter(|&i| mask[i] == 0.0))
475 .collect();
476 let valid = mask.iter().filter(|&&m| m != 0.0).count();
477 let reorder = |x: &[f32], dim: usize| -> Vec<f32> {
478 let mut o = vec![0f32; t * dim];
479 for (new, &old) in order.iter().enumerate() {
480 o[new * dim..(new + 1) * dim].copy_from_slice(&x[old * dim..(old + 1) * dim]);
481 }
482 o
483 };
484 let v = self.v_conn.run(&reorder(&vfeat, 4096), t, valid, pool);
485 let a = self.a_conn.run(&reorder(&afeat, 2048), t, valid, pool);
486 (v, a, t)
487 }
488}
489
490impl Connector {
491 fn run(&self, x: &[f32], t: usize, valid: usize, pool: Option<&Pool>) -> Vec<f32> {
496 let d = self.dim;
497 let regs = self.registers.len() / d;
498 let mut h = x.to_vec();
499 for i in valid..t {
500 let r = i % regs;
501 h[i * d..(i + 1) * d].copy_from_slice(&self.registers[r * d..(r + 1) * d]);
502 }
503 let pos: Vec<Vec<f64>> = (0..t).map(|i| vec![i as f64]).collect();
504 let pe = Rope::build(&pos, &[self.max_pos], d, self.heads, 10000.0);
505 for (attn, ff_in, ff_out) in &self.blocks {
506 let mut n = vec![0f32; t * d];
507 for i in 0..t {
508 rms_plain(&h[i * d..(i + 1) * d], &mut n[i * d..(i + 1) * d]);
509 }
510 let a = attn.forward(&n, t, &n, t, Some(&pe), Some(&pe), None, pool);
511 for (v, &y) in h.iter_mut().zip(&a) {
512 *v += y;
513 }
514 let mut n2 = vec![0f32; t * d];
515 for i in 0..t {
516 rms_plain(&h[i * d..(i + 1) * d], &mut n2[i * d..(i + 1) * d]);
517 }
518 let mut g = ff_in.apply(&n2, t, pool);
519 for v in g.iter_mut() {
520 *v = gelu_tanh(*v);
521 }
522 let f = ff_out.apply(&g, t, pool);
523 for (v, &y) in h.iter_mut().zip(&f) {
524 *v += y;
525 }
526 }
527 let mut out = vec![0f32; t * d];
528 for i in 0..t {
529 rms_plain(&h[i * d..(i + 1) * d], &mut out[i * d..(i + 1) * d]);
530 }
531 let _ = self.dh;
532 out
533 }
534}