use anyhow::{Context, Result};
use inferencelayer::server::{RequestParams, generate_once};
fn main() -> Result<()> {
let mut model = None;
let mut prompt = None;
let mut max_tokens = 32usize;
let mut chat = false;
let mut solo = false;
let mut args = std::env::args().skip(1);
while let Some(a) = args.next() {
match a.as_str() {
"--model" => model = args.next(),
"--prompt" => prompt = args.next(),
"--max-tokens" => max_tokens = args.next().context("--max-tokens value")?.parse()?,
"--chat" => chat = true,
"--solo" => solo = true,
other => anyhow::bail!("unknown argument {other}"),
}
}
let model = model.context("--model <dir> required")?;
let prompt = prompt.context("--prompt required")?;
let dir = std::path::Path::new(&model);
let ctx = inferencelayer::GpuCtx::new()?;
eprintln!("backend: {}", ctx.backend);
let t0 = std::time::Instant::now();
let w = inferencelayer::Weights::load(&ctx, dir)?;
eprintln!(
"loaded {:?} ({} layers) in {:.1}s",
w.cfg.arch,
w.cfg.n_layers,
t0.elapsed().as_secs_f64()
);
let gpu = inferencelayer::Lfm2Gpu::new(&ctx, w);
let tok = tokenizers::Tokenizer::from_file(dir.join("tokenizer.json"))
.map_err(|e| anyhow::anyhow!("tokenizer: {e}"))?;
let text = if chat {
format!(
"<|im_start|>user\n{prompt}<|im_end|>\n<|im_start|>assistant\n<think>\n\n</think>\n\n"
)
} else {
prompt.clone()
};
let ids = tok
.encode(text.as_str(), true)
.map_err(|e| anyhow::anyhow!("encode: {e}"))?
.get_ids()
.to_vec();
eprintln!("prompt: {} tokens", ids.len());
let out = if solo {
let (out, rate, prefill_ms) = gpu.decode(&ctx, &ids, max_tokens)?;
eprintln!(
"[--solo: M=1 loop, prompt ingested one token at a time] \
decode {} tokens at {rate:.1} tok/s (prompt ingest {prefill_ms:.0} ms)",
out.len()
);
out
} else {
let eos = eos_ids(dir);
let (out, st) = generate_once(
&gpu,
&ctx,
ids.clone(),
max_tokens,
RequestParams::default(),
eos,
)?;
eprintln!(
"prefill: {} tokens in {:.2} s ({:.0} tok/s) | decode: {} tokens at {:.1} tok/s",
ids.len(),
st.prefill_s,
st.prefill_tok_s,
out.len(),
st.decode_tok_s
);
out
};
println!("{}", tok.decode(&out, true).unwrap_or_default());
Ok(())
}
fn eos_ids(dir: &std::path::Path) -> Vec<u32> {
let mut out = Vec::new();
for name in ["generation_config.json", "config.json"] {
let Ok(bytes) = std::fs::read(dir.join(name)) else {
continue;
};
let Ok(v) = serde_json::from_slice::<serde_json::Value>(&bytes) else {
continue;
};
match v.get("eos_token_id") {
Some(serde_json::Value::Number(n)) => out.extend(n.as_u64().map(|x| x as u32)),
Some(serde_json::Value::Array(a)) => {
out.extend(a.iter().filter_map(|x| x.as_u64()).map(|x| x as u32))
}
_ => {}
}
if !out.is_empty() {
break;
}
}
out
}