use ffai_argus::engine::{ArgusBytes, SmolVlm};
use ffai_core::engine::{VlmEngine, VlmOptions};
use ffai_core::types::{ImageBuffer, PixelFormat};
fn main() {
let mut args = std::env::args().skip(1);
let usage = "usage: from_bytes_smoke <model-dir> <image.rgba> <w> <h> [prompt]";
let dir = args.next().expect(usage);
let rgba_path = args.next().expect(usage);
let width: u32 = args.next().expect(usage).parse().expect("width");
let height: u32 = args.next().expect(usage).parse().expect("height");
let prompt = args.next().unwrap_or_default();
let read = |name: &str| {
std::fs::read(std::path::Path::new(&dir).join(name))
.unwrap_or_else(|e| panic!("{dir}/{name}: {e}"))
};
let engine = SmolVlm::from_bytes(ArgusBytes {
weights: read("model.safetensors"),
config: String::from_utf8(read("config.json")).expect("config.json is not UTF-8"),
tokenizer: read("tokenizer.json"),
})
.expect("from_bytes");
let data = std::fs::read(&rgba_path).unwrap_or_else(|e| panic!("{rgba_path}: {e}"));
let want = width as usize * height as usize * 4;
assert_eq!(data.len(), want, "{rgba_path}: expected {want} bytes of RGBA");
let image = ImageBuffer {
data,
width,
height,
format: PixelFormat::Rgba8,
};
let opts = VlmOptions {
prompt: (!prompt.trim().is_empty()).then_some(prompt),
max_new_tokens: Some(40),
..VlmOptions::default()
};
println!("image: {width}x{height}");
let _ = engine.describe_image_unsplit(&image, &opts);
let t = std::time::Instant::now();
let fast = engine.describe_image_unsplit(&image, &opts).expect("unsplit");
let t_fast = t.elapsed().as_secs_f64();
println!("NATIVE describe_image_unsplit (1 tile): {t_fast:8.3} s");
println!(" {}", fast.trim());
ffai_core::cost::start();
let _ = engine.describe_image_unsplit(&image, &opts);
println!("{}", ffai_core::cost::stop().report("unsplit(1 tile)"));
let ops = ffai_argus::siglip::prof::take();
if !ops.is_empty() {
let tot: f64 = ops.iter().map(|(_, ms)| ms).sum();
println!("
VISION TOWER PER-OP (unsplit, 1 tile) — total {tot:.0} ms");
for (name, ms) in ops.iter().take(14) {
println!(" {name:<28} {ms:9.1} ms {:5.1} %", 100.0 * ms / tot);
}
}
ffai_core::cost::start();
let _ = engine.describe_image(&image, &opts);
println!("{}", ffai_core::cost::stop().report("split(17 tiles)"));
let (_, tr) = engine.describe_image_traced(&image, &opts).expect("traced");
let tower: f64 = tr.tower_per_tile_ms.iter().sum();
let steps: f64 = tr.step_ms.iter().sum();
let total = tr.preprocess_ms + tower + tr.assemble_ms + tr.prefill_ms + steps + tr.detokenize_ms;
println!("
STAGE SPLIT (17-tile path, {} tiles):", tr.tiles);
for (name, ms) in [
("preprocess", tr.preprocess_ms),
("vision tower", tower),
("assemble", tr.assemble_ms),
("prefill", tr.prefill_ms),
("decode steps", steps),
("detokenize", tr.detokenize_ms),
] {
println!(" {name:14} {ms:9.1} ms {:5.1} %", 100.0 * ms / total);
}
println!(" {:14} {total:9.1} ms", "TOTAL");
if !tr.tower_per_tile_ms.is_empty() {
let per = tower / tr.tower_per_tile_ms.len() as f64;
println!(" per tile {per:9.1} ms");
}
}