use memra_engine::Engine;
use memra_engine::vision_gemma::{
GV_MERGE, GV_TOK_BEGIN, GV_TOK_END, GV_TOK_SOFT, GemmaVisionTower, gemma_prep_image,
};
use memra_gguf::GgufFile;
use std::path::Path;
fn main() -> Result<(), Box<dyn std::error::Error>> {
let mut a = std::env::args().skip(1);
let model_path = a.next().expect("model.gguf");
let mmproj = a.next().expect("mmproj.gguf");
let image = a.next().expect("image path");
let prompt = a.next().expect("prompt");
let n_gen: usize = a.next().and_then(|s| s.parse().ok()).unwrap_or(200);
let e = Engine::new(0)?;
let g = GgufFile::open(&model_path)?;
let tok = memra_tokenizer::Tokenizer::from_gguf(&g)?;
let model = memra_engine::hybrid::HybridModel::load(&e, &g)?;
let tower = GemmaVisionTower::load(&e, Path::new(&mmproj))?;
let (patches, gw, gh) = gemma_prep_image(&std::fs::read(&image)?)?;
let n_soft = (gw / GV_MERGE) * (gh / GV_MERGE);
let rows = tower.forward(&e, &patches, gw, gh)?;
eprintln!("[e2e] image grid {gw}x{gh} -> {n_soft} soft tokens");
const PLACEHOLDER: &str = "\u{1f5bc}"; let rendered = match prompt.strip_prefix("RAW:") {
Some(raw) => raw.replace("<__media__>", PLACEHOLDER),
None => {
let content = format!("{PLACEHOLDER}{prompt}");
tok.apply_chat_template(&[("user", &content)], true)
}
};
let (pre, post) = rendered
.split_once(PLACEHOLDER)
.ok_or("placeholder lost in template render")?;
let mut ids = tok.encode(pre, true);
let span_start = ids.len() + 1; ids.push(GV_TOK_BEGIN);
ids.extend(std::iter::repeat_n(GV_TOK_SOFT, n_soft));
ids.push(GV_TOK_END);
ids.extend(tok.encode(post, false));
eprintln!(
"[e2e] prompt {} tokens (span at {span_start}+{n_soft})",
ids.len()
);
let overlay = memra_engine::vision::EmbedOverlay {
rows,
spans: vec![(span_start, 0, n_soft)],
};
let causal_wrong_arm = std::env::var("MEMRA_GV_CAUSAL").as_deref() == Ok("1");
let mut cache = memra_engine::cache::Cache::new(&e, &model.cfg, ids.len() + n_gen + 8)?;
let t0 = std::time::Instant::now();
if causal_wrong_arm {
unsafe { std::env::set_var("MEMRA_GV_FORCE_CAUSAL", "1") };
}
let (logits, _seed, _hidden) =
model.prime_cache_overlaid(&e, &ids, &mut cache, 0, Some(&overlay))?;
eprintln!(
"[e2e] prime {} tokens in {:.2}s",
ids.len(),
t0.elapsed().as_secs_f32()
);
{
let mut idx: Vec<usize> = (0..logits.len()).collect();
idx.sort_unstable_by(|&a, &b| logits[b].total_cmp(&logits[a]));
let top: Vec<String> = idx[..8]
.iter()
.map(|&i| format!("({i}, {:.4})", logits[i]))
.collect();
println!("TOP8: {}", top.join(" "));
}
let mut next = memra_engine::forward::argmax(&logits) as u32;
let mut out_ids = Vec::with_capacity(n_gen);
let eot = tok.id_of("<end_of_turn>");
for _ in 0..n_gen {
out_ids.push(next);
if Some(next) == eot {
break;
}
let ll = model.decode_step(&e, next, &mut cache)?;
next = memra_engine::forward::argmax(&ll) as u32;
}
println!("ANSWER: {}", tok.decode(&out_ids));
Ok(())
}