#[cfg(feature = "mkl")]
extern crate intel_mkl_src;
#[cfg(feature = "accelerate")]
extern crate accelerate_src;
use anyhow::{Error as E, Result, bail};
use clap::Parser;
use fuel::lazy::Tensor;
use fuel::lazy_mixformer::{MixFormerActivation, MixFormerConfig};
use fuel::lazy_moondream::{
MoondreamConfig, MoondreamModel, MoondreamProjectionConfig, MoondreamVisionConfig,
MoondreamWeights,
};
use fuel::safetensors::MmapedSafetensors;
use fuel::{Device, Shape};
use std::sync::Arc;
use tokenizers::Tokenizer;
#[derive(Parser)]
struct Args {
#[arg(long)]
cpu: bool,
#[arg(long)]
tracing: bool,
#[arg(long)]
verbose_prompt: bool,
#[arg(long)]
prompt: String,
#[arg(long)]
image: String,
#[arg(long)]
temperature: Option<f64>,
#[arg(long)]
top_p: Option<f64>,
#[arg(long, default_value_t = 0)]
seed: u64,
#[arg(long, default_value_t = 5000)]
sample_len: usize,
#[arg(long, default_value_t = 1.0)]
repeat_penalty: f32,
#[arg(long, default_value_t = 64)]
repeat_last_n: usize,
#[arg(long)]
model_id: Option<String>,
#[arg(long)]
revision: Option<String>,
#[arg(long)]
quantized: bool,
#[arg(long)]
f16: bool,
#[arg(long)]
model_file: Option<String>,
#[arg(long)]
tokenizer_file: Option<String>,
}
pub fn load_image_nchw<P: AsRef<std::path::Path>>(p: P) -> Result<Vec<f32>> {
let img = image::ImageReader::open(p)?
.decode()
.map_err(|e| E::msg(format!("decode image: {e}")))?
.resize_to_fill(378, 378, image::imageops::FilterType::Triangle);
let img = img.to_rgb8();
let (w, h) = (img.width() as usize, img.height() as usize);
let raw = img.into_raw(); let mut nchw = vec![0.0_f32; 3 * h * w];
for y in 0..h {
for x in 0..w {
for c in 0..3 {
let src = (y * w + x) * 3 + c;
let dst = c * h * w + y * w + x;
let v = raw[src] as f32 / 255.0;
nchw[dst] = (v - 0.5) / 0.5;
}
}
}
Ok(nchw)
}
#[tokio::main]
async fn main() -> anyhow::Result<()> {
use tracing_chrome::ChromeLayerBuilder;
use tracing_subscriber::prelude::*;
let args = Args::parse();
let _guard = if args.tracing {
let (chrome_layer, guard) = ChromeLayerBuilder::new().build();
tracing_subscriber::registry().with(chrome_layer).init();
Some(guard)
} else {
None
};
println!(
"avx: {}, neon: {}, f16c: {}",
fuel::utils::with_avx(),
fuel::utils::with_neon(),
fuel::utils::with_f16c()
);
println!(
"temp: {:.2} repeat-penalty: {:.2} repeat-last-n: {}",
args.temperature.unwrap_or(0.),
args.repeat_penalty,
args.repeat_last_n
);
if args.quantized {
bail!(
"the lazy moondream binary does not yet support --quantized \
(no lazy_quantized_moondream module). Drop the flag to use the \
safetensors weights."
);
}
if args.f16 {
bail!(
"the lazy moondream binary does not yet support --f16 (lazy_moondream \
is F32-only). Drop the flag."
);
}
let _ = args.cpu;
let device = Device::cpu();
let start = std::time::Instant::now();
let api = hf_hub::api::tokio::Api::new()?;
let (model_id, revision) = match args.model_id {
Some(model_id) => (model_id.to_string(), None),
None => (
"vikhyatk/moondream1".to_string(),
Some("f6e9da68e8f1b78b8f3ee10905d56826db7a5802"),
),
};
let revision = match (args.revision, revision) {
(Some(r), _) => r,
(None, Some(r)) => r.to_string(),
(None, None) => "main".to_string(),
};
let repo = api.repo(hf_hub::Repo::with_revision(
model_id,
hf_hub::RepoType::Model,
revision,
));
let model_file = match args.model_file {
Some(m) => m.into(),
None => repo.get("model.safetensors").await?,
};
let tokenizer = match args.tokenizer_file {
Some(m) => m.into(),
None => repo.get("tokenizer.json").await?,
};
println!("retrieved the files in {:?}", start.elapsed());
let tokenizer = Tokenizer::from_file(tokenizer).map_err(E::msg)?;
let start = std::time::Instant::now();
let cfg = moondream_v2_lazy_config();
let st = unsafe { MmapedSafetensors::multi(&[model_file]) }
.map_err(|e| E::msg(format!("mmap safetensors: {e}")))?;
let weights = MoondreamWeights::load_from_mmapped(&st, &cfg)
.map_err(|e| E::msg(format!("load weights: {e}")))?;
let model = MoondreamModel {
config: cfg.clone(),
weights,
};
println!("loaded the model in {:?}", start.elapsed());
let start = std::time::Instant::now();
let image_data = load_image_nchw(&args.image)?;
let pixel_values = Tensor::from_f32(
Arc::<[f32]>::from(image_data),
Shape::from_dims(&[
1,
cfg.vision.num_channels,
cfg.vision.image_size,
cfg.vision.image_size,
]),
&device,
)?;
println!(
"loaded image (1, {}, {}, {}) in {:?}",
cfg.vision.num_channels,
cfg.vision.image_size,
cfg.vision.image_size,
start.elapsed()
);
let prompt = format!("\n\nQuestion: {0}\n\nAnswer:", args.prompt);
let encoded = tokenizer.encode(prompt.as_str(), true).map_err(E::msg)?;
if encoded.get_ids().is_empty() {
bail!("Empty prompts are not supported in the Moondream model.")
}
if args.verbose_prompt {
for (token, id) in encoded.get_tokens().iter().zip(encoded.get_ids().iter()) {
let token = token.replace('▁', " ").replace("<0x0A>", "\n");
println!("{id:7} -> '{token}'");
}
}
let special_token = match tokenizer.get_vocab(true).get("<|endoftext|>") {
Some(token) => *token,
None => bail!("cannot find the special token"),
};
let (bos_token, eos_token) = (special_token, special_token);
let mut tokens: Vec<u32> = std::iter::once(bos_token)
.chain(encoded.get_ids().iter().copied())
.collect();
let mut generated_tokens = 0_usize;
println!("starting the inference loop");
use std::io::Write;
let vocab_size = cfg.text.vocab_size;
let start_gen = std::time::Instant::now();
for index in 0..args.sample_len {
let logits = model
.forward(&pixel_values, &tokens)
.map_err(|e| E::msg(format!("forward: {e}")))?;
let logits_data = logits.realize_f32();
let seq = cfg.vision.num_patches + tokens.len();
let last_off = (seq - 1) * vocab_size;
let mut last_logits: Vec<f32> = logits_data[last_off..last_off + vocab_size].to_vec();
if args.repeat_penalty != 1.0 {
let start_at = tokens.len().saturating_sub(args.repeat_last_n);
apply_repeat_penalty(&mut last_logits, args.repeat_penalty, &tokens[start_at..]);
}
let next_token = sample(
&last_logits,
args.temperature.unwrap_or(0.0) as f32,
args.top_p.map(|p| p as f32),
args.seed.wrapping_add(index as u64),
);
tokens.push(next_token);
generated_tokens += 1;
if next_token == eos_token || tokens.ends_with(&[27, 10619, 29] ) {
break;
}
let token_str = tokenizer.decode(&[next_token], true).map_err(E::msg)?;
print!("{token_str}");
std::io::stdout().flush()?;
}
let dt = start_gen.elapsed();
println!(
"\ngenerated in {} seconds\n{generated_tokens} tokens generated ({:.2} token/s)",
dt.as_secs_f64(),
generated_tokens as f64 / dt.as_secs_f64()
);
Ok(())
}
fn moondream_v2_lazy_config() -> MoondreamConfig {
MoondreamConfig {
vision: MoondreamVisionConfig::v2(),
projection: MoondreamProjectionConfig::v2(),
text: MixFormerConfig {
vocab_size: 51200,
hidden_size: 2048,
n_inner: None, num_hidden_layers: 24,
num_attention_heads: 32,
rotary_dim: 32,
layer_norm_eps: 1e-5,
max_position_embeddings: 2048,
rope_theta: 10_000.0,
hidden_activation: MixFormerActivation::GeluPytorchTanh,
tie_word_embeddings: false,
},
}
}
fn apply_repeat_penalty(logits: &mut [f32], penalty: f32, context: &[u32]) {
let mut seen = std::collections::HashSet::new();
for &t in context {
if !seen.insert(t) {
continue;
}
let idx = t as usize;
if idx < logits.len() {
let v = logits[idx];
logits[idx] = if v >= 0.0 { v / penalty } else { v * penalty };
}
}
}
fn sample(logits: &[f32], temperature: f32, top_p: Option<f32>, seed: u64) -> u32 {
if temperature <= 0.0 {
let mut best_i = 0usize;
let mut best = logits[0];
for (i, &v) in logits.iter().enumerate().skip(1) {
if v > best {
best = v;
best_i = i;
}
}
return best_i as u32;
}
let max_l = logits.iter().cloned().fold(f32::NEG_INFINITY, f32::max);
let inv_t = 1.0 / temperature.max(1e-6);
let mut probs: Vec<f32> = logits
.iter()
.map(|&x| ((x - max_l) * inv_t).exp())
.collect();
let sum: f32 = probs.iter().sum();
for p in &mut probs {
*p /= sum.max(1e-30);
}
let mut idx: Vec<usize> = (0..probs.len()).collect();
idx.sort_unstable_by(|&a, &b| probs[b].partial_cmp(&probs[a]).unwrap());
let mut keep_mask: Vec<bool> = vec![true; probs.len()];
if let Some(p_cut) = top_p {
let mut cum2 = 0.0;
let mut allow = true;
for &i in &idx {
if !keep_mask[i] {
continue;
}
if !allow {
keep_mask[i] = false;
continue;
}
cum2 += probs[i];
if cum2 >= p_cut {
allow = false;
}
}
}
let mut filtered: Vec<f32> = probs
.iter()
.enumerate()
.map(|(i, p)| if keep_mask[i] { *p } else { 0.0 })
.collect();
let s: f32 = filtered.iter().sum();
if s > 0.0 {
for v in &mut filtered {
*v /= s;
}
} else {
return 0;
}
let mut state = seed
.wrapping_mul(6364136223846793005)
.wrapping_add(1442695040888963407);
state ^= state >> 33;
state = state.wrapping_mul(0xff51_afd7_ed55_8ccd);
state ^= state >> 33;
let r = (state as f32) / (u64::MAX as f32);
let mut cum = 0.0;
for (i, p) in filtered.iter().enumerate() {
cum += *p;
if r <= cum {
return i as u32;
}
}
(filtered.len() - 1) as u32
}