use crate::{EncodedTensor, LayerCodebook, LayerType, NeuralError, Result};
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DecoderConfig {
pub apply_residuals: bool,
pub batch_size: usize,
pub use_gpu: bool,
pub prefetch: bool,
}
impl Default for DecoderConfig {
fn default() -> Self {
Self {
apply_residuals: true,
batch_size: 1024,
use_gpu: false,
prefetch: true,
}
}
}
#[derive(Debug, Clone)]
pub struct DecodedTensor {
pub name: String,
pub shape: Vec<usize>,
pub data: Vec<f32>,
pub stats: DecodingStats,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct DecodingStats {
pub decode_time_us: u64,
pub lookup_time_us: u64,
pub residual_time_us: u64,
pub vectors_decoded: usize,
}
#[derive(Debug)]
pub struct NeuralDecoder {
config: DecoderConfig,
codebooks: LayerCodebook,
}
impl NeuralDecoder {
pub fn new(config: DecoderConfig, codebooks: LayerCodebook) -> Self {
Self { config, codebooks }
}
pub fn decode_tensor(&self, encoded: &EncodedTensor) -> Result<DecodedTensor> {
let start = std::time::Instant::now();
let codebook = self
.codebooks
.get(encoded.layer_type)
.ok_or_else(|| NeuralError::CodebookNotFound(format!("{:?}", encoded.layer_type)))?;
let lookup_start = std::time::Instant::now();
let mut data = codebook.decode_batch(&encoded.indices);
let lookup_time = lookup_start.elapsed();
let residual_start = std::time::Instant::now();
if self.config.apply_residuals {
if let Some(residuals) = encoded.residuals.as_ref() {
self.apply_residuals(&mut data, residuals, encoded.residual_scale);
}
}
let residual_time = residual_start.elapsed();
let total_time = start.elapsed();
let stats = DecodingStats {
decode_time_us: total_time.as_micros() as u64,
lookup_time_us: lookup_time.as_micros() as u64,
residual_time_us: residual_time.as_micros() as u64,
vectors_decoded: encoded.indices.len(),
};
Ok(DecodedTensor {
name: encoded.name.clone(),
shape: encoded.shape.clone(),
data,
stats,
})
}
fn apply_residuals(&self, data: &mut [f32], residuals: &[i8], scale: f32) {
for (d, &r) in data.iter_mut().zip(residuals.iter()) {
*d += r as f32 * scale;
}
}
pub fn decode_tensors(&self, encoded: &[EncodedTensor]) -> Result<Vec<DecodedTensor>> {
encoded.iter().map(|e| self.decode_tensor(e)).collect()
}
pub fn decode_streaming<'a>(
&'a self,
encoded: &'a EncodedTensor,
chunk_size: usize,
) -> impl Iterator<Item = Result<Vec<f32>>> + 'a {
let codebook = self.codebooks.get(encoded.layer_type);
encoded
.indices
.chunks(chunk_size)
.enumerate()
.map(move |(i, chunk)| {
let cb = codebook.ok_or_else(|| {
NeuralError::CodebookNotFound(format!("{:?}", encoded.layer_type))
})?;
let mut data: Vec<f32> = chunk
.iter()
.flat_map(|&idx| {
cb.get_centroid(idx as usize)
.map(|c| c.to_vec())
.unwrap_or_else(|| vec![0.0; cb.config.centroid_dim])
})
.collect();
if self.config.apply_residuals {
if let Some(residuals) = encoded.residuals.as_ref() {
let dim = cb.config.centroid_dim;
let start = i * chunk_size * dim;
let end = (start + data.len()).min(residuals.len());
if start < residuals.len() {
self.apply_residuals(
&mut data[..end - start],
&residuals[start..end],
encoded.residual_scale,
);
}
}
}
Ok(data)
})
}
pub fn config(&self) -> &DecoderConfig {
&self.config
}
pub fn estimate_throughput(&self, stats: &DecodingStats) -> f32 {
if stats.decode_time_us == 0 {
return 0.0;
}
let bytes_decoded = stats.vectors_decoded * 64 * 4; let seconds = stats.decode_time_us as f64 / 1_000_000.0;
(bytes_decoded as f64 / seconds / 1e9) as f32
}
}
#[derive(Debug)]
pub struct GpuDecoder {
codebook_buffers: Vec<GpuBuffer>,
output_buffer: GpuBuffer,
}
#[derive(Debug, Default)]
struct GpuBuffer {
size: usize,
device_ptr: u64,
}
impl GpuDecoder {
pub fn new(_codebooks: &LayerCodebook) -> Result<Self> {
Ok(Self {
codebook_buffers: Vec::new(),
output_buffer: GpuBuffer::default(),
})
}
pub fn decode_gpu(&self, _indices: &[u16], _layer_type: LayerType) -> Result<Vec<f32>> {
Err(NeuralError::DecodingError("GPU not available".into()))
}
pub fn gpu_memory_usage(&self) -> usize {
let codebook_mem: usize = self.codebook_buffers.iter().map(|b| b.size).sum();
codebook_mem + self.output_buffer.size
}
pub fn is_available(&self) -> bool {
self.output_buffer.device_ptr != 0
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{EncoderConfig, NeuralEncoder};
#[test]
fn test_decode_tensor() {
let codebooks = LayerCodebook::with_defaults("test");
let encoder = NeuralEncoder::new(EncoderConfig::default(), codebooks.clone());
let original: Vec<f32> = (0..640).map(|i| (i as f32 / 640.0) - 0.5).collect();
let shape = vec![10, 64];
let encoded = encoder
.encode_tensor("test.weight", &original, &shape, LayerType::AttentionQK)
.unwrap();
let decoder = NeuralDecoder::new(DecoderConfig::default(), codebooks);
let decoded = decoder.decode_tensor(&encoded).unwrap();
assert_eq!(decoded.name, "test.weight");
assert_eq!(decoded.shape, vec![10, 64]);
assert_eq!(decoded.data.len(), 640);
}
#[test]
fn test_decode_streaming() {
let codebooks = LayerCodebook::with_defaults("test");
let encoder = NeuralEncoder::new(EncoderConfig::default(), codebooks.clone());
let original: Vec<f32> = (0..640).map(|i| (i as f32 / 640.0) - 0.5).collect();
let encoded = encoder
.encode_tensor("test", &original, &[10, 64], LayerType::AttentionQK)
.unwrap();
let decoder = NeuralDecoder::new(DecoderConfig::default(), codebooks);
let chunks: Vec<_> = decoder
.decode_streaming(&encoded, 3)
.collect::<Result<Vec<_>>>()
.unwrap();
assert!(!chunks.is_empty());
}
#[test]
fn test_residual_application() {
let decoder = NeuralDecoder::new(
DecoderConfig::default(),
LayerCodebook::with_defaults("test"),
);
let mut data = vec![1.0, 2.0, 3.0, 4.0];
let residuals = vec![10i8, -10, 20, -20];
let scale = 0.01;
decoder.apply_residuals(&mut data, &residuals, scale);
assert!((data[0] - 1.1).abs() < 0.001);
assert!((data[1] - 1.9).abs() < 0.001);
assert!((data[2] - 3.2).abs() < 0.001);
assert!((data[3] - 3.8).abs() < 0.001);
}
#[test]
fn test_gpu_decoder_construction() {
let codebooks = LayerCodebook::with_defaults("test");
let decoder = GpuDecoder::new(&codebooks).unwrap();
let result = decoder.decode_gpu(&[0, 1, 2], LayerType::AttentionQK);
assert!(result.is_err());
}
}