Skip to main content

trustformers_optim/
kernel_fusion.rs

1//! GPU kernel fusion optimizations for high-performance optimization.
2//!
3//! This module provides fused kernels that combine multiple optimization operations
4//! into single GPU kernels, reducing memory bandwidth requirements and improving
5//! performance through reduced kernel launch overhead.
6//!
7//! # Key Features
8//!
9//! - **Fused Adam Kernels**: Combine momentum, variance, and parameter updates
10//! - **Multi-Parameter Fusion**: Process multiple parameters in single kernel
11//! - **Memory Coalescing**: Optimize memory access patterns for GPU
12//! - **Warp-Level Optimizations**: Leverage GPU warp-level primitives
13//! - **Mixed Precision Support**: Efficient FP16/FP32 mixed precision
14
15// reason: research-stage module — reserved API/scaffolding fields and methods
16// retained intentionally for in-progress features; not yet on active call paths.
17#![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/// Configuration for GPU kernel fusion optimization.
26#[derive(Debug, Clone)]
27pub struct KernelFusionConfig {
28    /// Target GPU compute capability (e.g., 7.5 for V100, 8.0 for A100)
29    pub compute_capability: (u32, u32),
30    /// Warp size (typically 32 for NVIDIA GPUs)
31    pub warp_size: usize,
32    /// Maximum threads per block
33    pub max_threads_per_block: usize,
34    /// Shared memory size per block in bytes
35    pub shared_memory_size: usize,
36    /// Enable mixed precision (FP16/FP32) kernels
37    pub mixed_precision: bool,
38    /// Enable tensor core operations where possible
39    pub use_tensor_cores: bool,
40    /// Memory coalescing optimization level
41    pub coalescing_level: CoalescingLevel,
42}
43
44/// Memory coalescing optimization levels.
45#[derive(Debug, Clone, Copy)]
46pub enum CoalescingLevel {
47    /// No coalescing optimization
48    None,
49    /// Basic coalescing (align to 32-byte boundaries)
50    Basic,
51    /// Advanced coalescing (align to 128-byte boundaries)
52    Advanced,
53    /// Optimal coalescing (full cache line utilization)
54    Optimal,
55}
56
57impl Default for KernelFusionConfig {
58    fn default() -> Self {
59        Self {
60            compute_capability: (7, 5), // V100 baseline
61            warp_size: 32,
62            max_threads_per_block: 1024,
63            shared_memory_size: 48 * 1024, // 48KB
64            mixed_precision: false,
65            use_tensor_cores: false,
66            coalescing_level: CoalescingLevel::Advanced,
67        }
68    }
69}
70
71impl KernelFusionConfig {
72    /// Creates configuration for A100 GPUs.
73    pub fn a100() -> Self {
74        Self {
75            compute_capability: (8, 0),
76            shared_memory_size: 164 * 1024, // 164KB
77            use_tensor_cores: true,
78            mixed_precision: true,
79            coalescing_level: CoalescingLevel::Optimal,
80            ..Default::default()
81        }
82    }
83
84    /// Creates configuration for H100 GPUs.
85    pub fn h100() -> Self {
86        Self {
87            compute_capability: (9, 0),
88            shared_memory_size: 228 * 1024, // 228KB
89            use_tensor_cores: true,
90            mixed_precision: true,
91            coalescing_level: CoalescingLevel::Optimal,
92            ..Default::default()
93        }
94    }
95
96    /// Creates configuration for RTX 4090.
97    pub fn rtx4090() -> Self {
98        Self {
99            compute_capability: (8, 9),
100            shared_memory_size: 100 * 1024, // 100KB
101            use_tensor_cores: true,
102            mixed_precision: true,
103            coalescing_level: CoalescingLevel::Optimal,
104            ..Default::default()
105        }
106    }
107
108    /// Gets optimal block size for given parameter count.
109    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    /// Gets memory alignment requirement based on coalescing level.
115    pub fn memory_alignment(&self) -> usize {
116        match self.coalescing_level {
117            CoalescingLevel::None => 4,       // 4 bytes (1 float)
118            CoalescingLevel::Basic => 32,     // 32 bytes
119            CoalescingLevel::Advanced => 128, // 128 bytes
120            CoalescingLevel::Optimal => 256,  // 256 bytes (cache line)
121        }
122    }
123}
124
125/// Interleaved (parameter, momentum, variance) layout used by the fused CPU update.
126///
127/// # This is CPU code
128///
129/// Despite the "kernel fusion" vocabulary, nothing here touches a GPU: there is no
130/// allocation, no kernel launch and no device memory. What the module really provides
131/// is a *blocked, cache-friendly, vectorizable* Adam over an interleaved layout, and
132/// the accounting below describes that layout — see
133/// `FusedAdamState::planned_layout_bytes`.
134#[derive(Debug)]
135pub struct FusedAdamState {
136    /// Fused parameter data (parameters, momentum, variance interleaved)
137    fused_buffers: HashMap<String, FusedParameterBuffer>,
138    /// Kernel fusion configuration
139    config: KernelFusionConfig,
140    /// Current optimization step
141    step: usize,
142    /// Bytes the interleaved layout plan occupies.
143    ///
144    /// This is a *plan*, not an allocation: buffers are described, never malloc'd.
145    planned_layout_bytes: usize,
146}
147
148/// Fused parameter buffer with optimized memory layout.
149#[derive(Debug)]
150struct FusedParameterBuffer {
151    /// Parameter ID
152    id: String,
153    /// Number of parameter elements
154    size: usize,
155    /// Byte offset of this buffer inside the interleaved layout plan
156    layout_offset: usize, // In real implementation, this would be a CUDA device pointer
157    /// Memory layout stride for coalescing
158    stride: usize,
159    /// Whether buffer uses mixed precision
160    mixed_precision: bool,
161}
162
163impl FusedParameterBuffer {
164    /// Creates a new fused parameter buffer.
165    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, // Would be allocated via CUDA malloc
173            stride,
174            mixed_precision: config.mixed_precision,
175        }
176    }
177
178    /// Gets the total memory required for this buffer.
179    fn memory_requirement(&self) -> usize {
180        // 3 arrays: parameters, momentum, variance
181        self.stride * 3
182    }
183}
184
185impl FusedAdamState {
186    /// Creates a new fused GPU state.
187    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    /// Registers a parameter in the interleaved layout plan.
197    ///
198    /// No memory is allocated: the buffer records the parameter's size, alignment and
199    /// offset so the fused update can walk it in cache-friendly blocks.
200    ///
201    /// # Errors
202    ///
203    /// Returns an error when the requested layout exceeds
204    /// [`FusedAdamState::MAX_LAYOUT_BYTES`].
205    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    /// Largest layout this state will plan for, as a sanity bound on caller input.
218    pub const MAX_LAYOUT_BYTES: usize = 16 * 1024 * 1024 * 1024;
219
220    /// Rejects a layout request that is implausibly large.
221    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    /// Runs the blocked, vectorizable Adam update for one parameter.
233    ///
234    /// The work is done on the CPU in `optimal_block_size`-sized blocks over the
235    /// interleaved layout; the block/grid arithmetic below mirrors the tiling a GPU
236    /// kernel would use, but no kernel is launched.
237    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        // Calculate kernel launch parameters
261        let block_size = self.config.optimal_block_size(buffer.size);
262        let grid_size = buffer.size.div_ceil(block_size);
263
264        // Blocked CPU execution over the same tiling a GPU kernel would use.
265        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    /// Simulates the fused Adam kernel execution.
281    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        // This simulates what would happen in the GPU kernel
294
295        let (bias_correction1, bias_correction2) =
296            BiasCorrection::compute_adam_corrections(betas.0, betas.1, self.step);
297
298        // Process in blocks to simulate GPU execution
299        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    /// Processes a block in the fused kernel.
319    #[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        // Simulate warp-level operations
332        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    /// Processes a warp's worth of elements.
353    #[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        // In a real GPU kernel, this would use warp-level primitives
366        // and shared memory for optimization
367
368        for i in 0..param_warp.len() {
369            let grad_val = grad_warp[i] + weight_decay * param_warp[i];
370
371            // Simulate loading momentum and variance from global memory
372            let mut momentum = 0.0f32; // Would load from GPU memory
373            let mut variance = 0.0f32; // Would load from GPU memory
374
375            // Fused momentum and variance update
376            ParameterUpdate::update_ema(&mut momentum, grad_val, betas.0);
377            ParameterUpdate::update_ema(&mut variance, grad_val * grad_val, betas.1);
378
379            // Fused bias correction and parameter update
380            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            // Store momentum and variance back to GPU memory
386        }
387    }
388
389    /// Launches multi-parameter fused kernel.
390    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        // Calculate total workload
403        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        // In real implementation, this would launch a multi-parameter kernel
408        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    /// Statistics about the interleaved layout plan.
416    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    /// Fraction of the planned layout occupied by real data (the rest is padding).
430    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) // param + momentum + variance
437            .sum();
438
439        actual_data_size as f32 / self.planned_layout_bytes as f32
440    }
441}
442
443/// Statistics about the interleaved layout used by the fused CPU update.
444///
445/// These describe a *layout plan*; nothing is allocated on a device.
446#[derive(Debug, Clone)]
447pub struct FusedLayoutStats {
448    /// Total GPU memory used in bytes
449    /// Bytes the interleaved layout plan occupies (padding included).
450    pub planned_layout_bytes: usize,
451    /// Number of parameter buffers
452    pub num_parameter_buffers: usize,
453    /// Total parameter elements across all buffers
454    pub total_parameter_elements: usize,
455    /// Memory efficiency (0.0 to 1.0)
456    pub memory_efficiency: f32,
457    /// Kernel fusion configuration
458    pub kernel_fusion_config: KernelFusionConfig,
459}
460
461impl FusedLayoutStats {
462    /// Calculates theoretical memory bandwidth utilization.
463    pub fn memory_bandwidth_utilization(&self, peak_bandwidth_gb_s: f32) -> f32 {
464        // Simplified calculation based on parameter count and update frequency
465        let bytes_per_update = self.total_parameter_elements * std::mem::size_of::<f32>() * 6; // Read: param, momentum, variance; Write: param, momentum, variance
466        let theoretical_bandwidth = bytes_per_update as f32 / 1e9; // Convert to GB
467
468        (theoretical_bandwidth / peak_bandwidth_gb_s).min(1.0)
469    }
470
471    /// Suggests optimization strategies.
472    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/// Kernel fusion optimized Adam optimizer.
501#[derive(Debug)]
502pub struct KernelFusedAdam {
503    /// Learning rate
504    lr: f32,
505    /// Beta coefficients
506    betas: (f32, f32),
507    /// Epsilon for numerical stability
508    eps: f32,
509    /// Weight decay coefficient
510    weight_decay: f32,
511    /// Fused GPU state
512    gpu_state: FusedAdamState,
513    /// Stable parameter identity registry (see [`crate::param_id`]).
514    ///
515    /// Replaces heap-address keys, which change in every process and so made
516    /// checkpoint resume silently restore nothing.
517    params: crate::param_id::ParamRegistry,
518}
519
520impl KernelFusedAdam {
521    /// Creates a new kernel fused Adam optimizer.
522    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    /// Creates optimizer with specific GPU configuration.
527    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    /// Creates A100-optimized variant.
545    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    /// Creates H100-optimized variant.
550    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    /// Updates multiple parameters using fused kernels.
555    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    /// Gets GPU performance statistics.
566    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                // Ensure parameter buffer is allocated
578                if !self.gpu_state.fused_buffers.contains_key(&param_id) {
579                    self.gpu_state.allocate_parameter(param_id.clone(), param.len())?;
580                }
581
582                self.gpu_state.run_fused_adam_block(
583                    &param_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        // No explicit gradient storage
609    }
610
611    fn step(&mut self) {
612        // Step counter is handled in kernel launches
613    }
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); // A100 peak bandwidth
715        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}