#[cfg(feature = "mkl")]
extern crate intel_mkl_src;
#[cfg(feature = "accelerate")]
extern crate accelerate_src;
use anyhow::{Error as E, Result};
use clap::Parser;
use fuel::lazy_bert::{BertConfig, BertModel, BertWeights};
use std::io::Write as IoWrite;
#[derive(Parser, Debug)]
#[command(author, version, about, long_about = None)]
struct Args {
#[arg(long)]
cpu: bool,
#[arg(long)]
tracing: bool,
#[arg(long)]
prompt: Option<String>,
#[arg(long, default_value = "1")]
n: usize,
#[arg(long, default_value = "true")]
normalize_embeddings: bool,
#[arg(long, default_value = "false")]
approximate_gelu: bool,
}
fn main() -> Result<()> {
use tracing_chrome::ChromeLayerBuilder;
use tracing_subscriber::prelude::*;
let args = Args::parse();
let _guard = if args.tracing {
println!("tracing...");
let (chrome_layer, guard) = ChromeLayerBuilder::new().build();
tracing_subscriber::registry().with(chrome_layer).init();
Some(guard)
} else {
None
};
let start = std::time::Instant::now();
let _device = fuel_examples::device(args.cpu)?;
let _ = args.approximate_gelu;
let (model, tokenizer) = build_model_and_tokenizer_from_bytes()?;
let hidden_size = model.config.hidden_size;
if let Some(prompt) = args.prompt {
let tokens = tokenizer
.encode(&prompt, true)
.map_err(|e| E::msg(format!("tokenize: {e}")))?;
println!("Loaded and encoded {:?}", start.elapsed());
for idx in 0..args.n {
let t0 = std::time::Instant::now();
let hidden = model
.forward(&tokens)
.map_err(|e| E::msg(format!("forward: {e}")))?;
let data = hidden.realize_f32();
if idx == 0 {
println!("hidden state: shape=[1, {}, {}]", tokens.len(), hidden_size);
let n_print = data.len().min(8);
println!("first {n_print} values: {:?}", &data[..n_print]);
}
println!("Took {:?}", t0.elapsed());
}
} else {
let sentences = [
"The cat sits outside",
"A man is playing guitar",
"I love pasta",
"The new movie is awesome",
"The cat plays in the garden",
"A woman watches TV",
"The new movie is so great",
"Do you like pizza?",
];
let n_sentences = sentences.len();
let mut pooled: Vec<Vec<f32>> = Vec::with_capacity(n_sentences);
for s in sentences.iter() {
let tokens = tokenizer
.encode(s, true)
.map_err(|e| E::msg(format!("tokenize: {e}")))?;
let hidden = model
.forward(&tokens)
.map_err(|e| E::msg(format!("forward: {e}")))?;
let data = hidden.realize_f32();
let seq = tokens.len();
let mut sum = vec![0.0_f32; hidden_size];
for t in 0..seq {
let row = &data[t * hidden_size..(t + 1) * hidden_size];
for (acc, &x) in sum.iter_mut().zip(row) {
*acc += x;
}
}
for v in &mut sum {
*v /= seq as f32;
}
if args.normalize_embeddings {
let norm: f32 = sum.iter().map(|x| x * x).sum::<f32>().sqrt().max(1e-12);
for v in &mut sum {
*v /= norm;
}
}
pooled.push(sum);
}
println!("pooled embeddings: {} x {}", n_sentences, hidden_size);
let mut similarities = vec![];
for i in 0..n_sentences {
for j in (i + 1)..n_sentences {
let e_i = &pooled[i];
let e_j = &pooled[j];
let mut dot = 0.0_f32;
let mut a = 0.0_f32;
let mut b = 0.0_f32;
for k in 0..hidden_size {
dot += e_i[k] * e_j[k];
a += e_i[k] * e_i[k];
b += e_j[k] * e_j[k];
}
let cosine_similarity = dot / (a * b).sqrt();
similarities.push((cosine_similarity, i, j));
}
}
similarities.sort_by(|u, v| v.0.total_cmp(&u.0));
for &(score, i, j) in similarities[..5].iter() {
println!("score: {score:.2} '{}' '{}'", sentences[i], sentences[j])
}
}
Ok(())
}
pub fn build_model_and_tokenizer_from_bytes() -> Result<(BertModel, fuel::lazy_bert::BertTokenizer)>
{
let config_data: &[u8] = include_bytes!(env!("FUEL_BUILDTIME_MODEL_CONFIG"));
let tokenizer_data: &[u8] = include_bytes!(env!("FUEL_BUILDTIME_MODEL_TOKENIZER"));
let weights_data: &[u8] = include_bytes!(env!("FUEL_BUILDTIME_MODEL_WEIGHTS"));
let config_string = std::str::from_utf8(config_data)?;
let config = BertConfig::from_hf_json_str(config_string)
.map_err(|e| E::msg(format!("parsing bert config: {e}")))?;
let tmp_dir = std::env::temp_dir();
let weights_path = tmp_dir.join("fuel_bert_single_weights.safetensors");
{
let mut f = std::fs::File::create(&weights_path)?;
f.write_all(weights_data)?;
f.flush()?;
}
let st = unsafe { fuel::safetensors::MmapedSafetensors::new(&weights_path) }
.map_err(|e| E::msg(format!("mmap embedded safetensors: {e}")))?;
let weights = BertWeights::load_from_mmapped(&st, &config)
.map_err(|e| E::msg(format!("load bert weights: {e}")))?;
let model = BertModel::new(config, weights);
let tokenizer_path = tmp_dir.join("fuel_bert_single_tokenizer.json");
{
let mut f = std::fs::File::create(&tokenizer_path)?;
f.write_all(tokenizer_data)?;
f.flush()?;
}
let tokenizer = fuel::lazy_bert::BertTokenizer::from_file(&tokenizer_path)
.map_err(|e| E::msg(format!("bert tokenizer: {e}")))?;
Ok((model, tokenizer))
}