184 lines
5.4 KiB
Rust
184 lines
5.4 KiB
Rust
//! Test Flash Attention backward pass
|
|
//! Run with: cargo run -p rtx-flash-metal-attention --example backward_test --release
|
|
|
|
use rtx_flash_metal_attention::{FlashAttention, FlashAttentionConfig};
|
|
use rtx_tensor::{Device, Tensor};
|
|
use std::time::Instant;
|
|
|
|
fn main() {
|
|
println!("=== Flash Attention Backward Pass Test ===\n");
|
|
|
|
// Get Metal device
|
|
let metal_device = match Device::metal(0) {
|
|
Ok(d) => {
|
|
println!("Metal device: Metal(0)");
|
|
d
|
|
}
|
|
Err(e) => {
|
|
eprintln!("Metal device not available: {:?}", e);
|
|
return;
|
|
}
|
|
};
|
|
|
|
// Create FlashAttention
|
|
println!("Creating FlashAttention...");
|
|
let config = FlashAttentionConfig::default();
|
|
let attn = match FlashAttention::new(config) {
|
|
Ok(a) => {
|
|
println!("FlashAttention created successfully!");
|
|
a
|
|
}
|
|
Err(e) => {
|
|
eprintln!("Failed to create FlashAttention: {:?}", e);
|
|
return;
|
|
}
|
|
};
|
|
|
|
// Test parameters
|
|
let batch_size = 2;
|
|
let num_heads = 4;
|
|
let seq_len = 64;
|
|
let head_dim = 64;
|
|
|
|
println!("\nTest configuration:");
|
|
println!(" Batch size: {}", batch_size);
|
|
println!(" Num heads: {}", num_heads);
|
|
println!(" Sequence length: {}", seq_len);
|
|
println!(" Head dimension: {}", head_dim);
|
|
|
|
let shape = &[batch_size, num_heads, seq_len, head_dim];
|
|
|
|
// Create input tensors
|
|
println!("\nCreating tensors...");
|
|
let q = Tensor::ones(shape, &metal_device).expect("Q tensor");
|
|
let k = Tensor::ones(shape, &metal_device).expect("K tensor");
|
|
let v = Tensor::ones(shape, &metal_device).expect("V tensor");
|
|
|
|
// Forward pass
|
|
println!("\n--- Forward Pass ---");
|
|
let start = Instant::now();
|
|
let (output, lse) = match attn.forward(&q, &k, &v) {
|
|
Ok((o, l)) => {
|
|
println!("Forward pass successful!");
|
|
println!(" Output shape: {:?}", o.shape());
|
|
println!(" LSE shape: {:?}", l.shape());
|
|
(o, l)
|
|
}
|
|
Err(e) => {
|
|
eprintln!("Forward pass failed: {:?}", e);
|
|
return;
|
|
}
|
|
};
|
|
let forward_time = start.elapsed();
|
|
println!(
|
|
" Forward time: {:.3}ms",
|
|
forward_time.as_secs_f64() * 1000.0
|
|
);
|
|
|
|
// Create gradient output (same shape as output)
|
|
let grad_output = Tensor::ones(shape, &metal_device).expect("grad_output tensor");
|
|
|
|
// Backward pass
|
|
println!("\n--- Backward Pass ---");
|
|
let start = Instant::now();
|
|
let (dq, dk, dv) = match attn.backward(&grad_output, &q, &k, &v, &output, &lse) {
|
|
Ok((dq, dk, dv)) => {
|
|
println!("Backward pass successful!");
|
|
println!(" dQ shape: {:?}", dq.shape());
|
|
println!(" dK shape: {:?}", dk.shape());
|
|
println!(" dV shape: {:?}", dv.shape());
|
|
(dq, dk, dv)
|
|
}
|
|
Err(e) => {
|
|
eprintln!("Backward pass failed: {:?}", e);
|
|
return;
|
|
}
|
|
};
|
|
let backward_time = start.elapsed();
|
|
println!(
|
|
" Backward time: {:.3}ms",
|
|
backward_time.as_secs_f64() * 1000.0
|
|
);
|
|
|
|
// Verify gradient shapes match input shapes
|
|
println!("\n--- Shape Verification ---");
|
|
let q_shape = q.shape();
|
|
let k_shape = k.shape();
|
|
let v_shape = v.shape();
|
|
|
|
let dq_matches = dq.shape() == q_shape;
|
|
let dk_matches = dk.shape() == k_shape;
|
|
let dv_matches = dv.shape() == v_shape;
|
|
|
|
println!(
|
|
" dQ shape matches Q: {} ({:?} == {:?})",
|
|
dq_matches,
|
|
dq.shape(),
|
|
q_shape
|
|
);
|
|
println!(
|
|
" dK shape matches K: {} ({:?} == {:?})",
|
|
dk_matches,
|
|
dk.shape(),
|
|
k_shape
|
|
);
|
|
println!(
|
|
" dV shape matches V: {} ({:?} == {:?})",
|
|
dv_matches,
|
|
dv.shape(),
|
|
v_shape
|
|
);
|
|
|
|
if dq_matches && dk_matches && dv_matches {
|
|
println!("\n✓ All gradient shapes are correct!");
|
|
} else {
|
|
println!("\n✗ Shape mismatch detected!");
|
|
}
|
|
|
|
// Benchmark multiple iterations
|
|
println!("\n--- Performance Benchmark ---");
|
|
let iterations = 50;
|
|
|
|
// Warmup
|
|
for _ in 0..10 {
|
|
let _ = attn.forward(&q, &k, &v);
|
|
let _ = attn.backward(&grad_output, &q, &k, &v, &output, &lse);
|
|
}
|
|
|
|
// Forward benchmark
|
|
let start = Instant::now();
|
|
for _ in 0..iterations {
|
|
let _ = attn.forward(&q, &k, &v);
|
|
}
|
|
let avg_forward = start.elapsed().as_secs_f64() * 1000.0 / iterations as f64;
|
|
|
|
// Backward benchmark
|
|
let start = Instant::now();
|
|
for _ in 0..iterations {
|
|
let _ = attn.backward(&grad_output, &q, &k, &v, &output, &lse);
|
|
}
|
|
let avg_backward = start.elapsed().as_secs_f64() * 1000.0 / iterations as f64;
|
|
|
|
println!(" Average forward time: {:.4}ms", avg_forward);
|
|
println!(" Average backward time: {:.4}ms", avg_backward);
|
|
println!(
|
|
" Backward/Forward ratio: {:.2}x",
|
|
avg_backward / avg_forward
|
|
);
|
|
|
|
let total_elements = (batch_size * num_heads * seq_len * head_dim) as f64;
|
|
let forward_throughput = total_elements / (avg_forward / 1000.0) / 1e6;
|
|
let backward_throughput = total_elements / (avg_backward / 1000.0) / 1e6;
|
|
|
|
println!(
|
|
"\n Forward throughput: {:.2} M elements/s",
|
|
forward_throughput
|
|
);
|
|
println!(
|
|
" Backward throughput: {:.2} M elements/s",
|
|
backward_throughput
|
|
);
|
|
|
|
println!("\n=== Test Complete ===");
|
|
}
|