use std::time::Instant;
use abaddon::attention_cache::{CacheType, QuantizationGranularity};
use abaddon::models::Llama;
use abaddon::Tokenizer;
use anyhow::Result;
use candle_core::{DType, Device, IndexOp, Tensor};
fn main() -> Result<()> {
println!("=== Llama Model-Agnostic Cache Integration Test ===\n");
let has_cuda = candle_core::utils::cuda_is_available();
println!("CUDA available: {}", has_cuda);
let device = if has_cuda {
Device::new_cuda(0)?
} else {
Device::Cpu
};
println!("Using device: {:?}\n", device);
let model_id = "HuggingFaceTB/SmolLM2-135M-Instruct";
println!("Loading model: {model_id}");
let tokenizer_start = Instant::now();
let api = hf_hub::api::sync::Api::new()?;
let repo = api.model(model_id.to_string());
let tokenizer_path = repo.get("tokenizer.json")?;
let tokenizer = Tokenizer::from_file(tokenizer_path)?;
println!("Tokenizer loaded in {:?}", tokenizer_start.elapsed());
let config_path = repo.get("config.json")?;
let config: abaddon::models::LlamaConfig =
serde_json::from_str(&std::fs::read_to_string(config_path)?)?;
println!(
"Config: hidden_size={}, layers={}, heads={}, kv_heads={}",
config.hidden_size,
config.num_hidden_layers,
config.num_attention_heads,
config.num_kv_heads()
);
let weights_start = Instant::now();
let weights = repo.get("model.safetensors")?;
let dtype = if has_cuda { DType::BF16 } else { DType::F32 };
let vb = unsafe { candle_nn::VarBuilder::from_mmaped_safetensors(&[weights], dtype, &device)? };
println!("Weights loaded in {:?}\n", weights_start.elapsed());
let prompts = vec![
"The capital of France is",
"In machine learning,",
"The fibonacci sequence",
];
let cache_types: Vec<(&str, CacheType)> = {
let mut types = vec![
("Standard", CacheType::Standard),
(
"Quantized (PerToken)",
CacheType::Quantized(QuantizationGranularity::PerToken),
),
];
#[cfg(feature = "cuda")]
if has_cuda {
types.push(("CUDA Quantized", CacheType::CudaQuantized { device_id: 0 }));
}
types
};
println!("=== Testing {} cache types ===\n", cache_types.len());
let mut all_results: Vec<(&str, Vec<String>, Vec<std::time::Duration>)> = Vec::new();
for (cache_name, cache_type) in &cache_types {
println!("--- Cache Type: {} ---", cache_name);
let mut texts = Vec::new();
let mut times = Vec::new();
for prompt in &prompts {
let mut model =
Llama::load_with_cache_type(config.clone(), vb.clone(), cache_type.clone())?;
let token_ids = tokenizer.encode(prompt, true)?;
let input_tensor = Tensor::new(&token_ids[..], &device)?.unsqueeze(0)?;
let gen_start = Instant::now();
let mut current_tokens = token_ids.clone();
let mut logits = model.forward(&input_tensor, 0)?;
for _ in 0..15 {
let next_token = sample_token(&logits)?;
current_tokens.push(next_token);
let next_input = Tensor::new(&[next_token], &device)?.unsqueeze(0)?;
logits = model.forward(&next_input, current_tokens.len() - 1)?;
}
let gen_time = gen_start.elapsed();
let text = tokenizer.decode(¤t_tokens, true)?;
println!(
" \"{prompt}\" -> \"{}\" ({:.2?})",
&text[prompt.len()..].trim(),
gen_time
);
println!(
" Cache: seq_len={}, memory={:.2} KB",
model.cache_seq_len(),
model.cache_memory_bytes() as f64 / 1024.0
);
texts.push(text);
times.push(gen_time);
}
all_results.push((cache_name, texts, times));
println!();
}
println!("=== Results Comparison ===\n");
if all_results.len() >= 2 {
let (base_name, base_texts, base_times) = &all_results[0];
println!("Baseline: {}", base_name);
for (name, texts, times) in &all_results[1..] {
println!("\nComparing {} vs {}:", name, base_name);
let mut match_count = 0;
for (i, (base_text, other_text)) in base_texts.iter().zip(texts.iter()).enumerate() {
let matches = base_text == other_text;
if matches {
match_count += 1;
}
println!(
" Prompt {}: {}",
i + 1,
if matches { "MATCH ✓" } else { "DIFFER ✗" }
);
if !matches {
println!(" Base: \"{}\"", base_text);
println!(" Other: \"{}\"", other_text);
}
}
let base_total: f64 = base_times.iter().map(|t| t.as_secs_f64()).sum();
let other_total: f64 = times.iter().map(|t| t.as_secs_f64()).sum();
let speedup = base_total / other_total;
println!(
" Outputs: {}/{} match, Speedup: {:.2}x",
match_count,
base_texts.len(),
speedup
);
}
}
println!("\n=== Longer Sequence Benchmark (50 tokens) ===\n");
let long_prompt = "Explain how attention works in neural networks:";
let token_ids = tokenizer.encode(long_prompt, true)?;
println!(
"Prompt: \"{}\" ({} input tokens)",
long_prompt,
token_ids.len()
);
for (cache_name, cache_type) in &cache_types {
let mut model =
Llama::load_with_cache_type(config.clone(), vb.clone(), cache_type.clone())?;
let input_tensor = Tensor::new(&token_ids[..], &device)?.unsqueeze(0)?;
let start = Instant::now();
let mut current_tokens = token_ids.clone();
let mut logits = model.forward(&input_tensor, 0)?;
for _ in 0..50 {
let next_token = sample_token(&logits)?;
current_tokens.push(next_token);
let next_input = Tensor::new(&[next_token], &device)?.unsqueeze(0)?;
logits = model.forward(&next_input, current_tokens.len() - 1)?;
}
let elapsed = start.elapsed();
let tok_per_sec = 50.0 / elapsed.as_secs_f64();
let cache_kb = model.cache_memory_bytes() as f64 / 1024.0;
println!(
"{:20}: {:.2?} ({:.1} tok/s), cache: {:.1} KB",
cache_name, elapsed, tok_per_sec, cache_kb
);
}
println!("\n=== Test Complete ===");
Ok(())
}
fn sample_token(logits: &Tensor) -> Result<u32> {
let dims = logits.dims();
let logits = match dims.len() {
3 => {
let seq_len = dims[1];
logits.i((0, seq_len - 1, ..))?
},
2 => logits.i((dims[0] - 1, ..))?,
1 => logits.clone(),
_ => anyhow::bail!("Unexpected logits shape: {:?}", dims),
};
let logits = logits.to_dtype(DType::F32)?;
let logits: Vec<f32> = logits.to_vec1()?;
let token = logits
.iter()
.enumerate()
.max_by(|a, b| a.1.partial_cmp(b.1).unwrap())
.map(|(idx, _)| idx as u32)
.unwrap_or(0);
Ok(token)
}