use std::sync::Arc;
use std::time::Instant;
use cudarc::driver::CudaDevice;
use super::compute::ComputeEngine;
use super::generate::{GenerationStats, SamplingParams, TokenCallback};
use super::kernels::sampling::{RepetitionPenalty, SamplingKernel};
use super::lazy_weight_store::LazyWeightStore;
use super::InferenceError;
pub struct LazyGenerator {
weights: LazyWeightStore,
engine: ComputeEngine,
sampler: SamplingKernel,
#[allow(dead_code)]
device: Arc<CudaDevice>,
vocab_size: usize,
position: usize,
rng_state: u64,
}
impl LazyGenerator {
pub fn new(weights: LazyWeightStore, max_seq_len: usize) -> Result<Self, InferenceError> {
let device = weights.device().clone();
let config = weights.config.clone();
let vocab_size = config.vocab_size;
let engine = ComputeEngine::new(config, max_seq_len, device.clone())?;
let sampler = SamplingKernel::new(device.clone())?;
Ok(Self {
weights,
engine,
sampler,
device,
vocab_size,
position: 0,
rng_state: 42,
})
}
pub fn reset(&mut self) {
self.engine.reset_cache();
self.position = 0;
}
pub fn layer_stats(&self) -> super::lazy_layers::LazyLayerStats {
self.weights.stats()
}
pub fn generate(
&mut self,
input_ids: &[u32],
params: Option<&SamplingParams>,
) -> Result<Vec<u32>, InferenceError> {
let params = params.cloned().unwrap_or_default();
let mut output_ids = Vec::new();
self.reset();
self.rng_state = params.seed;
let _hidden = self.engine.prefill_lazy(input_ids, &mut self.weights)?;
self.position = input_ids.len();
let mut rep_penalty =
RepetitionPenalty::new(params.repetition_penalty, params.repetition_context);
for &id in input_ids {
rep_penalty.add_token(id);
}
for _ in 0..params.max_tokens {
let logits = self.engine.get_logits()?;
let next_token = self.sample_token(logits, ¶ms, &mut rep_penalty)?;
if params.stop_tokens.contains(&next_token) {
break;
}
output_ids.push(next_token);
rep_penalty.add_token(next_token);
let _hidden = self.engine.decode_lazy(next_token, &mut self.weights)?;
self.position += 1;
}
Ok(output_ids)
}
pub fn generate_stream(
&mut self,
input_ids: &[u32],
params: Option<&SamplingParams>,
mut callback: TokenCallback,
) -> Result<GenerationStats, InferenceError> {
let params = params.cloned().unwrap_or_default();
let start_time = Instant::now();
self.reset();
self.rng_state = params.seed;
let mut stats = GenerationStats::default();
let prefill_start = Instant::now();
let _hidden = self.engine.prefill_lazy(input_ids, &mut self.weights)?;
self.position = input_ids.len();
stats.prefill_time_ms = prefill_start.elapsed().as_secs_f64() * 1000.0;
stats.prefill_tokens = input_ids.len();
let mut rep_penalty =
RepetitionPenalty::new(params.repetition_penalty, params.repetition_context);
for &id in input_ids {
rep_penalty.add_token(id);
}
for _ in 0..params.max_tokens {
let forward_start = Instant::now();
let logits = self.engine.get_logits()?;
let next_token = self.sample_token(logits, ¶ms, &mut rep_penalty)?;
let decode_time = forward_start.elapsed().as_secs_f64() * 1000.0;
stats.forward_time_ms += decode_time;
if params.stop_tokens.contains(&next_token) {
break;
}
stats.tokens_generated += 1;
if !callback(next_token) {
break;
}
rep_penalty.add_token(next_token);
let _hidden = self.engine.decode_lazy(next_token, &mut self.weights)?;
self.position += 1;
}
stats.total_time_ms = start_time.elapsed().as_secs_f64() * 1000.0;
if stats.forward_time_ms > 0.0 {
stats.tokens_per_second =
stats.tokens_generated as f64 / (stats.forward_time_ms / 1000.0);
}
let layer_stats = self.weights.stats();
tracing::info!(
"Lazy loading stats: {} loads, {} evictions, {:.1}% hit rate",
layer_stats.total_loads,
layer_stats.total_evictions,
layer_stats.hit_rate * 100.0
);
Ok(stats)
}
fn sample_token(
&mut self,
mut logits: super::tensor::GpuTensor,
params: &SamplingParams,
rep_penalty: &mut RepetitionPenalty,
) -> Result<u32, InferenceError> {
if params.repetition_penalty != 1.0 && !rep_penalty.context.is_empty() {
let mut host_logits = vec![0u8; logits.numel() * 2];
logits.copy_to_host(&mut host_logits)?;
let mut logits_f32: Vec<f32> = host_logits
.chunks_exact(2)
.map(|c| half::f16::from_le_bytes([c[0], c[1]]).to_f32())
.collect();
rep_penalty.apply(&mut logits_f32);
let logits_f16: Vec<u8> = logits_f32
.iter()
.flat_map(|&f| half::f16::from_f32(f).to_le_bytes())
.collect();
logits.copy_from_host(&logits_f16)?;
}
if params.temperature == 0.0 || params.temperature < 0.01 {
self.sampler.sample_greedy(&logits)
} else {
self.rng_state = self
.rng_state
.wrapping_mul(6364136223846793005)
.wrapping_add(1);
self.sampler.sample(
&mut logits,
params.temperature,
params.top_k,
params.top_p,
self.rng_state,
)
}
}
}
impl std::fmt::Debug for LazyGenerator {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let stats = self.weights.stats();
f.debug_struct("LazyGenerator")
.field("vocab_size", &self.vocab_size)
.field("position", &self.position)
.field("layers_loaded", &stats.layers_loaded)
.field("total_layers", &self.weights.num_layers())
.field("vram_used_mb", &(stats.vram_used / (1024 * 1024)))
.field("hit_rate", &format!("{:.1}%", stats.hit_rate * 100.0))
.finish()
}
}