1use crate::dit::{Proj, cmf_f32};
11use crate::pool::Pool;
12use cortiq_core::CmfModel;
13use std::sync::Arc;
14
15#[derive(Clone, Debug)]
18pub struct MossConfig {
19 pub dim: usize,
20 pub ffn_dim: usize,
21 pub freq_dim: usize,
22 pub latent_channels: usize,
23 pub num_heads: usize,
24 pub num_layers: usize,
25 pub text_dim: usize,
26 pub rope_base: f64,
27}
28
29impl MossConfig {
30 pub fn from_json(v: &serde_json::Value) -> Result<Self, String> {
31 let u = |name: &str| -> Result<usize, String> {
32 v[name]
33 .as_u64()
34 .map(|v| v as usize)
35 .ok_or_else(|| format!("MOSS config missing integer {name}"))
36 };
37 let dim = u("dim")?;
38 let num_heads = u("num_heads")?;
39 let freq_dim = u("freq_dim")?;
40 if dim == 0 || num_heads == 0 || dim % num_heads != 0 || (dim / num_heads) % 8 != 0 {
41 return Err(format!(
42 "invalid MOSS dim/head geometry: dim={dim}, num_heads={num_heads}"
43 ));
44 }
45 if freq_dim == 0 || freq_dim % 2 != 0 {
46 return Err(format!(
47 "MOSS freq_dim must be positive and even, got {freq_dim}"
48 ));
49 }
50 Ok(Self {
51 dim,
52 ffn_dim: u("ffn_dim")?,
53 freq_dim,
54 latent_channels: u("latent_channels")?,
55 num_heads,
56 num_layers: u("num_layers")?,
57 text_dim: u("text_dim")?,
58 rope_base: v["rope_base"].as_f64().unwrap_or(10_000.0),
59 })
60 }
61
62 pub fn head_dim(&self) -> usize {
63 self.dim / self.num_heads
64 }
65}
66
67struct TextBlock {
68 norm1: Vec<f32>,
69 norm2: Vec<f32>,
70 q_norm: Vec<f32>,
71 k_norm: Vec<f32>,
72 qkv: Proj,
73 qkv_b: Vec<f32>,
74 proj: Proj,
75 proj_b: Vec<f32>,
76 ffn_gate: Proj,
77 ffn_gate_b: Vec<f32>,
78 ffn_up: Proj,
79 ffn_up_b: Vec<f32>,
80 ffn_down: Proj,
81 ffn_down_b: Vec<f32>,
82}
83
84struct Block {
85 norm1: Vec<f32>,
86 norm2: Vec<f32>,
87 q_norm: Vec<f32>,
88 k_norm: Vec<f32>,
89 q: Proj,
90 k: Proj,
91 v: Proj,
92 o: Proj,
93 ffn_up_gate: Proj,
94 ffn_down: Proj,
95}
96
97pub struct MossDit {
100 pub cfg: MossConfig,
101 video_embedder: Proj,
102 video_embedder_b: Vec<f32>,
103 text_proj: Proj,
104 text_proj_b: Vec<f32>,
105 text_blocks: Vec<TextBlock>,
106 time_0_w: Vec<f32>,
107 time_0_b: Vec<f32>,
108 time_2_w: Vec<f32>,
109 time_2_b: Vec<f32>,
110 shared_mod_w: Vec<f32>,
111 shared_mod_b: Vec<f32>,
112 head_mod_w: Vec<f32>,
113 head_mod_b: Vec<f32>,
114 layers: Vec<Block>,
115 final_norm_video: Vec<f32>,
116 final_linear_video: Proj,
117 pool: Option<Arc<Pool>>,
118}
119
120impl MossDit {
121 pub fn from_cmf(model: &Arc<CmfModel>) -> Result<Self, String> {
123 let cfg: serde_json::Value = serde_json::from_slice(
124 model
125 .tensor_bytes("moss.config_json")
126 .map_err(|e| e.to_string())?,
127 )
128 .map_err(|e| format!("moss.config_json: {e}"))?;
129 let cfg = MossConfig::from_json(&cfg)?;
130 let f = |suffix: &str| cmf_f32(model, &format!("moss.{suffix}"));
131 let p = |suffix: &str| Proj::from_model(model, &format!("moss.{suffix}"));
132 let text_blocks = (0..2)
133 .map(|i| {
134 let x = format!("text_embedder.blocks.{i}");
135 Ok(TextBlock {
136 norm1: f(&format!("{x}.norm1.weight"))?,
137 norm2: f(&format!("{x}.norm2.weight"))?,
138 q_norm: f(&format!("{x}.q_norm.weight"))?,
139 k_norm: f(&format!("{x}.k_norm.weight"))?,
140 qkv: p(&format!("{x}.qkv.weight"))?,
141 qkv_b: f(&format!("{x}.qkv.bias"))?,
142 proj: p(&format!("{x}.proj.weight"))?,
143 proj_b: f(&format!("{x}.proj.bias"))?,
144 ffn_gate: p(&format!("{x}.ffn_gate.weight"))?,
145 ffn_gate_b: f(&format!("{x}.ffn_gate.bias"))?,
146 ffn_up: p(&format!("{x}.ffn_up.weight"))?,
147 ffn_up_b: f(&format!("{x}.ffn_up.bias"))?,
148 ffn_down: p(&format!("{x}.ffn_down.weight"))?,
149 ffn_down_b: f(&format!("{x}.ffn_down.bias"))?,
150 })
151 })
152 .collect::<Result<Vec<_>, String>>()?;
153 let layers = (0..cfg.num_layers)
154 .map(|i| {
155 let x = format!("layers.{i}");
156 Ok(Block {
157 norm1: f(&format!("{x}.norm1.weight"))?,
158 norm2: f(&format!("{x}.norm2.weight"))?,
159 q_norm: f(&format!("{x}.q_norm.weight"))?,
160 k_norm: f(&format!("{x}.k_norm.weight"))?,
161 q: p(&format!("{x}.proj.q.weight"))?,
162 k: p(&format!("{x}.proj.k.weight"))?,
163 v: p(&format!("{x}.proj.v.weight"))?,
164 o: p(&format!("{x}.proj.o.weight"))?,
165 ffn_up_gate: p(&format!("{x}.proj.ffn_up_gate.weight"))?,
166 ffn_down: p(&format!("{x}.proj.ffn_down.weight"))?,
167 })
168 })
169 .collect::<Result<Vec<_>, String>>()?;
170 let dit = Self {
171 video_embedder: p("video_embedder.weight")?,
172 video_embedder_b: f("video_embedder.bias")?,
173 text_proj: p("text_embedder.proj.weight")?,
174 text_proj_b: f("text_embedder.proj.bias")?,
175 time_0_w: f("time_embedding.0.weight")?,
176 time_0_b: f("time_embedding.0.bias")?,
177 time_2_w: f("time_embedding.2.weight")?,
178 time_2_b: f("time_embedding.2.bias")?,
179 shared_mod_w: f("shared_block_modulation.weight")?,
180 shared_mod_b: f("shared_block_modulation.bias")?,
181 head_mod_w: f("video_head_modulation.weight")?,
182 head_mod_b: f("video_head_modulation.bias")?,
183 final_norm_video: f("final_norm_video.weight")?,
184 final_linear_video: p("final_linear_video.weight")?,
185 text_blocks,
186 layers,
187 pool: Pool::from_env(),
188 cfg,
189 };
190 dit.validate()?;
191 Ok(dit)
192 }
193
194 fn validate(&self) -> Result<(), String> {
195 let d = self.cfg.dim;
196 if self.layers.len() != self.cfg.num_layers || self.text_blocks.len() != 2 {
197 return Err("MOSS block count mismatch".into());
198 }
199 if self.video_embedder.cols() != self.cfg.latent_channels
200 || self.video_embedder.rows() != d
201 || self.text_proj.cols() != self.cfg.text_dim
202 || self.text_proj.rows() != d
203 || self.final_linear_video.cols() != d
204 || self.final_linear_video.rows() != self.cfg.latent_channels
205 || self.final_norm_video.len() != d
206 {
207 return Err("MOSS top-level projection geometry mismatch".into());
208 }
209 if self.shared_mod_w.len() != 6 * d * d || self.shared_mod_b.len() != 6 * d {
210 return Err("MOSS shared modulation geometry mismatch".into());
211 }
212 if self.head_mod_w.len() != 2 * d * d || self.head_mod_b.len() != 2 * d {
213 return Err("MOSS head modulation geometry mismatch".into());
214 }
215 Ok(())
216 }
217
218 fn pool(&self) -> Option<&Pool> {
219 self.pool.as_deref()
220 }
221
222 pub fn prepare_text(&self, text: &[f32], tokens: usize) -> Result<Vec<f32>, String> {
227 if tokens == 0 || text.len() != tokens * self.cfg.text_dim {
228 return Err(format!(
229 "MOSS text conditioning has {} values for {tokens} tokens of width {}",
230 text.len(),
231 self.cfg.text_dim
232 ));
233 }
234 let mut x = linear(
235 &self.text_proj,
236 text,
237 tokens,
238 Some(&self.text_proj_b),
239 self.pool(),
240 );
241 let rope = Rope::new(&self.cfg, 0, 0, tokens);
242 for b in &self.text_blocks {
243 text_block(b, &mut x, tokens, &rope, &self.cfg, self.pool());
244 }
245 Ok(x)
246 }
247
248 pub fn forward(
253 &self,
254 latents: &[f32],
255 h: usize,
256 w: usize,
257 timestep: f32,
258 text: &[f32],
259 text_tokens: usize,
260 skip_layers: &[usize],
261 ) -> Result<Vec<f32>, String> {
262 if h == 0 || w == 0 || latents.len() != self.cfg.latent_channels * h * w {
263 return Err("MOSS latent shape is not [C,H,W]".into());
264 }
265 if text_tokens == 0 || text.len() != text_tokens * self.cfg.dim {
266 return Err("MOSS prepared text shape mismatch".into());
267 }
268 if skip_layers.iter().any(|&i| i >= self.layers.len()) {
269 return Err("MOSS skip layer out of range".into());
270 }
271 let image_tokens = h * w;
272 let mut image_in = vec![0f32; image_tokens * self.cfg.latent_channels];
273 for token in 0..image_tokens {
274 for c in 0..self.cfg.latent_channels {
275 image_in[token * self.cfg.latent_channels + c] = latents[c * image_tokens + token];
276 }
277 }
278 let video = linear(
279 &self.video_embedder,
280 &image_in,
281 image_tokens,
282 Some(&self.video_embedder_b),
283 self.pool(),
284 );
285 let mut x = Vec::with_capacity((image_tokens + text_tokens) * self.cfg.dim);
286 x.extend_from_slice(&video);
287 x.extend_from_slice(text);
288 let rope = Rope::new(&self.cfg, h, w, text_tokens);
289 let time = self.time_embedding(timestep);
290 let modulation = linear_small(&time, &self.shared_mod_w, &self.shared_mod_b);
291 let head = linear_small(&time, &self.head_mod_w, &self.head_mod_b);
292 for (i, block) in self.layers.iter().enumerate() {
293 if !skip_layers.contains(&i) {
294 main_block(
295 block,
296 &mut x,
297 image_tokens + text_tokens,
298 &rope,
299 &modulation,
300 &self.cfg,
301 self.pool(),
302 );
303 }
304 }
305 let d = self.cfg.dim;
306 let mut image = x[..image_tokens * d].to_vec();
307 rms_rows_inplace(
308 &mut image,
309 image_tokens,
310 d,
311 &self.final_norm_video,
312 None,
313 None,
314 self.pool(),
315 );
316 let (shift, scale) = (&head[..d], &head[d..]);
317 for row in image.chunks_exact_mut(d) {
318 for ((v, &s), &a) in row.iter_mut().zip(scale).zip(shift) {
319 *v = *v * (1.0 + s) + a;
320 }
321 }
322 let out = linear(
323 &self.final_linear_video,
324 &image,
325 image_tokens,
326 None,
327 self.pool(),
328 );
329 let mut velocity = vec![0f32; self.cfg.latent_channels * image_tokens];
330 for token in 0..image_tokens {
331 for c in 0..self.cfg.latent_channels {
332 velocity[c * image_tokens + token] = out[token * self.cfg.latent_channels + c];
333 }
334 }
335 Ok(velocity)
336 }
337
338 fn time_embedding(&self, t: f32) -> Vec<f32> {
339 let half = self.cfg.freq_dim / 2;
342 let mut input = vec![0f32; self.cfg.freq_dim];
343 for i in 0..half {
344 let freq = self.cfg.rope_base.powf(-(i as f64) / half as f64);
345 let p = t as f64 * freq;
346 input[i] = p.cos() as f32;
347 input[half + i] = p.sin() as f32;
348 }
349 let mut h = linear_small(&input, &self.time_0_w, &self.time_0_b);
350 for v in &mut h {
351 *v = silu(*v);
352 }
353 linear_small(&h, &self.time_2_w, &self.time_2_b)
354 }
355}
356
357struct Rope {
361 sin: Vec<f32>,
362 cos: Vec<f32>,
363}
364
365impl Rope {
366 fn new(cfg: &MossConfig, h: usize, w: usize, text_len: usize) -> Self {
367 let hd = cfg.head_dim();
368 let bands = hd / 8;
369 let n_img = h * w;
370 let n = n_img + text_len;
371 let mut sin = vec![0f32; n * hd];
372 let mut cos = vec![0f32; n * hd];
373 for token in 0..n {
374 let coords = if token < n_img {
375 let yy = token / w;
376 let xx = token % w;
377 [
378 0.0,
379 text_len as f64,
380 yy as f64 - (h.saturating_sub(1) as f64) / 2.0,
381 xx as f64 - (w.saturating_sub(1) as f64) / 2.0,
382 ]
383 } else {
384 [0.0, (token - n_img) as f64, 0.0, 0.0]
385 };
386 for axis in 0..4 {
387 for band in 0..bands {
388 let freq = 1.0 / cfg.rope_base.powf(band as f64 / bands as f64);
389 let phase = coords[axis] * freq;
390 let j = axis * bands + band;
391 sin[token * hd + j] = phase.sin() as f32;
392 cos[token * hd + j] = phase.cos() as f32;
393 sin[token * hd + hd / 2 + j] = phase.sin() as f32;
394 cos[token * hd + hd / 2 + j] = phase.cos() as f32;
395 }
396 }
397 }
398 Self { sin, cos }
399 }
400}
401
402struct SendRows(*mut f32);
405unsafe impl Send for SendRows {}
406unsafe impl Sync for SendRows {}
407impl SendRows {
408 #[allow(clippy::mut_from_ref)]
415 unsafe fn row(&self, off: usize, len: usize) -> &mut [f32] {
416 unsafe { std::slice::from_raw_parts_mut(self.0.add(off), len) }
417 }
418}
419
420fn rows(pool: Option<&Pool>, n: usize, f: &(dyn Fn(usize, usize) + Sync)) {
421 match pool {
422 Some(p) => p.run_rows(n, f),
423 None => f(0, n),
424 }
425}
426
427fn silu(x: f32) -> f32 {
428 x / (1.0 + (-x).exp())
429}
430
431fn situ(hidden: f32, gate: f32) -> f32 {
432 (4.0 * (gate / 4.0).tanh() * gate.sigmoid()) * (25.0 * (hidden / 25.0).tanh())
433}
434
435trait Sigmoid {
436 fn sigmoid(self) -> Self;
437}
438impl Sigmoid for f32 {
439 fn sigmoid(self) -> Self {
440 1.0 / (1.0 + (-self).exp())
441 }
442}
443
444fn linear_small(x: &[f32], w: &[f32], bias: &[f32]) -> Vec<f32> {
445 assert_eq!(w.len() % x.len(), 0, "small MOSS linear geometry");
446 let rows = w.len() / x.len();
447 assert_eq!(bias.len(), rows, "small MOSS linear bias geometry");
448 w.chunks_exact(x.len())
449 .zip(bias)
450 .map(|(r, &b)| b + r.iter().zip(x).map(|(&a, &v)| a * v).sum::<f32>())
451 .collect()
452}
453
454fn linear(p: &Proj, x: &[f32], n: usize, bias: Option<&[f32]>, pool: Option<&Pool>) -> Vec<f32> {
455 assert_eq!(x.len(), n * p.cols(), "MOSS projection input geometry");
456 let mut out = vec![0f32; n * p.rows()];
457 p.matmat(x, n, &mut out, pool);
458 if let Some(b) = bias {
459 assert_eq!(b.len(), p.rows(), "MOSS projection bias geometry");
460 for row in out.chunks_exact_mut(p.rows()) {
461 for (v, &b) in row.iter_mut().zip(b) {
462 *v += b;
463 }
464 }
465 }
466 out
467}
468
469fn rms_rows_inplace(
471 x: &mut [f32],
472 n: usize,
473 d: usize,
474 weight: &[f32],
475 scale: Option<&[f32]>,
476 shift: Option<&[f32]>,
477 pool: Option<&Pool>,
478) {
479 assert_eq!(x.len(), n * d);
480 assert_eq!(weight.len(), d);
481 if let Some(s) = scale {
482 assert_eq!(s.len(), d);
483 }
484 if let Some(s) = shift {
485 assert_eq!(s.len(), d);
486 }
487 let ptr = SendRows(x.as_mut_ptr());
488 rows(pool, n, &|lo, hi| {
489 for r in lo..hi {
490 let row = unsafe { ptr.row(r * d, d) };
492 let mean = row.iter().map(|&v| v * v).sum::<f32>() / d as f32;
493 let inv = 1.0 / (mean + 1e-6).sqrt();
494 for j in 0..d {
495 let mut v = row[j] * inv * weight[j];
496 if let Some(s) = scale {
497 v *= 1.0 + s[j];
498 }
499 if let Some(a) = shift {
500 v += a[j];
501 }
502 row[j] = v;
503 }
504 }
505 });
506}
507
508fn qk_norm_rope(
509 q: &mut [f32],
510 n: usize,
511 heads: usize,
512 hd: usize,
513 weight: &[f32],
514 rope: &Rope,
515 pool: Option<&Pool>,
516) {
517 assert_eq!(q.len(), n * heads * hd);
518 assert_eq!(weight.len(), hd);
519 let ptr = SendRows(q.as_mut_ptr());
520 rows(pool, n, &|lo, hi| {
521 for t in lo..hi {
522 for h in 0..heads {
523 let row = unsafe { ptr.row((t * heads + h) * hd, hd) };
525 let mean = row.iter().map(|&v| v * v).sum::<f32>() / hd as f32;
526 let inv = 1.0 / (mean + 1e-6).sqrt();
527 for j in 0..hd {
528 row[j] *= inv * weight[j];
529 }
530 for j in 0..hd / 2 {
531 let a = row[j];
532 let b = row[j + hd / 2];
533 let c = rope.cos[t * hd + j];
534 let s = rope.sin[t * hd + j];
535 row[j] = a * c - b * s;
536 row[j + hd / 2] = b * c + a * s;
537 }
538 }
539 }
540 });
541}
542
543fn softmax(row: &mut [f32]) {
544 let max = row.iter().fold(f32::NEG_INFINITY, |a, &b| a.max(b));
545 let mut sum = 0.0f32;
546 for v in row.iter_mut() {
547 *v = (*v - max).exp();
548 sum += *v;
549 }
550 if sum > 0.0 {
551 for v in row.iter_mut() {
552 *v /= sum;
553 }
554 }
555}
556
557fn attention(
561 q: &[f32],
562 k: &[f32],
563 v: &[f32],
564 n: usize,
565 heads: usize,
566 hd: usize,
567 pool: Option<&Pool>,
568) -> Vec<f32> {
569 let hidden = heads * hd;
570 let mut qh = vec![0f32; heads * n * hd];
571 let mut kh = vec![0f32; heads * n * hd];
572 let mut vh = vec![0f32; heads * n * hd];
573 for t in 0..n {
574 for h in 0..heads {
575 let src = &q[(t * heads + h) * hd..(t * heads + h + 1) * hd];
576 qh[(h * n + t) * hd..(h * n + t + 1) * hd].copy_from_slice(src);
577 let src = &k[(t * heads + h) * hd..(t * heads + h + 1) * hd];
578 kh[(h * n + t) * hd..(h * n + t + 1) * hd].copy_from_slice(src);
579 let src = &v[(t * heads + h) * hd..(t * heads + h + 1) * hd];
580 vh[(h * n + t) * hd..(h * n + t + 1) * hd].copy_from_slice(src);
581 }
582 }
583 let mut out = vec![0f32; n * hidden];
584 if n >= 128
585 && crate::gpu::enabled_here()
586 && !crate::gpu::mm_killed()
587 && crate::gpu::dit_attention(
588 &qh,
589 &kh,
590 &vh,
591 heads,
592 heads,
593 n,
594 hd,
595 1.0 / (hd as f32).sqrt(),
596 &mut out,
597 )
598 {
599 return out;
600 }
601 let mut scores = vec![0f32; n * n];
602 let mut vals_t = vec![0f32; hd * n];
603 let mut head_out = vec![0f32; n * hd];
604 for h in 0..heads {
605 let qs = &qh[h * n * hd..(h + 1) * n * hd];
606 let ks = &kh[h * n * hd..(h + 1) * n * hd];
607 let vs = &vh[h * n * hd..(h + 1) * n * hd];
608 crate::fcd_ops::gemm_nt(qs, ks, &mut scores, n, hd, n, pool);
611 let sp = SendRows(scores.as_mut_ptr());
612 rows(pool, n, &|lo, hi| {
613 for r in lo..hi {
614 softmax(unsafe { sp.row(r * n, n) });
616 }
617 });
618 for d in 0..hd {
619 for t in 0..n {
620 vals_t[d * n + t] = vs[t * hd + d];
621 }
622 }
623 crate::fcd_ops::gemm_nt(&scores, &vals_t, &mut head_out, n, n, hd, pool);
624 for t in 0..n {
625 out[t * hidden + h * hd..t * hidden + (h + 1) * hd]
626 .copy_from_slice(&head_out[t * hd..(t + 1) * hd]);
627 }
628 }
629 out
630}
631
632fn text_block(
633 b: &TextBlock,
634 x: &mut [f32],
635 n: usize,
636 rope: &Rope,
637 cfg: &MossConfig,
638 pool: Option<&Pool>,
639) {
640 let d = cfg.dim;
641 let mut norm = x.to_vec();
642 rms_rows_inplace(&mut norm, n, d, &b.norm1, None, None, pool);
643 let qkv = linear(&b.qkv, &norm, n, Some(&b.qkv_b), pool);
644 let mut q = qkv[..n * d].to_vec();
645 let mut k = qkv[n * d..2 * n * d].to_vec();
646 let v = qkv[2 * n * d..].to_vec();
647 qk_norm_rope(
648 &mut q,
649 n,
650 cfg.num_heads,
651 cfg.head_dim(),
652 &b.q_norm,
653 rope,
654 pool,
655 );
656 qk_norm_rope(
657 &mut k,
658 n,
659 cfg.num_heads,
660 cfg.head_dim(),
661 &b.k_norm,
662 rope,
663 pool,
664 );
665 let attn = attention(&q, &k, &v, n, cfg.num_heads, cfg.head_dim(), pool);
666 let out = linear(&b.proj, &attn, n, Some(&b.proj_b), pool);
667 for (a, &v) in x.iter_mut().zip(&out) {
668 *a += v;
669 }
670 let mut norm = x.to_vec();
671 rms_rows_inplace(&mut norm, n, d, &b.norm2, None, None, pool);
672 let gate = linear(&b.ffn_gate, &norm, n, Some(&b.ffn_gate_b), pool);
673 let mut up = linear(&b.ffn_up, &norm, n, Some(&b.ffn_up_b), pool);
674 for (a, &g) in up.iter_mut().zip(&gate) {
675 *a = situ(*a, g);
676 }
677 let out = linear(&b.ffn_down, &up, n, Some(&b.ffn_down_b), pool);
678 for (a, &v) in x.iter_mut().zip(&out) {
679 *a += v;
680 }
681}
682
683fn main_block(
684 b: &Block,
685 x: &mut [f32],
686 n: usize,
687 rope: &Rope,
688 modulate: &[f32],
689 cfg: &MossConfig,
690 pool: Option<&Pool>,
691) {
692 let d = cfg.dim;
693 assert_eq!(modulate.len(), 6 * d);
694 let (shift_a, rest) = modulate.split_at(d);
695 let (scale_a, rest) = rest.split_at(d);
696 let (gate_a, rest) = rest.split_at(d);
697 let (shift_f, rest) = rest.split_at(d);
698 let (scale_f, gate_f) = rest.split_at(d);
699 let mut norm = x.to_vec();
700 rms_rows_inplace(
701 &mut norm,
702 n,
703 d,
704 &b.norm1,
705 Some(scale_a),
706 Some(shift_a),
707 pool,
708 );
709 let mut q = linear(&b.q, &norm, n, None, pool);
710 let mut k = linear(&b.k, &norm, n, None, pool);
711 let v = linear(&b.v, &norm, n, None, pool);
712 qk_norm_rope(
713 &mut q,
714 n,
715 cfg.num_heads,
716 cfg.head_dim(),
717 &b.q_norm,
718 rope,
719 pool,
720 );
721 qk_norm_rope(
722 &mut k,
723 n,
724 cfg.num_heads,
725 cfg.head_dim(),
726 &b.k_norm,
727 rope,
728 pool,
729 );
730 let attn = attention(&q, &k, &v, n, cfg.num_heads, cfg.head_dim(), pool);
731 let out = linear(&b.o, &attn, n, None, pool);
732 for row in 0..n {
733 for j in 0..d {
734 x[row * d + j] += out[row * d + j] * gate_a[j];
735 }
736 }
737 let mut norm = x.to_vec();
738 rms_rows_inplace(
739 &mut norm,
740 n,
741 d,
742 &b.norm2,
743 Some(scale_f),
744 Some(shift_f),
745 pool,
746 );
747 let up_gate = linear(&b.ffn_up_gate, &norm, n, None, pool);
748 let inter = cfg.ffn_dim;
749 let mut ffn = vec![0f32; n * inter];
750 for row in 0..n {
751 for i in 0..inter {
752 ffn[row * inter + i] = situ(
753 up_gate[row * 2 * inter + i],
754 up_gate[row * 2 * inter + inter + i],
755 );
756 }
757 }
758 let out = linear(&b.ffn_down, &ffn, n, None, pool);
759 for row in 0..n {
760 for j in 0..d {
761 x[row * d + j] += out[row * d + j] * gate_f[j];
762 }
763 }
764}
765
766#[cfg(test)]
767mod tests {
768 use super::*;
769
770 #[test]
771 fn rope_layout_repeats_quarter_axis_frequencies() {
772 let cfg = MossConfig {
773 dim: 64,
774 ffn_dim: 64,
775 freq_dim: 8,
776 latent_channels: 4,
777 num_heads: 1,
778 num_layers: 1,
779 text_dim: 8,
780 rope_base: 10_000.0,
781 };
782 let r = Rope::new(&cfg, 2, 2, 3);
783 assert_eq!(r.sin.len(), 7 * 64);
784 for t in 0..7 {
785 assert_eq!(
786 &r.sin[t * 64..t * 64 + 32],
787 &r.sin[t * 64 + 32..(t + 1) * 64]
788 );
789 assert_eq!(
790 &r.cos[t * 64..t * 64 + 32],
791 &r.cos[t * 64 + 32..(t + 1) * 64]
792 );
793 }
794 }
795
796 #[test]
797 fn situ_matches_definition() {
798 let h = 2.5f32;
799 let g = -1.25f32;
800 let got = situ(h, g);
801 let want =
802 (4.0 * (g / 4.0).tanh() * (1.0 / (1.0 + (-g).exp()))) * (25.0 * (h / 25.0).tanh());
803 assert!((got - want).abs() < 1e-7);
804 }
805}