| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| use std::sync::Arc; |
| use std::time::Instant; |
|
|
| use meganeura::graph::Op; |
| use meganeura::train::{Mode, SessionConfig}; |
| use meganeura::{Graph, Session}; |
|
|
| use crate::dinov3::Config; |
|
|
| |
| #[derive(Debug, Clone)] |
| pub struct Timing { |
| pub label: String, |
| |
| |
| pub best_ms: f64, |
| pub mean_ms: f64, |
| pub median_ms: f64, |
| pub p25_ms: f64, |
| pub p75_ms: f64, |
| pub max_ms: f64, |
| |
| pub samples_ms: Vec<f64>, |
| |
| pub macs: u64, |
| } |
|
|
| impl Timing { |
| |
| |
| pub fn gflops(&self) -> f64 { |
| (self.macs as f64 * 2.0) / (self.median_ms / 1000.0) / 1e9 |
| } |
|
|
| |
| pub fn json_line(&self) -> String { |
| let label = self.label.replace('\\', "\\\\").replace('"', "\\\""); |
| let samples = self |
| .samples_ms |
| .iter() |
| .map(|v| format!("{v:.6}")) |
| .collect::<Vec<_>>() |
| .join(","); |
| format!( |
| "{{\"schema_version\":1,\"kind\":\"dinovision_benchmark\",\ |
| \"label\":\"{label}\",\"macs\":{},\"median_ms\":{:.6},\ |
| \"p25_ms\":{:.6},\"p75_ms\":{:.6},\"min_ms\":{:.6},\ |
| \"max_ms\":{:.6},\"mean_ms\":{:.6},\"samples_ms\":[{samples}]}}", |
| self.macs, |
| self.median_ms, |
| self.p25_ms, |
| self.p75_ms, |
| self.best_ms, |
| self.max_ms, |
| self.mean_ms, |
| ) |
| } |
| } |
|
|
| impl std::fmt::Display for Timing { |
| fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { |
| write!( |
| f, |
| "{:<28} median {:>8.2} ms IQR [{:>7.2}, {:>7.2}] min/max [{:>7.2}, {:>7.2}] {:>7.1} GFLOP/s", |
| self.label, |
| self.median_ms, |
| self.p25_ms, |
| self.p75_ms, |
| self.best_ms, |
| self.max_ms, |
| self.gflops() |
| ) |
| } |
| } |
|
|
| |
| |
| |
| |
| pub fn describe_device(gpu: &blade_graphics::Context) { |
| let info = gpu.device_information(); |
| log::info!("device : {}", info.device_name); |
| log::info!("driver : {} {}", info.driver_name, info.driver_info); |
|
|
| let caps = meganeura::runtime::auto_tune(gpu, 0).coop_caps; |
| if caps.f16_tile == 0 && caps.f32_tile == 0 { |
| log::warn!("coop matrix : NOT AVAILABLE — expect the register-tiled scalar matmul"); |
| } else { |
| log::info!( |
| "coop matrix : available (f32 tile {}, f16 tile {})", |
| caps.f32_tile, |
| caps.f16_tile |
| ); |
| } |
| } |
|
|
| |
| |
| |
| |
| |
| |
| pub fn fill_parameters(session: &mut Session, graph: &Graph) { |
| let mut state = 0x2545_F491_4F6C_DD1Du64; |
| let mut scratch = Vec::new(); |
| for node in graph.nodes() { |
| let Op::Parameter { name } = &node.op else { |
| continue; |
| }; |
| scratch.clear(); |
| scratch.reserve(node.ty.num_elements()); |
| for _ in 0..node.ty.num_elements() { |
| |
| state ^= state >> 12; |
| state ^= state << 25; |
| state ^= state >> 27; |
| let bits = state.wrapping_mul(0x2545_F491_4F6C_DD1D) >> 40; |
| scratch.push((bits as f32 / (1u32 << 24) as f32 - 0.5) * 0.05); |
| } |
| session.set_parameter(name, &scratch); |
| } |
| } |
|
|
| pub(crate) fn time_session(session: &mut Session, label: &str, macs: u64, iters: usize) -> Timing { |
| |
| |
| |
| let warmups = std::env::var("DINOVISION_WARMUPS") |
| .ok() |
| .and_then(|s| s.parse::<usize>().ok()) |
| .unwrap_or(5); |
| for _ in 0..warmups { |
| session.step(); |
| session.wait(); |
| } |
|
|
| let mut samples_ms = Vec::with_capacity(iters.max(1)); |
| for _ in 0..iters.max(1) { |
| let start = Instant::now(); |
| session.step(); |
| session.wait(); |
| samples_ms.push(start.elapsed().as_secs_f64() * 1000.0); |
| } |
|
|
| let mut sorted = samples_ms.clone(); |
| sorted.sort_by(f64::total_cmp); |
| let quantile = |q: f64| { |
| let at = q * (sorted.len() - 1) as f64; |
| let lo = at.floor() as usize; |
| let hi = at.ceil() as usize; |
| sorted[lo] + (sorted[hi] - sorted[lo]) * (at - lo as f64) |
| }; |
| Timing { |
| label: label.to_string(), |
| best_ms: sorted[0], |
| mean_ms: samples_ms.iter().sum::<f64>() / samples_ms.len() as f64, |
| median_ms: quantile(0.5), |
| p25_ms: quantile(0.25), |
| p75_ms: quantile(0.75), |
| max_ms: sorted[sorted.len() - 1], |
| samples_ms, |
| macs, |
| } |
| } |
|
|
| |
| |
| |
| |
| |
| pub fn matmul_throughput( |
| gpu: Arc<blade_graphics::Context>, |
| m: usize, |
| k: usize, |
| n: usize, |
| iters: usize, |
| ) -> Timing { |
| let mut g = Graph::new(); |
| let a = g.input("a", &[m, k]); |
| let b = g.parameter("b", &[k, n]); |
| let y = g.matmul(a, b); |
| g.set_outputs(vec![y]); |
|
|
| let (mut session, _) = meganeura::train::build( |
| &g, |
| SessionConfig { |
| mode: Mode::Inference, |
| gpu: Some(gpu), |
| ..Default::default() |
| }, |
| ); |
| fill_parameters(&mut session, &g); |
| session.set_input("a", &vec![0.01f32; m * k]); |
|
|
| time_session( |
| &mut session, |
| &format!("matmul {m}x{k}x{n}"), |
| (m * k * n) as u64, |
| iters, |
| ) |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| pub fn matmul_shapes(gpu: Arc<blade_graphics::Context>, iters: usize) -> Vec<Timing> { |
| let mut results = Vec::new(); |
| for (m, k, n, note) in [ |
| (201, 384, 1536, "encoder MLP up, as it runs"), |
| (256, 384, 1536, "m padded to a whole tile: +27% MACs"), |
| (201, 384, 384, "24 workgroups"), |
| (201, 384, 1536, "96 workgroups"), |
| (201, 384, 6144, "384 workgroups"), |
| (201, 1536, 1536, "4x the k-tiles per workgroup"), |
| (512, 512, 512, "square, no tail"), |
| (1024, 1024, 1024, "square, no tail"), |
| (2048, 2048, 2048, "square, kernel ceiling"), |
| ] { |
| let t = matmul_throughput(gpu.clone(), m, k, n, iters); |
| log::info!("{t} ({note})"); |
| results.push(t); |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| for size in [224, 240, 224] { |
| let c = Config::vits16().at_resolution(size).with_layers(3); |
| let mut t = encoder_forward(gpu.clone(), &c, iters); |
| t.label = format!("dinov3 @{size} 3L"); |
| log::info!( |
| "{t} (grid {}x{}, {} tokens)", |
| c.grid(), |
| c.grid(), |
| c.num_tokens() |
| ); |
| results.push(t); |
| } |
| results |
| } |
|
|
| |
| |
| |
| |
| pub fn build_encoder_session( |
| gpu: Arc<blade_graphics::Context>, |
| config: &Config, |
| cache: Option<&std::path::Path>, |
| ) -> (Session, Graph) { |
| let mut g = Graph::new(); |
| let out = crate::dinov3::build_encoder(&mut g, config); |
| g.set_outputs(vec![out]); |
|
|
| let build_start = Instant::now(); |
| let (session, _) = meganeura::train::build( |
| &g, |
| SessionConfig { |
| mode: Mode::Inference, |
| gpu: Some(gpu), |
| cache, |
| ..Default::default() |
| }, |
| ); |
| |
| |
| |
| log::info!( |
| "session build: {:.1} s (cache {})", |
| build_start.elapsed().as_secs_f64(), |
| if cache.is_some() { "on" } else { "off" } |
| ); |
| (session, g) |
| } |
|
|
| |
| pub fn encoder_forward(gpu: Arc<blade_graphics::Context>, config: &Config, iters: usize) -> Timing { |
| let (mut session, g) = build_encoder_session(gpu, config, None); |
| fill_parameters(&mut session, &g); |
| session.set_input( |
| "patches", |
| &vec![0.1f32; config.num_patches() * config.patch_dim()], |
| ); |
|
|
| time_session( |
| &mut session, |
| &format!("dinov3 vits16 @{}", config.image_size), |
| config.forward_macs(), |
| iters, |
| ) |
| } |
|
|
| |
| |
| |
| pub fn roundtrip_forward( |
| gpu: Arc<blade_graphics::Context>, |
| config: &Config, |
| submission_chunks: usize, |
| iters: usize, |
| ) -> Timing { |
| let mut graph = Graph::new(); |
| let encoder_output = crate::dinov3::build_encoder(&mut graph, config); |
| let reconstruction = crate::decoder::attach_to_encoder(&mut graph, config, encoder_output); |
| graph.set_outputs(vec![reconstruction]); |
| let (mut session, _) = meganeura::train::build( |
| &graph, |
| SessionConfig { |
| mode: Mode::Inference, |
| gpu: Some(gpu), |
| ..Default::default() |
| }, |
| ); |
| session.set_submission_chunks(submission_chunks); |
| fill_parameters(&mut session, &graph); |
| session.set_input( |
| "patches", |
| &vec![0.1f32; config.num_patches() * config.patch_dim()], |
| ); |
| time_session( |
| &mut session, |
| &format!( |
| "roundtrip @{} {}L x{}", |
| config.image_size, config.num_hidden_layers, submission_chunks |
| ), |
| config.forward_macs() + crate::decoder::forward_macs(config), |
| iters, |
| ) |
| } |
|
|
| |
| |
| |
| pub fn run_all(gpu: Arc<blade_graphics::Context>, iters: usize) -> Vec<Timing> { |
| describe_device(&gpu); |
|
|
| let base = Config::vits16(); |
| let tokens = base.num_tokens(); |
| let hidden = base.hidden_size; |
| let mut results = Vec::new(); |
|
|
| |
| for (m, k, n) in [ |
| (tokens, hidden, hidden), |
| (tokens, hidden, base.intermediate_size), |
| (tokens, base.intermediate_size, hidden), |
| ] { |
| let t = matmul_throughput(gpu.clone(), m, k, n, iters); |
| log::info!("{t}"); |
| results.push(t); |
| } |
|
|
| |
| |
| |
| for size in [224, 256] { |
| let config = base.clone().at_resolution(size); |
| let t = encoder_forward(gpu.clone(), &config, iters); |
| log::info!( |
| "{t} (grid {}x{}, {} tokens)", |
| config.grid(), |
| config.grid(), |
| config.num_tokens() |
| ); |
| results.push(t); |
| } |
|
|
| |
| |
| |
| |
| for layers in [6, 3] { |
| let c = base.clone().at_resolution(224).with_layers(layers); |
| let mut t = encoder_forward(gpu.clone(), &c, iters); |
| t.label = format!("dinov3 @224 {layers}L"); |
| log::info!("{t} ({layers} of 12 layers)"); |
| results.push(t); |
| } |
| for size in [160, 128] { |
| let c = base.clone().at_resolution(size); |
| let mut t = encoder_forward(gpu.clone(), &c, iters); |
| t.label = format!("dinov3 vits16 @{size}"); |
| log::info!("{t} (grid {}x{})", c.grid(), c.grid()); |
| results.push(t); |
| } |
|
|
| |
| |
| |
| let reconstruction = base.clone().at_resolution(224).with_layers(3); |
| |
| |
| for chunks in [4, 16, 1, 12, 2, 8] { |
| let t = roundtrip_forward(gpu.clone(), &reconstruction, chunks, iters); |
| log::info!("{t} (joined encoder + decoder, {chunks} submission chunks)"); |
| results.push(t); |
| } |
|
|
| |
| |
| |
| let f16 = base.clone().at_resolution(224).with_f16_weights(true); |
| let mut t = encoder_forward(gpu.clone(), &f16, iters); |
| t.label = "dinov3 vits16 @224 f16w".to_string(); |
| log::info!("{t} (f16 weight storage, f32 arithmetic)"); |
| results.push(t); |
|
|
| |
| |
| |
| let chunked = base.clone().at_resolution(224); |
| let (mut session, g) = build_encoder_session(gpu, &chunked, None); |
| session.set_submission_chunks(12); |
| fill_parameters(&mut session, &g); |
| session.set_input( |
| "patches", |
| &vec![0.1f32; chunked.num_patches() * chunked.patch_dim()], |
| ); |
| let t = time_session( |
| &mut session, |
| "dinov3 vits16 @224 x12", |
| chunked.forward_macs(), |
| iters, |
| ); |
| log::info!("{t} (12 submissions instead of 1)"); |
| results.push(t); |
|
|
| results |
| } |
|
|