1#![allow(dead_code)]
18
19use crate::common::{BiasCorrection, ParameterUpdate};
20use std::collections::HashMap;
21use trustformers_core::errors::{Result, TrustformersError};
22use trustformers_core::tensor::Tensor;
23use trustformers_core::traits::Optimizer;
24
25#[derive(Debug, Clone)]
27pub struct KernelFusionConfig {
28 pub compute_capability: (u32, u32),
30 pub warp_size: usize,
32 pub max_threads_per_block: usize,
34 pub shared_memory_size: usize,
36 pub mixed_precision: bool,
38 pub use_tensor_cores: bool,
40 pub coalescing_level: CoalescingLevel,
42}
43
44#[derive(Debug, Clone, Copy)]
46pub enum CoalescingLevel {
47 None,
49 Basic,
51 Advanced,
53 Optimal,
55}
56
57impl Default for KernelFusionConfig {
58 fn default() -> Self {
59 Self {
60 compute_capability: (7, 5), warp_size: 32,
62 max_threads_per_block: 1024,
63 shared_memory_size: 48 * 1024, mixed_precision: false,
65 use_tensor_cores: false,
66 coalescing_level: CoalescingLevel::Advanced,
67 }
68 }
69}
70
71impl KernelFusionConfig {
72 pub fn a100() -> Self {
74 Self {
75 compute_capability: (8, 0),
76 shared_memory_size: 164 * 1024, use_tensor_cores: true,
78 mixed_precision: true,
79 coalescing_level: CoalescingLevel::Optimal,
80 ..Default::default()
81 }
82 }
83
84 pub fn h100() -> Self {
86 Self {
87 compute_capability: (9, 0),
88 shared_memory_size: 228 * 1024, use_tensor_cores: true,
90 mixed_precision: true,
91 coalescing_level: CoalescingLevel::Optimal,
92 ..Default::default()
93 }
94 }
95
96 pub fn rtx4090() -> Self {
98 Self {
99 compute_capability: (8, 9),
100 shared_memory_size: 100 * 1024, use_tensor_cores: true,
102 mixed_precision: true,
103 coalescing_level: CoalescingLevel::Optimal,
104 ..Default::default()
105 }
106 }
107
108 pub fn optimal_block_size(&self, param_count: usize) -> usize {
110 let warp_aligned = param_count.div_ceil(self.warp_size) * self.warp_size;
111 warp_aligned.min(self.max_threads_per_block)
112 }
113
114 pub fn memory_alignment(&self) -> usize {
116 match self.coalescing_level {
117 CoalescingLevel::None => 4, CoalescingLevel::Basic => 32, CoalescingLevel::Advanced => 128, CoalescingLevel::Optimal => 256, }
122 }
123}
124
125#[derive(Debug)]
135pub struct FusedAdamState {
136 fused_buffers: HashMap<String, FusedParameterBuffer>,
138 config: KernelFusionConfig,
140 step: usize,
142 planned_layout_bytes: usize,
146}
147
148#[derive(Debug)]
150struct FusedParameterBuffer {
151 id: String,
153 size: usize,
155 layout_offset: usize, stride: usize,
159 mixed_precision: bool,
161}
162
163impl FusedParameterBuffer {
164 fn new(id: String, size: usize, config: &KernelFusionConfig) -> Self {
166 let alignment = config.memory_alignment();
167 let stride = (size * std::mem::size_of::<f32>()).div_ceil(alignment) * alignment;
168
169 Self {
170 id,
171 size,
172 layout_offset: 0, stride,
174 mixed_precision: config.mixed_precision,
175 }
176 }
177
178 fn memory_requirement(&self) -> usize {
180 self.stride * 3
182 }
183}
184
185impl FusedAdamState {
186 pub fn new(config: KernelFusionConfig) -> Self {
188 Self {
189 fused_buffers: HashMap::new(),
190 config,
191 step: 0,
192 planned_layout_bytes: 0,
193 }
194 }
195
196 pub fn allocate_parameter(&mut self, id: String, size: usize) -> Result<()> {
206 let buffer = FusedParameterBuffer::new(id.clone(), size, &self.config);
207 let memory_required = buffer.memory_requirement();
208
209 self.check_layout_budget(memory_required)?;
210
211 self.planned_layout_bytes += memory_required;
212 self.fused_buffers.insert(id, buffer);
213
214 Ok(())
215 }
216
217 pub const MAX_LAYOUT_BYTES: usize = 16 * 1024 * 1024 * 1024;
219
220 fn check_layout_budget(&self, size: usize) -> Result<()> {
222 if size > Self::MAX_LAYOUT_BYTES {
223 return Err(TrustformersError::tensor_op_error(
224 "fused layout request exceeds the 16 GiB sanity bound",
225 "check_layout_budget",
226 ));
227 }
228
229 Ok(())
230 }
231
232 pub fn run_fused_adam_block(
238 &mut self,
239 param_id: &str,
240 param: &mut [f32],
241 grad: &[f32],
242 lr: f32,
243 betas: (f32, f32),
244 eps: f32,
245 weight_decay: f32,
246 ) -> Result<()> {
247 let buffer = self.fused_buffers.get(param_id).ok_or_else(|| {
248 TrustformersError::tensor_op_error("Parameter buffer not found", "run_fused_adam_block")
249 })?;
250
251 if param.len() != buffer.size || grad.len() != buffer.size {
252 return Err(TrustformersError::tensor_op_error(
253 "Size mismatch",
254 "run_fused_adam_block",
255 ));
256 }
257
258 self.step += 1;
259
260 let block_size = self.config.optimal_block_size(buffer.size);
262 let grid_size = buffer.size.div_ceil(block_size);
263
264 self.simulate_fused_adam_kernel(
266 param,
267 grad,
268 buffer,
269 lr,
270 betas,
271 eps,
272 weight_decay,
273 block_size,
274 grid_size,
275 )?;
276
277 Ok(())
278 }
279
280 fn simulate_fused_adam_kernel(
282 &self,
283 param: &mut [f32],
284 grad: &[f32],
285 buffer: &FusedParameterBuffer,
286 lr: f32,
287 betas: (f32, f32),
288 eps: f32,
289 weight_decay: f32,
290 block_size: usize,
291 grid_size: usize,
292 ) -> Result<()> {
293 let (bias_correction1, bias_correction2) =
296 BiasCorrection::compute_adam_corrections(betas.0, betas.1, self.step);
297
298 for block_idx in 0..grid_size {
300 let start = block_idx * block_size;
301 let end = (start + block_size).min(buffer.size);
302
303 self.process_fused_block(
304 &mut param[start..end],
305 &grad[start..end],
306 lr,
307 betas,
308 bias_correction1,
309 bias_correction2,
310 eps,
311 weight_decay,
312 );
313 }
314
315 Ok(())
316 }
317
318 #[inline]
320 fn process_fused_block(
321 &self,
322 param_block: &mut [f32],
323 grad_block: &[f32],
324 lr: f32,
325 betas: (f32, f32),
326 bias_correction1: f32,
327 bias_correction2: f32,
328 eps: f32,
329 weight_decay: f32,
330 ) {
331 let warp_size = self.config.warp_size;
333 let num_warps = param_block.len().div_ceil(warp_size);
334
335 for warp_idx in 0..num_warps {
336 let warp_start = warp_idx * warp_size;
337 let warp_end = (warp_start + warp_size).min(param_block.len());
338
339 self.process_warp(
340 &mut param_block[warp_start..warp_end],
341 &grad_block[warp_start..warp_end],
342 lr,
343 betas,
344 bias_correction1,
345 bias_correction2,
346 eps,
347 weight_decay,
348 );
349 }
350 }
351
352 #[inline]
354 fn process_warp(
355 &self,
356 param_warp: &mut [f32],
357 grad_warp: &[f32],
358 lr: f32,
359 betas: (f32, f32),
360 bias_correction1: f32,
361 bias_correction2: f32,
362 eps: f32,
363 weight_decay: f32,
364 ) {
365 for i in 0..param_warp.len() {
369 let grad_val = grad_warp[i] + weight_decay * param_warp[i];
370
371 let mut momentum = 0.0f32; let mut variance = 0.0f32; ParameterUpdate::update_ema(&mut momentum, grad_val, betas.0);
377 ParameterUpdate::update_ema(&mut variance, grad_val * grad_val, betas.1);
378
379 let m_hat = momentum / bias_correction1;
381 let v_hat = variance / bias_correction2;
382
383 ParameterUpdate::adam_update(&mut param_warp[i], lr, m_hat, v_hat, eps);
384
385 }
387 }
388
389 pub fn launch_multi_param_kernel(
391 &mut self,
392 params: Vec<(&str, &mut [f32], &[f32])>,
393 lr: f32,
394 betas: (f32, f32),
395 eps: f32,
396 weight_decay: f32,
397 ) -> Result<()> {
398 if params.is_empty() {
399 return Ok(());
400 }
401
402 let total_elements: usize = params.iter().map(|(_, p, _)| p.len()).sum();
404 let block_size = self.config.optimal_block_size(total_elements);
405 let _grid_size = total_elements.div_ceil(block_size);
406
407 for (param_id, param, grad) in params {
409 self.run_fused_adam_block(param_id, param, grad, lr, betas, eps, weight_decay)?;
410 }
411
412 Ok(())
413 }
414
415 pub fn fused_layout_stats(&self) -> FusedLayoutStats {
417 let total_buffers = self.fused_buffers.len();
418 let total_elements: usize = self.fused_buffers.values().map(|b| b.size).sum();
419
420 FusedLayoutStats {
421 planned_layout_bytes: self.planned_layout_bytes,
422 num_parameter_buffers: total_buffers,
423 total_parameter_elements: total_elements,
424 memory_efficiency: self.calculate_memory_efficiency(),
425 kernel_fusion_config: self.config.clone(),
426 }
427 }
428
429 fn calculate_memory_efficiency(&self) -> f32 {
431 if self.planned_layout_bytes == 0 {
432 return 1.0;
433 }
434
435 let actual_data_size: usize = self.fused_buffers.values()
436 .map(|b| b.size * std::mem::size_of::<f32>() * 3) .sum();
438
439 actual_data_size as f32 / self.planned_layout_bytes as f32
440 }
441}
442
443#[derive(Debug, Clone)]
447pub struct FusedLayoutStats {
448 pub planned_layout_bytes: usize,
451 pub num_parameter_buffers: usize,
453 pub total_parameter_elements: usize,
455 pub memory_efficiency: f32,
457 pub kernel_fusion_config: KernelFusionConfig,
459}
460
461impl FusedLayoutStats {
462 pub fn memory_bandwidth_utilization(&self, peak_bandwidth_gb_s: f32) -> f32 {
464 let bytes_per_update = self.total_parameter_elements * std::mem::size_of::<f32>() * 6; let theoretical_bandwidth = bytes_per_update as f32 / 1e9; (theoretical_bandwidth / peak_bandwidth_gb_s).min(1.0)
469 }
470
471 pub fn optimization_suggestions(&self) -> Vec<String> {
473 let mut suggestions = Vec::new();
474
475 if self.memory_efficiency < 0.8 {
476 suggestions.push("Poor memory efficiency; review alignment and coalescing".to_string());
477 }
478
479 if self.num_parameter_buffers > 1000 {
480 suggestions.push("Many small buffers; consider parameter grouping".to_string());
481 }
482
483 let compute_capability = self.kernel_fusion_config.compute_capability;
484 if compute_capability.0 < 8 && self.kernel_fusion_config.use_tensor_cores {
485 suggestions.push("Tensor cores require compute capability 7.0+".to_string());
486 }
487
488 if !self.kernel_fusion_config.mixed_precision && compute_capability.0 >= 7 {
489 suggestions.push("Consider enabling mixed precision for newer GPUs".to_string());
490 }
491
492 if suggestions.is_empty() {
493 suggestions.push("GPU kernel fusion appears well optimized".to_string());
494 }
495
496 suggestions
497 }
498}
499
500#[derive(Debug)]
502pub struct KernelFusedAdam {
503 lr: f32,
505 betas: (f32, f32),
507 eps: f32,
509 weight_decay: f32,
511 gpu_state: FusedAdamState,
513 params: crate::param_id::ParamRegistry,
518}
519
520impl KernelFusedAdam {
521 pub fn new(lr: f32, betas: (f32, f32), eps: f32, weight_decay: f32) -> Self {
523 Self::with_config(lr, betas, eps, weight_decay, KernelFusionConfig::default())
524 }
525
526 pub fn with_config(
528 lr: f32,
529 betas: (f32, f32),
530 eps: f32,
531 weight_decay: f32,
532 config: KernelFusionConfig,
533 ) -> Self {
534 Self {
535 lr,
536 betas,
537 eps,
538 weight_decay,
539 gpu_state: FusedAdamState::new(config),
540 params: crate::param_id::ParamRegistry::new(),
541 }
542 }
543
544 pub fn for_a100(lr: f32, betas: (f32, f32), eps: f32, weight_decay: f32) -> Self {
546 Self::with_config(lr, betas, eps, weight_decay, KernelFusionConfig::a100())
547 }
548
549 pub fn for_h100(lr: f32, betas: (f32, f32), eps: f32, weight_decay: f32) -> Self {
551 Self::with_config(lr, betas, eps, weight_decay, KernelFusionConfig::h100())
552 }
553
554 pub fn update_fused(&mut self, params: Vec<(&str, &mut [f32], &[f32])>) -> Result<()> {
556 self.gpu_state.launch_multi_param_kernel(
557 params,
558 self.lr,
559 self.betas,
560 self.eps,
561 self.weight_decay,
562 )
563 }
564
565 pub fn gpu_stats(&self) -> FusedLayoutStats {
567 self.gpu_state.fused_layout_stats()
568 }
569}
570
571impl Optimizer for KernelFusedAdam {
572 fn update(&mut self, parameter: &mut Tensor, grad: &Tensor) -> Result<()> {
573 match (parameter, grad) {
574 (Tensor::F32(param), Tensor::F32(grad_arr)) => {
575 let param_id = self.params.key_for_addr(param.as_ptr() as usize, param.len())?;
576
577 if !self.gpu_state.fused_buffers.contains_key(¶m_id) {
579 self.gpu_state.allocate_parameter(param_id.clone(), param.len())?;
580 }
581
582 self.gpu_state.run_fused_adam_block(
583 ¶m_id,
584 param.as_slice_mut().ok_or_else(|| {
585 TrustformersError::invalid_state(
586 "param tensor should have contiguous layout".to_string(),
587 )
588 })?,
589 grad_arr.as_slice().ok_or_else(|| {
590 TrustformersError::invalid_state(
591 "gradient tensor should have contiguous layout".to_string(),
592 )
593 })?,
594 self.lr,
595 self.betas,
596 self.eps,
597 self.weight_decay,
598 )
599 },
600 _ => Err(TrustformersError::tensor_op_error(
601 "Unsupported tensor types for KernelFusedAdam",
602 "update",
603 )),
604 }
605 }
606
607 fn zero_grad(&mut self) {
608 }
610
611 fn step(&mut self) {
612 }
614
615 fn get_lr(&self) -> f32 {
616 self.lr
617 }
618
619 fn set_lr(&mut self, lr: f32) {
620 self.lr = lr;
621 }
622}
623
624#[cfg(test)]
625mod tests {
626 use super::*;
627
628 #[test]
629 fn test_kernel_fusion_config() {
630 let config = KernelFusionConfig::default();
631 assert_eq!(config.warp_size, 32);
632 assert_eq!(config.compute_capability, (7, 5));
633
634 let a100_config = KernelFusionConfig::a100();
635 assert_eq!(a100_config.compute_capability, (8, 0));
636 assert!(a100_config.use_tensor_cores);
637
638 let block_size = config.optimal_block_size(1000);
639 assert!(block_size > 0);
640 assert!(block_size % config.warp_size == 0);
641 }
642
643 #[test]
644 fn test_fused_gpu_state() {
645 let config = KernelFusionConfig::default();
646 let mut state = FusedAdamState::new(config);
647
648 assert_eq!(state.planned_layout_bytes, 0);
649
650 state
651 .allocate_parameter("param1".to_string(), 1000)
652 .expect("Operation failed in test");
653 assert!(state.planned_layout_bytes > 0);
654 assert!(state.fused_buffers.contains_key("param1"));
655 }
656
657 #[test]
658 fn test_kernel_fused_adam() {
659 let optimizer = KernelFusedAdam::new(1e-3, (0.9, 0.999), 1e-8, 0.01);
660 assert_eq!(optimizer.get_lr(), 1e-3);
661 assert_eq!(optimizer.betas, (0.9, 0.999));
662
663 let stats = optimizer.gpu_stats();
664 assert_eq!(stats.num_parameter_buffers, 0);
665 assert_eq!(stats.total_parameter_elements, 0);
666 }
667
668 #[test]
669 fn test_fused_layout_stats() {
670 let config = KernelFusionConfig::a100();
671 let mut state = FusedAdamState::new(config);
672
673 state
674 .allocate_parameter("param1".to_string(), 1000)
675 .expect("Operation failed in test");
676 state
677 .allocate_parameter("param2".to_string(), 2000)
678 .expect("Operation failed in test");
679
680 let stats = state.fused_layout_stats();
681 assert_eq!(stats.num_parameter_buffers, 2);
682 assert_eq!(stats.total_parameter_elements, 3000);
683 assert!(stats.memory_efficiency > 0.0);
684 assert!(stats.memory_efficiency <= 1.0);
685
686 let suggestions = stats.optimization_suggestions();
687 assert!(!suggestions.is_empty());
688 }
689
690 #[test]
691 fn test_memory_alignment() {
692 let config = KernelFusionConfig::default();
693 let alignment = config.memory_alignment();
694 assert!(alignment > 0);
695 assert!(alignment.is_power_of_two());
696
697 let optimal_config = KernelFusionConfig {
698 coalescing_level: CoalescingLevel::Optimal,
699 ..Default::default()
700 };
701 assert!(optimal_config.memory_alignment() >= config.memory_alignment());
702 }
703
704 #[test]
705 fn test_bandwidth_utilization() {
706 let stats = FusedLayoutStats {
707 planned_layout_bytes: 1024 * 1024,
708 num_parameter_buffers: 10,
709 total_parameter_elements: 10000,
710 memory_efficiency: 0.9,
711 kernel_fusion_config: KernelFusionConfig::a100(),
712 };
713
714 let utilization = stats.memory_bandwidth_utilization(1555.0); assert!(utilization >= 0.0);
716 assert!(utilization <= 1.0);
717 }
718
719 #[test]
720 fn test_specialized_configs() {
721 let a100_opt = KernelFusedAdam::for_a100(1e-3, (0.9, 0.999), 1e-8, 0.01);
722 let h100_opt = KernelFusedAdam::for_h100(1e-3, (0.9, 0.999), 1e-8, 0.01);
723
724 let a100_stats = a100_opt.gpu_stats();
725 let h100_stats = h100_opt.gpu_stats();
726
727 assert_eq!(a100_stats.kernel_fusion_config.compute_capability, (8, 0));
728 assert_eq!(h100_stats.kernel_fusion_config.compute_capability, (9, 0));
729 assert!(
730 h100_stats.kernel_fusion_config.shared_memory_size
731 > a100_stats.kernel_fusion_config.shared_memory_size
732 );
733 }
734}