| name | fixed-tensor-testing |
| description | Test ML functions with fixed input tensors for reproducibility. |
Testing with Fixed Input Tensors
Purpose
Fixed tensor testing ensures deterministic, reproducible results for loss functions and model outputs. Enables verification without training dependencies.
Creating Fixed Tensors
1. Deterministic Seeding
import torch
import numpy as np
torch.manual_seed(42)
np.random.seed(42)
if torch.cuda.is_available():
torch.cuda.manual_seed_all(42)
2. Creating Test Tensors
log_probs = torch.randn(batch_size, seq_len)
log_probs = torch.clamp(log_probs, min=-5.0, max=0.0)
seq_lengths = torch.randint(10, 100, (batch_size,))
input_ids = torch.randint(0, vocab_size, (batch_size, seq_len))
3. Example Test Setup
batch_size = 4
seq_len = 10
torch.manual_seed(123)
log_probs = torch.randn(batch_size, seq_len)
log_probs = torch.clamp(log_probs, -5.0, 0.0)
mask = torch.ones(batch_size, seq_len, dtype=torch.bool)
mask[:, seq_len-2:] = False
seq_lengths = mask.sum(dim=1).float()
Saving Test Results
Save as .npz (NumPy Compressed)
import numpy as np
results = {
'losses': loss.cpu().detach().numpy(),
'log_probs': log_probs.cpu().detach().numpy(),
}
np.savez_compressed('/path/to/results.npz', **results)
Load .npz Files
data = np.load('/path/to/results.npz')
losses = data['losses']
print(f"Shape: {losses.shape}, dtype: {losses.dtype}")
Assertions and Validation
Basic Checks
assert torch.isfinite(loss).all(), "Loss contains NaN or Inf"
assert loss.item() > 0, "Loss should be positive"
assert loss.shape == expected_shape, f"Shape mismatch: {loss.shape}"
assert loss.item() < 100, "Loss unreasonably large"
assert log_probs.min() >= -6.0, "Log probs too small"
Reproducibility Checks
loss1 = compute_loss(fixed_tensors)
loss2 = compute_loss(fixed_tensors)
assert torch.allclose(loss1, loss2), "Loss not reproducible"
Common Test Patterns
1. Unit Test for Loss Function
def test_simpo_loss():
batch_size = 8
torch.manual_seed(42)
log_probs = torch.randn(batch_size, 20)
seq_lengths = torch.full((batch_size,), 20.0)
beta = 2.0
gamma = 1.0
loss = compute_simpo_loss(log_probs, seq_lengths, beta, gamma)
assert loss.shape == torch.Size([])
assert torch.isfinite(loss)
assert loss.item() > 0
return loss.item()
2. Gradient Flow Test
x = torch.randn(5, 10, requires_grad=True)
loss = some_loss_function(x)
loss.backward()
assert x.grad is not None
assert not torch.allclose(x.grad, torch.zeros_like(x.grad))
3. Numerical Stability Test
extreme_inputs = [
torch.full((5,), -100.0),
torch.full((5,), 0.0),
torch.zeros(5),
]
for inp in extreme_inputs:
try:
loss = compute_loss(inp)
assert torch.isfinite(loss), f"Loss not finite for {inp}"
except Exception as e:
print(f"Failed with input {inp}: {e}")
Best Practices
- Use consistent seeds across test runs
- Document expected outputs for regression testing
- Test edge cases (empty batches, single samples, extreme values)
- Validate shapes before and after operations
- Check numerical stability with both typical and extreme inputs
- Save results in standard formats (.npz, .pt, .json)
Debugging Fixed Tensor Tests
def debug_loss(log_probs, seq_lengths, beta, gamma):
rewards = beta * log_probs / seq_lengths.unsqueeze(1)
print(f"Rewards shape: {rewards.shape}, min: {rewards.min():.4f}, max: {rewards.max():.4f}")
print(f"Final loss: {loss.item():.6f}")
return loss