# Agent 146: MAMBA-2 Batch Shape Mismatch Fix ## Mission Fix tensor shape mismatch in MAMBA-2 training batch logic preventing model training. ## Error Analysis ### Original Error ``` Error: cannot broadcast [1, 256] to [16, 16] Location: ml::mamba::Mamba2SSM::train_batch ``` **Root Cause Identified:** 1. **Batching Issue**: Data loader creates individual sequences with shape `[1, seq_len, d_model]`, but training code expected batched tensors `[batch_size, seq_len, d_model]` 2. **Shape Mismatch**: `delta` parameter is `[d_model]` (256 elements) but SSM matrices are `[d_state, d_state]` (16×16), causing broadcast failures ## Files Modified ### 1. `/home/jgrusewski/Work/foxhunt/ml/src/mamba/mod.rs` **Change 1: Fix train_batch to properly batch individual sequences (lines 895-952)** **BEFORE:** ```rust fn train_batch(&mut self, batch: &[(Tensor, Tensor)], epoch: usize) -> Result { let mut total_loss = 0.0; for (input, target) in batch { // Zero gradients self.zero_gradients()?; // Forward pass with selective scan let output = self.forward_with_gradients(input)?; // ... (processes each sample individually) } } ``` **AFTER:** ```rust fn train_batch(&mut self, batch: &[(Tensor, Tensor)], _epoch: usize) -> Result { if batch.is_empty() { return Ok(0.0); } // FIXED: Batch all individual sequences together into a single batched tensor // Individual sequences are shape [1, seq_len, d_model], we need [batch_size, seq_len, d_model] let actual_batch_size = batch.len(); // Collect all input tensors and concatenate along batch dimension let input_tensors: Vec<&Tensor> = batch.iter().map(|(input, _)| input).collect(); let batched_input = if actual_batch_size == 1 { input_tensors[0].clone() } else { Tensor::cat(&input_tensors.iter().map(|t| (*t).clone()).collect::>(), 0)? }; // Collect all target tensors and concatenate let target_tensors: Vec<&Tensor> = batch.iter().map(|(_, target)| target).collect(); let batched_target = if actual_batch_size == 1 { target_tensors[0].clone() } else { Tensor::cat(&target_tensors.iter().map(|t| (*t).clone()).collect::>(), 0)? }; // Forward pass with selective scan on batched input let output = self.forward_with_gradients(&batched_input)?; // ... (processes entire batch together) } ``` **Change 2: Fix discretize_ssm to handle dt shape mismatch (lines 648-660)** **BEFORE:** ```rust fn discretize_ssm(&self, A_cont: &Tensor, dt: &Tensor) -> Result { let dt_expanded = dt.unsqueeze(0)?.broadcast_as(A_cont.shape())?; // FAILS: [1, 256] → [16, 16] let A_scaled = (A_cont * &dt_expanded)?; // ... } ``` **AFTER:** ```rust fn discretize_ssm(&self, A_cont: &Tensor, dt: &Tensor) -> Result { // FIXED: dt is [d_model] but A_cont is [d_state, d_state] // Use mean of dt as a scalar tensor for discretization let dt_tensor = dt.mean_all()?.to_dtype(DType::F32)?; // Keep as 0-D F32 tensor // A_discrete = exp(A_cont * dt) // For simplicity, using first-order approximation: I + A_cont * dt let A_scaled = A_cont.broadcast_mul(&dt_tensor)?; let identity = Tensor::eye(A_cont.dim(0)?, DType::F32, A_cont.device())?; let A_discrete = (&identity + &A_scaled)?; Ok(A_discrete) } ``` **Change 3: Apply same fix to discretize_ssm_input (lines 667-676)** **Change 4: Apply same fix to discretize_ssm_with_gradients (lines 1065-1085)** **Change 5: Apply same fix to discretize_ssm_input_with_gradients (lines 1092-1106)** ## Technical Details ### Issue 1: Batch Dimension Mismatch **Problem**: DbnSequenceLoader creates tensors with shape `[1, seq_len, d_model]` for each sequence, but MAMBA-2 expects `[batch_size, seq_len, d_model]`. **Solution**: Concatenate individual sequences along dimension 0 (batch dimension) before forward pass: - Input: `[(1, 60, 256), (1, 60, 256), ...]` (8 sequences) - Output: `(8, 60, 256)` (single batched tensor) **Benefits**: - Proper batching for efficient GPU utilization - Correct tensor shapes for SSM operations - Maintains gradient flow through entire batch ### Issue 2: Delta Parameter Shape Mismatch **Problem**: Delta parameter is `[d_model]` (256 elements) representing per-feature time steps, but SSM discretization tries to broadcast it to `[d_state, d_state]` (16×16) matrices. **Solution**: Use mean of delta as a scalar (0-D tensor) for matrix discretization: - Original: `dt.unsqueeze(0)?.broadcast_as([16, 16])` → FAILS - Fixed: `dt.mean_all()?.to_dtype(DType::F32)?` → scalar broadcast → SUCCESS **Rationale**: SSM discretization requires a single time-step parameter, not per-feature steps. Taking the mean provides a representative value while maintaining differentiability for gradient computation. ## Current Status ### Remaining Issue **Error**: `dtype mismatch in mul, lhs: F64, rhs: F32` **Cause**: `mean_all()` returns F64, but matrices are F32. The `to_dtype(DType::F32)` conversion may not work correctly on CUDA tensors in Candle. **Next Step**: Extract scalar value and create new F32 scalar tensor directly: ```rust let dt_scalar = dt.mean_all()?.to_scalar::()?; let dt_tensor = Tensor::new(&[dt_scalar], A_cont.device())?; // F32 scalar tensor on same device let A_scaled = A_cont.broadcast_mul(&dt_tensor)?; ``` ## Summary **Fixed Issues:** 1. ✅ Batch concatenation - individual sequences properly batched 2. ✅ Shape mismatch logic - delta broadcast issue identified 3. ⏳ DType conversion - needs one more iteration **Files Modified:** 1 file (`ml/src/mamba/mod.rs`) **Lines Changed:** ~150 lines (5 functions modified) **Build Status:** ✅ Compiles successfully **Test Status:** ⏳ Pending final dtype fix **Next Agent**: Complete dtype conversion fix and validate training loop executes successfully for 3 epochs.