use super::*;
use crate::webgpu::{ComputeContext, ComputeError};
use crate::webgpu::backend::{ComputeBackend, BackendSelector};
use crate::webgpu::memory::BufferHandle;
use num_traits::Float;
use std::sync::Arc;
use wgpu::util::DeviceExt;
pub struct GpuOptimizedAdam<T: Float + Send + Sync + Default + std::fmt::Debug + 'static> {
learning_rate: T,
beta1: T,
beta2: T,
epsilon: T,
weight_decay: T,
step: u32,
device: Arc<wgpu::Device>,
queue: Arc<wgpu::Queue>,
weight_buffers: Vec<wgpu::Buffer>,
gradient_buffers: Vec<wgpu::Buffer>,
m_moment_buffers: Vec<wgpu::Buffer>,
v_moment_buffers: Vec<wgpu::Buffer>,
activation_buffers: Vec<wgpu::Buffer>,
forward_pipeline: wgpu::ComputePipeline,
gradient_pipeline: wgpu::ComputePipeline,
adam_pipeline: wgpu::ComputePipeline,
forward_bind_groups: Vec<wgpu::BindGroup>,
gradient_bind_groups: Vec<wgpu::BindGroup>,
adam_bind_groups: Vec<wgpu::BindGroup>,
layer_sizes: Vec<usize>,
total_gpu_time_ms: f64,
kernel_launches: u64,
}
impl<T: Float + Send + Sync + Default + std::fmt::Debug + 'static> GpuOptimizedAdam<T> {
pub async fn new(
network: &Network<T>,
learning_rate: T,
device: Arc<wgpu::Device>,
queue: Arc<wgpu::Queue>,
) -> Result<Self, ComputeError> {
let layer_sizes: Vec<usize> = network.layers.iter()
.map(|layer| layer.neurons.iter().filter(|n| !n.is_bias).count())
.collect();
let shader_module = device.create_shader_module(wgpu::ShaderModuleDescriptor {
label: Some("Gradient Operations"),
source: wgpu::ShaderSource::Wgsl(
include_str!("../webgpu/shaders/gradient_operations.wgsl").into()
),
});
let adam_shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
label: Some("Adam Optimizer"),
source: wgpu::ShaderSource::Wgsl(
include_str!("../webgpu/shaders/adam_optimizer.wgsl").into()
),
});
let forward_pipeline = Self::create_forward_pipeline(&device, &shader_module);
let gradient_pipeline = Self::create_gradient_pipeline(&device, &shader_module);
let adam_pipeline = Self::create_adam_pipeline(&device, &adam_shader);
let mut weight_buffers = Vec::new();
let mut gradient_buffers = Vec::new();
let mut m_moment_buffers = Vec::new();
let mut v_moment_buffers = Vec::new();
let mut activation_buffers = Vec::new();
for i in 1..layer_sizes.len() {
let prev_size = layer_sizes[i-1];
let curr_size = layer_sizes[i];
let weight_count = prev_size * curr_size + curr_size;
let weights = Self::extract_layer_weights(network, i);
let weight_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
label: Some(&format!("Layer {} Weights", i)),
contents: bytemuck::cast_slice(&weights),
usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST,
});
weight_buffers.push(weight_buffer);
let gradient_buffer = device.create_buffer(&wgpu::BufferDescriptor {
label: Some(&format!("Layer {} Gradients", i)),
size: (weight_count * std::mem::size_of::<f32>()) as u64,
usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_SRC,
mapped_at_creation: false,
});
gradient_buffers.push(gradient_buffer);
let zeros = vec![0.0f32; weight_count];
let m_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
label: Some(&format!("Layer {} M Moments", i)),
contents: bytemuck::cast_slice(&zeros),
usage: wgpu::BufferUsages::STORAGE,
});
m_moment_buffers.push(m_buffer);
let v_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
label: Some(&format!("Layer {} V Moments", i)),
contents: bytemuck::cast_slice(&zeros),
usage: wgpu::BufferUsages::STORAGE,
});
v_moment_buffers.push(v_buffer);
let activation_buffer = device.create_buffer(&wgpu::BufferDescriptor {
label: Some(&format!("Layer {} Activations", i)),
size: (curr_size * std::mem::size_of::<f32>()) as u64,
usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST,
mapped_at_creation: false,
});
activation_buffers.push(activation_buffer);
}
let forward_bind_groups = Self::create_forward_bind_groups(
&device, &forward_pipeline, &weight_buffers, &activation_buffers
);
let gradient_bind_groups = Self::create_gradient_bind_groups(
&device, &gradient_pipeline, &weight_buffers, &gradient_buffers, &activation_buffers
);
let adam_bind_groups = Self::create_adam_bind_groups(
&device, &adam_pipeline, &weight_buffers, &gradient_buffers,
&m_moment_buffers, &v_moment_buffers
);
Ok(Self {
learning_rate,
beta1: T::from(0.9).unwrap(),
beta2: T::from(0.999).unwrap(),
epsilon: T::from(1e-8).unwrap(),
weight_decay: T::zero(),
step: 0,
device,
queue,
weight_buffers,
gradient_buffers,
m_moment_buffers,
v_moment_buffers,
activation_buffers,
forward_pipeline,
gradient_pipeline,
adam_pipeline,
forward_bind_groups,
gradient_bind_groups,
adam_bind_groups,
layer_sizes,
total_gpu_time_ms: 0.0,
kernel_launches: 0,
})
}
pub async fn train_epoch_gpu(
&mut self,
training_data: &TrainingData<T>,
) -> Result<T, ComputeError> {
let start_time = std::time::Instant::now();
self.step += 1;
let batch_size = training_data.inputs.len();
let mut total_error = T::zero();
let input_buffer = self.device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
label: Some("Input Batch"),
contents: bytemuck::cast_slice(&Self::flatten_batch(&training_data.inputs)),
usage: wgpu::BufferUsages::STORAGE,
});
let target_buffer = self.device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
label: Some("Target Batch"),
contents: bytemuck::cast_slice(&Self::flatten_batch(&training_data.outputs)),
usage: wgpu::BufferUsages::STORAGE,
});
let mut encoder = self.device.create_command_encoder(&wgpu::CommandEncoderDescriptor {
label: Some("Training Encoder"),
});
{
let mut compute_pass = encoder.begin_compute_pass(&wgpu::ComputePassDescriptor {
label: Some("Forward Pass"),
});
for (i, bind_group) in self.forward_bind_groups.iter().enumerate() {
compute_pass.set_pipeline(&self.forward_pipeline);
compute_pass.set_bind_group(0, bind_group, &[]);
let workgroup_count = ((self.layer_sizes[i+1] * batch_size) + 255) / 256;
compute_pass.dispatch_workgroups(workgroup_count as u32, 1, 1);
self.kernel_launches += 1;
}
}
{
let mut compute_pass = encoder.begin_compute_pass(&wgpu::ComputePassDescriptor {
label: Some("Gradient Computation"),
});
for (i, bind_group) in self.gradient_bind_groups.iter().enumerate().rev() {
compute_pass.set_pipeline(&self.gradient_pipeline);
compute_pass.set_bind_group(0, bind_group, &[]);
let workgroup_count = ((self.layer_sizes[i] * self.layer_sizes[i+1]) + 255) / 256;
compute_pass.dispatch_workgroups(workgroup_count as u32, 1, 1);
self.kernel_launches += 1;
}
}
{
let mut compute_pass = encoder.begin_compute_pass(&wgpu::ComputePassDescriptor {
label: Some("Adam Updates"),
});
for (i, bind_group) in self.adam_bind_groups.iter().enumerate() {
compute_pass.set_pipeline(&self.adam_pipeline);
compute_pass.set_bind_group(0, bind_group, &[]);
let weight_count = self.layer_sizes[i] * self.layer_sizes[i+1] + self.layer_sizes[i+1];
let workgroup_count = (weight_count + 63) / 64; compute_pass.dispatch_workgroups(workgroup_count as u32, 1, 1);
self.kernel_launches += 1;
}
}
self.queue.submit(std::iter::once(encoder.finish()));
let elapsed = start_time.elapsed();
self.total_gpu_time_ms += elapsed.as_secs_f64() * 1000.0;
Ok(total_error / T::from(batch_size).unwrap())
}
fn create_forward_pipeline(device: &wgpu::Device, shader: &wgpu::ShaderModule) -> wgpu::ComputePipeline {
todo!("Implement forward pipeline creation")
}
fn create_gradient_pipeline(device: &wgpu::Device, shader: &wgpu::ShaderModule) -> wgpu::ComputePipeline {
let layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
label: Some("Gradient Pipeline Layout"),
bind_group_layouts: &[],
push_constant_ranges: &[],
});
device.create_compute_pipeline(&wgpu::ComputePipelineDescriptor {
label: Some("Gradient Computation Pipeline"),
layout: Some(&layout),
module: shader,
entry_point: "weight_gradient_main",
})
}
fn create_adam_pipeline(device: &wgpu::Device, shader: &wgpu::ShaderModule) -> wgpu::ComputePipeline {
let layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
label: Some("Adam Pipeline Layout"),
bind_group_layouts: &[],
push_constant_ranges: &[],
});
device.create_compute_pipeline(&wgpu::ComputePipelineDescriptor {
label: Some("Adam Optimizer Pipeline"),
layout: Some(&layout),
module: shader,
entry_point: "adam_update",
})
}
fn extract_layer_weights(network: &Network<T>, layer_idx: usize) -> Vec<f32> {
let mut weights = Vec::new();
let layer = &network.layers[layer_idx];
for neuron in layer.neurons.iter().filter(|n| !n.is_bias) {
if !neuron.connections.is_empty() {
weights.push(neuron.connections[0].weight.to_f32().unwrap());
}
for conn in neuron.connections.iter().skip(1) {
weights.push(conn.weight.to_f32().unwrap());
}
}
weights
}
fn flatten_batch(batch: &[Vec<T>]) -> Vec<f32> {
let mut flattened = Vec::new();
for sample in batch {
for value in sample {
flattened.push(value.to_f32().unwrap());
}
}
flattened
}
fn create_forward_bind_groups(
device: &wgpu::Device,
pipeline: &wgpu::ComputePipeline,
weight_buffers: &[wgpu::Buffer],
activation_buffers: &[wgpu::Buffer],
) -> Vec<wgpu::BindGroup> {
Vec::new() }
fn create_gradient_bind_groups(
device: &wgpu::Device,
pipeline: &wgpu::ComputePipeline,
weight_buffers: &[wgpu::Buffer],
gradient_buffers: &[wgpu::Buffer],
activation_buffers: &[wgpu::Buffer],
) -> Vec<wgpu::BindGroup> {
Vec::new() }
fn create_adam_bind_groups(
device: &wgpu::Device,
pipeline: &wgpu::ComputePipeline,
weight_buffers: &[wgpu::Buffer],
gradient_buffers: &[wgpu::Buffer],
m_moment_buffers: &[wgpu::Buffer],
v_moment_buffers: &[wgpu::Buffer],
) -> Vec<wgpu::BindGroup> {
Vec::new() }
pub fn get_stats(&self) -> (f64, u64) {
(self.total_gpu_time_ms, self.kernel_launches)
}
}