Whole-workspace rustfmt pass picked up while iterating on Mamba GPU backward work. Verified formatting-only via diff sampling; no logic changed. Co-Authored-By: Claude Sonnet 5 <[email protected]>
769 lines
24 KiB
Rust
769 lines
24 KiB
Rust
//! BEiT (BERT Pre-training for Images) Implementation
|
|
//!
|
|
//! Self-supervised learning framework that learns visual representations by predicting
|
|
//! discrete visual tokens of masked image patches. Based on "BEiT: BERT Pre-Training
|
|
//! of Image Transformers" (Bao et al., 2021).
|
|
//!
|
|
//! Key features:
|
|
//! - Visual tokenizer using discrete VAE (dVAE)
|
|
//! - Blockwise masking strategy (~40% masking ratio)
|
|
//! - Vision Transformer backbone
|
|
//! - Cross-entropy loss for token prediction
|
|
//! - Integration with existing ViT implementation
|
|
|
|
use crate::prelude::*;
|
|
use parking_lot::RwLock;
|
|
use std::sync::Arc;
|
|
|
|
/// Configuration for the visual tokenizer (discrete VAE)
|
|
#[derive(Debug, Clone)]
|
|
pub struct VisualTokenizerConfig {
|
|
/// Vocabulary size for discrete tokens
|
|
pub vocab_size: usize,
|
|
/// Encoder embedding dimension
|
|
pub encoder_dim: usize,
|
|
/// Decoder embedding dimension
|
|
pub decoder_dim: usize,
|
|
/// Number of encoder layers
|
|
pub num_encoder_layers: usize,
|
|
/// Number of decoder layers
|
|
pub num_decoder_layers: usize,
|
|
/// Output embedding dimension
|
|
pub embed_dim: usize,
|
|
/// Codebook size (usually same as vocab_size)
|
|
pub codebook_size: usize,
|
|
}
|
|
|
|
impl Default for VisualTokenizerConfig {
|
|
fn default() -> Self {
|
|
Self {
|
|
vocab_size: 8192,
|
|
encoder_dim: 256,
|
|
decoder_dim: 256,
|
|
num_encoder_layers: 2,
|
|
num_decoder_layers: 2,
|
|
embed_dim: 768,
|
|
codebook_size: 8192,
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Visual tokenizer using discrete VAE for converting image patches to tokens
|
|
#[derive(Debug)]
|
|
pub struct VisualTokenizer {
|
|
config: VisualTokenizerConfig,
|
|
encoder: Arc<RwLock<Tensor>>,
|
|
decoder: Arc<RwLock<Tensor>>,
|
|
codebook: Arc<RwLock<Tensor>>,
|
|
device: Device,
|
|
}
|
|
|
|
impl VisualTokenizer {
|
|
/// Create new visual tokenizer
|
|
pub fn new(config: VisualTokenizerConfig, device: &Device) -> Result<Self> {
|
|
// Initialize encoder and decoder weights
|
|
let encoder = Tensor::randn(
|
|
&[3 * 16 * 16, config.encoder_dim], // 3 channels * 16x16 patch
|
|
device,
|
|
)?;
|
|
|
|
let decoder = Tensor::randn(&[config.decoder_dim, 3 * 16 * 16], device)?;
|
|
|
|
// Initialize codebook for discrete tokens
|
|
let codebook = Tensor::randn(&[config.codebook_size, config.embed_dim], device)?;
|
|
|
|
Ok(Self {
|
|
config,
|
|
encoder: Arc::new(RwLock::new(encoder)),
|
|
decoder: Arc::new(RwLock::new(decoder)),
|
|
codebook: Arc::new(RwLock::new(codebook)),
|
|
device: device.clone(),
|
|
})
|
|
}
|
|
|
|
/// Get vocabulary size
|
|
pub fn vocab_size(&self) -> usize {
|
|
self.config.vocab_size
|
|
}
|
|
|
|
/// Get codebook size
|
|
pub fn codebook_size(&self) -> usize {
|
|
self.config.codebook_size
|
|
}
|
|
|
|
/// Encode image patches to discrete tokens
|
|
pub fn encode(&self, patches: &Tensor) -> Result<Tensor> {
|
|
let encoder = self.encoder.read();
|
|
let codebook = self.codebook.read();
|
|
|
|
let shape = patches.shape();
|
|
let batch_size = shape[0];
|
|
|
|
// Flatten patches for encoding
|
|
let shape_vec = patches.shape();
|
|
let patch_size: usize = shape_vec.iter().skip(1).product::<usize>();
|
|
let flattened = patches.reshape(&[batch_size, patch_size])?;
|
|
|
|
// Encode to latent space
|
|
let encoded = flattened.matmul(&*encoder)?;
|
|
|
|
// Quantize to nearest codebook entry (simplified)
|
|
// In practice, would use proper vector quantization
|
|
let mut token_ids = Vec::new();
|
|
for b in 0..batch_size {
|
|
// Simple argmax quantization
|
|
let token_id = b % self.config.vocab_size; // Simplified for testing
|
|
token_ids.push(token_id as i64);
|
|
}
|
|
|
|
let token_ids_f32: Vec<f32> = token_ids.iter().map(|&x| x as f32).collect();
|
|
Ok(Tensor::from_data(
|
|
token_ids_f32,
|
|
vec![batch_size],
|
|
&self.device,
|
|
)?)
|
|
}
|
|
|
|
/// Decode discrete tokens back to image patches
|
|
pub fn decode(&self, tokens: &Tensor) -> Result<Tensor> {
|
|
let decoder = self.decoder.read();
|
|
let codebook = self.codebook.read();
|
|
|
|
// Lookup codebook embeddings
|
|
let embeddings = self.lookup_codebook(tokens)?;
|
|
|
|
// Decode to patches
|
|
let decoded = embeddings.matmul(&*decoder)?;
|
|
|
|
// Reshape to patch format
|
|
let batch_size = tokens.shape()[0];
|
|
Ok(decoded.reshape(&[batch_size, 3, 16, 16])?)
|
|
}
|
|
|
|
/// Look up codebook embeddings for token IDs
|
|
pub fn lookup_codebook(&self, token_ids: &Tensor) -> Result<Tensor> {
|
|
let codebook = self.codebook.read();
|
|
let tokens_data = token_ids.to_vec()?;
|
|
let batch_size = token_ids.shape()[0];
|
|
|
|
let mut embeddings_data = vec![0.0f32; batch_size * self.config.embed_dim];
|
|
let codebook_data = codebook.to_vec()?;
|
|
|
|
for (b, &token_id) in tokens_data.iter().enumerate() {
|
|
let token_id = token_id as usize % self.config.codebook_size;
|
|
let src_start = token_id * self.config.embed_dim;
|
|
let dst_start = b * self.config.embed_dim;
|
|
|
|
for i in 0..self.config.embed_dim {
|
|
embeddings_data[dst_start + i] = codebook_data[src_start + i];
|
|
}
|
|
}
|
|
|
|
Ok(Tensor::from_data(
|
|
embeddings_data,
|
|
vec![batch_size, self.config.embed_dim],
|
|
&self.device,
|
|
)?)
|
|
}
|
|
}
|
|
|
|
/// Configuration for blockwise masking strategy
|
|
#[derive(Debug, Clone)]
|
|
pub struct BlockwiseMaskingConfig {
|
|
/// Fraction of patches to mask (default: 0.4)
|
|
pub mask_ratio: f32,
|
|
/// Size of each block for masking (default: 2)
|
|
pub block_size: usize,
|
|
/// Minimum number of blocks to mask (default: 3)
|
|
pub min_blocks: usize,
|
|
/// Total number of patches in the image
|
|
pub num_patches: usize,
|
|
}
|
|
|
|
impl Default for BlockwiseMaskingConfig {
|
|
fn default() -> Self {
|
|
Self {
|
|
mask_ratio: 0.4,
|
|
block_size: 2,
|
|
min_blocks: 3,
|
|
num_patches: 196, // 14x14 patches for 224x224 image with 16x16 patches
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Result of blockwise masking operation
|
|
#[derive(Debug)]
|
|
pub struct BlockwiseMaskResult {
|
|
/// Boolean mask for each patch (true = masked, false = visible)
|
|
pub mask: Vec<Vec<bool>>,
|
|
/// Number of blocks that were masked
|
|
pub num_masked_blocks: usize,
|
|
/// Total number of masked patches
|
|
pub num_masked_patches: usize,
|
|
}
|
|
|
|
/// Blockwise masking strategy implementation
|
|
#[derive(Debug)]
|
|
pub struct BlockwiseMasker {
|
|
config: BlockwiseMaskingConfig,
|
|
}
|
|
|
|
impl BlockwiseMasker {
|
|
/// Create new blockwise masker
|
|
pub fn new(config: BlockwiseMaskingConfig) -> Self {
|
|
Self { config }
|
|
}
|
|
|
|
/// Get mask ratio
|
|
pub fn mask_ratio(&self) -> f32 {
|
|
self.config.mask_ratio
|
|
}
|
|
|
|
/// Get block size
|
|
pub fn block_size(&self) -> usize {
|
|
self.config.block_size
|
|
}
|
|
|
|
/// Get minimum blocks
|
|
pub fn min_blocks(&self) -> usize {
|
|
self.config.min_blocks
|
|
}
|
|
|
|
/// Generate blockwise mask for a batch of images
|
|
pub fn generate_mask(
|
|
&self,
|
|
batch_size: usize,
|
|
num_patches: usize,
|
|
seed: Option<u64>,
|
|
) -> Result<BlockwiseMaskResult> {
|
|
let patches_per_side = (num_patches as f32).sqrt() as usize;
|
|
let blocks_per_side = patches_per_side / self.config.block_size;
|
|
let total_blocks = blocks_per_side * blocks_per_side;
|
|
|
|
let target_masked_patches = (num_patches as f32 * self.config.mask_ratio) as usize;
|
|
let patches_per_block = self.config.block_size * self.config.block_size;
|
|
let target_blocks = (target_masked_patches / patches_per_block).max(self.config.min_blocks);
|
|
let actual_blocks = target_blocks.min(total_blocks);
|
|
|
|
let mut batch_masks = Vec::new();
|
|
let mut rng_state = seed.unwrap_or(42);
|
|
|
|
for _ in 0..batch_size {
|
|
let mut mask = vec![false; num_patches];
|
|
|
|
// Generate random block indices to mask
|
|
let mut block_indices: Vec<usize> = (0..total_blocks).collect();
|
|
|
|
// Simple deterministic shuffle based on RNG state
|
|
for i in (1..block_indices.len()).rev() {
|
|
rng_state = rng_state.wrapping_mul(1103515245).wrapping_add(12345);
|
|
let j = (rng_state as usize) % (i + 1);
|
|
block_indices.swap(i, j);
|
|
}
|
|
|
|
// Select blocks to mask
|
|
for &block_idx in block_indices.iter().take(actual_blocks) {
|
|
let block_row = block_idx / blocks_per_side;
|
|
let block_col = block_idx % blocks_per_side;
|
|
|
|
// Mark all patches in this block as masked
|
|
for patch_row in 0..self.config.block_size {
|
|
for patch_col in 0..self.config.block_size {
|
|
let global_row = block_row * self.config.block_size + patch_row;
|
|
let global_col = block_col * self.config.block_size + patch_col;
|
|
|
|
if global_row < patches_per_side && global_col < patches_per_side {
|
|
let patch_idx = global_row * patches_per_side + global_col;
|
|
if patch_idx < num_patches {
|
|
mask[patch_idx] = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
batch_masks.push(mask);
|
|
}
|
|
|
|
let num_masked_patches = if batch_masks.is_empty() {
|
|
0
|
|
} else {
|
|
batch_masks[0].iter().filter(|&&x| x).count()
|
|
};
|
|
|
|
Ok(BlockwiseMaskResult {
|
|
mask: batch_masks,
|
|
num_masked_blocks: actual_blocks,
|
|
num_masked_patches,
|
|
})
|
|
}
|
|
}
|
|
|
|
/// Configuration for masked patch predictor
|
|
#[derive(Debug, Clone)]
|
|
pub struct MaskedPatchPredictorConfig {
|
|
/// Input encoder dimension
|
|
pub encoder_dim: usize,
|
|
/// Visual token vocabulary size
|
|
pub vocab_size: usize,
|
|
/// Number of prediction layers
|
|
pub num_layers: usize,
|
|
}
|
|
|
|
/// Masked patch predictor head for token prediction
|
|
#[derive(Debug)]
|
|
pub struct MaskedPatchPredictor {
|
|
config: MaskedPatchPredictorConfig,
|
|
layers: Vec<Arc<RwLock<Tensor>>>,
|
|
output_projection: Arc<RwLock<Tensor>>,
|
|
device: Device,
|
|
}
|
|
|
|
impl MaskedPatchPredictor {
|
|
/// Create new masked patch predictor
|
|
pub fn new(config: MaskedPatchPredictorConfig, device: &Device) -> Result<Self> {
|
|
let mut layers = Vec::new();
|
|
|
|
// Create prediction layers
|
|
for _ in 0..config.num_layers {
|
|
let layer = Tensor::randn(&[config.encoder_dim, config.encoder_dim], device)?;
|
|
layers.push(Arc::new(RwLock::new(layer)));
|
|
}
|
|
|
|
// Output projection to vocabulary
|
|
let output_projection = Tensor::randn(&[config.encoder_dim, config.vocab_size], device)?;
|
|
|
|
Ok(Self {
|
|
config,
|
|
layers,
|
|
output_projection: Arc::new(RwLock::new(output_projection)),
|
|
device: device.clone(),
|
|
})
|
|
}
|
|
|
|
/// Get vocabulary size
|
|
pub fn vocab_size(&self) -> usize {
|
|
self.config.vocab_size
|
|
}
|
|
|
|
/// Forward pass through predictor
|
|
pub fn forward(&self, features: &Tensor) -> Result<Tensor> {
|
|
let mut x = features.clone();
|
|
|
|
// Apply prediction layers with ReLU activation
|
|
for layer_weight in &self.layers {
|
|
let weight = layer_weight.read();
|
|
x = x.matmul(&*weight)?.relu()?;
|
|
}
|
|
|
|
// Final projection to vocabulary
|
|
let output_proj = self.output_projection.read();
|
|
Ok(x.matmul(&*output_proj)?)
|
|
}
|
|
|
|
/// Compute cross-entropy loss for token prediction
|
|
pub fn compute_cross_entropy_loss(logits: &Tensor, targets: &Tensor) -> Result<Tensor> {
|
|
// Simplified cross-entropy loss
|
|
// In practice would use proper softmax + cross-entropy
|
|
let shape = logits.shape();
|
|
let batch_size = shape[0];
|
|
let seq_len = shape[1];
|
|
|
|
// Compute softmax numerically stable
|
|
let max_vals = logits.max_keepdim(Some(2i32), true)?;
|
|
let logits_shifted = logits.sub(&max_vals)?;
|
|
let exp_logits = logits_shifted.exp()?;
|
|
let sum_exp = exp_logits.sum(Some(2))?.unsqueeze(2)?;
|
|
let log_sum_exp = sum_exp.log()?;
|
|
let log_probs = logits_shifted.sub(&log_sum_exp)?;
|
|
|
|
// Gather log probabilities for targets
|
|
let targets_data = targets.to_vec()?;
|
|
let log_probs_data = log_probs.to_vec()?;
|
|
let vocab_size = shape[2];
|
|
|
|
let mut loss_sum = 0.0f32;
|
|
let mut count = 0;
|
|
|
|
for b in 0..batch_size {
|
|
for s in 0..seq_len {
|
|
let target_id = targets_data[b * seq_len + s] as usize;
|
|
if target_id < vocab_size {
|
|
let log_prob_idx = b * seq_len * vocab_size + s * vocab_size + target_id;
|
|
loss_sum -= log_probs_data[log_prob_idx];
|
|
count += 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
let loss = if count > 0 {
|
|
loss_sum / count as f32
|
|
} else {
|
|
0.0
|
|
};
|
|
Ok(Tensor::from_data(
|
|
vec![loss],
|
|
vec![1usize],
|
|
logits.device(),
|
|
)?)
|
|
}
|
|
}
|
|
|
|
/// BEiT configuration combining all components
|
|
#[derive(Debug, Clone)]
|
|
pub struct BEiTConfig {
|
|
/// Masking ratio (default: 0.4)
|
|
pub mask_ratio: f32,
|
|
/// Number of patches to mask
|
|
pub num_mask_patches: usize,
|
|
/// Block size for blockwise masking
|
|
pub block_size: usize,
|
|
/// Minimum blocks to mask
|
|
pub min_blocks: usize,
|
|
/// Visual token codebook size
|
|
pub codebook_size: usize,
|
|
/// Number of decoder layers for token prediction
|
|
pub decoder_layers: usize,
|
|
/// ViT encoder dimension
|
|
pub encoder_dim: usize,
|
|
/// Patch size
|
|
pub patch_size: usize,
|
|
/// Image size
|
|
pub image_size: usize,
|
|
/// Number of ViT layers
|
|
pub num_layers: usize,
|
|
/// Number of attention heads
|
|
pub num_heads: usize,
|
|
}
|
|
|
|
impl Default for BEiTConfig {
|
|
fn default() -> Self {
|
|
Self {
|
|
mask_ratio: 0.4,
|
|
num_mask_patches: 75, // ~75 patches for 196 total with 0.4 ratio
|
|
block_size: 2,
|
|
min_blocks: 3,
|
|
codebook_size: 8192,
|
|
decoder_layers: 1,
|
|
encoder_dim: 768,
|
|
patch_size: 16,
|
|
image_size: 224,
|
|
num_layers: 12,
|
|
num_heads: 12,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl BEiTConfig {
|
|
/// Set mask ratio
|
|
pub fn with_mask_ratio(mut self, mask_ratio: f32) -> Self {
|
|
self.mask_ratio = mask_ratio;
|
|
self
|
|
}
|
|
|
|
/// Set codebook size
|
|
pub fn with_codebook_size(mut self, codebook_size: usize) -> Self {
|
|
self.codebook_size = codebook_size;
|
|
self
|
|
}
|
|
|
|
/// Set block size
|
|
pub fn with_block_size(mut self, block_size: usize) -> Self {
|
|
self.block_size = block_size;
|
|
self
|
|
}
|
|
}
|
|
|
|
/// Result from BEiT training step
|
|
#[derive(Debug)]
|
|
pub struct BEiTTrainingResult {
|
|
/// Cross-entropy loss for token prediction
|
|
pub loss: Tensor,
|
|
/// Prediction accuracy on masked patches
|
|
pub accuracy: f32,
|
|
/// Number of masked patches in this batch
|
|
pub num_masked_patches: usize,
|
|
}
|
|
|
|
/// Main BEiT trainer implementing the full pre-training pipeline
|
|
#[derive(Debug)]
|
|
pub struct BEiTTrainer {
|
|
config: BEiTConfig,
|
|
visual_tokenizer: VisualTokenizer,
|
|
masker: BlockwiseMasker,
|
|
vit_encoder: Arc<RwLock<Tensor>>, // Simplified ViT encoder
|
|
predictor: MaskedPatchPredictor,
|
|
device: Device,
|
|
is_training: bool,
|
|
}
|
|
|
|
impl BEiTTrainer {
|
|
/// Create new BEiT trainer
|
|
pub fn new(
|
|
config: BEiTConfig,
|
|
in_channels: usize,
|
|
image_size: usize,
|
|
device: &Device,
|
|
) -> Result<Self> {
|
|
// Create visual tokenizer
|
|
let tokenizer_config = VisualTokenizerConfig {
|
|
vocab_size: config.codebook_size,
|
|
codebook_size: config.codebook_size,
|
|
embed_dim: config.encoder_dim,
|
|
..Default::default()
|
|
};
|
|
let visual_tokenizer = VisualTokenizer::new(tokenizer_config, device)?;
|
|
|
|
// Create blockwise masker
|
|
let num_patches = (image_size / config.patch_size).pow(2);
|
|
let masking_config = BlockwiseMaskingConfig {
|
|
mask_ratio: config.mask_ratio,
|
|
block_size: config.block_size,
|
|
min_blocks: config.min_blocks,
|
|
num_patches,
|
|
};
|
|
let masker = BlockwiseMasker::new(masking_config);
|
|
|
|
// Create simplified ViT encoder (in practice would use full ViT)
|
|
let patch_dim = config.patch_size * config.patch_size * in_channels;
|
|
let vit_encoder = Tensor::randn(&[patch_dim, config.encoder_dim], device)?;
|
|
|
|
// Create masked patch predictor
|
|
let predictor_config = MaskedPatchPredictorConfig {
|
|
encoder_dim: config.encoder_dim,
|
|
vocab_size: config.codebook_size,
|
|
num_layers: config.decoder_layers,
|
|
};
|
|
let predictor = MaskedPatchPredictor::new(predictor_config, device)?;
|
|
|
|
Ok(Self {
|
|
config,
|
|
visual_tokenizer,
|
|
masker,
|
|
vit_encoder: Arc::new(RwLock::new(vit_encoder)),
|
|
predictor,
|
|
device: device.clone(),
|
|
is_training: true,
|
|
})
|
|
}
|
|
|
|
/// Get device
|
|
pub fn device(&self) -> &Device {
|
|
&self.device
|
|
}
|
|
|
|
/// Set to training mode
|
|
pub fn train(&mut self) {
|
|
self.is_training = true;
|
|
}
|
|
|
|
/// Set to evaluation mode
|
|
pub fn eval(&mut self) {
|
|
self.is_training = false;
|
|
}
|
|
|
|
/// Check if in training mode
|
|
pub fn is_training(&self) -> bool {
|
|
self.is_training
|
|
}
|
|
|
|
/// Perform one BEiT training step
|
|
pub fn train_step(&mut self, images: &Tensor, seed: Option<u64>) -> Result<BEiTTrainingResult> {
|
|
let batch_size = images.shape()[0];
|
|
let num_patches = (self.config.image_size / self.config.patch_size).pow(2);
|
|
|
|
// Step 1: Convert images to patches and get ground truth tokens
|
|
let patches = self.extract_patches(images)?;
|
|
let target_tokens = self.visual_tokenizer.encode(&patches)?;
|
|
|
|
// Step 2: Generate blockwise mask
|
|
let mask_result = self.masker.generate_mask(batch_size, num_patches, seed)?;
|
|
|
|
// Step 3: Apply mask and encode visible patches with ViT
|
|
let masked_patches = self.apply_mask(&patches, &mask_result.mask)?;
|
|
let encoded_features = self.encode_patches(&masked_patches)?;
|
|
|
|
// Step 4: Predict tokens for masked patches
|
|
let logits = self.predictor.forward(&encoded_features)?;
|
|
|
|
// Step 5: Compute loss and accuracy on masked patches
|
|
let masked_targets = self.extract_masked_targets(&target_tokens, &mask_result.mask)?;
|
|
let loss = MaskedPatchPredictor::compute_cross_entropy_loss(&logits, &masked_targets)?;
|
|
let accuracy = self.compute_accuracy(&logits, &masked_targets)?;
|
|
|
|
Ok(BEiTTrainingResult {
|
|
loss,
|
|
accuracy,
|
|
num_masked_patches: mask_result.num_masked_patches,
|
|
})
|
|
}
|
|
|
|
/// Extract features in evaluation mode (no masking)
|
|
pub fn extract_features(&self, images: &Tensor) -> Result<Tensor> {
|
|
let patches = self.extract_patches(images)?;
|
|
let batch_size = patches.shape()[0];
|
|
|
|
// Encode all patches without masking
|
|
let encoded = self.encode_patches(&patches)?;
|
|
|
|
// Global average pooling to get image-level features
|
|
Ok(encoded.mean(&[1i32], false)?) // Average over patch dimension
|
|
}
|
|
|
|
fn extract_patches(&self, images: &Tensor) -> Result<Tensor> {
|
|
// Simplified patch extraction
|
|
let shape = images.shape();
|
|
let batch_size = shape[0];
|
|
let channels = shape[1];
|
|
let height = shape[2];
|
|
let width = shape[3];
|
|
|
|
let patch_size = self.config.patch_size;
|
|
let patches_per_row = height / patch_size;
|
|
let patches_per_col = width / patch_size;
|
|
let num_patches = patches_per_row * patches_per_col;
|
|
let patch_volume = patch_size * patch_size * channels;
|
|
|
|
// Simulate patch extraction by reshaping
|
|
Ok(images.reshape(&[batch_size, num_patches, patch_volume])?)
|
|
}
|
|
|
|
fn apply_mask(&self, patches: &Tensor, _masks: &[Vec<bool>]) -> Result<Tensor> {
|
|
// Return only visible patches (simplified implementation)
|
|
// In practice would properly handle variable-length sequences
|
|
Ok(patches.clone())
|
|
}
|
|
|
|
fn encode_patches(&self, patches: &Tensor) -> Result<Tensor> {
|
|
let encoder = self.vit_encoder.read();
|
|
Ok(patches.matmul(&*encoder)?)
|
|
}
|
|
|
|
fn extract_masked_targets(&self, targets: &Tensor, masks: &[Vec<bool>]) -> Result<Tensor> {
|
|
// Simplified: create targets for masked positions
|
|
let batch_size = targets.shape()[0];
|
|
let num_masked = masks[0].iter().filter(|&&x| x).count();
|
|
|
|
let mut masked_targets = vec![0.0f32; batch_size * num_masked];
|
|
let target_data = targets.to_vec()?;
|
|
|
|
for b in 0..batch_size {
|
|
let mut masked_idx = 0;
|
|
for (_, &is_masked) in masks[b].iter().enumerate() {
|
|
if is_masked && masked_idx < num_masked {
|
|
// Use original target (simplified)
|
|
masked_targets[b * num_masked + masked_idx] = target_data[b];
|
|
masked_idx += 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
Ok(Tensor::from_data(
|
|
masked_targets,
|
|
vec![batch_size, num_masked],
|
|
&self.device,
|
|
)?)
|
|
}
|
|
|
|
fn compute_accuracy(&self, logits: &Tensor, targets: &Tensor) -> Result<f32> {
|
|
// Simplified accuracy computation
|
|
let predictions = logits.argmax(Some(2 as i32), false)?;
|
|
let pred_data = predictions.to_vec()?;
|
|
let target_data = targets.to_vec()?;
|
|
|
|
let mut correct = 0;
|
|
let total = pred_data.len();
|
|
|
|
for (pred, target) in pred_data.iter().zip(target_data.iter()) {
|
|
if pred == target {
|
|
correct += 1;
|
|
}
|
|
}
|
|
|
|
Ok(correct as f32 / total as f32)
|
|
}
|
|
}
|
|
|
|
/// Configuration for BEiT fine-tuning
|
|
#[derive(Debug, Clone)]
|
|
pub struct BEiTFineTuningConfig {
|
|
/// Number of classes for classification
|
|
pub num_classes: usize,
|
|
/// Input feature dimension from pre-trained BEiT
|
|
pub feature_dim: usize,
|
|
/// Dropout probability
|
|
pub dropout: f32,
|
|
/// Whether to use layer normalization
|
|
pub use_layer_norm: bool,
|
|
}
|
|
|
|
/// Fine-tuning adapter for downstream classification tasks
|
|
#[derive(Debug)]
|
|
pub struct BEiTFineTuningAdapter {
|
|
config: BEiTFineTuningConfig,
|
|
classifier: Arc<RwLock<Tensor>>,
|
|
bias: Arc<RwLock<Tensor>>,
|
|
device: Device,
|
|
}
|
|
|
|
impl BEiTFineTuningAdapter {
|
|
/// Create new fine-tuning adapter
|
|
pub fn new(config: BEiTFineTuningConfig, device: &Device) -> Result<Self> {
|
|
let classifier = Tensor::randn(&[config.feature_dim, config.num_classes], device)?;
|
|
|
|
let bias = Tensor::zeros(&[config.num_classes], device)?;
|
|
|
|
Ok(Self {
|
|
config,
|
|
classifier: Arc::new(RwLock::new(classifier)),
|
|
bias: Arc::new(RwLock::new(bias)),
|
|
device: device.clone(),
|
|
})
|
|
}
|
|
|
|
/// Get number of classes
|
|
pub fn num_classes(&self) -> usize {
|
|
self.config.num_classes
|
|
}
|
|
|
|
/// Forward pass for classification
|
|
pub fn forward(&self, features: &Tensor) -> Result<Tensor> {
|
|
let classifier = self.classifier.read();
|
|
let bias = self.bias.read();
|
|
|
|
Ok(features.matmul(&*classifier)?.add(&*bias)?)
|
|
}
|
|
|
|
/// Compute classification loss
|
|
pub fn compute_classification_loss(&self, logits: &Tensor, targets: &Tensor) -> Result<Tensor> {
|
|
// Simplified cross-entropy loss for classification
|
|
let batch_size = logits.shape()[0];
|
|
let num_classes = logits.shape()[1];
|
|
|
|
// Compute softmax
|
|
let max_vals = logits.max_keepdim(Some(1i32), true)?;
|
|
let logits_shifted = logits.sub(&max_vals)?;
|
|
let exp_logits = logits_shifted.exp()?;
|
|
let sum_exp = exp_logits.sum(Some(1))?.unsqueeze(1)?;
|
|
let log_sum_exp = sum_exp.log()?;
|
|
let log_probs = logits_shifted.sub(&log_sum_exp)?;
|
|
|
|
// Gather log probabilities for targets
|
|
let targets_data = targets.to_vec()?;
|
|
let log_probs_data = log_probs.to_vec()?;
|
|
|
|
let mut loss_sum = 0.0f32;
|
|
|
|
for b in 0..batch_size {
|
|
let target_id = targets_data[b] as usize;
|
|
if target_id < num_classes {
|
|
let log_prob_idx = b * num_classes + target_id;
|
|
loss_sum -= log_probs_data[log_prob_idx];
|
|
}
|
|
}
|
|
|
|
let loss = loss_sum / batch_size as f32;
|
|
Ok(Tensor::from_data(vec![loss], vec![1usize], &self.device)?)
|
|
}
|
|
}
|