use crate::Result;
#[derive(Debug, Clone)]
pub struct KnowledgeDistillationEngine {
temperature: f32,
alpha: f32, beta: f32, learning_rate: f32,
batch_size: usize,
}
impl KnowledgeDistillationEngine {
pub fn new(temperature: f32, alpha: f32, beta: f32, learning_rate: f32) -> Self {
Self {
temperature,
alpha,
beta,
learning_rate,
batch_size: 32,
}
}
pub fn extract_teacher_knowledge(&mut self, input_data: &[f32]) -> Result<TeacherKnowledge> {
if input_data.is_empty() {
return Err("Cannot extract knowledge from empty input data".into());
}
let mut teacher_outputs = Vec::new();
let mut hidden_states = Vec::new();
for chunk in input_data.chunks(self.batch_size) {
let batch_output = self.process_teacher_batch(chunk)?;
teacher_outputs.extend(batch_output.logits);
hidden_states.push(batch_output.hidden_states);
}
Ok(TeacherKnowledge {
output_distributions: teacher_outputs,
hidden_representations: hidden_states,
attention_weights: Vec::new(), })
}
pub fn apply_distillation_loss(
&self,
student_outputs: &[f32],
teacher_knowledge: &TeacherKnowledge,
hard_targets: &[f32],
) -> Result<f32> {
if student_outputs.len() != teacher_knowledge.output_distributions.len() {
return Err("Student and teacher output dimensions must match".into());
}
let soft_loss = self.compute_kl_divergence_loss(
student_outputs,
&teacher_knowledge.output_distributions,
);
let hard_loss = self.compute_cross_entropy_loss(student_outputs, hard_targets);
let total_loss = self.alpha * soft_loss + self.beta * hard_loss;
Ok(total_loss)
}
fn process_teacher_batch(&self, input_batch: &[f32]) -> Result<TeacherModelOutput> {
let batch_size = input_batch.len();
let mut processed = input_batch.to_vec();
for _layer in 0..12 { processed = self.apply_attention_layer(&processed)?;
processed = self.apply_feed_forward(&processed)?;
processed = self.apply_layer_norm(&processed);
}
let vocab_size = 32000;
let logits = self.project_to_vocabulary(&processed, vocab_size);
Ok(TeacherModelOutput {
logits,
hidden_states: processed,
})
}
fn apply_attention_layer(&self, input: &[f32]) -> Result<Vec<f32>> {
let seq_len = input.len();
let mut output = vec![0.0; seq_len];
let num_heads = 8;
let head_dim = seq_len / num_heads;
for head in 0..num_heads {
let start_idx = head * head_dim;
let end_idx = (head + 1) * head_dim;
for i in start_idx..end_idx.min(seq_len) {
let mut attention_sum = 0.0;
for j in start_idx..end_idx.min(seq_len) {
let attention_weight = (input[i] * input[j] / (seq_len as f32).sqrt()).exp();
attention_sum += attention_weight * input[j];
}
output[i] = attention_sum / (end_idx - start_idx) as f32;
}
}
Ok(output)
}
fn apply_feed_forward(&self, input: &[f32]) -> Result<Vec<f32>> {
let hidden_size = input.len();
let intermediate_size = hidden_size * 4;
let mut intermediate = vec![0.0; intermediate_size];
for i in 0..intermediate_size {
let weighted_sum: f32 = input.iter()
.enumerate()
.map(|(j, &val)| val * ((i + j) as f32 / (hidden_size + i) as f32))
.sum();
intermediate[i] = weighted_sum.max(0.0); }
let mut output = vec![0.0; hidden_size];
for i in 0..hidden_size {
output[i] = intermediate.iter()
.enumerate()
.map(|(j, &val)| val * ((i + j) as f32 / (intermediate_size + i) as f32))
.sum();
}
Ok(output)
}
fn apply_layer_norm(&self, input: &[f32]) -> Vec<f32> {
let mean = input.iter().sum::<f32>() / input.len() as f32;
let variance = input.iter()
.map(|&x| (x - mean).powi(2))
.sum::<f32>() / input.len() as f32;
let std_dev = (variance + 1e-5).sqrt();
input.iter()
.map(|&x| (x - mean) / std_dev)
.collect()
}
fn project_to_vocabulary(&self, hidden_states: &[f32], vocab_size: usize) -> Vec<f32> {
let mut logits = vec![0.0; vocab_size.min(hidden_states.len())];
for i in 0..logits.len() {
logits[i] = hidden_states[i % hidden_states.len()] * (i as f32 + 1.0).ln();
}
logits
}
fn compute_kl_divergence_loss(&self, student_logits: &[f32], teacher_logits: &[f32]) -> f32 {
let student_probs = self.softmax_with_temperature(student_logits, self.temperature);
let teacher_probs = self.softmax_with_temperature(teacher_logits, self.temperature);
let mut kl_loss = 0.0;
for (&p_t, &p_s) in teacher_probs.iter().zip(student_probs.iter()) {
if p_t > 1e-8 && p_s > 1e-8 {
kl_loss += p_t * (p_t / p_s).ln();
}
}
kl_loss * self.temperature * self.temperature }
fn compute_cross_entropy_loss(&self, predictions: &[f32], targets: &[f32]) -> f32 {
let pred_probs = self.softmax(predictions);
let mut ce_loss = 0.0;
for (&pred, &target) in pred_probs.iter().zip(targets.iter()) {
if pred > 1e-8 {
ce_loss -= target * pred.ln();
}
}
ce_loss / predictions.len() as f32
}
fn softmax_with_temperature(&self, logits: &[f32], temperature: f32) -> Vec<f32> {
let scaled_logits: Vec<f32> = logits.iter().map(|&x| x / temperature).collect();
self.softmax(&scaled_logits)
}
fn softmax(&self, logits: &[f32]) -> Vec<f32> {
let max_logit = logits.iter().fold(f32::NEG_INFINITY, |max, &x| max.max(x));
let exp_logits: Vec<f32> = logits.iter().map(|&x| (x - max_logit).exp()).collect();
let sum_exp: f32 = exp_logits.iter().sum();
exp_logits.iter().map(|&x| x / sum_exp).collect()
}
}
#[derive(Debug, Clone)]
pub struct TeacherKnowledge {
pub output_distributions: Vec<f32>,
pub hidden_representations: Vec<Vec<f32>>,
pub attention_weights: Vec<Vec<f32>>,
}
#[derive(Debug, Clone)]
pub struct TeacherModelOutput {
pub logits: Vec<f32>,
pub hidden_states: Vec<f32>,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_knowledge_extraction() {
let mut engine = KnowledgeDistillationEngine::new(3.0, 0.7, 0.3, 0.001);
let input_data = vec![1.0, 2.0, 3.0, 4.0, 5.0];
let knowledge = engine.extract_teacher_knowledge(&input_data).unwrap();
assert!(!knowledge.output_distributions.is_empty());
assert!(!knowledge.hidden_representations.is_empty());
}
#[test]
fn test_distillation_loss() {
let engine = KnowledgeDistillationEngine::new(3.0, 0.7, 0.3, 0.001);
let student_outputs = vec![0.1, 0.2, 0.3, 0.4];
let teacher_outputs = vec![0.15, 0.25, 0.35, 0.25];
let hard_targets = vec![0.0, 0.0, 1.0, 0.0];
let teacher_knowledge = TeacherKnowledge {
output_distributions: teacher_outputs,
hidden_representations: vec![vec![0.1, 0.2, 0.3, 0.4]],
attention_weights: vec![],
};
let loss = engine.apply_distillation_loss(&student_outputs, &teacher_knowledge, &hard_targets).unwrap();
assert!(loss > 0.0);
assert!(loss < 10.0); }
#[test]
fn test_layer_operations() {
let engine = KnowledgeDistillationEngine::new(3.0, 0.7, 0.3, 0.001);
let input = vec![1.0, -0.5, 2.0, 0.0];
let attention_output = engine.apply_attention_layer(&input).unwrap();
assert_eq!(attention_output.len(), input.len());
let ff_output = engine.apply_feed_forward(&input).unwrap();
assert_eq!(ff_output.len(), input.len());
let norm_output = engine.apply_layer_norm(&input);
assert_eq!(norm_output.len(), input.len());
let mean: f32 = norm_output.iter().sum::<f32>() / norm_output.len() as f32;
assert!((mean.abs()) < 1e-5); }
#[test]
fn test_softmax_operations() {
let engine = KnowledgeDistillationEngine::new(2.0, 0.5, 0.5, 0.01);
let logits = vec![1.0, 2.0, 3.0, 0.5];
let probs = engine.softmax(&logits);
let sum: f32 = probs.iter().sum();
assert!((sum - 1.0).abs() < 1e-5);
let temp_probs = engine.softmax_with_temperature(&logits, 2.0);
let temp_sum: f32 = temp_probs.iter().sum();
assert!((temp_sum - 1.0).abs() < 1e-5);
}
}