#[cfg(feature = "mkl")]
extern crate intel_mkl_src;
#[cfg(feature = "accelerate")]
extern crate accelerate_src;
use anyhow::{Context, Error as E, Result};
use clap::{Parser, ValueEnum};
use std::path::PathBuf;
use fuel::lazy_distilbert::{DistilBertConfig, DistilBertModel, DistilBertWeights};
use hf_hub::{Repo, RepoType, api::sync::Api};
use tokenizers::Tokenizer;
#[derive(Clone, Debug, Copy, PartialEq, Eq, ValueEnum)]
enum Which {
#[value(name = "distilbert")]
DistilBert,
#[value(name = "distilbertformaskedlm")]
DistilbertForMaskedLM,
}
#[derive(Parser, Debug)]
#[command(author, version, about, long_about = None)]
struct Args {
#[arg(long)]
cpu: bool,
#[arg(long)]
tracing: bool,
#[arg(long, default_value = "distilbert")]
model: Which,
#[arg(long)]
model_id: Option<String>,
#[arg(long)]
revision: Option<String>,
#[arg(long)]
prompt: String,
#[arg(long, default_value = "1")]
n: usize,
#[arg(long, default_value = "5")]
top_k: usize,
}
fn resolve_model_and_revision(args: &Args) -> (String, String) {
let default_model = "distilbert-base-uncased".to_string();
let default_revision = "main".to_string();
match (args.model_id.clone(), args.revision.clone()) {
(Some(model_id), Some(revision)) => (model_id, revision),
(Some(model_id), None) => (model_id, default_revision),
(None, Some(revision)) => (default_model, revision),
(None, None) => (default_model, default_revision),
}
}
fn download_model_files(model_id: &str, revision: &str) -> Result<(PathBuf, PathBuf, PathBuf)> {
let repo = Repo::with_revision(model_id.to_string(), RepoType::Model, revision.to_string());
let api = Api::new()?;
let api = api.repo(repo);
let config = api.get("config.json")?;
let tokenizer = api.get("tokenizer.json")?;
let weights = api.get("model.safetensors")?;
Ok((config, tokenizer, weights))
}
fn setup_tracing(args: &Args) -> Option<impl Drop> {
if args.tracing {
use tracing_chrome::ChromeLayerBuilder;
use tracing_subscriber::prelude::*;
println!("tracing...");
let (chrome_layer, guard) = ChromeLayerBuilder::new().build();
tracing_subscriber::registry().with(chrome_layer).init();
Some(guard)
} else {
None
}
}
fn main() -> Result<()> {
let args = Args::parse();
let _guard = setup_tracing(&args);
let _ = fuel_examples::device(args.cpu)?;
if args.n != 1 {
eprintln!(
"warning: --n is ignored -- the lazy port runs the prompt exactly once regardless \
of this value (the eager binary's repeat-for-timing loop was not ported)."
);
}
let (model_id, revision) = resolve_model_and_revision(&args);
let (_config_path, tokenizer_path, weights_path) = download_model_files(&model_id, &revision)?;
let tokenizer = Tokenizer::from_file(tokenizer_path).map_err(E::msg)?;
let config = DistilBertConfig::distilbert_base();
let st = unsafe { fuel::safetensors::MmapedSafetensors::multi(&[weights_path]) }
.map_err(|e| E::msg(format!("mmap safetensors: {e}")))?;
let weights = DistilBertWeights::load_from_mmapped(&st, &config)
.map_err(|e| E::msg(format!("load weights: {e}")))?;
let model = DistilBertModel {
config: config.clone(),
weights,
};
let (token_ids, _mask) = prepare_inputs(&args, &tokenizer)?;
println!("token_ids: {token_ids:?}");
let hidden = model
.forward(&token_ids, None)
.map_err(|e| E::msg(format!("forward: {e}")))?;
let hidden_data = hidden.realize_f32();
let seq = token_ids.len();
let dim = config.dim;
match args.model {
Which::DistilBert => {
println!("embeddings ({} tokens, dim={})", seq, dim);
for t in 0..seq {
let off = t * dim;
let preview: Vec<f32> = hidden_data[off..off + dim.min(8)].to_vec();
println!(" token {t:3}: {preview:?}");
}
}
Which::DistilbertForMaskedLM => {
println!(
"Masked-LM path (top-{}) over [{}] currently emits hidden states; \
lazy_distilbert tied-LM head not yet wired.",
args.top_k, args.prompt,
);
for t in 0..seq {
let off = t * dim;
let preview: Vec<f32> = hidden_data[off..off + dim.min(8)].to_vec();
println!(" token {t:3}: {preview:?}");
}
}
}
Ok(())
}
fn prepare_inputs(args: &Args, tokenizer: &Tokenizer) -> Result<(Vec<u32>, Vec<u8>)> {
let mut binding = tokenizer.clone();
let tokenizer_configured = binding
.with_padding(None)
.with_truncation(None)
.map_err(E::msg)?;
let tokens = tokenizer_configured
.encode(args.prompt.clone(), true)
.map_err(E::msg)?
.get_ids()
.to_vec();
let mask = match args.model {
Which::DistilbertForMaskedLM => attention_mask_maskedlm(tokenizer, &args.prompt)?,
Which::DistilBert => attention_mask(tokens.len()),
};
Ok((tokens, mask))
}
fn attention_mask(size: usize) -> Vec<u8> {
(0..size)
.flat_map(|i| (0..size).map(move |j| u8::from(j > i)))
.collect()
}
fn attention_mask_maskedlm(tokenizer: &Tokenizer, input: &str) -> Result<Vec<u8>> {
let tokens = tokenizer.encode(input, true).map_err(E::msg)?;
let seq_len = tokens.get_attention_mask().to_vec().len();
let mask_token_id = tokenizer
.token_to_id("[MASK]")
.context("Mask token, \"[MASK]\", not found in tokenizer.")?;
let mut attention_mask_vec = Vec::with_capacity(seq_len * seq_len);
let ids = tokens.get_ids();
for _ in 0..seq_len {
for id in ids.iter() {
let mask_value = if id == &mask_token_id { 1u8 } else { 0u8 };
attention_mask_vec.push(mask_value);
}
}
Ok(attention_mask_vec)
}