Initial commit

This commit is contained in:
redclawsystems
2026-03-04 00:08:42 +00:00
commit 4d88dc0584
4449 changed files with 1556714 additions and 0 deletions
@@ -0,0 +1,912 @@
//! SDPA Backend Auto-Selection for RustyTorch++
//!
//! Automatically selects the optimal Scaled Dot-Product Attention backend based on:
//! - Hardware capabilities (GPU architecture, Tensor Cores, memory)
//! - Input characteristics (sequence length, head dimension, batch size)
//! - User preferences and constraints
//!
//! # Backends
//!
//! 1. **FlashAttention**: Optimal for long sequences, uses tiling for O(1) memory
//! 2. **Math**: Standard attention, best for small sequences or debugging
//! 3. **Memory-Efficient**: Chunked attention, trades compute for memory
//! 4. **CuDNN**: NVIDIA's optimized implementation (when available)
//!
//! # Usage
//!
//! ```rust,ignore
//! use rtx_flash_attention::backend_selector::{SdpaBackendSelector, SdpaBackend};
//!
//! let selector = SdpaBackendSelector::new(SdpaConfig::default())?;
//!
//! // Automatic selection
//! let backend = selector.select(&query, &key, &value)?;
//!
//! // Or get recommendation
//! let recommendation = selector.recommend(seq_len, head_dim, batch_size)?;
//! println!("Recommended backend: {:?}", recommendation.backend);
//! println!("Expected speedup: {:.2}x", recommendation.expected_speedup);
//! ```
use std::collections::HashMap;
// =============================================================================
// Backends
// =============================================================================
/// Available SDPA backends
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum SdpaBackend {
/// FlashAttention v2 - optimal for long sequences
FlashAttention,
/// Standard mathematical attention - simple, debuggable
Math,
/// Memory-efficient chunked attention
MemoryEfficient,
/// NVIDIA cuDNN attention (Ampere+)
CuDnn,
/// Custom Metal implementation for Apple Silicon
Metal,
/// Fallback CPU implementation
Cpu,
}
impl std::fmt::Display for SdpaBackend {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
SdpaBackend::FlashAttention => write!(f, "FlashAttention"),
SdpaBackend::Math => write!(f, "Math"),
SdpaBackend::MemoryEfficient => write!(f, "MemoryEfficient"),
SdpaBackend::CuDnn => write!(f, "cuDNN"),
SdpaBackend::Metal => write!(f, "Metal"),
SdpaBackend::Cpu => write!(f, "CPU"),
}
}
}
// =============================================================================
// Hardware Detection
// =============================================================================
/// Detected hardware capabilities
#[derive(Debug, Clone)]
pub struct HardwareCapabilities {
/// Device type
pub device_type: DeviceType,
/// GPU compute capability (e.g., 8.0 for A100)
pub compute_capability: Option<(u32, u32)>,
/// Total device memory (bytes)
pub total_memory: usize,
/// Available device memory (bytes)
pub available_memory: usize,
/// Has Tensor Cores
pub has_tensor_cores: bool,
/// Has FP16 support
pub has_fp16: bool,
/// Has BF16 support
pub has_bf16: bool,
/// Has FP8 support
pub has_fp8: bool,
/// Number of SMs (CUDA) or compute units
pub num_compute_units: u32,
/// Memory bandwidth (GB/s)
pub memory_bandwidth_gbps: f32,
/// Supports FlashAttention
pub supports_flash_attention: bool,
/// Supports cuDNN attention
pub supports_cudnn_attention: bool,
}
/// Device type
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum DeviceType {
/// CPU
Cpu,
/// NVIDIA CUDA GPU
Cuda,
/// AMD ROCm GPU
Rocm,
/// Apple Metal
Metal,
/// Intel XPU
Xpu,
}
impl HardwareCapabilities {
/// Detect capabilities for CPU
pub fn detect_cpu() -> Self {
Self {
device_type: DeviceType::Cpu,
compute_capability: None,
total_memory: 0,
available_memory: 0,
has_tensor_cores: false,
has_fp16: true,
has_bf16: false,
has_fp8: false,
num_compute_units: std::thread::available_parallelism().map(std::num::NonZero::get).unwrap_or(4) as u32,
memory_bandwidth_gbps: 50.0, // Typical DDR5
supports_flash_attention: false,
supports_cudnn_attention: false,
}
}
/// Detect capabilities for CUDA device
pub fn detect_cuda(device_id: usize) -> Self {
// In production, this would query CUDA runtime
// For now, simulate A100 capabilities
Self {
device_type: DeviceType::Cuda,
compute_capability: Some((8, 0)), // A100
total_memory: 80 * 1024 * 1024 * 1024, // 80GB
available_memory: 70 * 1024 * 1024 * 1024,
has_tensor_cores: true,
has_fp16: true,
has_bf16: true,
has_fp8: false, // Only Hopper+
num_compute_units: 108, // A100 SMs
memory_bandwidth_gbps: 2039.0, // A100 HBM2e
supports_flash_attention: true,
supports_cudnn_attention: true,
}
}
/// Detect capabilities for Metal device
pub fn detect_metal() -> Self {
Self {
device_type: DeviceType::Metal,
compute_capability: None,
total_memory: 32 * 1024 * 1024 * 1024, // 32GB unified
available_memory: 28 * 1024 * 1024 * 1024,
has_tensor_cores: false,
has_fp16: true,
has_bf16: false,
has_fp8: false,
num_compute_units: 40, // M2 Max GPU cores
memory_bandwidth_gbps: 400.0, // M2 Max
supports_flash_attention: true, // Custom MSL implementation
supports_cudnn_attention: false,
}
}
/// Check if hardware supports a specific backend
pub fn supports_backend(&self, backend: SdpaBackend) -> bool {
match backend {
SdpaBackend::FlashAttention => self.supports_flash_attention,
SdpaBackend::Math => true, // Always supported
SdpaBackend::MemoryEfficient => true,
SdpaBackend::CuDnn => self.supports_cudnn_attention,
SdpaBackend::Metal => self.device_type == DeviceType::Metal,
SdpaBackend::Cpu => true,
}
}
}
// =============================================================================
// Input Characteristics
// =============================================================================
/// Characteristics of the attention input
#[derive(Debug, Clone)]
pub struct AttentionInputInfo {
/// Batch size
pub batch_size: usize,
/// Number of attention heads
pub num_heads: usize,
/// Sequence length (query)
pub seq_len_q: usize,
/// Sequence length (key/value)
pub seq_len_kv: usize,
/// Head dimension
pub head_dim: usize,
/// Data type
pub dtype: AttentionDType,
/// Is causal attention
pub is_causal: bool,
/// Has attention mask
pub has_mask: bool,
/// Dropout probability
pub dropout: f32,
}
/// Data types for attention
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum AttentionDType {
Float32,
Float16,
BFloat16,
Float8E4M3,
Float8E5M2,
}
impl AttentionInputInfo {
/// Estimate memory required for this attention operation
pub fn estimate_memory_bytes(&self) -> usize {
let bytes_per_element = match self.dtype {
AttentionDType::Float32 => 4,
AttentionDType::Float16 | AttentionDType::BFloat16 => 2,
AttentionDType::Float8E4M3 | AttentionDType::Float8E5M2 => 1,
};
// Q, K, V, Output, Attention weights
let qkvo_size = self.batch_size * self.num_heads * self.seq_len_q * self.head_dim * bytes_per_element * 4;
// Attention scores (for math backend)
let attn_size = self.batch_size * self.num_heads * self.seq_len_q * self.seq_len_kv * bytes_per_element;
qkvo_size + attn_size
}
/// Estimate FLOPs for this attention operation
pub fn estimate_flops(&self) -> usize {
// QK^T: 2 * B * H * N * N * D
let qk_flops = 2 * self.batch_size * self.num_heads * self.seq_len_q * self.seq_len_kv * self.head_dim;
// Softmax: ~5 * B * H * N * N
let softmax_flops = 5 * self.batch_size * self.num_heads * self.seq_len_q * self.seq_len_kv;
// AttnV: 2 * B * H * N * N * D
let av_flops = 2 * self.batch_size * self.num_heads * self.seq_len_q * self.seq_len_kv * self.head_dim;
qk_flops + softmax_flops + av_flops
}
}
// =============================================================================
// Selection Configuration
// =============================================================================
/// Configuration for backend selection
#[derive(Debug, Clone)]
pub struct SdpaConfig {
/// Preferred backend (if supported)
pub preferred_backend: Option<SdpaBackend>,
/// Disabled backends
pub disabled_backends: Vec<SdpaBackend>,
/// Enable auto-tuning
pub enable_autotuning: bool,
/// Memory budget (bytes, 0 = no limit)
pub memory_budget: usize,
/// Optimize for latency vs throughput
pub optimize_for: OptimizeFor,
/// Minimum sequence length for FlashAttention
pub flash_min_seq_len: usize,
/// Enable deterministic mode
pub deterministic: bool,
/// Enable debug mode (use Math backend)
pub debug_mode: bool,
}
/// Optimization target
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum OptimizeFor {
/// Minimize latency (single request)
Latency,
/// Maximize throughput (batch processing)
Throughput,
/// Balance both
Balanced,
}
impl Default for SdpaConfig {
fn default() -> Self {
Self {
preferred_backend: None,
disabled_backends: Vec::new(),
enable_autotuning: true,
memory_budget: 0,
optimize_for: OptimizeFor::Balanced,
flash_min_seq_len: 128,
deterministic: false,
debug_mode: false,
}
}
}
// =============================================================================
// Backend Recommendation
// =============================================================================
/// Recommendation for which backend to use
#[derive(Debug, Clone)]
pub struct BackendRecommendation {
/// Recommended backend
pub backend: SdpaBackend,
/// Confidence score (0.0 to 1.0)
pub confidence: f32,
/// Expected speedup vs Math backend
pub expected_speedup: f32,
/// Expected memory usage (bytes)
pub expected_memory: usize,
/// Reason for recommendation
pub reason: String,
/// Alternative backends ranked
pub alternatives: Vec<(SdpaBackend, f32)>,
}
// =============================================================================
// Backend Selector
// =============================================================================
/// SDPA Backend Selector
pub struct SdpaBackendSelector {
/// Configuration
config: SdpaConfig,
/// Detected hardware capabilities
hardware: HardwareCapabilities,
/// Cached recommendations
cache: std::sync::RwLock<HashMap<CacheKey, BackendRecommendation>>,
/// Performance history for tuning
perf_history: std::sync::RwLock<Vec<PerformanceRecord>>,
}
/// Cache key for recommendations
#[derive(Debug, Clone, Hash, PartialEq, Eq)]
struct CacheKey {
batch_size: usize,
num_heads: usize,
seq_len_q: usize,
seq_len_kv: usize,
head_dim: usize,
is_causal: bool,
}
/// Performance record for auto-tuning
#[derive(Debug, Clone)]
struct PerformanceRecord {
key: CacheKey,
backend: SdpaBackend,
latency_us: f64,
memory_bytes: usize,
}
impl SdpaBackendSelector {
/// Create a new selector with configuration
pub fn new(config: SdpaConfig) -> Self {
// Auto-detect hardware
let hardware = if cfg!(feature = "cuda") {
HardwareCapabilities::detect_cuda(0)
} else if cfg!(target_os = "macos") {
HardwareCapabilities::detect_metal()
} else {
HardwareCapabilities::detect_cpu()
};
Self {
config,
hardware,
cache: std::sync::RwLock::new(HashMap::new()),
perf_history: std::sync::RwLock::new(Vec::new()),
}
}
/// Create selector with specific hardware capabilities
pub fn with_hardware(config: SdpaConfig, hardware: HardwareCapabilities) -> Self {
Self {
config,
hardware,
cache: std::sync::RwLock::new(HashMap::new()),
perf_history: std::sync::RwLock::new(Vec::new()),
}
}
/// Get hardware capabilities
pub fn hardware(&self) -> &HardwareCapabilities {
&self.hardware
}
/// Select optimal backend for given input
pub fn select(&self, input: &AttentionInputInfo) -> BackendRecommendation {
// Check debug mode
if self.config.debug_mode {
return BackendRecommendation {
backend: SdpaBackend::Math,
confidence: 1.0,
expected_speedup: 1.0,
expected_memory: input.estimate_memory_bytes(),
reason: "Debug mode enabled".to_string(),
alternatives: vec![],
};
}
// Check preferred backend
if let Some(preferred) = self.config.preferred_backend {
if self.is_backend_available(preferred, input) {
return BackendRecommendation {
backend: preferred,
confidence: 1.0,
expected_speedup: self.estimate_speedup(preferred, input),
expected_memory: self.estimate_memory(preferred, input),
reason: "User preferred backend".to_string(),
alternatives: self.get_alternatives(input),
};
}
}
// Check cache
let cache_key = CacheKey {
batch_size: input.batch_size,
num_heads: input.num_heads,
seq_len_q: input.seq_len_q,
seq_len_kv: input.seq_len_kv,
head_dim: input.head_dim,
is_causal: input.is_causal,
};
{
let cache = self.cache.read().unwrap();
if let Some(rec) = cache.get(&cache_key) {
return rec.clone();
}
}
// Compute recommendation
let recommendation = self.compute_recommendation(input);
// Cache it
{
let mut cache = self.cache.write().unwrap();
cache.insert(cache_key, recommendation.clone());
}
recommendation
}
/// Check if a backend is available for the given input
fn is_backend_available(&self, backend: SdpaBackend, input: &AttentionInputInfo) -> bool {
// Check if disabled
if self.config.disabled_backends.contains(&backend) {
return false;
}
// Check hardware support
if !self.hardware.supports_backend(backend) {
return false;
}
// Backend-specific checks
match backend {
SdpaBackend::FlashAttention => {
// FlashAttention has constraints
input.seq_len_q >= self.config.flash_min_seq_len
&& input.head_dim <= 256
&& (input.head_dim == 32 || input.head_dim == 64 || input.head_dim == 128 || input.head_dim == 256)
}
SdpaBackend::CuDnn => {
// cuDNN constraints
input.head_dim <= 128 && !input.has_mask
}
_ => true,
}
}
/// Compute recommendation for input
fn compute_recommendation(&self, input: &AttentionInputInfo) -> BackendRecommendation {
let available_backends = self.get_available_backends(input);
if available_backends.is_empty() {
return BackendRecommendation {
backend: SdpaBackend::Math,
confidence: 0.5,
expected_speedup: 1.0,
expected_memory: input.estimate_memory_bytes(),
reason: "Fallback to Math (no optimized backend available)".to_string(),
alternatives: vec![],
};
}
// Score each backend
let mut scores: Vec<(SdpaBackend, f32, String)> = available_backends
.iter()
.map(|&b| {
let (score, reason) = self.score_backend(b, input);
(b, score, reason)
})
.collect();
// Sort by score (descending)
scores.sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap_or(std::cmp::Ordering::Equal));
let (best_backend, best_score, reason) = scores.first().cloned().unwrap();
let alternatives: Vec<(SdpaBackend, f32)> = scores
.iter()
.skip(1)
.map(|(b, s, _)| (*b, *s))
.collect();
BackendRecommendation {
backend: best_backend,
confidence: best_score,
expected_speedup: self.estimate_speedup(best_backend, input),
expected_memory: self.estimate_memory(best_backend, input),
reason,
alternatives,
}
}
/// Get available backends for input
fn get_available_backends(&self, input: &AttentionInputInfo) -> Vec<SdpaBackend> {
[
SdpaBackend::FlashAttention,
SdpaBackend::CuDnn,
SdpaBackend::MemoryEfficient,
SdpaBackend::Math,
SdpaBackend::Metal,
SdpaBackend::Cpu,
]
.iter()
.filter(|&&b| self.is_backend_available(b, input))
.copied()
.collect()
}
/// Score a backend for given input
fn score_backend(&self, backend: SdpaBackend, input: &AttentionInputInfo) -> (f32, String) {
let seq_len = input.seq_len_q.max(input.seq_len_kv);
let memory_required = self.estimate_memory(backend, input);
// Check memory budget
if self.config.memory_budget > 0 && memory_required > self.config.memory_budget {
return (0.0, "Exceeds memory budget".to_string());
}
match backend {
SdpaBackend::FlashAttention => {
if seq_len >= 2048 {
(0.95, format!("FlashAttention optimal for seq_len={}", seq_len))
} else if seq_len >= 512 {
(0.85, "FlashAttention good for medium sequences".to_string())
} else {
(0.6, "FlashAttention has overhead for short sequences".to_string())
}
}
SdpaBackend::CuDnn => {
if seq_len <= 512 && input.head_dim <= 64 {
(0.9, "cuDNN optimal for small attention".to_string())
} else {
(0.7, "cuDNN usable but not optimal".to_string())
}
}
SdpaBackend::MemoryEfficient => {
if memory_required > self.hardware.available_memory / 2 {
(0.85, "Memory-efficient prevents OOM".to_string())
} else {
(0.5, "Memory-efficient not needed".to_string())
}
}
SdpaBackend::Math => {
if seq_len <= 128 {
(0.8, "Math backend efficient for tiny sequences".to_string())
} else {
(0.3, "Math backend as fallback".to_string())
}
}
SdpaBackend::Metal => {
if self.hardware.device_type == DeviceType::Metal {
(0.9, "Native Metal implementation".to_string())
} else {
(0.0, "Metal not available".to_string())
}
}
SdpaBackend::Cpu => {
(0.1, "CPU fallback".to_string())
}
}
}
/// Estimate speedup for backend
fn estimate_speedup(&self, backend: SdpaBackend, input: &AttentionInputInfo) -> f32 {
let seq_len = input.seq_len_q.max(input.seq_len_kv);
match backend {
SdpaBackend::FlashAttention => {
if seq_len >= 4096 { 5.0 }
else if seq_len >= 2048 { 3.5 }
else if seq_len >= 1024 { 2.5 }
else if seq_len >= 512 { 1.8 }
else { 1.2 }
}
SdpaBackend::CuDnn => {
if seq_len <= 512 { 2.0 }
else { 1.5 }
}
SdpaBackend::MemoryEfficient => 1.3,
SdpaBackend::Metal => 2.5,
SdpaBackend::Math => 1.0,
SdpaBackend::Cpu => 0.1,
}
}
/// Estimate memory for backend
fn estimate_memory(&self, backend: SdpaBackend, input: &AttentionInputInfo) -> usize {
let base_memory = input.estimate_memory_bytes();
match backend {
SdpaBackend::FlashAttention => {
// FlashAttention uses O(N) instead of O(N^2) for attention matrix
let qkvo_size = base_memory / 2; // Q, K, V, O only
let softmax_lse = input.batch_size * input.num_heads * input.seq_len_q * 4;
qkvo_size + softmax_lse
}
SdpaBackend::MemoryEfficient => {
// Chunked, uses fraction of full attention matrix
base_memory / 4
}
_ => base_memory,
}
}
/// Get alternative backends ranked
fn get_alternatives(&self, input: &AttentionInputInfo) -> Vec<(SdpaBackend, f32)> {
self.get_available_backends(input)
.into_iter()
.map(|b| (b, self.score_backend(b, input).0))
.collect()
}
/// Record performance for auto-tuning
pub fn record_performance(
&self,
input: &AttentionInputInfo,
backend: SdpaBackend,
latency_us: f64,
memory_bytes: usize,
) {
if !self.config.enable_autotuning {
return;
}
let record = PerformanceRecord {
key: CacheKey {
batch_size: input.batch_size,
num_heads: input.num_heads,
seq_len_q: input.seq_len_q,
seq_len_kv: input.seq_len_kv,
head_dim: input.head_dim,
is_causal: input.is_causal,
},
backend,
latency_us,
memory_bytes,
};
let mut history = self.perf_history.write().unwrap();
history.push(record);
// Keep last 1000 records
if history.len() > 1000 {
history.remove(0);
}
}
/// Clear caches
pub fn clear_cache(&self) {
self.cache.write().unwrap().clear();
self.perf_history.write().unwrap().clear();
}
}
// =============================================================================
// Global Selector
// =============================================================================
static GLOBAL_SELECTOR: std::sync::OnceLock<SdpaBackendSelector> = std::sync::OnceLock::new();
fn init_global_selector() -> &'static SdpaBackendSelector {
GLOBAL_SELECTOR.get_or_init(|| SdpaBackendSelector::new(SdpaConfig::default()))
}
/// Get the global SDPA backend selector
pub fn get_selector() -> &'static SdpaBackendSelector {
init_global_selector()
}
/// Convenience function to select backend
pub fn select_backend(input: &AttentionInputInfo) -> BackendRecommendation {
get_selector().select(input)
}
// =============================================================================
// Tests
// =============================================================================
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_sdpa_backend_display() {
assert_eq!(format!("{}", SdpaBackend::FlashAttention), "FlashAttention");
assert_eq!(format!("{}", SdpaBackend::Math), "Math");
}
#[test]
fn test_hardware_capabilities_cpu() {
let hw = HardwareCapabilities::detect_cpu();
assert_eq!(hw.device_type, DeviceType::Cpu);
assert!(!hw.supports_flash_attention);
assert!(hw.supports_backend(SdpaBackend::Math));
}
#[test]
fn test_hardware_capabilities_cuda() {
let hw = HardwareCapabilities::detect_cuda(0);
assert_eq!(hw.device_type, DeviceType::Cuda);
assert!(hw.has_tensor_cores);
assert!(hw.supports_flash_attention);
}
#[test]
fn test_attention_input_info_memory() {
let input = AttentionInputInfo {
batch_size: 4,
num_heads: 32,
seq_len_q: 1024,
seq_len_kv: 1024,
head_dim: 64,
dtype: AttentionDType::Float16,
is_causal: true,
has_mask: false,
dropout: 0.0,
};
let memory = input.estimate_memory_bytes();
assert!(memory > 0);
}
#[test]
fn test_sdpa_config_default() {
let config = SdpaConfig::default();
assert!(config.preferred_backend.is_none());
assert!(config.enable_autotuning);
assert!(!config.deterministic);
}
#[test]
fn test_selector_debug_mode() {
let config = SdpaConfig {
debug_mode: true,
..Default::default()
};
let selector = SdpaBackendSelector::new(config);
let input = AttentionInputInfo {
batch_size: 4,
num_heads: 32,
seq_len_q: 1024,
seq_len_kv: 1024,
head_dim: 64,
dtype: AttentionDType::Float16,
is_causal: true,
has_mask: false,
dropout: 0.0,
};
let rec = selector.select(&input);
assert_eq!(rec.backend, SdpaBackend::Math);
}
#[test]
fn test_selector_long_sequence() {
let hw = HardwareCapabilities::detect_cuda(0);
let selector = SdpaBackendSelector::with_hardware(SdpaConfig::default(), hw);
let input = AttentionInputInfo {
batch_size: 4,
num_heads: 32,
seq_len_q: 8192,
seq_len_kv: 8192,
head_dim: 64,
dtype: AttentionDType::Float16,
is_causal: true,
has_mask: false,
dropout: 0.0,
};
let rec = selector.select(&input);
assert_eq!(rec.backend, SdpaBackend::FlashAttention);
assert!(rec.expected_speedup > 2.0);
}
#[test]
fn test_selector_short_sequence() {
let hw = HardwareCapabilities::detect_cuda(0);
let selector = SdpaBackendSelector::with_hardware(SdpaConfig::default(), hw);
let input = AttentionInputInfo {
batch_size: 1,
num_heads: 8,
seq_len_q: 64,
seq_len_kv: 64,
head_dim: 64,
dtype: AttentionDType::Float16,
is_causal: false,
has_mask: false,
dropout: 0.0,
};
let rec = selector.select(&input);
// Should prefer cuDNN or Math for short sequences
assert!(rec.backend == SdpaBackend::CuDnn || rec.backend == SdpaBackend::Math);
}
#[test]
fn test_selector_preferred_backend() {
let config = SdpaConfig {
preferred_backend: Some(SdpaBackend::MemoryEfficient),
..Default::default()
};
let hw = HardwareCapabilities::detect_cuda(0);
let selector = SdpaBackendSelector::with_hardware(config, hw);
let input = AttentionInputInfo {
batch_size: 4,
num_heads: 32,
seq_len_q: 2048,
seq_len_kv: 2048,
head_dim: 64,
dtype: AttentionDType::Float16,
is_causal: true,
has_mask: false,
dropout: 0.0,
};
let rec = selector.select(&input);
assert_eq!(rec.backend, SdpaBackend::MemoryEfficient);
}
#[test]
fn test_backend_recommendation() {
let rec = BackendRecommendation {
backend: SdpaBackend::FlashAttention,
confidence: 0.95,
expected_speedup: 3.5,
expected_memory: 1024 * 1024,
reason: "Test".to_string(),
alternatives: vec![(SdpaBackend::CuDnn, 0.7)],
};
assert_eq!(rec.backend, SdpaBackend::FlashAttention);
assert!(rec.confidence > 0.9);
}
#[test]
fn test_estimate_flops() {
let input = AttentionInputInfo {
batch_size: 4,
num_heads: 32,
seq_len_q: 1024,
seq_len_kv: 1024,
head_dim: 64,
dtype: AttentionDType::Float16,
is_causal: true,
has_mask: false,
dropout: 0.0,
};
let flops = input.estimate_flops();
assert!(flops > 0);
}
#[test]
fn test_record_performance() {
let selector = SdpaBackendSelector::new(SdpaConfig::default());
let input = AttentionInputInfo {
batch_size: 4,
num_heads: 32,
seq_len_q: 1024,
seq_len_kv: 1024,
head_dim: 64,
dtype: AttentionDType::Float16,
is_causal: true,
has_mask: false,
dropout: 0.0,
};
selector.record_performance(&input, SdpaBackend::FlashAttention, 100.0, 1024);
// No panic = success
}
}