pub mod mlx_rng;
const NUM_LATENT_CHANNELS: usize = 32;
pub const PACKED_CHANNELS: usize = NUM_LATENT_CHANNELS * 4;
const VAE_SCALE_FACTOR: usize = 8;
#[must_use]
pub fn latent_grid(height: usize, width: usize) -> (usize, usize) {
let full_h = 2 * (height / (VAE_SCALE_FACTOR * 2));
let full_w = 2 * (width / (VAE_SCALE_FACTOR * 2));
(full_h / 2, full_w / 2)
}
#[must_use]
pub fn create_noise(seed: u64, height: usize, width: usize) -> Vec<f32> {
let (lat_h, lat_w) = latent_grid(height, width);
let seq = lat_h * lat_w;
let n = PACKED_CHANNELS * seq;
let raw = mlx_rng::normal(n, mlx_rng::key(seed));
let mut packed = vec![0.0f32; n];
for c in 0..PACKED_CHANNELS {
let row_base = c * seq;
for s in 0..seq {
packed[s * PACKED_CHANNELS + c] = raw[row_base + s];
}
}
packed
}
#[must_use]
pub fn img_ids(lat_h: usize, lat_w: usize) -> Vec<f32> {
let seq = lat_h * lat_w;
let mut ids = vec![0.0f32; seq * 4];
for i in 0..seq {
let base = i * 4;
ids[base + 1] = (i / lat_w) as f32; ids[base + 2] = (i % lat_w) as f32; }
ids
}
#[must_use]
pub fn txt_ids(seq_txt: usize) -> Vec<f32> {
let mut ids = vec![0.0f32; seq_txt * 4];
for j in 0..seq_txt {
ids[j * 4 + 3] = j as f32; }
ids
}
fn compute_empirical_mu(image_seq_len: usize, num_steps: usize) -> f64 {
let l = image_seq_len as f64;
let (a1, b1) = (8.73809524e-05_f64, 1.89833333_f64);
let (a2, b2) = (0.00016927_f64, 0.45666666_f64);
if image_seq_len > 4300 {
return a2 * l + b2;
}
let m_200 = a2 * l + b2;
let m_10 = a1 * l + b1;
let a = (m_200 - m_10) / 190.0_f64;
let b = m_200 - 200.0_f64 * a;
a * (num_steps as f64) + b
}
#[must_use]
pub fn flow_match_schedule(image_seq_len: usize, steps: usize) -> (Vec<f32>, Vec<f32>) {
if steps == 0 {
return (Vec::new(), vec![0.0f32]);
}
let mu = compute_empirical_mu(image_seq_len, steps);
let exp_mu = (mu as f32).exp();
let last = 1.0_f32 / (steps as f32);
let mut sigmas: Vec<f32> = Vec::with_capacity(steps + 1);
let mut timesteps: Vec<f32> = Vec::with_capacity(steps);
for i in 0..steps {
let t = if steps == 1 {
1.0_f32
} else {
1.0_f32 + (i as f32) * (last - 1.0_f32) / ((steps - 1) as f32)
};
let shifted = exp_mu / (exp_mu + (1.0_f32 / t - 1.0_f32));
sigmas.push(shifted);
timesteps.push(shifted * 1000.0_f32);
}
sigmas.push(0.0_f32);
(timesteps, sigmas)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn latent_grid_512() {
assert_eq!(latent_grid(512, 512), (32, 32));
}
#[test]
fn latent_grid_1024() {
assert_eq!(latent_grid(1024, 1024), (64, 64));
}
#[test]
fn create_noise_shape() {
let v = create_noise(42, 512, 512);
assert_eq!(v.len(), 1024 * PACKED_CHANNELS);
assert!(v.iter().all(|x| x.is_finite()));
}
#[test]
fn create_noise_pack_direction() {
let (lat_h, lat_w) = latent_grid(512, 512);
let seq = lat_h * lat_w;
let n = PACKED_CHANNELS * seq;
let raw = mlx_rng::normal(n, mlx_rng::key(42));
let packed = create_noise(42, 512, 512);
for c in 0..4 {
assert_eq!(packed[c], raw[c * seq]);
}
assert_eq!(packed[PACKED_CHANNELS], raw[1]);
}
#[test]
fn img_ids_layout() {
let ids = img_ids(32, 32);
assert_eq!(ids.len(), 1024 * 4);
assert_eq!(&ids[0..4], &[0.0, 0.0, 0.0, 0.0]);
assert_eq!(&ids[4..8], &[0.0, 0.0, 1.0, 0.0]);
assert_eq!(&ids[31 * 4..31 * 4 + 4], &[0.0, 0.0, 31.0, 0.0]);
assert_eq!(&ids[32 * 4..32 * 4 + 4], &[0.0, 1.0, 0.0, 0.0]);
assert_eq!(&ids[33 * 4..33 * 4 + 4], &[0.0, 1.0, 1.0, 0.0]);
}
#[test]
fn txt_ids_layout() {
let ids = txt_ids(512);
assert_eq!(ids.len(), 512 * 4);
assert_eq!(&ids[0..4], &[0.0, 0.0, 0.0, 0.0]);
assert_eq!(&ids[4..8], &[0.0, 0.0, 0.0, 1.0]);
assert_eq!(&ids[511 * 4..511 * 4 + 4], &[0.0, 0.0, 0.0, 511.0]);
}
#[test]
#[allow(clippy::excessive_precision)]
fn schedule_1024_4() {
let (timesteps, sigmas) = flow_match_schedule(1024, 4);
let exp_ts = [
1000.0_f32,
958.0853881835938,
883.9818115234375,
717.49658203125,
];
let exp_sg = [
1.0_f32,
0.9580853581428528,
0.8839818239212036,
0.7174965739250183,
0.0,
];
assert_eq!(timesteps.len(), 4);
assert_eq!(sigmas.len(), 5);
for (a, b) in timesteps.iter().zip(exp_ts.iter()) {
assert!((a - b).abs() <= 1e-3, "timestep {a} vs {b}");
}
for (a, b) in sigmas.iter().zip(exp_sg.iter()) {
assert!((a - b).abs() <= 1e-6, "sigma {a} vs {b}");
}
}
#[test]
fn schedule_first_last() {
let (timesteps, sigmas) = flow_match_schedule(1024, 4);
assert_eq!(sigmas[0], 1.0);
assert_eq!(timesteps[0], 1000.0);
assert_eq!(*sigmas.last().unwrap_or(&-1.0), 0.0);
}
}