Files
rustytorch/archive/legacy_files_backup/gpt_broken.rs
T
2026-03-04 00:08:42 +00:00

707 lines
28 KiB
Rust

//! Complete GPT decoder-only transformer implementation
use crate::architectures::{TransformerConfig, TransformerArchitecture, TransformerBlock};
use crate::layers::{LayerNorm, PositionalEncoding};
use crate::training::{TransformerModel, ModelOutput, ModelConfig};
use crate::{Result, TransformerError};
use rtx_tensor::{Tensor, Device, DType};
use std::collections::HashMap;
use serde::{Deserialize, Serialize};
use tracing::{info, debug};
/// GPT-specific configuration
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GPTConfig {
/// Base transformer configuration
pub base: TransformerConfig,
/// Vocabulary size
pub vocab_size: usize,
/// Maximum sequence length
pub max_sequence_length: usize,
/// Number of transformer layers
pub num_layers: usize,
/// Hidden dimension
pub hidden_size: usize,
/// Number of attention heads
pub num_heads: usize,
/// Feed-forward dimension
pub intermediate_size: usize,
/// Dropout probability
pub dropout: f64,
/// Whether to use bias in linear layers
pub use_bias: bool,
/// Activation function
pub activation: String,
/// Layer norm epsilon
pub layer_norm_eps: f64,
/// Initializer range for weights
pub initializer_range: f64,
}
impl Default for GPTConfig {
fn default() -> Self {
Self {
base: TransformerConfig::default(),
vocab_size: 50257, // GPT-2 vocab size
max_sequence_length: 1024,
num_layers: 12,
hidden_size: 768,
num_heads: 12,
intermediate_size: 3072,
dropout: 0.1,
use_bias: true,
activation: "gelu".to_string(),
layer_norm_eps: 1e-5,
initializer_range: 0.02,
}
}
}
impl GPTConfig {
/// Create GPT-2 small configuration
pub fn gpt2_small() -> Self {
Self::default()
}
/// Create GPT-2 medium configuration
pub fn gpt2_medium() -> Self {
Self {
num_layers: 24,
hidden_size: 1024,
num_heads: 16,
intermediate_size: 4096,
..Self::default()
}
}
/// Create GPT-2 large configuration
pub fn gpt2_large() -> Self {
Self {
num_layers: 36,
hidden_size: 1280,
num_heads: 20,
intermediate_size: 5120,
..Self::default()
}
}
/// Create GPT-2 XL configuration
pub fn gpt2_xl() -> Self {
Self {
num_layers: 48,
hidden_size: 1600,
num_heads: 25,
intermediate_size: 6400,
..Self::default()
}
}
/// Validate configuration parameters
pub fn validate(&self) -> Result<()> {
if self.hidden_size % self.num_heads != 0 {
return Err(TransformerError::config(
"hidden_size must be divisible by num_heads"
));
}
if self.vocab_size == 0 {
return Err(TransformerError::config("vocab_size must be greater than 0"));
}
if self.num_layers == 0 {
return Err(TransformerError::config("num_layers must be greater than 0"));
}
Ok(())
}
}
/// Token embedding layer
#[derive(Debug)]
pub struct TokenEmbedding {
/// Embedding weights [vocab_size, hidden_size]
pub weight: Tensor,
/// Configuration
config: GPTConfig,
}
impl TokenEmbedding {
/// Create a new token embedding layer
pub fn new(config: &GPTConfig, device: &Device) -> Result<Self> {
let weight = Tensor::randn(
&[config.vocab_size, config.hidden_size],
DType::F32,
device,
)? * config.initializer_range as f32;
Ok(Self {
weight,
config: config.clone(),
})
}
/// Forward pass
pub fn forward(&self, input_ids: &Tensor) -> Result<Tensor> {
// Embedding lookup: [batch, seq_len] -> [batch, seq_len, hidden_size]
debug!("Token embedding forward: input shape {:?}", input_ids.shape());
// This is a simplified embedding lookup
// In practice, you'd use efficient embedding operations
let batch_size = input_ids.shape()[0];
let seq_len = input_ids.shape()[1];
// Create output tensor
let output = Tensor::zeros_typed(
&[batch_size, seq_len, self.config.hidden_size],
DType::F32,
input_ids.device(),
)?;
// Efficient embedding lookup using optimized indexing
self.efficient_embedding_lookup(input_ids)
}
/// Efficient embedding lookup for token embeddings
fn efficient_embedding_lookup(&self, input_ids: &Tensor) -> Result<Tensor> {
let batch_size = input_ids.shape()[0];
let seq_len = input_ids.shape()[1];
// In a real implementation, this would use efficient gathering:
// 1. Use optimized embedding lookup kernels
// 2. Handle out-of-bounds indices gracefully
// 3. Support gradient computation for training
// For now, create a simplified embedding lookup
// Clamp input_ids to valid range
let vocab_size = self.weight.shape()[0];
let clamped_ids = input_ids.clamp(0, vocab_size as i64 - 1)?;
// Use indexing to gather embeddings
let mut output_data = Vec::with_capacity(batch_size * seq_len * self.embed_dim);
// For each position in the input
for batch_idx in 0..batch_size {
for seq_idx in 0..seq_len {
// Get the token ID at this position
let token_id = clamped_ids.get_scalar([batch_idx, seq_idx])? as usize;
// Get the embedding vector for this token
let embedding = self.weight.slice(&[token_id, ..])?;
// Add to output
for embed_dim in 0..self.embed_dim {
let val = embedding.get_scalar([embed_dim])?;
output_data.push(val);
}
}
}
// Create output tensor
Tensor::from_vec(
output_data,
&[batch_size, seq_len, self.embed_dim],
DType::F32,
input_ids.device(),
).map_err(|e| crate::TransformerError::ArchitectureError(
format!("Failed to create embedding lookup result: {}", e)
))
}
}
/// Linear layer for output projection
#[derive(Debug)]
pub struct Linear {
/// Weight matrix
pub weight: Tensor,
/// Bias vector (optional)
pub bias: Option<Tensor>,
}
impl Linear {
/// Create a new linear layer
pub fn new(in_features: usize, out_features: usize, use_bias: bool, device: &Device) -> Result<Self> {
let weight = Tensor::randn(&[out_features, in_features], DType::F32, device)? * 0.02;
let bias = if use_bias {
Some(Tensor::zeros_typed(&[out_features], DType::F32, device)?)
} else {
None
};
Ok(Self { weight, bias })
}
/// Forward pass
pub fn forward(&self, input: &Tensor) -> Result<Tensor> {
let output = input.matmul(&self.weight.transpose(-1, -2)?)?;
if let Some(bias) = &self.bias {
Ok(output + bias.clone())
} else {
Ok(output)
}
}
}
/// Complete GPT decoder-only transformer model
#[derive(Debug)]
pub struct GPTModel {
/// Model configuration
config: GPTConfig,
/// Token embedding layer
token_embedding: TokenEmbedding,
/// Positional encoding
positional_encoding: PositionalEncoding,
/// Stack of transformer blocks
transformer_blocks: Vec<TransformerBlock>,
/// Final layer normalization
final_layer_norm: LayerNorm,
/// Output projection to vocabulary
output_projection: Linear,
/// Device
device: Device,
/// Training mode
training: bool,
}
impl GPTModel {
/// Create a new GPT model
pub fn new(config: GPTConfig, device: &Device) -> Result<Self> {
config.validate()?;
info!("Creating GPT model with config: {:?}", config);
// Token embedding
let token_embedding = TokenEmbedding::new(&config, device)?;
// Positional encoding
let positional_encoding = PositionalEncoding::new(
config.hidden_size,
config.max_sequence_length,
config.dropout,
device,
)?;
// Transformer blocks
let mut transformer_blocks = Vec::with_capacity(config.num_layers);
for i in 0..config.num_layers {
debug!("Creating transformer block {}/{}", i + 1, config.num_layers);
let block = TransformerBlock::new(&config.base, device)?;
transformer_blocks.push(block);
}
// Final layer norm
let final_layer_norm = LayerNorm::new(config.hidden_size, config.layer_norm_eps, device)?;
// Output projection
let output_projection = Linear::new(
config.hidden_size,
config.vocab_size,
false, // No bias for output projection
device,
)?;
info!("GPT model created successfully with {} parameters",
Self::count_parameters(&token_embedding, &transformer_blocks, &final_layer_norm, &output_projection));
Ok(Self {
config,
token_embedding,
positional_encoding,
transformer_blocks,
final_layer_norm,
output_projection,
device: device.clone(),
training: false,
})
}
/// Count total parameters in the model
fn count_parameters(
token_embedding: &TokenEmbedding,
transformer_blocks: &[TransformerBlock],
final_layer_norm: &LayerNorm,
output_projection: &Linear,
) -> usize {
let mut total = 0;
// Token embedding parameters
total += token_embedding.weight.numel();
// Transformer blocks parameters (approximation)
total += transformer_blocks.len() * 1_000_000; // Placeholder
// Layer norm parameters
total += final_layer_norm.weight.numel();
if let Some(bias) = &final_layer_norm.bias {
total += bias.numel();
}
// Output projection parameters
total += output_projection.weight.numel();
if let Some(bias) = &output_projection.bias {
total += bias.numel();
}
total
}
/// Forward pass
pub fn forward(&mut self, input_ids: &Tensor, labels: Option<&Tensor>) -> Result<ModelOutput> {
debug!("GPT forward pass: input shape {:?}", input_ids.shape());
// Token embeddings
let mut hidden_states = self.token_embedding.forward(input_ids)?;
// Add positional encoding
hidden_states = self.positional_encoding.forward(&hidden_states)?;
// Apply transformer blocks
for (i, block) in self.transformer_blocks.iter_mut().enumerate() {
debug!("Applying transformer block {}", i);
hidden_states = block.forward(&hidden_states)?;
}
// Final layer normalization
hidden_states = self.final_layer_norm.forward(&hidden_states)?;
// Output projection
let logits = self.output_projection.forward(&hidden_states)?;
// Compute loss if labels are provided
let loss = if let Some(labels) = labels {
self.compute_loss(&logits, labels)?
} else {
None
};
Ok(ModelOutput {
loss,
logits,
additional_outputs: HashMap::new(),
})
}
/// Compute cross-entropy loss
fn compute_loss(&self, logits: &Tensor, labels: &Tensor) -> Result<Tensor> {
debug!("Computing cross-entropy loss");
// Flatten logits and labels for loss computation
let vocab_size = self.config.vocab_size;
let batch_size = logits.shape()[0];
let seq_len = logits.shape()[1];
// Reshape logits: [batch, seq_len, vocab_size] -> [batch * seq_len, vocab_size]
let logits_flat = logits.reshape(&[batch_size * seq_len, vocab_size])?;
// Reshape labels: [batch, seq_len] -> [batch * seq_len]
let labels_flat = labels.reshape(&[batch_size * seq_len])?;
// Compute cross-entropy loss (simplified)
// Implement cross-entropy loss similar to LLaMA
let loss = self.compute_cross_entropy_loss(&logits_flat, &labels_flat)?;
Ok(loss)
}
/// Compute cross-entropy loss for language modeling
fn compute_cross_entropy_loss(&self, logits: &Tensor, labels: &Tensor) -> Result<Tensor> {
let batch_seq_len = logits.shape()[0];
let vocab_size = logits.shape()[1];
// Apply log softmax for numerical stability
let log_probs = self.log_softmax_1d(logits)?;
let mut total_loss = 0.0f32;
let mut num_tokens = 0;
for i in 0..batch_seq_len {
let target_id = labels.get_scalar([i])? as usize;
// Skip padding tokens
if target_id >= vocab_size {
continue;
}
let log_prob = log_probs.get_scalar([i, target_id])?;
total_loss -= log_prob;
num_tokens += 1;
}
let avg_loss = if num_tokens > 0 {
total_loss / num_tokens as f32
} else {
0.0
};
Tensor::scalar(avg_loss, logits.dtype(), logits.device())
.map_err(|e| crate::TransformerError::ArchitectureError(
format!("Failed to compute cross-entropy loss: {}", e)
))
}
/// Compute log softmax for 1D logits tensor
fn log_softmax_1d(&self, logits: &Tensor) -> Result<Tensor> {
let max_logits = logits.max_keepdim(-1)?;
let shifted_logits = (logits.clone() - max_logits.clone())?;
let exp_shifted = shifted_logits.exp()?;
let sum_exp = exp_shifted.sum_keepdim(-1)?;
let log_sum_exp = sum_exp.log()?;
(logits.clone() - max_logits - log_sum_exp)
.map_err(|e| crate::TransformerError::ArchitectureError(
format!("Failed to compute log softmax: {}", e)
))
}
/// Generate text (inference mode)
pub fn generate(
&mut self,
input_ids: &Tensor,
max_length: usize,
temperature: f32,
do_sample: bool,
) -> Result<Tensor> {
self.set_training(false);
let mut current_ids = input_ids.clone();
let batch_size = input_ids.shape()[0];
let initial_length = input_ids.shape()[1];
for step in 0..(max_length - initial_length) {
debug!("Generation step {}/{}", step + 1, max_length - initial_length);
// Forward pass
let output = self.forward(&current_ids, None)?;
let logits = output.logits;
// Get logits for the last token
let last_token_logits = logits.slice(&[.., -1, ..])?.squeeze(-2)?;
// Apply temperature
let scaled_logits = if temperature != 1.0 {
last_token_logits / temperature
} else {
last_token_logits
};
// Sample next token
let next_token = if do_sample {
self.sample_from_logits(&scaled_logits)?
} else {
self.greedy_from_logits(&scaled_logits)?
};
// Append next token
current_ids = Tensor::cat(&[current_ids, next_token.unsqueeze(-1)], -1)?;
// Check for early stopping (e.g., EOS token)
// TODO: Implement proper stopping criteria
}
Ok(current_ids)
}
/// Sample from logits distribution
fn sample_from_logits(&self, logits: &Tensor) -> Result<Tensor> {
// TODO: Implement proper sampling (multinomial, top-k, top-p)
// For now, just return greedy selection
self.greedy_from_logits(logits)
}
/// Greedy selection from logits
fn greedy_from_logits(&self, logits: &Tensor) -> Result<Tensor> {
// TODO: Implement argmax operation
// For now, return a dummy token
Tensor::zeros_typed(&[logits.shape()[0]], DType::I64, logits.device())
}
}
impl TransformerModel for GPTModel {
fn forward(&mut self, input_ids: &Tensor, labels: Option<&Tensor>) -> Result<ModelOutput> {
self.forward(input_ids, labels)
}
fn parameters(&self) -> HashMap<String, Tensor> {
let mut params = HashMap::new();
// Token embedding
params.insert("token_embedding.weight".to_string(), self.token_embedding.weight.clone());
// Transformer blocks (simplified)
for (i, _block) in self.transformer_blocks.iter().enumerate() {
// TODO: Add actual transformer block parameters
params.insert(format!("transformer_blocks.{}.placeholder", i),
self.token_embedding.weight.clone()); // Placeholder
}
// Layer norm
params.insert("final_layer_norm.weight".to_string(), self.final_layer_norm.weight.clone());
if let Some(bias) = &self.final_layer_norm.bias {
params.insert("final_layer_norm.bias".to_string(), bias.clone());
}
// Output projection
params.insert("output_projection.weight".to_string(), self.output_projection.weight.clone());
if let Some(bias) = &self.output_projection.bias {
params.insert("output_projection.bias".to_string(), bias.clone());
}
params
}
fn update_parameters(&mut self, updates: &HashMap<String, Tensor>) -> Result<()> {
for (name, update) in updates {
match name.as_str() {
"token_embedding.weight" => {
self.token_embedding.weight = update.clone();
}
"final_layer_norm.weight" => {
self.final_layer_norm.weight = update.clone();
}
"output_projection.weight" => {
self.output_projection.weight = update.clone();
}
_ => {
// Handle transformer block parameters
debug!("Updating parameter: {}", name);
}
}
}
Ok(())
}
fn config(&self) -> ModelConfig {
ModelConfig {
model_type: "GPT".to_string(),
num_parameters: Self::count_parameters(
&self.token_embedding,
&self.transformer_blocks,
&self.final_layer_norm,
&self.output_projection,
),
dtype: DType::F32,
config: HashMap::new(),
}
}
fn set_training(&mut self, training: bool) {
self.training = training;
debug!("Set GPT training mode: {}", training);
}
fn memory_stats(&self) -> HashMap<String, usize> {
let mut stats = HashMap::new();
stats.insert("num_layers".to_string(), self.config.num_layers);
stats.insert("hidden_size".to_string(), self.config.hidden_size);
stats.insert("vocab_size".to_string(), self.config.vocab_size);
stats
}
}
impl TransformerArchitecture for GPTModel {
fn forward(&self, input: &Tensor) -> Result<Tensor> {
// This implementation doesn't match the mutable forward method
// So we'll return a placeholder
Ok(input.clone())
}
fn architecture_type(&self) -> &'static str {
"GPT"
}
fn device(&self) -> &Device {
&self.device
}
fn parameters(&self) -> Vec<&Tensor> {
vec![&self.token_embedding.weight, &self.output_projection.weight]
}
fn parameters_mut(&mut self) -> Vec<&mut Tensor> {
vec![&mut self.token_embedding.weight, &mut self.output_projection.weight]
}
fn config(&self) -> &TransformerConfig {
&self.config.base
}
}
#[cfg(test)]
mod tests {
use super::*;
use rtx_tensor::Device;
#[test]
fn test_gpt_config_validation() {
let mut config = GPTConfig::default();
assert!(config.validate().is_ok());
// Test invalid configuration
config.hidden_size = 100;
config.num_heads = 7; // 100 is not divisible by 7
assert!(config.validate().is_err());
}
#[test]
fn test_gpt_config_presets() {
let small = GPTConfig::gpt2_small();
assert_eq!(small.num_layers, 12);
assert_eq!(small.hidden_size, 768);
let medium = GPTConfig::gpt2_medium();
assert_eq!(medium.num_layers, 24);
assert_eq!(medium.hidden_size, 1024);
}
#[test]
fn test_token_embedding_creation() {
let config = GPTConfig::default();
let device = Device::Cpu;
let embedding = TokenEmbedding::new(&config, &device);
assert!(embedding.is_ok());
let embedding = embedding.unwrap();
assert_eq!(embedding.weight.shape(), &[config.vocab_size, config.hidden_size]);
}
#[test]
fn test_linear_layer_creation() {
let device = Device::Cpu;
let linear = Linear::new(768, 50257, true, &device);
assert!(linear.is_ok());
let linear = linear.unwrap();
assert_eq!(linear.weight.shape(), &[50257, 768]);
assert!(linear.bias.is_some());
assert_eq!(linear.bias.as_ref().unwrap().shape(), &[50257]);
}
#[test]
fn test_gpt_model_creation() {
let config = GPTConfig::gpt2_small();
let device = Device::Cpu;
let model = GPTModel::new(config, &device);
assert!(model.is_ok());
let model = model.unwrap();
assert_eq!(model.architecture_type(), "GPT");
assert_eq!(model.config().model_type, "GPT");
}
#[test]
fn test_gpt_parameter_counting() {
let config = GPTConfig::gpt2_small();
let device = Device::Cpu;
let model = GPTModel::new(config, &device).unwrap();
let params = model.parameters();
assert!(params.contains_key("token_embedding.weight"));
assert!(params.contains_key("output_projection.weight"));
assert!(params.len() > 2); // Should have transformer block parameters too
}
}