#![allow(clippy::manual_div_ceil)]
use crate::webgpu::error::ComputeError;
use std::collections::HashMap;
#[cfg(feature = "gpu")]
use crate::webgpu::shaders::webgpu_shaders::ShaderType;
#[cfg(not(feature = "gpu"))]
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum ShaderType {
MatrixVectorMultiply,
BatchMatrixVectorMultiply,
Neural,
Compute,
Training,
}
#[derive(Debug, Clone)]
pub struct GpuCapabilities {
pub max_workgroup_size: [u32; 3],
pub max_threads_per_workgroup: u32,
pub preferred_workgroup_size: u32,
pub memory_bandwidth_gbps: f32,
pub compute_units: u32,
pub max_shared_memory_bytes: u32,
pub supports_subgroups: bool,
pub subgroup_size: u32,
}
#[derive(Debug, Clone)]
pub struct KernelConfig {
pub workgroup_size: [u32; 3],
pub num_workgroups: [u32; 3],
pub use_vectorized_access: bool,
pub tile_size: u32,
pub elements_per_thread: u32,
pub estimated_gflops: f32,
}
#[derive(Debug, Clone)]
pub struct OptimizationMetrics {
pub memory_utilization: f32,
pub compute_utilization: f32,
pub occupancy: f32,
pub memory_efficiency: f32,
pub estimated_execution_time_us: f32,
}
#[derive(Debug)]
pub struct KernelOptimizer {
gpu_capabilities: GpuCapabilities,
config_cache: HashMap<(ShaderType, usize), KernelConfig>,
performance_history: HashMap<(ShaderType, usize), Vec<OptimizationMetrics>>,
}
impl KernelOptimizer {
pub fn new(gpu_capabilities: GpuCapabilities) -> Self {
Self {
gpu_capabilities,
config_cache: HashMap::new(),
performance_history: HashMap::new(),
}
}
pub fn with_default_capabilities() -> Self {
let default_caps = GpuCapabilities {
max_workgroup_size: [256, 256, 64],
max_threads_per_workgroup: 1024,
preferred_workgroup_size: 256,
memory_bandwidth_gbps: 500.0, compute_units: 20,
max_shared_memory_bytes: 32768,
supports_subgroups: true,
subgroup_size: 32,
};
Self::new(default_caps)
}
pub fn optimize_matrix_vector_multiply(
&mut self,
rows: usize,
cols: usize,
) -> Result<KernelConfig, ComputeError> {
let cache_key = (ShaderType::MatrixVectorMultiply, rows * cols);
if let Some(cached_config) = self.config_cache.get(&cache_key) {
return Ok(cached_config.clone());
}
let config = self.calculate_matrix_vector_config(rows, cols)?;
self.config_cache.insert(cache_key, config.clone());
Ok(config)
}
pub fn optimize_batch_matrix_vector(
&mut self,
rows: usize,
cols: usize,
batch_size: usize,
) -> Result<KernelConfig, ComputeError> {
let cache_key = (
ShaderType::BatchMatrixVectorMultiply,
rows * cols * batch_size,
);
if let Some(cached_config) = self.config_cache.get(&cache_key) {
return Ok(cached_config.clone());
}
let config = self.calculate_batch_matrix_vector_config(rows, cols, batch_size)?;
self.config_cache.insert(cache_key, config.clone());
Ok(config)
}
pub fn optimize_activation_function(
&mut self,
shader_type: ShaderType,
vector_size: usize,
) -> Result<KernelConfig, ComputeError> {
let cache_key = (shader_type.clone(), vector_size);
if let Some(cached_config) = self.config_cache.get(&cache_key) {
return Ok(cached_config.clone());
}
let config = self.calculate_activation_config(shader_type.clone(), vector_size)?;
self.config_cache.insert(cache_key, config.clone());
Ok(config)
}
pub fn record_performance(
&mut self,
shader_type: ShaderType,
data_size: usize,
metrics: OptimizationMetrics,
) {
let key = (shader_type, data_size);
let history = self.performance_history.entry(key.clone()).or_default();
history.push(metrics);
if let Some(history) = self.performance_history.get_mut(&key) {
if history.len() > 10 {
history.remove(0);
}
}
}
pub fn predict_performance(
&self,
shader_type: &ShaderType,
data_size: usize,
) -> Option<OptimizationMetrics> {
let key = (shader_type.clone(), data_size);
if let Some(history) = self.performance_history.get(&key) {
if !history.is_empty() {
let count = history.len() as f32;
let avg_memory_util =
history.iter().map(|m| m.memory_utilization).sum::<f32>() / count;
let avg_compute_util =
history.iter().map(|m| m.compute_utilization).sum::<f32>() / count;
let avg_occupancy = history.iter().map(|m| m.occupancy).sum::<f32>() / count;
let avg_memory_eff =
history.iter().map(|m| m.memory_efficiency).sum::<f32>() / count;
let avg_exec_time = history
.iter()
.map(|m| m.estimated_execution_time_us)
.sum::<f32>()
/ count;
return Some(OptimizationMetrics {
memory_utilization: avg_memory_util,
compute_utilization: avg_compute_util,
occupancy: avg_occupancy,
memory_efficiency: avg_memory_eff,
estimated_execution_time_us: avg_exec_time,
});
}
}
None
}
pub fn auto_tune_workgroup_size(
&mut self,
shader_type: ShaderType,
data_size: usize,
) -> Result<[u32; 3], ComputeError> {
let test_sizes = vec![64, 128, 256, 512, 1024];
let mut best_config = [256, 1, 1]; let mut best_score = 0.0f32;
for size in test_sizes {
if size <= self.gpu_capabilities.max_threads_per_workgroup {
let config = [size, 1, 1];
let score = self.evaluate_workgroup_config(&shader_type, data_size, config);
if score > best_score {
best_score = score;
best_config = config;
}
}
}
Ok(best_config)
}
pub fn clear_caches(&mut self) {
self.config_cache.clear();
self.performance_history.clear();
}
pub fn get_gpu_capabilities(&self) -> &GpuCapabilities {
&self.gpu_capabilities
}
fn calculate_matrix_vector_config(
&self,
rows: usize,
_cols: usize,
) -> Result<KernelConfig, ComputeError> {
let workgroup_size = if rows >= 1024 {
[256, 1, 1] } else if rows >= 256 {
[128, 1, 1] } else {
[64, 1, 1] };
let num_workgroups = [
((rows as u32 + workgroup_size[0] - 1) / workgroup_size[0]),
1,
1,
];
Ok(KernelConfig {
workgroup_size,
num_workgroups,
use_vectorized_access: true,
tile_size: 16,
elements_per_thread: 4,
estimated_gflops: self.estimate_matrix_vector_gflops(rows),
})
}
fn calculate_batch_matrix_vector_config(
&self,
rows: usize,
_cols: usize,
batch_size: usize,
) -> Result<KernelConfig, ComputeError> {
let total_work = rows * batch_size;
let workgroup_size = if total_work >= 4096 {
[16, 16, 1] } else if total_work >= 1024 {
[16, 8, 1] } else {
[8, 8, 1] };
let num_workgroups = [
((rows as u32 + workgroup_size[0] - 1) / workgroup_size[0]),
((batch_size as u32 + workgroup_size[1] - 1) / workgroup_size[1]),
1,
];
Ok(KernelConfig {
workgroup_size,
num_workgroups,
use_vectorized_access: true,
tile_size: 16,
elements_per_thread: 1,
estimated_gflops: self.estimate_batch_matrix_vector_gflops(rows, batch_size),
})
}
fn calculate_activation_config(
&self,
_shader_type: ShaderType,
vector_size: usize,
) -> Result<KernelConfig, ComputeError> {
let workgroup_size = if vector_size >= 2048 {
[256, 1, 1] } else if vector_size >= 512 {
[128, 1, 1]
} else {
[64, 1, 1]
};
let num_workgroups = [
((vector_size as u32 + workgroup_size[0] - 1) / workgroup_size[0]),
1,
1,
];
Ok(KernelConfig {
workgroup_size,
num_workgroups,
use_vectorized_access: true,
tile_size: 1, elements_per_thread: 1,
estimated_gflops: self.estimate_activation_gflops(vector_size),
})
}
fn evaluate_workgroup_config(
&self,
_shader_type: &ShaderType,
data_size: usize,
workgroup_config: [u32; 3],
) -> f32 {
let threads_per_workgroup = workgroup_config[0] * workgroup_config[1] * workgroup_config[2];
let num_workgroups = (data_size as u32 + threads_per_workgroup - 1) / threads_per_workgroup;
let max_workgroups_per_sm =
self.gpu_capabilities.max_threads_per_workgroup / threads_per_workgroup;
let occupancy = (max_workgroups_per_sm.min(8) as f32) / 8.0;
let memory_efficiency = if threads_per_workgroup >= 32 {
1.0
} else {
threads_per_workgroup as f32 / 32.0
};
let parallelism_score = (num_workgroups as f32)
.min(self.gpu_capabilities.compute_units as f32)
/ self.gpu_capabilities.compute_units as f32;
occupancy * 0.4 + memory_efficiency * 0.3 + parallelism_score * 0.3
}
fn estimate_matrix_vector_gflops(&self, rows: usize) -> f32 {
let base_gflops = self.gpu_capabilities.compute_units as f32 * 100.0; let problem_efficiency = if rows >= 1024 {
0.8
} else {
rows as f32 / 1024.0 * 0.8
};
base_gflops * problem_efficiency
}
fn estimate_batch_matrix_vector_gflops(&self, rows: usize, batch_size: usize) -> f32 {
let single_gflops = self.estimate_matrix_vector_gflops(rows);
let batch_efficiency = (batch_size as f32).min(16.0) / 16.0; single_gflops * batch_size as f32 * batch_efficiency
}
fn estimate_activation_gflops(&self, vector_size: usize) -> f32 {
let memory_bound_gflops = self.gpu_capabilities.memory_bandwidth_gbps * 4.0; let problem_efficiency = (vector_size as f32 / 1024.0).min(1.0);
memory_bound_gflops * problem_efficiency
}
}
impl Default for KernelOptimizer {
fn default() -> Self {
Self::with_default_capabilities()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_kernel_optimizer_creation() {
let optimizer = KernelOptimizer::with_default_capabilities();
assert_eq!(optimizer.gpu_capabilities.preferred_workgroup_size, 256);
}
#[test]
fn test_matrix_vector_optimization() {
let mut optimizer = KernelOptimizer::with_default_capabilities();
let config = optimizer
.optimize_matrix_vector_multiply(1024, 512)
.unwrap();
assert_eq!(config.workgroup_size[0], 256);
assert!(config.use_vectorized_access);
assert!(config.estimated_gflops > 0.0);
}
#[test]
fn test_performance_recording() {
let mut optimizer = KernelOptimizer::with_default_capabilities();
let metrics = OptimizationMetrics {
memory_utilization: 0.8,
compute_utilization: 0.9,
occupancy: 0.75,
memory_efficiency: 0.85,
estimated_execution_time_us: 100.0,
};
optimizer.record_performance(ShaderType::MatrixVectorMultiply, 1024, metrics);
let prediction = optimizer.predict_performance(&ShaderType::MatrixVectorMultiply, 1024);
assert!(prediction.is_some());
assert_eq!(prediction.unwrap().memory_utilization, 0.8);
}
}