| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| use meganeura::{Graph, NodeId}; |
|
|
| use crate::dinov3::Config; |
|
|
| |
| pub const STAGES: [usize; 4] = [256, 128, 64, 32]; |
| |
| |
| |
| pub const BLEND_STAGES: usize = 2; |
| |
| const GROUP_SIZE: usize = 16; |
| const EPS: f32 = 1e-5; |
|
|
| |
| pub fn parameter_count(config: &Config) -> usize { |
| let mut total = 0; |
| let mut in_c = config.hidden_size; |
| for (i, &out_c) in STAGES.iter().enumerate() { |
| total += out_c * in_c * 9 + out_c + out_c * 2; |
| if i < BLEND_STAGES { |
| total += out_c * out_c * 9 + out_c + out_c * 2; |
| } |
| in_c = out_c; |
| } |
| total += 3 * in_c * 9 + 3; |
| total |
| } |
|
|
| |
| |
| |
| pub fn forward_macs(config: &Config) -> u64 { |
| let mut total = 0u64; |
| let mut in_c = config.hidden_size; |
| let mut hw = config.grid(); |
| for (i, &out_c) in STAGES.iter().enumerate() { |
| total += (out_c * in_c * 9 * hw * hw) as u64; |
| if i < BLEND_STAGES { |
| total += (out_c * out_c * 9 * hw * hw) as u64; |
| } |
| hw *= 2; |
| in_c = out_c; |
| } |
| total + (3 * in_c * 9 * hw * hw) as u64 |
| } |
|
|
| |
| fn block( |
| g: &mut Graph, |
| x: NodeId, |
| name: &str, |
| batch: usize, |
| in_c: usize, |
| out_c: usize, |
| hw: usize, |
| ) -> NodeId { |
| let kernel = g.parameter(&format!("{name}.weight"), &[out_c, in_c, 3, 3]); |
| let x = g.conv2d( |
| x, |
| kernel, |
| batch as u32, |
| in_c as u32, |
| hw as u32, |
| hw as u32, |
| out_c as u32, |
| 3, |
| 3, |
| 1, |
| 1, |
| ); |
| let bias = g.parameter(&format!("{name}.bias"), &[out_c]); |
| let x = g.add_per_channel(x, bias, out_c as u32, (hw * hw) as u32); |
|
|
| let gn_w = g.parameter(&format!("{name}.norm.weight"), &[out_c]); |
| let gn_b = g.parameter(&format!("{name}.norm.bias"), &[out_c]); |
| let x = g.group_norm( |
| x, |
| gn_w, |
| gn_b, |
| batch as u32, |
| out_c as u32, |
| (hw * hw) as u32, |
| (out_c / GROUP_SIZE) as u32, |
| EPS, |
| ); |
| g.silu(x) |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| pub fn build_decoder(g: &mut Graph, config: &Config, features: NodeId, batch: usize) -> NodeId { |
| let mut x = features; |
| let mut in_c = config.hidden_size; |
| let mut hw = config.grid(); |
|
|
| for (i, &out_c) in STAGES.iter().enumerate() { |
| x = block(g, x, &format!("dec.{i}"), batch, in_c, out_c, hw); |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| if i < BLEND_STAGES { |
| x = block(g, x, &format!("dec.{i}b"), batch, out_c, out_c, hw); |
| } |
| x = g.upsample_2x(x, batch as u32, out_c as u32, hw as u32, hw as u32); |
| hw *= 2; |
| in_c = out_c; |
| } |
| assert_eq!( |
| hw, config.image_size, |
| "stage count does not reach the image resolution" |
| ); |
|
|
| let kernel = g.parameter("dec.out.weight", &[3, in_c, 3, 3]); |
| let x = g.conv2d( |
| x, |
| kernel, |
| batch as u32, |
| in_c as u32, |
| hw as u32, |
| hw as u32, |
| 3, |
| 3, |
| 3, |
| 1, |
| 1, |
| ); |
| let bias = g.parameter("dec.out.bias", &[3]); |
| let x = g.add_per_channel(x, bias, 3, (hw * hw) as u32); |
| |
| |
| |
| g.sigmoid(x) |
| } |
|
|
| |
| |
| |
| |
| |
| |
| pub fn attach_to_encoder(g: &mut Graph, config: &Config, encoder_out: NodeId) -> NodeId { |
| let hidden = config.hidden_size; |
| let patches = config.num_patches(); |
| let prefix = config.num_prefix_tokens(); |
|
|
| |
| |
| let patch_tokens = g.split_b( |
| encoder_out, |
| 1, |
| (prefix * hidden) as u32, |
| (patches * hidden) as u32, |
| 1, |
| ); |
| let patch_tokens = g.reshape(patch_tokens, &[patches, hidden]); |
| let planes = g.transpose(patch_tokens); |
| let planes = g.reshape(planes, &[hidden * patches]); |
| build_decoder(g, config, planes, 1) |
| } |
|
|
| |
| |
| |
| |
| |
| pub fn load_parameters( |
| session: &mut meganeura::Session, |
| graph: &Graph, |
| path: &std::path::Path, |
| ) -> Result<(), Box<dyn std::error::Error>> { |
| let bytes = std::fs::read(path)?; |
| let values: Vec<f32> = bytes |
| .chunks_exact(4) |
| .map(|c| f32::from_le_bytes([c[0], c[1], c[2], c[3]])) |
| .collect(); |
|
|
| let mut offset = 0; |
| let mut loaded = 0; |
| for node in graph.nodes() { |
| let meganeura::graph::Op::Parameter { name } = &node.op else { |
| continue; |
| }; |
| |
| |
| if !name.starts_with("dec.") { |
| continue; |
| } |
| let n = node.ty.num_elements(); |
| if offset + n > values.len() { |
| return Err(format!( |
| "{} is too short: needed {} values by parameter '{name}', have {}", |
| path.display(), |
| offset + n, |
| values.len() |
| ) |
| .into()); |
| } |
| session.set_parameter(name, &values[offset..offset + n]); |
| offset += n; |
| loaded += 1; |
| } |
| if offset != values.len() { |
| return Err(format!( |
| "{} has {} values but the graph consumed {offset}; the decoder \ |
| architecture and the weights disagree", |
| path.display(), |
| values.len() |
| ) |
| .into()); |
| } |
| log::info!("loaded {loaded} decoder parameters from {}", path.display()); |
| Ok(()) |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| pub fn patch_features_to_nchw(features: &[f32], config: &Config) -> Vec<f32> { |
| let hidden = config.hidden_size; |
| let patches = config.num_patches(); |
| let skip = config.num_prefix_tokens(); |
| assert_eq!(features.len(), config.num_tokens() * hidden); |
|
|
| let mut out = vec![0.0f32; hidden * patches]; |
| for p in 0..patches { |
| let src = (skip + p) * hidden; |
| for c in 0..hidden { |
| out[c * patches + p] = features[src + c]; |
| } |
| } |
| out |
| } |
|
|
| |
| |
| |
| |
| pub fn psnr(a: &[f32], b: &[f32]) -> f32 { |
| assert_eq!(a.len(), b.len()); |
| let mse: f64 = a |
| .iter() |
| .zip(b) |
| .map(|(x, y)| { |
| let d = (x - y) as f64; |
| d * d |
| }) |
| .sum::<f64>() |
| / a.len() as f64; |
| if mse <= f64::EPSILON { |
| return f32::INFINITY; |
| } |
| (10.0 * (1.0 / mse).log10()) as f32 |
| } |
|
|
| #[cfg(test)] |
| mod tests { |
| use super::*; |
|
|
| #[test] |
| fn stages_reach_the_image_resolution() { |
| let c = Config::vits16(); |
| assert_eq!(c.grid() * 2usize.pow(STAGES.len() as u32), c.image_size); |
| } |
|
|
| #[test] |
| fn every_stage_divides_into_groups() { |
| for &c in &STAGES { |
| assert_eq!(c % GROUP_SIZE, 0, "{c} channels do not group evenly"); |
| } |
| } |
|
|
| #[test] |
| fn parameter_count_is_modest() { |
| let n = parameter_count(&Config::vits16()); |
| assert!( |
| (1_000_000..4_000_000).contains(&n), |
| "unexpected decoder size: {n}" |
| ); |
| } |
|
|
| #[test] |
| fn forward_macs_match_the_deployed_decoder() { |
| let c = Config::vits16().at_resolution(224).with_layers(3); |
| assert_eq!(forward_macs(&c), 1_141_604_352); |
| } |
|
|
| #[test] |
| fn graph_builds_with_the_right_output_shape() { |
| let c = Config::vits16(); |
| let mut g = Graph::new(); |
| let feat = g.input("feat", &[c.hidden_size * c.num_patches()]); |
| let out = build_decoder(&mut g, &c, feat, 1); |
| assert_eq!( |
| g.node(out).ty.num_elements(), |
| 3 * c.image_size * c.image_size |
| ); |
| } |
|
|
| |
| |
| #[test] |
| fn nchw_rearrangement_is_a_transpose_past_the_prefix() { |
| let c = Config::vits16(); |
| let h = c.hidden_size; |
| |
| |
| let features: Vec<f32> = (0..c.num_tokens() * h) |
| .map(|i| ((i / h) * 1000 + (i % h)) as f32) |
| .collect(); |
| let nchw = patch_features_to_nchw(&features, &c); |
| assert_eq!(nchw.len(), h * c.num_patches()); |
|
|
| for &(p, ch) in &[(0usize, 0usize), (37, 5), (195, 383)] { |
| let token = c.num_prefix_tokens() + p; |
| assert_eq!( |
| nchw[ch * c.num_patches() + p], |
| (token * 1000 + ch) as f32, |
| "patch {p} channel {ch} came from the wrong token" |
| ); |
| } |
| } |
|
|
| #[test] |
| fn psnr_behaves() { |
| let a = vec![0.5f32; 100]; |
| assert!(psnr(&a, &a).is_infinite(), "identical images are lossless"); |
| |
| let b: Vec<f32> = a.iter().map(|v| v + 0.1).collect(); |
| assert!((psnr(&a, &b) - 20.0).abs() < 0.1); |
| } |
| } |
|
|