Files
rustytorch/crates/training/rtx-transformers/src/ssl/simmim_simplified.rs
T
osobhandClaude Sonnet 5 4aaa36a57a style: cargo fmt --workspace (whitespace/wrapping only, no semantic change)
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]>
2026-08-10 07:09:36 -07:00

689 lines
20 KiB
Rust

//! Simplified SimMIM (Simple Masked Image Modeling) Implementation
//!
//! This is a simplified version that works with the current tensor library state.
//! It focuses on the algorithmic structure and can be upgraded when tensor operations are stable.
use crate::prelude::*;
use parking_lot::RwLock;
use std::sync::Arc;
/// Mock tensor for simplified implementation
#[derive(Debug, Clone)]
pub struct SimpleTensor {
data: Vec<f32>,
shape: Vec<usize>,
device: Device,
}
impl SimpleTensor {
pub fn zeros(shape: Vec<usize>, device: &Device) -> Result<Self> {
let size = shape.iter().product();
Ok(Self {
data: vec![0.0; size],
shape,
device: device.clone(),
})
}
pub fn randn(shape: Vec<usize>, device: &Device) -> Result<Self> {
let size = shape.iter().product();
let mut data = Vec::with_capacity(size);
let mut seed = 42u32;
for _ in 0..size {
seed = seed.wrapping_mul(1103515245).wrapping_add(12345);
let val = (seed as f32 / u32::MAX as f32) * 2.0 - 1.0;
data.push(val);
}
Ok(Self {
data,
shape,
device: device.clone(),
})
}
pub fn from_data(data: Vec<f32>, shape: Vec<usize>, device: &Device) -> Result<Self> {
Ok(Self {
data,
shape,
device: device.clone(),
})
}
pub fn shape(&self) -> &[usize] {
&self.shape
}
pub fn device(&self) -> &Device {
&self.device
}
pub fn to_vec<T>(&self) -> Result<Vec<T>>
where
T: Clone + From<f32>,
{
Ok(self.data.iter().map(|&x| T::from(x)).collect())
}
pub fn matmul(&self, _other: &Self) -> Result<Self> {
// Simplified matrix multiplication - just return a tensor of appropriate shape
let result_shape = vec![self.shape[0], _other.shape[1]];
let result_data = vec![1.0; result_shape.iter().product()];
Ok(Self {
data: result_data,
shape: result_shape,
device: self.device.clone(),
})
}
pub fn add(&self, other: &Self) -> Result<Self> {
let mut result_data = self.data.clone();
for (i, &val) in other.data.iter().enumerate() {
if i < result_data.len() {
result_data[i] += val;
}
}
Ok(Self {
data: result_data,
shape: self.shape.clone(),
device: self.device.clone(),
})
}
pub fn sub(&self, other: &Self) -> Result<Self> {
let mut result_data = self.data.clone();
for (i, &val) in other.data.iter().enumerate() {
if i < result_data.len() {
result_data[i] -= val;
}
}
Ok(Self {
data: result_data,
shape: self.shape.clone(),
device: self.device.clone(),
})
}
pub fn abs(&self) -> Result<Self> {
let result_data = self.data.iter().map(|x| x.abs()).collect();
Ok(Self {
data: result_data,
shape: self.shape.clone(),
device: self.device.clone(),
})
}
pub fn pow_scalar(&self, exp: f32) -> Result<Self> {
let result_data = self.data.iter().map(|x| x.powf(exp)).collect();
Ok(Self {
data: result_data,
shape: self.shape.clone(),
device: self.device.clone(),
})
}
pub fn mean(&self, _dims: &[usize]) -> Result<Self> {
let mean_val = if self.data.is_empty() {
0.0
} else {
self.data.iter().sum::<f32>() / self.data.len() as f32
};
Ok(Self {
data: vec![mean_val],
shape: vec![],
device: self.device.clone(),
})
}
pub fn reshape(&self, new_shape: &[usize]) -> Result<Self> {
Ok(Self {
data: self.data.clone(),
shape: new_shape.to_vec(),
device: self.device.clone(),
})
}
pub fn full(shape: &[usize], value: f32, device: &Device) -> Result<Self> {
let size = shape.iter().product();
Ok(Self {
data: vec![value; size],
shape: shape.to_vec(),
device: device.clone(),
})
}
}
/// Configuration for different encoder types
#[derive(Debug, Clone, PartialEq)]
pub enum EncoderType {
/// Vision Transformer encoder
ViT,
/// Swin Transformer encoder
Swin,
}
/// Type of prediction head
#[derive(Debug, Clone, PartialEq)]
pub enum PredictionHeadType {
/// Simple linear layer
Linear,
}
/// Loss function type
#[derive(Debug, Clone, PartialEq)]
pub enum LossType {
/// L1 (MAE) loss
L1,
/// L2 (MSE) loss
L2,
}
/// SimMIM configuration parameters
#[derive(Debug, Clone)]
pub struct SimMIMConfig {
/// Fraction of patches to mask
pub mask_ratio: f32,
/// Mask patch size (for hierarchical masking in Swin)
pub mask_patch_size: usize,
/// Type of prediction head
pub prediction_head: PredictionHeadType,
/// Loss function type
pub loss_type: LossType,
/// Whether to normalize pixel targets
pub norm_pix_loss: bool,
/// Encoder embedding dimension
pub encoder_dim: usize,
/// Image patch size
pub patch_size: usize,
/// Type of encoder (ViT or Swin)
pub encoder_type: EncoderType,
/// Input image size
pub image_size: usize,
/// Number of input channels
pub in_channels: usize,
}
impl Default for SimMIMConfig {
fn default() -> Self {
Self {
mask_ratio: 0.6,
mask_patch_size: 32,
prediction_head: PredictionHeadType::Linear,
loss_type: LossType::L1,
norm_pix_loss: false,
encoder_dim: 768,
patch_size: 16,
encoder_type: EncoderType::ViT,
image_size: 224,
in_channels: 3,
}
}
}
impl SimMIMConfig {
/// Create configuration optimized for ViT
pub fn for_vit() -> Self {
Self {
encoder_type: EncoderType::ViT,
mask_ratio: 0.6,
mask_patch_size: 16,
..Default::default()
}
}
/// Create configuration optimized for Swin Transformer
pub fn for_swin() -> Self {
Self {
encoder_type: EncoderType::Swin,
mask_ratio: 0.32,
mask_patch_size: 32,
..Default::default()
}
}
/// Set mask ratio
pub fn with_mask_ratio(mut self, mask_ratio: f32) -> Self {
self.mask_ratio = mask_ratio.clamp(0.0, 1.0);
self
}
/// Set encoder type
pub fn with_encoder_type(mut self, encoder_type: EncoderType) -> Self {
self.encoder_type = encoder_type;
self
}
/// Set whether to normalize pixel loss
pub fn with_norm_pix_loss(mut self, norm_pix_loss: bool) -> Self {
self.norm_pix_loss = norm_pix_loss;
self
}
}
/// Result from random patch masking
#[derive(Debug)]
pub struct PatchMaskResult {
/// Boolean mask for each patch (true = masked, false = visible)
pub mask_indices: Vec<Vec<bool>>,
/// Indices of visible patches for each batch item
pub visible_indices: Vec<Vec<usize>>,
/// Total number of masked patches per batch item
pub num_masked_patches: usize,
}
/// Random patch masker for SimMIM
#[derive(Debug)]
pub struct RandomPatchMasker {
mask_ratio: f32,
}
impl RandomPatchMasker {
/// Create new random patch masker
pub fn new(mask_ratio: f32) -> Self {
Self {
mask_ratio: mask_ratio.clamp(0.0, 1.0),
}
}
/// Get mask ratio
pub fn mask_ratio(&self) -> f32 {
self.mask_ratio
}
/// Generate random patch mask for a batch
pub fn mask_patches(
&self,
patches: &SimpleTensor,
seed: Option<u64>,
) -> Result<PatchMaskResult> {
let shape = patches.shape();
if shape.len() != 3 {
return Err(TransformerError::InvalidInput(
"Patches tensor must be 3D [batch, patches, embed_dim]".into(),
));
}
let batch_size = shape[0];
let num_patches = shape[1];
if num_patches == 0 {
return Err(TransformerError::InvalidInput(
"Number of patches cannot be zero".into(),
));
}
let num_masked = (num_patches as f32 * self.mask_ratio).round() as usize;
let num_visible = num_patches - num_masked;
let mut mask_indices = Vec::new();
let mut visible_indices = Vec::new();
let mut rng_state = seed.unwrap_or(42);
for _ in 0..batch_size {
// Create all patch indices
let mut indices: Vec<usize> = (0..num_patches).collect();
// Shuffle indices deterministically
for i in (1..indices.len()).rev() {
rng_state = rng_state.wrapping_mul(1103515245).wrapping_add(12345);
let j = (rng_state as usize) % (i + 1);
indices.swap(i, j);
}
// Select visible patches
let visible = indices[..num_visible].to_vec();
visible_indices.push(visible.clone());
// Create boolean mask
let mut mask = vec![true; num_patches]; // Start with all masked
for &idx in &visible {
mask[idx] = false; // Mark visible patches
}
mask_indices.push(mask);
}
Ok(PatchMaskResult {
mask_indices,
visible_indices,
num_masked_patches: num_masked,
})
}
}
/// Learnable mask token embedding
#[derive(Debug)]
pub struct MaskTokenEmbedding {
token: Arc<RwLock<SimpleTensor>>,
embed_dim: usize,
device: Device,
}
impl MaskTokenEmbedding {
/// Create new mask token embedding
pub fn new(embed_dim: usize, device: &Device) -> Result<Self> {
let token = SimpleTensor::randn(vec![1, embed_dim], device)?;
Ok(Self {
token: Arc::new(RwLock::new(token)),
embed_dim,
device: device.clone(),
})
}
/// Get embedding dimension
pub fn embed_dim(&self) -> usize {
self.embed_dim
}
/// Get the mask token
pub fn get_token(&self) -> Result<SimpleTensor> {
let token = self.token.read();
Ok(token.clone())
}
/// Broadcast mask token to specified shape
pub fn broadcast(&self, batch_size: usize, num_masked: usize) -> Result<SimpleTensor> {
let token = self.token.read();
let token_data = token.to_vec::<f32>()?;
let mut broadcasted_data = vec![0.0f32; batch_size * num_masked * self.embed_dim];
for b in 0..batch_size {
for m in 0..num_masked {
let base_idx = b * num_masked * self.embed_dim + m * self.embed_dim;
for d in 0..self.embed_dim {
broadcasted_data[base_idx + d] = token_data[d % token_data.len()];
}
}
}
SimpleTensor::from_data(
broadcasted_data,
vec![batch_size, num_masked, self.embed_dim],
&self.device,
)
}
}
/// Linear prediction head for pixel reconstruction
#[derive(Debug)]
pub struct LinearPredictionHead {
linear: Arc<RwLock<SimpleTensor>>,
bias: Arc<RwLock<SimpleTensor>>,
input_dim: usize,
output_dim: usize,
device: Device,
}
impl LinearPredictionHead {
/// Create new linear prediction head
pub fn new(input_dim: usize, output_dim: usize, device: &Device) -> Result<Self> {
let linear = SimpleTensor::randn(vec![input_dim, output_dim], device)?;
let bias = SimpleTensor::zeros(vec![output_dim], device)?;
Ok(Self {
linear: Arc::new(RwLock::new(linear)),
bias: Arc::new(RwLock::new(bias)),
input_dim,
output_dim,
device: device.clone(),
})
}
/// Get input dimension
pub fn input_dim(&self) -> usize {
self.input_dim
}
/// Get output dimension
pub fn output_dim(&self) -> usize {
self.output_dim
}
/// Forward pass through prediction head
pub fn forward(&self, input: &SimpleTensor) -> Result<SimpleTensor> {
let linear = self.linear.read();
let bias = self.bias.read();
let output = input.matmul(&*linear)?;
output.add(&*bias)
}
}
/// SimMIM loss computation utilities
pub struct SimMIMLoss;
impl SimMIMLoss {
/// Compute L1 (MAE) loss between predicted and target pixels
pub fn compute_l1_loss(
predicted: &SimpleTensor,
target: &SimpleTensor,
) -> Result<SimpleTensor> {
let pred_shape = predicted.shape();
let target_shape = target.shape();
if pred_shape != target_shape {
return Err(TransformerError::InvalidInput(format!(
"Predicted and target shapes must match: {:?} vs {:?}",
pred_shape, target_shape
)));
}
let diff = predicted.sub(target)?;
let abs_diff = diff.abs()?;
abs_diff.mean(&[])
}
/// Compute normalized L1 loss
pub fn compute_l1_loss_normalized(
predicted: &SimpleTensor,
target: &SimpleTensor,
) -> Result<SimpleTensor> {
Self::compute_l1_loss(predicted, target)
}
/// Compute L2 (MSE) loss between predicted and target pixels
pub fn compute_l2_loss(
predicted: &SimpleTensor,
target: &SimpleTensor,
) -> Result<SimpleTensor> {
let pred_shape = predicted.shape();
let target_shape = target.shape();
if pred_shape != target_shape {
return Err(TransformerError::InvalidInput(format!(
"Predicted and target shapes must match: {:?} vs {:?}",
pred_shape, target_shape
)));
}
let diff = predicted.sub(target)?;
let squared_diff = diff.pow_scalar(2.0)?;
squared_diff.mean(&[])
}
}
/// Result from SimMIM training step
#[derive(Debug)]
pub struct SimMIMTrainingResult {
/// Reconstruction loss
pub loss: SimpleTensor,
/// Number of masked patches in this batch
pub num_masked_patches: usize,
/// Actual mask ratio used
pub mask_ratio: f32,
}
/// Main SimMIM trainer
#[derive(Debug)]
pub struct SimMIMTrainer {
config: SimMIMConfig,
masker: RandomPatchMasker,
mask_token: MaskTokenEmbedding,
prediction_head: LinearPredictionHead,
encoder: Arc<RwLock<SimpleTensor>>,
device: Device,
is_training: bool,
}
impl SimMIMTrainer {
/// Create new SimMIM trainer
pub fn new(
config: SimMIMConfig,
in_channels: usize,
image_size: usize,
device: &Device,
) -> Result<Self> {
let masker = RandomPatchMasker::new(config.mask_ratio);
let mask_token = MaskTokenEmbedding::new(config.encoder_dim, device)?;
let patch_volume = config.patch_size * config.patch_size * in_channels;
let prediction_head = LinearPredictionHead::new(config.encoder_dim, patch_volume, device)?;
let encoder = SimpleTensor::randn(vec![patch_volume, config.encoder_dim], device)?;
Ok(Self {
config,
masker,
mask_token,
prediction_head,
encoder: Arc::new(RwLock::new(encoder)),
device: device.clone(),
is_training: true,
})
}
/// Get configuration
pub fn config(&self) -> &SimMIMConfig {
&self.config
}
/// 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 SimMIM training step
pub fn train_step(
&mut self,
images: &SimpleTensor,
seed: Option<u64>,
) -> Result<SimMIMTrainingResult> {
let patches = self.extract_patches(images)?;
let target_pixels = patches.clone();
let mask_result = self.masker.mask_patches(&patches, seed)?;
let masked_patches = self.apply_mask_tokens(&patches, &mask_result)?;
let encoded_features = self.encode_patches(&masked_patches)?;
let masked_features = self.extract_masked_features(&encoded_features, &mask_result)?;
let predicted_pixels = self.prediction_head.forward(&masked_features)?;
let target_masked_pixels = self.extract_masked_targets(&target_pixels, &mask_result)?;
let loss = match self.config.loss_type {
LossType::L1 => {
if self.config.norm_pix_loss {
SimMIMLoss::compute_l1_loss_normalized(
&predicted_pixels,
&target_masked_pixels,
)?
} else {
SimMIMLoss::compute_l1_loss(&predicted_pixels, &target_masked_pixels)?
}
}
LossType::L2 => SimMIMLoss::compute_l2_loss(&predicted_pixels, &target_masked_pixels)?,
};
Ok(SimMIMTrainingResult {
loss,
num_masked_patches: mask_result.num_masked_patches,
mask_ratio: self.masker.mask_ratio(),
})
}
/// Extract features in evaluation mode
pub fn extract_features(&self, images: &SimpleTensor) -> Result<SimpleTensor> {
let patches = self.extract_patches(images)?;
let encoded = self.encode_patches(&patches)?;
encoded.mean(&[1])
}
fn extract_patches(&self, images: &SimpleTensor) -> Result<SimpleTensor> {
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;
images.reshape(&[batch_size, num_patches, patch_volume])
}
fn apply_mask_tokens(
&self,
patches: &SimpleTensor,
mask_result: &PatchMaskResult,
) -> Result<SimpleTensor> {
// Simplified implementation - just return the original patches
Ok(patches.clone())
}
fn encode_patches(&self, patches: &SimpleTensor) -> Result<SimpleTensor> {
let encoder = self.encoder.read();
patches.matmul(&*encoder)
}
fn extract_masked_features(
&self,
features: &SimpleTensor,
mask_result: &PatchMaskResult,
) -> Result<SimpleTensor> {
let shape = features.shape();
let batch_size = shape[0];
let embed_dim = shape[2];
let num_masked = mask_result.num_masked_patches;
let result_data = vec![1.0f32; batch_size * num_masked * embed_dim];
SimpleTensor::from_data(
result_data,
vec![batch_size, num_masked, embed_dim],
&self.device,
)
}
fn extract_masked_targets(
&self,
targets: &SimpleTensor,
mask_result: &PatchMaskResult,
) -> Result<SimpleTensor> {
let shape = targets.shape();
let batch_size = shape[0];
let patch_volume = shape[2];
let num_masked = mask_result.num_masked_patches;
let result_data = vec![0.5f32; batch_size * num_masked * patch_volume];
SimpleTensor::from_data(
result_data,
vec![batch_size, num_masked, patch_volume],
&self.device,
)
}
}