1use crate::Pipeline;
10use crate::moss_image::MossDit;
11use crate::sampler::{SamplerConfig, SplitMix64};
12use crate::vae::Flux2VaeDecoder;
13use cortiq_core::CmfModel;
14use std::path::Path;
15use std::sync::Arc;
16use std::time::Instant;
17
18pub const ARCH_NAME: &str = "moss_image";
20pub const MAX_TOKENS: usize = 4096;
21
22#[derive(Clone, Debug)]
26pub struct MossDefaults {
27 pub height: usize,
28 pub width: usize,
29 pub steps: usize,
30 pub cfg: f32,
31 pub shift: f32,
32 pub negative_prompt: String,
33 pub max_sequence_length: usize,
34 pub uncond_skip_layers: Vec<usize>,
35}
36
37impl Default for MossDefaults {
38 fn default() -> Self {
39 Self {
40 height: 1024,
41 width: 1024,
42 steps: 50,
43 cfg: 3.0,
44 shift: 5.0,
45 negative_prompt: String::new(),
46 max_sequence_length: MAX_TOKENS,
47 uncond_skip_layers: vec![22],
48 }
49 }
50}
51
52impl MossDefaults {
53 pub fn from_json(v: &serde_json::Value) -> Self {
54 let d = Self::default();
55 let usize_or =
56 |key: &str, fallback| v[key].as_u64().map(|x| x as usize).unwrap_or(fallback);
57 let layers = v["uncond_skip_layers"]
58 .as_array()
59 .map(|a| {
60 a.iter()
61 .filter_map(serde_json::Value::as_u64)
62 .map(|i| i as usize)
63 .collect()
64 })
65 .unwrap_or(d.uncond_skip_layers.clone());
66 Self {
67 height: usize_or("height", d.height),
68 width: usize_or("width", d.width),
69 steps: usize_or("steps", d.steps),
70 cfg: v["cfg"].as_f64().map(|x| x as f32).unwrap_or(d.cfg),
71 shift: v["shift"].as_f64().map(|x| x as f32).unwrap_or(d.shift),
72 negative_prompt: v["negative_prompt"]
73 .as_str()
74 .unwrap_or(&d.negative_prompt)
75 .to_string(),
76 max_sequence_length: usize_or("max_sequence_length", d.max_sequence_length),
77 uncond_skip_layers: layers,
78 }
79 }
80
81 pub fn of(model: &CmfModel) -> Self {
82 model
83 .tensor_bytes("moss.defaults_json")
84 .ok()
85 .and_then(|b| serde_json::from_slice::<serde_json::Value>(b).ok())
86 .map(|v| Self::from_json(&v))
87 .unwrap_or_default()
88 }
89}
90
91#[derive(Clone, Copy, Debug)]
95struct MossScheduler {
96 num_train_timesteps: usize,
97 training_shift: f32,
98}
99
100impl MossScheduler {
101 fn of(model: &CmfModel) -> Result<Self, String> {
102 let json: serde_json::Value = serde_json::from_slice(
103 model
104 .tensor_bytes("moss.scheduler_json")
105 .map_err(|e| e.to_string())?,
106 )
107 .map_err(|e| format!("moss.scheduler_json: {e}"))?;
108 let num_train_timesteps = json["num_train_timesteps"]
109 .as_u64()
110 .ok_or("MOSS scheduler missing num_train_timesteps")?
111 as usize;
112 let training_shift = json["training_shift"]
113 .as_f64()
114 .ok_or("MOSS scheduler missing training_shift")? as f32;
115 if num_train_timesteps < 2 || !training_shift.is_finite() || training_shift <= 0.0 {
116 return Err("MOSS scheduler has invalid training grid/shift".into());
117 }
118 Ok(Self {
119 num_train_timesteps,
120 training_shift,
121 })
122 }
123}
124
125#[derive(Clone, Debug)]
127pub struct MossParams {
128 pub height: usize,
129 pub width: usize,
130 pub steps: usize,
131 pub cfg: f32,
132 pub shift: f32,
133 pub max_tokens: usize,
134 pub seed: u64,
135 pub num_images: usize,
136 pub negative_prompt: Option<String>,
137 pub uncond_skip_layers: Option<Vec<usize>>,
140 pub slg_layers: Vec<usize>,
143 pub slg_scale: f32,
145 pub slg_start: f32,
148 pub slg_stop: f32,
151}
152
153impl MossParams {
154 pub fn from_defaults(d: &MossDefaults) -> Self {
155 Self {
156 height: d.height,
157 width: d.width,
158 steps: d.steps,
159 cfg: d.cfg,
160 shift: d.shift,
161 max_tokens: d.max_sequence_length,
162 seed: 42,
163 num_images: 1,
164 negative_prompt: None,
165 uncond_skip_layers: None,
166 slg_layers: Vec::new(),
167 slg_scale: 0.0,
168 slg_start: 0.0,
169 slg_stop: 1.0,
170 }
171 }
172}
173
174impl Default for MossParams {
175 fn default() -> Self {
176 Self::from_defaults(&MossDefaults::default())
177 }
178}
179
180pub struct MossImage {
182 pub rgb: Vec<u8>,
183 pub height: usize,
184 pub width: usize,
185 pub seed: u64,
186}
187
188impl MossImage {
189 pub fn save(&self, path: &Path) -> Result<(), String> {
190 if path
191 .extension()
192 .and_then(|e| e.to_str())
193 .is_some_and(|e| e.eq_ignore_ascii_case("ppm"))
194 {
195 let mut ppm = format!("P6\n{} {}\n255\n", self.width, self.height).into_bytes();
196 ppm.extend_from_slice(&self.rgb);
197 return std::fs::write(path, ppm).map_err(|e| format!("{}: {e}", path.display()));
198 }
199 image::RgbImage::from_raw(self.width as u32, self.height as u32, self.rgb.clone())
200 .ok_or("MOSS output dimensions overflow")?
201 .save(path)
202 .map_err(|e| format!("{}: {e}", path.display()))
203 }
204}
205
206#[derive(Clone, Debug, Default)]
207pub struct MossTimings {
208 pub text_encode: f64,
209 pub dit_load: f64,
210 pub text_prepare: f64,
211 pub steps: Vec<f64>,
212 pub vae: f64,
213 pub total: f64,
214}
215
216impl MossTimings {
217 pub fn median_step(&self) -> f64 {
218 if self.steps.is_empty() {
219 return 0.0;
220 }
221 let mut v = self.steps.clone();
222 v.sort_by(f64::total_cmp);
223 v[v.len() / 2]
224 }
225}
226
227pub fn moss_sigmas(steps: usize, shift: f32, training_shift: f32) -> Result<Vec<f32>, String> {
232 moss_sigmas_for_training_grid(steps, shift, training_shift, 1000)
233}
234
235fn moss_sigmas_for_training_grid(
236 steps: usize,
237 shift: f32,
238 training_shift: f32,
239 total: usize,
240) -> Result<Vec<f32>, String> {
241 if steps == 0
242 || total < 2
243 || !shift.is_finite()
244 || !training_shift.is_finite()
245 || shift <= 0.0
246 || training_shift <= 0.0
247 {
248 return Err("MOSS steps and shifts must be finite positive values".into());
249 }
250 let grid = |count: usize, s: f32| -> Vec<f32> {
251 let lower = s / (total as f32 + s - 1.0);
252 if count == 1 {
253 return vec![1.0];
254 }
255 (0..count)
256 .map(|i| {
257 let v = 1.0 + (lower - 1.0) * (i as f32 / (count - 1) as f32);
258 s * v / (1.0 + (s - 1.0) * v)
259 })
260 .collect()
261 };
262 let proposed = grid(steps, shift);
263 let mut training = grid(total, training_shift);
264 training.reverse();
265 let mut snapped = Vec::with_capacity(steps + 1);
266 for sigma in proposed {
267 let upper = training.partition_point(|v| *v < sigma).min(total - 1);
268 let lower = upper.saturating_sub(1);
269 let choose = if (sigma - training[lower]).abs() <= (training[upper] - sigma).abs() {
270 lower
271 } else {
272 upper
273 };
274 let value = training[choose];
275 if snapped.last().copied() != Some(value) {
276 snapped.push(value);
277 }
278 }
279 if snapped.is_empty()
280 || snapped
281 .windows(2)
282 .any(|s| !s[0].is_finite() || s[0] <= s[1])
283 {
284 return Err("MOSS scheduler produced an invalid non-descending sigma grid".into());
285 }
286 snapped.push(0.0);
287 Ok(snapped)
288}
289
290fn prompt_ids(pipeline: &Pipeline, prompt: &str, max: usize) -> Result<Vec<u32>, String> {
291 if max == 0 || max > MAX_TOKENS {
292 return Err(format!("--max-sequence-length must be in 1..={MAX_TOKENS}"));
293 }
294 let messages = [("user".to_string(), prompt.to_string())];
295 let mut ids = pipeline
298 .tokenizer
299 .apply_chat_template_opts(&messages, Some(true));
300 ids.truncate(max);
301 if ids.is_empty() {
302 return Err("MOSS template/tokenizer produced no tokens".into());
303 }
304 Ok(ids)
305}
306
307fn gauss(n: usize, seed: u64) -> Vec<f32> {
308 let mut rng = SplitMix64::new(seed);
309 let mut uniform = || (rng.next_u64() >> 11) as f64 / (1u64 << 53) as f64;
310 let mut out = Vec::with_capacity(n);
311 while out.len() < n {
312 let a = uniform().max(1e-300);
313 let b = uniform();
314 let r = (-2.0 * a.ln()).sqrt();
315 let phase = 2.0 * std::f64::consts::PI * b;
316 out.push((r * phase.cos()) as f32);
317 if out.len() < n {
318 out.push((r * phase.sin()) as f32);
319 }
320 }
321 out
322}
323
324fn moss_skip_active(step: usize, total_steps: usize, start: f32, stop: f32) -> bool {
328 total_steps > 0
329 && start <= step as f32 / (total_steps as f32)
330 && step as f32 / (total_steps as f32) < stop
331}
332
333fn apply_moss_guidance(
339 prediction: &mut [f32],
340 conditional: Option<&[f32]>,
341 uncond: Option<&[f32]>,
342 cfg: f32,
343 skipped: Option<&[f32]>,
344 slg_scale: f32,
345) -> Result<(), String> {
346 if let Some(uncond) = uncond {
347 if uncond.len() != prediction.len() {
348 return Err("MOSS unconditional prediction shape mismatch".into());
349 }
350 for (v, &u) in prediction.iter_mut().zip(uncond) {
351 *v = u + cfg * (*v - u);
352 }
353 }
354 match (conditional, skipped) {
355 (None, None) => Ok(()),
356 (Some(conditional), Some(skipped)) => {
357 if conditional.len() != prediction.len() || skipped.len() != prediction.len() {
358 return Err("MOSS SLG prediction shape mismatch".into());
359 }
360 if uncond.is_some() {
361 for ((v, &c), &s) in prediction.iter_mut().zip(conditional).zip(skipped) {
362 *v += slg_scale * (c - s);
363 }
364 } else {
365 for ((v, &c), &s) in prediction.iter_mut().zip(conditional).zip(skipped) {
366 *v = s + slg_scale * (c - s);
367 }
368 }
369 Ok(())
370 }
371 _ => Err("MOSS SLG needs both conditional and skipped predictions".into()),
372 }
373}
374
375fn pixels_to_u8(rgb: &[f32], height: usize, width: usize) -> Result<Vec<u8>, String> {
376 let plane = height
377 .checked_mul(width)
378 .ok_or("MOSS image dimensions overflow")?;
379 if rgb.len() != plane * 3 {
380 return Err(format!(
381 "Flux2 RGB has {} values, expected {}",
382 rgb.len(),
383 plane * 3
384 ));
385 }
386 let mut out = vec![0u8; plane * 3];
387 for px in 0..plane {
388 for c in 0..3 {
389 out[px * 3 + c] =
390 ((rgb[c * plane + px] * 0.5 + 0.5).clamp(0.0, 1.0) * 255.0).round_ties_even() as u8;
391 }
392 }
393 Ok(out)
394}
395
396pub fn generate_images(
400 model_path: &Path,
401 prompt: &str,
402 p: &MossParams,
403 mut progress: impl FnMut(usize, usize, usize),
404) -> Result<(Vec<MossImage>, MossTimings), String> {
405 if p.height == 0 || p.width == 0 || p.height % 16 != 0 || p.width % 16 != 0 {
406 return Err(format!(
407 "MOSS --height/--width must be positive multiples of 16 (got {}x{})",
408 p.width, p.height
409 ));
410 }
411 if p.height > 4096 || p.width > 4096 {
412 return Err("MOSS dimensions may not exceed 4096".into());
413 }
414 if p.num_images == 0
415 || p.steps == 0
416 || !p.cfg.is_finite()
417 || p.cfg < 0.0
418 || !p.shift.is_finite()
419 || p.shift <= 0.0
420 {
421 return Err("invalid MOSS steps, cfg or shift".into());
422 }
423 if !p.slg_scale.is_finite() || p.slg_scale < 0.0 {
424 return Err("MOSS --slg-scale must be finite and nonnegative".into());
425 }
426 if !p.slg_start.is_finite()
427 || !p.slg_stop.is_finite()
428 || p.slg_start < 0.0
429 || p.slg_start > p.slg_stop
430 || p.slg_stop > 1.0
431 {
432 return Err("MOSS requires 0 <= --slg-start <= --slg-stop <= 1".into());
433 }
434 let all_started = Instant::now();
435 let model = CmfModel::open(model_path).map_err(|e| format!("{}: {e}", model_path.display()))?;
436 if model.header.arch.arch_name != ARCH_NAME {
437 return Err(format!(
438 "{} has architecture {:?}, expected {ARCH_NAME}",
439 model_path.display(),
440 model.header.arch.arch_name
441 ));
442 }
443 let defaults = MossDefaults::of(&model);
444 let scheduler = MossScheduler::of(&model)?;
445 let neg_prompt = p
446 .negative_prompt
447 .clone()
448 .unwrap_or_else(|| defaults.negative_prompt.clone());
449 let do_cfg = p.cfg != 1.0;
451 let mut timing = MossTimings::default();
452
453 drop(model);
459
460 let text_started = Instant::now();
462 let (positive, positive_tokens, negative, negative_tokens) = {
463 let mut stage = crate::gpu::image_stage_scope();
468 let stage_model = Arc::new(
469 CmfModel::open(model_path).map_err(|e| format!("{}: {e}", model_path.display()))?,
470 );
471 stage.track_model(stage_model.uid());
472 let _cpu =
475 (std::env::var("CMF_MOSS_TE_GPU").as_deref() != Ok("1")).then(crate::gpu::pause_gpu);
476 let mut encoder = Pipeline::from_encoder_model(&stage_model, SamplerConfig::default())
477 .map_err(|e| format!("MOSS Qwen3.5 encoder: {e}"))?;
478 let pos_ids = prompt_ids(&encoder, prompt, p.max_tokens)?;
479 let pos_tokens = pos_ids.len();
480 let pos = encoder.encode_hidden_states(&pos_ids)?;
481 let (neg, neg_tokens) = if do_cfg {
482 let ids = prompt_ids(&encoder, &neg_prompt, p.max_tokens)?;
483 let n = ids.len();
484 (Some(encoder.encode_hidden_states(&ids)?), Some(n))
485 } else {
486 (None, None)
487 };
488 (pos, pos_tokens, neg, neg_tokens)
489 };
490 timing.text_encode = text_started.elapsed().as_secs_f64();
491
492 let vae_loader = {
498 let path = model_path.to_path_buf();
499 std::thread::spawn(move || -> Result<Flux2VaeDecoder, String> {
500 let stage_model =
501 Arc::new(CmfModel::open(&path).map_err(|e| format!("{}: {e}", path.display()))?);
502 Flux2VaeDecoder::from_cmf(&stage_model)
503 })
504 };
505
506 let (latents, latent_h, latent_w) = {
508 let mut stage = crate::gpu::image_stage_scope();
509 let stage_model = Arc::new(
510 CmfModel::open(model_path).map_err(|e| format!("{}: {e}", model_path.display()))?,
511 );
512 stage.track_model(stage_model.uid());
513 let started = Instant::now();
514 let dit = MossDit::from_cmf(&stage_model)?;
515 timing.dit_load = started.elapsed().as_secs_f64();
516 let positive = dit.prepare_text(&positive, positive_tokens)?;
517 let negative = match (&negative, negative_tokens) {
518 (Some(v), Some(n)) => Some((dit.prepare_text(v, n)?, n)),
519 _ => None,
520 };
521 timing.text_prepare = started.elapsed().as_secs_f64() - timing.dit_load;
522 let sigmas = moss_sigmas_for_training_grid(
523 p.steps,
524 p.shift,
525 scheduler.training_shift,
526 scheduler.num_train_timesteps,
527 )?;
528 let actual_steps = sigmas.len() - 1;
529 let skips = p
530 .uncond_skip_layers
531 .as_deref()
532 .unwrap_or(&defaults.uncond_skip_layers);
533 if skips.iter().any(|&i| i >= dit.cfg.num_layers) {
534 return Err(format!(
535 "MOSS uncond skip layer outside 0..{}",
536 dit.cfg.num_layers
537 ));
538 }
539 if p.slg_layers.iter().any(|&i| i >= dit.cfg.num_layers) {
540 return Err(format!("MOSS SLG layer outside 0..{}", dit.cfg.num_layers));
541 }
542 let (lh, lw) = (p.height / 16, p.width / 16);
543 let mut completed = Vec::with_capacity(p.num_images);
544 for image in 0..p.num_images {
545 let mut latent = gauss(
546 dit.cfg.latent_channels * lh * lw,
547 p.seed.wrapping_add(image as u64),
548 );
549 for step in 0..actual_steps {
550 let clock = Instant::now();
551 let t = sigmas[step] * scheduler.num_train_timesteps as f32;
554 let skip_active = moss_skip_active(step, actual_steps, p.slg_start, p.slg_stop);
559 let mut prediction =
560 dit.forward(&latent, lh, lw, t, &positive, positive_tokens, &[])?;
561 let use_slg = skip_active && !p.slg_layers.is_empty() && p.slg_scale > 0.0;
562 let conditional = use_slg.then(|| prediction.clone());
563 let uncond = if let Some((negative, neg_len)) = &negative {
564 let uncond_skips = if skip_active { skips } else { &[] };
565 Some(dit.forward(&latent, lh, lw, t, negative, *neg_len, uncond_skips)?)
566 } else {
567 None
568 };
569 let skipped = if conditional.is_some() {
570 Some(dit.forward(
571 &latent,
572 lh,
573 lw,
574 t,
575 &positive,
576 positive_tokens,
577 &p.slg_layers,
578 )?)
579 } else {
580 None
581 };
582 apply_moss_guidance(
583 &mut prediction,
584 conditional.as_deref(),
585 uncond.as_deref(),
586 p.cfg,
587 skipped.as_deref(),
588 p.slg_scale,
589 )?;
590 let dt = sigmas[step + 1] - sigmas[step];
591 for (x, v) in latent.iter_mut().zip(&prediction) {
592 *x += dt * v;
593 }
594 if latent.iter().any(|v| !v.is_finite()) {
595 return Err(format!(
596 "MOSS image {image}: non-finite latent at step {}",
597 step + 1
598 ));
599 }
600 timing.steps.push(clock.elapsed().as_secs_f64());
601 progress(image, step + 1, actual_steps);
602 }
603 completed.push(latent);
604 }
605 (completed, lh, lw)
606 };
607
608 let vae_started = Instant::now();
610 let images = {
611 let _stage = crate::gpu::image_stage_scope();
612 let vae = vae_loader
613 .join()
614 .map_err(|_| "MOSS VAE loader panicked".to_string())??;
615 if crate::gpu::enabled_here() {
619 let _ = vae.prewarm();
620 }
621 let mut images = Vec::with_capacity(latents.len());
622 for (image, latent) in latents.iter().enumerate() {
623 let rgb = vae.decode_fast(latent, latent_h, latent_w)?;
624 images.push(MossImage {
625 rgb: pixels_to_u8(&rgb, p.height, p.width)?,
626 height: p.height,
627 width: p.width,
628 seed: p.seed.wrapping_add(image as u64),
629 });
630 }
631 images
632 };
633 timing.vae = vae_started.elapsed().as_secs_f64();
634 timing.total = all_started.elapsed().as_secs_f64();
635 Ok((images, timing))
636}
637
638#[cfg(test)]
639mod tests {
640 use super::*;
641
642 fn contract() -> serde_json::Value {
643 serde_json::from_str(include_str!(concat!(
644 env!("CARGO_MANIFEST_DIR"),
645 "/tests/fixtures/moss_image_contract.json"
646 )))
647 .expect("valid MOSS contract fixture")
648 }
649
650 fn f32_le(path: &std::path::Path) -> Vec<f32> {
651 let bytes = std::fs::read(path).unwrap_or_else(|e| panic!("{}: {e}", path.display()));
652 assert_eq!(bytes.len() % 4, 0, "{} is f32-le", path.display());
653 bytes
654 .chunks_exact(4)
655 .map(|b| f32::from_le_bytes(b.try_into().expect("four bytes")))
656 .collect()
657 }
658
659 #[test]
660 fn scheduler_is_descending_and_ends_at_zero() {
661 let s = moss_sigmas(50, 5.0, 1.0).expect("valid scheduler");
662 assert_eq!(s.first().copied(), Some(1.0));
663 assert_eq!(s.last().copied(), Some(0.0));
664 assert!(s.windows(2).all(|p| p[0] > p[1]));
665 }
666
667 #[test]
672 fn scheduler_matches_official_contract() {
673 let fixture = contract();
674 for schedule in fixture["scheduler"]["schedules"]
675 .as_array()
676 .expect("scheduler cases")
677 {
678 let requested = schedule["steps_requested"].as_u64().expect("step count") as usize;
679 let want = schedule["sigmas"].as_array().expect("sigmas");
680 let got = moss_sigmas(requested, 5.0, 1.0).expect("official schedule");
681 assert_eq!(got.len(), want.len(), "{requested} steps");
682 for (index, (actual, expected)) in got.iter().zip(want).enumerate() {
683 let expected = expected.as_f64().expect("numeric sigma") as f32;
684 assert!(
685 (actual - expected).abs() <= 2e-6,
686 "{requested} steps, sigma {index}: got {actual:?}, expected {expected:?}"
687 );
688 }
689 }
690 }
691
692 #[test]
693 fn skip_guidance_window_matches_source_boundaries() {
694 assert!(moss_skip_active(0, 10, 0.0, 1.0));
696 assert!(moss_skip_active(9, 10, 0.0, 1.0));
697 assert!(!moss_skip_active(10, 10, 0.0, 1.0));
698 assert!(!moss_skip_active(0, 10, 0.1, 0.9));
699 assert!(moss_skip_active(1, 10, 0.1, 0.9));
700 assert!(!moss_skip_active(9, 10, 0.1, 0.9));
701 assert!(!moss_skip_active(0, 0, 0.0, 1.0));
702 }
703
704 #[test]
705 fn standard_slg_matches_source_cfg_order() {
706 let conditional = [2.0, -1.0];
707 let uncond = [1.0, 3.0];
708 let skipped = [0.0, 1.0];
709
710 let mut with_cfg = conditional;
712 apply_moss_guidance(
713 &mut with_cfg,
714 Some(&conditional),
715 Some(&uncond),
716 3.0,
717 Some(&skipped),
718 2.0,
719 )
720 .unwrap();
721 assert_eq!(with_cfg, [8.0, -13.0]);
722
723 let mut without_cfg = conditional;
726 apply_moss_guidance(
727 &mut without_cfg,
728 Some(&conditional),
729 None,
730 1.0,
731 Some(&skipped),
732 2.0,
733 )
734 .unwrap();
735 assert_eq!(without_cfg, [4.0, -3.0]);
736 }
737
738 #[test]
739 fn fixed_rng_is_repeatable() {
740 assert_eq!(gauss(11, 42), gauss(11, 42));
741 assert_ne!(gauss(11, 42), gauss(11, 43));
742 }
743
744 #[test]
749 fn packed_template_matches_source_contract_when_model_supplied() {
750 let Ok(path) = std::env::var("CMF_MOSS_TEST_MODEL") else {
751 return;
752 };
753 let model = Arc::new(CmfModel::open(&path).expect("open supplied MOSS CMF"));
754 assert_eq!(model.header.arch.arch_name, ARCH_NAME);
755 let encoder = Pipeline::from_encoder_model(&model, SamplerConfig::default())
756 .expect("open packed MOSS text encoder");
757 for case in contract()["tokenizer"]["cases"]
758 .as_array()
759 .expect("tokenizer cases")
760 {
761 let got = prompt_ids(
762 &encoder,
763 case["prompt"].as_str().expect("prompt"),
764 MAX_TOKENS,
765 )
766 .expect("apply stored MOSS template");
767 let want: Vec<u32> = case["ids"]
768 .as_array()
769 .expect("token ids")
770 .iter()
771 .map(|id| id.as_u64().expect("integer token") as u32)
772 .collect();
773 assert_eq!(got, want, "prompt {:?}", case["prompt"]);
774 }
775 }
776
777 #[test]
782 fn packed_flux2_decoder_matches_source_oracle_when_supplied() {
783 let (Ok(model_path), Ok(oracle_dir)) = (
784 std::env::var("CMF_MOSS_TEST_MODEL"),
785 std::env::var("CMF_MOSS_ORACLE_DIR"),
786 ) else {
787 return;
788 };
789 let model = CmfModel::open(&model_path).expect("open supplied MOSS CMF");
790 let vae = Flux2VaeDecoder::from_cmf(&model).expect("open packed Flux2 decoder");
791 let root = std::path::Path::new(&oracle_dir);
792 let packed = f32_le(&root.join("vae_packed_latent.f32"));
793 let want = f32_le(&root.join("vae_decode_ref.f32"));
794 let got = vae
795 .decode_fast(&packed, 3, 5)
796 .expect("decode source packed latent");
797 assert_eq!(got.len(), want.len());
798 let mut max_abs = 0.0f32;
799 let mut mean_abs = 0.0f64;
800 for (&actual, &expected) in got.iter().zip(&want) {
801 let delta = (actual - expected).abs();
802 max_abs = max_abs.max(delta);
803 mean_abs += delta as f64;
804 }
805 mean_abs /= got.len() as f64;
806 assert!(
807 max_abs <= 5e-3 && mean_abs <= 2e-4,
808 "Flux2 oracle drift: max_abs={max_abs:.6}, mean_abs={mean_abs:.6}"
809 );
810 }
811}