|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
|
| use std::sync::{Arc, Mutex, mpsc};
|
| use std::time::Instant;
|
|
|
| use crate::dinov3::Config;
|
| use crate::pca::{self, Basis};
|
|
|
|
|
|
|
| #[derive(Debug, Clone)]
|
| pub struct FeatureGrid {
|
|
|
|
|
|
|
| pub colors: Vec<f32>,
|
|
|
|
|
| pub grid: usize,
|
| pub prefix_tokens: usize,
|
|
|
| pub latency_ms: f64,
|
| }
|
|
|
| impl FeatureGrid {
|
|
|
|
|
| pub fn patch_rgb(&self) -> &[f32] {
|
| &self.colors[self.prefix_tokens * pca::COMPONENTS..]
|
| }
|
| }
|
|
|
|
|
| #[derive(Clone, Debug)]
|
| pub enum Display {
|
|
|
|
|
|
|
| PcaColour,
|
|
|
|
|
|
|
|
|
| Reconstruction(std::path::PathBuf),
|
| }
|
|
|
|
|
| enum Command {
|
|
|
|
|
| Submit(Vec<f32>),
|
|
|
| Refit,
|
| Stop,
|
| }
|
|
|
|
|
| pub struct Worker {
|
| tx: mpsc::Sender<Command>,
|
| latest: Arc<Mutex<Option<FeatureGrid>>>,
|
| generation: Arc<AtomicU64>,
|
| ready: Arc<AtomicBool>,
|
| handle: Option<std::thread::JoinHandle<()>>,
|
| }
|
|
|
| impl Worker {
|
|
|
|
|
|
|
|
|
|
|
| pub fn submit(&self, patches: Vec<f32>) -> bool {
|
| if !self.ready.load(Ordering::Acquire) {
|
| return false;
|
| }
|
| self.ready.store(false, Ordering::Release);
|
| self.tx.send(Command::Submit(patches)).is_ok()
|
| }
|
|
|
|
|
| pub fn request_refit(&self) {
|
| let _ = self.tx.send(Command::Refit);
|
| }
|
|
|
|
|
| pub fn latest(&self) -> Option<FeatureGrid> {
|
| self.latest.lock().ok().and_then(|g| g.clone())
|
| }
|
|
|
|
|
|
|
| pub fn generation(&self) -> u64 {
|
| self.generation.load(Ordering::Acquire)
|
| }
|
|
|
|
|
| pub fn is_ready(&self) -> bool {
|
| self.ready.load(Ordering::Acquire)
|
| }
|
| }
|
|
|
| impl Drop for Worker {
|
| fn drop(&mut self) {
|
| let _ = self.tx.send(Command::Stop);
|
| if let Some(handle) = self.handle.take() {
|
| let _ = handle.join();
|
| }
|
| }
|
| }
|
|
|
|
|
| pub enum Weights {
|
|
|
|
|
| SafeTensors(std::path::PathBuf),
|
|
|
|
|
|
|
| Synthetic,
|
| }
|
|
|
|
|
|
|
|
|
|
|
|
|
| pub fn spawn(
|
| gpu: Arc<blade_graphics::Context>,
|
| config: Config,
|
| weights: Weights,
|
| plan_cache: Option<std::path::PathBuf>,
|
| submission_chunks: usize,
|
| display: Display,
|
| ) -> Worker {
|
| let (tx, rx) = mpsc::channel();
|
| let latest = Arc::new(Mutex::new(None));
|
| let generation = Arc::new(AtomicU64::new(0));
|
| let ready = Arc::new(AtomicBool::new(false));
|
|
|
| let handle = {
|
| let latest = Arc::clone(&latest);
|
| let generation = Arc::clone(&generation);
|
| let ready = Arc::clone(&ready);
|
| std::thread::Builder::new()
|
| .name("dinovision-inference".into())
|
| .spawn(move || {
|
| worker_main(
|
| gpu,
|
| config,
|
| weights,
|
| plan_cache,
|
| submission_chunks,
|
| display,
|
| rx,
|
| latest,
|
| generation,
|
| ready,
|
| );
|
| })
|
| .expect("failed to spawn inference thread")
|
| };
|
|
|
| Worker {
|
| tx,
|
| latest,
|
| generation,
|
| ready,
|
| handle: Some(handle),
|
| }
|
| }
|
|
|
| #[allow(clippy::too_many_arguments)]
|
| fn worker_main(
|
| gpu: Arc<blade_graphics::Context>,
|
| config: Config,
|
| weights: Weights,
|
| plan_cache: Option<std::path::PathBuf>,
|
| submission_chunks: usize,
|
| display: Display,
|
| rx: mpsc::Receiver<Command>,
|
| latest: Arc<Mutex<Option<FeatureGrid>>>,
|
| generation: Arc<AtomicU64>,
|
| ready: Arc<AtomicBool>,
|
| ) {
|
|
|
|
|
|
|
|
|
| let reconstructing = matches!(display, Display::Reconstruction(_));
|
| let mut g = meganeura::Graph::new();
|
| let features = crate::dinov3::build_encoder(&mut g, &config);
|
| if reconstructing {
|
| let rgb = crate::decoder::attach_to_encoder(&mut g, &config, features);
|
| g.set_outputs(vec![rgb]);
|
| } else {
|
| let colors = pca::add_projection(&mut g, features, config.hidden_size);
|
| g.set_outputs(vec![colors, features]);
|
| }
|
|
|
| let build_start = Instant::now();
|
| let (mut session, _) = meganeura::train::build(&g, meganeura::train::SessionConfig {
|
| mode: meganeura::train::Mode::Inference,
|
| gpu: Some(gpu),
|
| cache: plan_cache.as_deref(),
|
| ..Default::default()
|
| });
|
| log::info!(
|
| "inference session ready in {:.1} s",
|
| build_start.elapsed().as_secs_f64()
|
| );
|
|
|
|
|
|
|
|
|
| session.set_submission_chunks(submission_chunks);
|
|
|
| match weights {
|
| Weights::SafeTensors(path) => {
|
| match meganeura::data::safetensors::SafeTensorsModel::load(path.clone()) {
|
| Ok(model) => {
|
| if let Err(e) = crate::weights::load_encoder(&mut session, &model, &config) {
|
| log::error!("failed to bind weights from {}: {e}", path.display());
|
| return;
|
| }
|
| }
|
| Err(e) => {
|
| log::error!("failed to read {}: {e}", path.display());
|
| return;
|
| }
|
| }
|
| }
|
| Weights::Synthetic => {
|
| log::warn!("running with synthetic weights — features are meaningless");
|
| crate::bench::fill_parameters(&mut session, &g);
|
| }
|
| }
|
|
|
| let tokens = config.num_tokens();
|
| let mut basis = Basis::placeholder(config.hidden_size);
|
| let mut refit_pending = false;
|
| let mut colors_out;
|
| let mut features_out = vec![0.0f32; tokens * config.hidden_size];
|
|
|
|
|
| let mut planar = Vec::new();
|
|
|
| match &display {
|
| Display::PcaColour => {
|
|
|
|
|
| refit_pending = true;
|
| apply_basis(&mut session, &basis);
|
| colors_out = vec![0.0f32; tokens * pca::COMPONENTS];
|
| }
|
| Display::Reconstruction(path) => {
|
| if let Err(e) = crate::decoder::load_parameters(&mut session, &g, path) {
|
| log::error!("failed to load the decoder: {e}");
|
| return;
|
| }
|
| let pixels = config.image_size * config.image_size;
|
| planar = vec![0.0f32; 3 * pixels];
|
| colors_out = vec![0.0f32; pixels * 3];
|
| }
|
| }
|
|
|
| ready.store(true, Ordering::Release);
|
|
|
| while let Ok(cmd) = rx.recv() {
|
| let patches = match cmd {
|
| Command::Submit(p) => p,
|
| Command::Refit => {
|
| refit_pending = true;
|
| continue;
|
| }
|
| Command::Stop => break,
|
| };
|
|
|
| let start = Instant::now();
|
| session.set_input("patches", &patches);
|
| session.step();
|
| session.wait();
|
|
|
| if reconstructing {
|
| session.read_output_by_index(0, &mut planar);
|
|
|
| let pixels = config.image_size * config.image_size;
|
| for p in 0..pixels {
|
| for c in 0..3 {
|
| colors_out[p * 3 + c] = planar[c * pixels + p];
|
| }
|
| }
|
| } else {
|
| session.read_output_by_index(0, &mut colors_out);
|
| }
|
|
|
| if refit_pending {
|
|
|
|
|
|
|
| session.read_output_by_index(1, &mut features_out);
|
| basis = Basis::fit(
|
| &features_out,
|
| tokens,
|
| config.hidden_size,
|
| config.num_prefix_tokens(),
|
| );
|
| apply_basis(&mut session, &basis);
|
| refit_pending = false;
|
| log::info!("refitted colour basis from live features");
|
|
|
|
|
|
|
| colors_out = basis.project(&features_out, tokens);
|
| }
|
|
|
| let grid = FeatureGrid {
|
| colors: colors_out.clone(),
|
| grid: if reconstructing {
|
| config.image_size
|
| } else {
|
| config.grid()
|
| },
|
| prefix_tokens: if reconstructing {
|
| 0
|
| } else {
|
| config.num_prefix_tokens()
|
| },
|
| latency_ms: start.elapsed().as_secs_f64() * 1000.0,
|
| };
|
| if let Ok(mut slot) = latest.lock() {
|
| *slot = Some(grid);
|
| }
|
| generation.fetch_add(1, Ordering::Release);
|
| ready.store(true, Ordering::Release);
|
| }
|
|
|
| log::info!("inference worker stopped");
|
| }
|
|
|
| fn apply_basis(session: &mut meganeura::Session, basis: &Basis) {
|
| session.set_parameter("pca.weight", &basis.weight_matrix());
|
| session.set_parameter("pca.bias", &basis.bias_vector());
|
| }
|
|
|