use forge::{AnyTokenizer, Device, Gpt2, Gpt2Config, Sampling, Tokenizer as _};
fn main() -> Result<(), Box<dyn std::error::Error>> {
let args: Vec<String> = std::env::args().collect();
let get = |flag: &str| -> Option<String> {
args.iter()
.position(|a| a == flag)
.and_then(|i| args.get(i + 1).cloned())
};
let dir = get("--model").unwrap_or_else(|| "models/gpt2".into());
let backend = get("--backend").unwrap_or_else(|| "wgpu".into());
let tokens: usize = get("--tokens").map_or(30, |s| s.parse().expect("--tokens"));
let topk: Option<usize> = get("--topk").map(|s| s.parse().expect("--topk"));
let temp: f32 = get("--temp").map_or(0.8, |s| s.parse().expect("--temp"));
let seed: u64 = get("--seed").map_or(42, |s| s.parse().expect("--seed"));
let device = match backend.as_str() {
"cpu" => Device::Cpu,
"wgpu" => Device::wgpu()?,
other => return Err(format!("unknown backend {other:?} (use cpu|wgpu)").into()),
};
println!("device: {}", device.describe());
let dir = std::path::Path::new(&dir);
let config =
Gpt2Config::from_json(dir.join("config.json")).unwrap_or_else(|_| Gpt2Config::gpt2());
let start = std::time::Instant::now();
let model = Gpt2::from_safetensors(dir.join("model.safetensors"), config, &device)?;
let tokenizer = AnyTokenizer::from_dir(dir)?;
println!(
"loaded {} in {:.1}s — {} vocab, {} tokens",
dir.display(),
start.elapsed().as_secs_f32(),
tokenizer.kind(),
tokenizer.vocab_size()
);
let prompt = get("--prompt").unwrap_or_else(|| match tokenizer.kind() {
"char" => "ROMEO:".into(),
_ => "The old lighthouse keeper".into(),
});
let sampling = match topk {
Some(k) => Sampling::TopK {
k,
temperature: temp,
seed,
},
None => Sampling::Greedy,
};
let start = std::time::Instant::now();
let text = model.generate(&tokenizer, &prompt, tokens, sampling)?;
let dt = start.elapsed().as_secs_f32();
println!(
"--- output ({tokens} tokens, {dt:.1}s, {:.2} tok/s) ---",
tokens as f32 / dt
);
println!("{text}");
Ok(())
}