Skip to main content

torsh_nn/core/
mod.rs

1//! Core module trait system for neural network modules
2//!
3//! This module provides the foundational Module trait and essential interfaces
4//! for all neural network components in ToRSh.
5
6pub mod module_ext;
7
8pub use module_ext::{ModuleExt, ParameterStats, ValidationReport};
9
10use torsh_core::device::DeviceType;
11use torsh_core::error::Result;
12use torsh_tensor::Tensor;
13
14// Conditional imports for std/no_std compatibility
15#[cfg(feature = "std")]
16use std::collections::HashMap;
17
18#[cfg(not(feature = "std"))]
19use hashbrown::HashMap;
20
21/// Base trait for all neural network modules
22///
23/// This trait provides the core interface for all neural network components,
24/// following PyTorch-compatible patterns for maximum interoperability.
25///
26/// # Design Philosophy
27///
28/// This trait is designed for maximum ergonomics while maintaining flexibility:
29/// - Most methods have sensible defaults to reduce boilerplate
30/// - Core functionality (forward, training mode) is required
31/// - Parameter management is streamlined with helper methods
32/// - Hook system is optional but well-integrated
33///
34/// # Required Methods
35///
36/// Only [`forward()`](Module::forward) must be implemented. All other methods have sensible defaults.
37///
38/// # Implementing a Custom Module
39///
40/// ## Basic Module (No Parameters)
41///
42/// ```rust
43/// use torsh_nn::{Module, ModuleBase};
44/// use torsh_tensor::Tensor;
45/// use torsh_core::error::Result;
46///
47/// /// Simple ReLU activation function
48/// struct MyReLU {
49///     base: ModuleBase,
50/// }
51///
52/// impl MyReLU {
53///     fn new() -> Self {
54///         Self {
55///             base: ModuleBase::new(),
56///         }
57///     }
58/// }
59///
60/// impl Module for MyReLU {
61///     fn forward(&self, input: &Tensor) -> Result<Tensor> {
62///         // Apply ReLU: max(0, x)
63///         input.relu()
64///     }
65///
66///     fn training(&self) -> bool {
67///         self.base.training()
68///     }
69///
70///     fn set_training(&mut self, training: bool) {
71///         self.base.set_training(training);
72///     }
73/// }
74/// ```
75///
76/// ## Module with Parameters
77///
78/// ```rust
79/// use torsh_nn::{Module, ModuleBase, Parameter};
80/// use torsh_tensor::{Tensor, creation};
81/// use torsh_core::error::Result;
82/// use std::collections::HashMap;
83///
84/// /// Custom linear layer with learnable weight and bias
85/// struct MyLinear {
86///     base: ModuleBase,
87///     in_features: usize,
88///     out_features: usize,
89/// }
90///
91/// impl MyLinear {
92///     fn new(in_features: usize, out_features: usize) -> Result<Self> {
93///         let mut base = ModuleBase::new();
94///
95///         // Initialize weight: [in_features, out_features]
96///         let weight = creation::randn(&[in_features, out_features])?;
97///         base.register_parameter("weight".to_string(), Parameter::new(weight));
98///
99///         // Initialize bias: [out_features]
100///         let bias = creation::zeros(&[out_features])?;
101///         base.register_parameter("bias".to_string(), Parameter::new(bias));
102///
103///         Ok(Self {
104///             base,
105///             in_features,
106///             out_features,
107///         })
108///     }
109/// }
110///
111/// impl Module for MyLinear {
112///     fn forward(&self, input: &Tensor) -> Result<Tensor> {
113///         // Get parameters
114///         let weight = self.base.parameters["weight"].tensor().read().clone();
115///         let bias = self.base.parameters["bias"].tensor().read().clone();
116///
117///         // Compute: input @ weight + bias
118///         let output = input.matmul(&weight)?;
119///         output.add(&bias)
120///     }
121///
122///     fn parameters(&self) -> HashMap<String, Parameter> {
123///         self.base.parameters.clone()
124///     }
125///
126///     fn named_parameters(&self) -> HashMap<String, Parameter> {
127///         self.base.named_parameters()
128///     }
129///
130///     fn training(&self) -> bool {
131///         self.base.training()
132///     }
133///
134///     fn set_training(&mut self, training: bool) {
135///         self.base.set_training(training);
136///     }
137/// }
138/// ```
139///
140/// ## Module with Training/Evaluation Modes
141///
142/// ```rust
143/// use torsh_nn::{Module, ModuleBase};
144/// use torsh_tensor::{Tensor, creation};
145/// use torsh_core::error::Result;
146///
147/// /// Dropout layer with different behavior in train/eval modes
148/// struct MyDropout {
149///     base: ModuleBase,
150///     p: f32,  // Dropout probability
151/// }
152///
153/// impl MyDropout {
154///     fn new(p: f32) -> Self {
155///         Self {
156///             base: ModuleBase::new(),
157///             p,
158///         }
159///     }
160/// }
161///
162/// impl Module for MyDropout {
163///     fn forward(&self, input: &Tensor) -> Result<Tensor> {
164///         if self.training() {
165///             // During training: randomly zero out units
166///             let rand_vals = creation::rand_like(input)?;
167///             let threshold = creation::full_like(input, self.p)?;
168///             // Keep elements where random value >= p
169///             let keep_mask = rand_vals.ge(&threshold)?;
170///             let zeros = creation::zeros_like(input)?;
171///             let masked = input.where_tensor(&keep_mask, &zeros)?;
172///             // Scale by 1/(1-p) to maintain expected value (inverted dropout)
173///             masked.div_scalar(1.0 - self.p)
174///         } else {
175///             // During evaluation: pass through unchanged
176///             Ok(input.clone())
177///         }
178///     }
179///
180///     fn training(&self) -> bool {
181///         self.base.training()
182///     }
183///
184///     fn set_training(&mut self, training: bool) {
185///         self.base.set_training(training);
186///     }
187/// }
188/// ```
189///
190/// # Complete Training Loop Example
191///
192/// ```rust,no_run
193/// use torsh_nn::prelude::{Module, Linear, Sequential};
194/// use torsh_tensor::{Tensor, creation};
195/// use torsh_core::error::Result;
196///
197/// fn train_eval_example() -> Result<()> {
198///     // 1. Create model
199///     let mut model = Sequential::new()
200///         .add(Linear::new(784, 128, true))
201///         .add(Linear::new(128, 10, true));
202///
203///     // 2. Set model to training mode
204///     model.train();
205///
206///     // 3. Forward pass
207///     let inputs = creation::randn(&[32, 784])?;
208///     let outputs = model.forward(&inputs)?;
209///
210///     // 4. Compute loss (simplified MSE)
211///     let targets = creation::randn(&[32, 10])?;
212///     let diff = outputs.sub(&targets)?;
213///     let loss = diff.pow_scalar(2.0)?.mean(None, false)?;
214///
215///     // 5. Backward pass (computes gradients)
216///     loss.backward()?;
217///
218///     // 6. Switch to evaluation mode
219///     model.eval();
220///     let test_input = creation::randn(&[10, 784])?;
221///     let test_output = model.forward(&test_input)?;
222///
223///     Ok(())
224/// }
225/// ```
226///
227/// # Method Categories
228///
229/// ## Core Methods (Required)
230/// - [`forward()`](Module::forward) - Forward pass computation
231///
232/// ## Parameter Management
233/// - [`parameters()`](Module::parameters) - Get all trainable parameters
234/// - [`named_parameters()`](Module::named_parameters) - Get parameters with names
235/// - [`all_parameters()`](Module::all_parameters) - Get parameters recursively
236/// - [`zero_grad()`](Module::zero_grad) - Clear all gradients
237///
238/// ## Buffer Management
239/// Buffers are persistent, *untrained* tensors (a `BatchNorm`'s `running_mean`,
240/// `running_var`, `num_batches_tracked`). They are not returned by
241/// `parameters()` and no optimizer touches them, but they are model state and
242/// travel in the checkpoint.
243/// - [`buffers()`](Module::buffers) - Get all buffers
244/// - [`named_buffers()`](Module::named_buffers) - Get buffers with names
245/// - [`all_named_buffers()`](Module::all_named_buffers) - Get buffers recursively
246///
247/// ## Training Mode Control
248/// - [`training()`](Module::training) - Check if in training mode
249/// - [`train()`](Module::train) - Set to training mode
250/// - [`eval()`](Module::eval) - Set to evaluation mode
251/// - [`set_training()`](Module::set_training) - Set training mode explicitly
252///
253/// ## Module Hierarchy
254/// - [`children()`](Module::children) - Get direct child modules
255/// - [`named_children()`](Module::named_children) - Get children with names
256/// - [`modules()`](Module::modules) - Get all modules recursively
257///
258/// ## State Management
259/// - [`state_dict()`](Module::state_dict) - Save module state (parameters *and* buffers)
260/// - [`load_state_dict()`](Module::load_state_dict) - Load module state (parameters *and* buffers)
261/// - [`to_device()`](Module::to_device) - Move module to device
262///
263/// ## Utilities
264/// - [`freeze()`](Module::freeze) - Freeze all parameters
265/// - [`unfreeze()`](Module::unfreeze) - Unfreeze all parameters
266/// - [`num_parameters()`](Module::num_parameters) - Count total parameters
267/// - [`diagnose()`](Module::diagnose) - Check module health
268///
269/// # PyTorch Compatibility
270///
271/// ToRSh's Module trait closely follows PyTorch's `nn.Module` interface:
272///
273/// | PyTorch | ToRSh | Notes |
274/// |---------|-------|-------|
275/// | `forward(x)` | `forward(&x)` | Returns `Result<Tensor>` |
276/// | `parameters()` | `parameters()` | Returns `HashMap<String, Parameter>` |
277/// | `named_buffers()` | `named_buffers()` | Returns `HashMap<String, Arc<RwLock<Tensor>>>` — live handles, not copies |
278/// | `train()` | `train()` | Sets training mode |
279/// | `eval()` | `eval()` | Sets evaluation mode |
280/// | `state_dict()` | `state_dict()` | Returns parameter **and buffer** tensors |
281/// | `load_state_dict()` | `load_state_dict()` | Loads parameters **and buffers** from a HashMap |
282/// | `to(device)` | `to_device(device)` | Moves to device |
283/// | `zero_grad()` | `zero_grad()` | Clears gradients |
284///
285/// One deliberate divergence: PyTorch exempts `num_batches_tracked` from
286/// strict-mode key checking for backwards compatibility with pre-0.4.1
287/// checkpoints. ToRSh grants no per-key exemptions — every buffer is required
288/// under `strict = true`.
289///
290/// # Best Practices
291///
292/// 1. **Always use `ModuleBase`**: Store a `ModuleBase` instance in your module to handle
293///    common functionality like parameters, buffers, and training state.
294///
295/// 2. **Register parameters in constructor**: Use `base.register_parameter()` to register
296///    all trainable parameters during module creation.
297///
298/// 3. **Implement training/eval behavior**: If your module behaves differently during
299///    training vs evaluation (like Dropout, BatchNorm), check `self.training()`.
300///
301/// 4. **Use `Result<Tensor>` for error handling**: Always return `Result<Tensor>` from
302///    `forward()` to properly propagate errors.
303///
304/// 5. **Delegate to `ModuleBase`**: Implement parameter and training mode methods by
305///    delegating to your `ModuleBase` instance.
306///
307/// 6. **Initialize parameters properly**: Use initialization methods from `torsh_nn::init`
308///    for better training convergence.
309pub trait Module: Send + Sync {
310    /// Forward pass through the module
311    ///
312    /// This is the only required method that must be implemented by all modules.
313    ///
314    /// # Arguments
315    /// * `input` - Input tensor
316    ///
317    /// # Returns
318    /// * `Result<Tensor>` - Output tensor or error
319    fn forward(&self, input: &Tensor) -> Result<Tensor>;
320
321    /// Get all parameters in the module (non-recursive)
322    ///
323    /// Override this method if your module has trainable parameters.
324    /// The default implementation returns an empty map.
325    ///
326    /// # Returns
327    /// * `HashMap<String, Parameter>` - Map of parameter names to parameters
328    fn parameters(&self) -> HashMap<String, crate::Parameter> {
329        HashMap::new()
330    }
331
332    /// Get named parameters (non-recursive)
333    ///
334    /// Default implementation delegates to `parameters()`. Override if you need
335    /// different behavior for named vs unnamed parameter access.
336    ///
337    /// # Returns
338    /// * `HashMap<String, Parameter>` - Map of parameter names to parameters
339    fn named_parameters(&self) -> HashMap<String, crate::Parameter> {
340        self.parameters()
341    }
342
343    /// Get all parameters recursively including submodules
344    ///
345    /// # Returns
346    /// * `HashMap<String, Parameter>` - Flattened map of all parameters
347    fn all_parameters(&self) -> HashMap<String, crate::Parameter> {
348        let mut all_params = self.parameters();
349
350        for child in self.children() {
351            let child_params = child.all_parameters();
352            for (name, param) in child_params {
353                all_params.insert(name, param);
354            }
355        }
356
357        all_params
358    }
359
360    /// Get all named parameters recursively with module prefixes
361    ///
362    /// # Returns
363    /// * `HashMap<String, Parameter>` - Hierarchical parameter names
364    fn all_named_parameters(&self) -> HashMap<String, crate::Parameter> {
365        let mut all_params = HashMap::new();
366
367        // Add own parameters
368        for (name, param) in self.named_parameters() {
369            all_params.insert(name, param);
370        }
371
372        // Add child parameters with prefixes
373        let children_named = self.named_children();
374        for (child_name, child) in children_named {
375            for (param_name, param) in child.all_named_parameters() {
376                let full_name = format!("{}.{}", child_name, param_name);
377                all_params.insert(full_name, param);
378            }
379        }
380
381        all_params
382    }
383
384    /// Get all named buffers recursively with module prefixes
385    ///
386    /// The buffer-side twin of [`all_named_parameters()`](Module::all_named_parameters),
387    /// and the recursion [`state_dict()`](Module::state_dict) uses to reach a
388    /// child's non-trainable state. Buffers are a module's *persistent, untrained*
389    /// tensors — a `BatchNorm`'s `running_mean` / `running_var` /
390    /// `num_batches_tracked` — which evaluation mode consumes in place of the
391    /// batch statistics, so losing them turns a trained normalization layer back
392    /// into a freshly constructed one.
393    ///
394    /// Child names come from [`named_children()`](Module::named_children); a
395    /// module that overrides only `children()` contributes no buffer names here,
396    /// exactly as it contributes no parameter names to `all_named_parameters()`.
397    ///
398    /// # Returns
399    /// * `HashMap<String, Arc<RwLock<Tensor>>>` - Hierarchical buffer names,
400    ///   holding the module's *live* handles rather than copies
401    fn all_named_buffers(&self) -> HashMap<String, std::sync::Arc<parking_lot::RwLock<Tensor>>> {
402        let mut all_buffers = self.named_buffers();
403
404        for (child_name, child) in self.named_children() {
405            for (buffer_name, buffer) in child.all_named_buffers() {
406                all_buffers.insert(format!("{}.{}", child_name, buffer_name), buffer);
407            }
408        }
409
410        all_buffers
411    }
412
413    /// Check if in training mode
414    ///
415    /// Default implementation returns true. Override if your module tracks training state.
416    fn training(&self) -> bool {
417        true
418    }
419
420    /// Set training mode
421    ///
422    /// Convenience method that calls `set_training(true)`.
423    fn train(&mut self) {
424        self.set_training(true);
425    }
426
427    /// Set evaluation mode
428    ///
429    /// Convenience method that calls `set_training(false)`.
430    fn eval(&mut self) {
431        self.set_training(false);
432    }
433
434    /// Set training mode (internal implementation)
435    ///
436    /// Default implementation does nothing. Override if your module needs to track
437    /// training state or propagate it to child modules.
438    fn set_training(&mut self, _training: bool) {
439        // Default: do nothing
440    }
441
442    /// Move module to device
443    ///
444    /// Default implementation does nothing. Override if your module has parameters
445    /// or buffers that need to be moved between devices.
446    fn to_device(&mut self, _device: DeviceType) -> Result<()> {
447        Ok(())
448    }
449
450    /// Load state dictionary into the module
451    ///
452    /// Restores **parameters and buffers**, matching PyTorch's
453    /// `nn.Module.load_state_dict`. Buffers are written through the live
454    /// handles published by [`all_named_buffers()`](Module::all_named_buffers),
455    /// so a `BatchNorm`'s running statistics come back into the layer itself
456    /// rather than into a detached copy.
457    ///
458    /// # Arguments
459    /// * `state_dict` - Map of parameter *and buffer* names to tensors
460    /// * `strict` - Whether to require exact name matches. Strict mode counts
461    ///   buffers: a checkpoint missing `running_mean` is rejected, with no
462    ///   per-key exemptions (PyTorch's legacy leniency for
463    ///   `num_batches_tracked` is deliberately not reproduced — silently
464    ///   accepting a partial checkpoint is the defect this method exists to
465    ///   prevent).
466    ///
467    /// # Returns
468    /// * `Result<()>` - Success or error with details about missing/unexpected keys
469    ///
470    /// # Errors
471    ///
472    /// Shapes are validated for every matching entry *before* anything is
473    /// written, so a corrupt checkpoint leaves the module exactly as it was
474    /// instead of half-loaded. A shape mismatch is an error in both strict and
475    /// non-strict mode; `strict` governs which *names* must be present, not
476    /// whether the values that are present have to fit.
477    fn load_state_dict(
478        &mut self,
479        state_dict: &HashMap<String, Tensor>,
480        strict: bool,
481    ) -> Result<()> {
482        let current_params = self.all_named_parameters();
483        let current_buffers = self.all_named_buffers();
484        let mut missing_keys = Vec::new();
485        let mut unexpected_keys = Vec::new();
486
487        // Check for missing parameters and buffers
488        for name in current_params.keys().chain(current_buffers.keys()) {
489            if !state_dict.contains_key(name) {
490                missing_keys.push(name.clone());
491            }
492        }
493
494        // Check for entries the module cannot consume
495        for name in state_dict.keys() {
496            if !current_params.contains_key(name) && !current_buffers.contains_key(name) {
497                unexpected_keys.push(name.clone());
498            }
499        }
500
501        if strict && (!missing_keys.is_empty() || !unexpected_keys.is_empty()) {
502            // Sorted so the report is reproducible; both maps iterate in
503            // unspecified order.
504            missing_keys.sort();
505            unexpected_keys.sort();
506            return Err(torsh_core::error::TorshError::Other(format!(
507                "State dict loading failed. Missing keys: {:?}, Unexpected keys: {:?}",
508                missing_keys, unexpected_keys
509            )));
510        }
511
512        // Validate every shape before mutating anything.
513        for (name, param) in &current_params {
514            if let Some(new_tensor) = state_dict.get(name) {
515                let current_shape = param.shape()?;
516                let new_shape = new_tensor.shape().dims().to_vec();
517                if current_shape != new_shape {
518                    return Err(torsh_core::error::TorshError::Other(format!(
519                        "Shape mismatch for parameter '{}': expected {:?}, got {:?}",
520                        name, current_shape, new_shape
521                    )));
522                }
523            }
524        }
525        for (name, buffer) in &current_buffers {
526            if let Some(new_tensor) = state_dict.get(name) {
527                // The read guard is a statement-scoped temporary: it must be
528                // released before the write pass below takes the same lock,
529                // because `parking_lot::RwLock` is not reentrant.
530                let current_shape = buffer.read().shape().dims().to_vec();
531                let new_shape = new_tensor.shape().dims().to_vec();
532                if current_shape != new_shape {
533                    return Err(torsh_core::error::TorshError::Other(format!(
534                        "Shape mismatch for buffer '{}': expected {:?}, got {:?}",
535                        name, current_shape, new_shape
536                    )));
537                }
538            }
539        }
540
541        // Load matching parameters
542        for (name, param) in current_params {
543            if let Some(new_tensor) = state_dict.get(&name) {
544                *param.tensor().write() = new_tensor.clone();
545            }
546        }
547
548        // Load matching buffers
549        for (name, buffer) in current_buffers {
550            if let Some(new_tensor) = state_dict.get(&name) {
551                *buffer.write() = new_tensor.clone();
552            }
553        }
554
555        Ok(())
556    }
557
558    /// Load state dictionary with default strict=true
559    fn load_state_dict_strict(&mut self, state_dict: &HashMap<String, Tensor>) -> Result<()> {
560        self.load_state_dict(state_dict, true)
561    }
562
563    /// Save state dictionary from the module
564    ///
565    /// Carries **parameters and buffers**, matching PyTorch's
566    /// `nn.Module.state_dict`. Buffers are a module's persistent untrained
567    /// state — `running_mean`, `running_var`, `num_batches_tracked` — and
568    /// evaluation mode consumes them in place of the batch statistics, so a
569    /// checkpoint that omitted them reloaded as a *different* model with no
570    /// error reported anywhere.
571    ///
572    /// Values are snapshots taken at call time, keyed by
573    /// [`all_named_parameters()`](Module::all_named_parameters) and
574    /// [`all_named_buffers()`](Module::all_named_buffers), which is exactly the
575    /// key set [`load_state_dict()`](Module::load_state_dict) expects back.
576    fn state_dict(&self) -> HashMap<String, Tensor> {
577        let mut state = HashMap::new();
578        for (name, param) in self.all_named_parameters() {
579            state.insert(name, param.clone_data());
580        }
581        for (name, buffer) in self.all_named_buffers() {
582            // PyTorch rejects registering a buffer under a name already taken
583            // by a parameter; nothing enforces that here, so the impossible
584            // case is resolved deterministically in the parameter's favour
585            // rather than by silently overwriting trainable state.
586            state.entry(name).or_insert_with(|| buffer.read().clone());
587        }
588        state
589    }
590
591    /// Get the module name (optional, for debugging and serialization)
592    fn name(&self) -> Option<&str> {
593        None
594    }
595
596    /// Get all buffers (non-trainable parameters)
597    fn buffers(&self) -> Vec<std::sync::Arc<parking_lot::RwLock<Tensor>>> {
598        Vec::new()
599    }
600
601    /// Get named buffers
602    fn named_buffers(&self) -> HashMap<String, std::sync::Arc<parking_lot::RwLock<Tensor>>> {
603        HashMap::new()
604    }
605
606    /// Get all direct child modules
607    ///
608    /// Default implementation returns an empty vector. Override if your module
609    /// contains child modules.
610    fn children(&self) -> Vec<&dyn Module> {
611        Vec::new()
612    }
613
614    /// Get all direct child modules with names
615    ///
616    /// Default implementation returns an empty vector. Override if your module
617    /// contains named child modules.
618    fn named_children(&self) -> Vec<(String, &dyn Module)> {
619        Vec::new()
620    }
621
622    /// Get all modules recursively (depth-first traversal)
623    fn modules(&self) -> Vec<&dyn Module>
624    where
625        Self: Sized,
626    {
627        let mut modules: Vec<&dyn Module> = vec![self];
628        for child in self.children() {
629            // Since child is &dyn Module, we need to use a different approach
630            // We'll just collect immediate children for now
631            modules.push(child);
632        }
633        modules
634    }
635
636    /// Get all modules recursively with hierarchical names
637    fn named_modules(&self) -> Vec<(String, &dyn Module)>
638    where
639        Self: Sized,
640    {
641        let mut modules: Vec<(String, &dyn Module)> = vec![(String::new(), self)];
642
643        for (child_name, child) in self.named_children() {
644            // Since child is &dyn Module, we need to use a different approach
645            // We'll just collect immediate named children for now
646            modules.push((child_name, child));
647        }
648
649        modules
650    }
651
652    /// Zero all gradients recursively
653    ///
654    /// Default implementation does nothing. Override if your module has parameters
655    /// with gradients that need to be zeroed.
656    fn zero_grad(&mut self) {
657        // Default: do nothing
658    }
659
660    /// Count total number of parameters
661    fn num_parameters(&self) -> usize {
662        self.all_parameters()
663            .values()
664            .map(|p| p.numel().unwrap_or(0))
665            .sum()
666    }
667
668    /// Count trainable parameters
669    fn num_trainable_parameters(&self) -> usize {
670        self.all_parameters()
671            .values()
672            .filter(|p| p.requires_grad())
673            .map(|p| p.numel().unwrap_or(0))
674            .sum()
675    }
676
677    /// Get memory usage estimate in bytes
678    fn memory_usage(&self) -> usize {
679        self.all_parameters()
680            .values()
681            .map(|p| p.numel().unwrap_or(0) * 4) // Assume f32 = 4 bytes
682            .sum()
683    }
684
685    /// Freeze all parameters (set requires_grad = false)
686    ///
687    /// Default implementation does nothing. Override if your module has parameters
688    /// that can be frozen/unfrozen.
689    fn freeze(&mut self) {
690        // Default: do nothing
691    }
692
693    /// Unfreeze all parameters (set requires_grad = true)
694    ///
695    /// Default implementation does nothing. Override if your module has parameters
696    /// that can be frozen/unfrozen.
697    fn unfreeze(&mut self) {
698        // Default: do nothing
699    }
700
701    /// Get string representation
702    fn extra_repr(&self) -> String {
703        String::new()
704    }
705
706    /// Register a hook for this module (default implementation does nothing)
707    fn register_hook(
708        &mut self,
709        _hook_type: crate::HookType,
710        _callback: crate::HookCallback,
711    ) -> Option<crate::HookHandle> {
712        None
713    }
714
715    /// Remove a hook by handle (default implementation does nothing)
716    fn remove_hook(&mut self, _hook_type: crate::HookType, _handle: crate::HookHandle) -> bool {
717        false
718    }
719
720    /// Execute hooks of a specific type (default implementation does nothing)
721    fn execute_hooks(
722        &self,
723        _hook_type: crate::HookType,
724        _input: &Tensor,
725        _output: Option<&Tensor>,
726    ) -> Result<()> {
727        Ok(())
728    }
729
730    /// Forward pass with hooks support
731    fn forward_with_hooks(&self, input: &Tensor) -> Result<Tensor> {
732        // Execute pre-forward hooks
733        self.execute_hooks(crate::HookType::PreForward, input, None)?;
734
735        // Perform forward pass
736        let output = self.forward(input)?;
737
738        // Execute post-forward hooks
739        self.execute_hooks(crate::HookType::PostForward, input, Some(&output))?;
740
741        Ok(output)
742    }
743
744    /// Check if module has hooks registered
745    fn has_hooks(&self, _hook_type: crate::HookType) -> bool {
746        false
747    }
748
749    // === Ergonomic Helper Methods ===
750
751    /// Convenient method to call forward and handle common patterns
752    ///
753    /// This is equivalent to `forward()` but provides a more ergonomic interface
754    /// for chaining operations.
755    fn call(&self, input: &Tensor) -> Result<Tensor> {
756        self.forward(input)
757    }
758
759    /// Apply the module to input (alias for forward)
760    ///
761    /// PyTorch-style method name for compatibility.
762    fn apply(&self, input: &Tensor) -> Result<Tensor> {
763        self.forward(input)
764    }
765
766    /// Check if the module has any parameters
767    fn has_parameters(&self) -> bool {
768        !self.parameters().is_empty()
769    }
770
771    /// Check if the module has any child modules
772    fn has_children(&self) -> bool {
773        !self.children().is_empty()
774    }
775
776    /// Get parameter count (convenience method)
777    fn parameter_count(&self) -> usize {
778        self.num_parameters()
779    }
780
781    /// Get trainable parameter count (convenience method)
782    fn trainable_parameter_count(&self) -> usize {
783        self.num_trainable_parameters()
784    }
785
786    /// Get memory usage in MB (convenience method)
787    fn memory_usage_mb(&self) -> f64 {
788        self.memory_usage() as f64 / (1024.0 * 1024.0)
789    }
790
791    /// Toggle training mode (convenience method)
792    fn toggle_training(&mut self) {
793        self.set_training(!self.training());
794    }
795
796    /// Check if module is in evaluation mode
797    fn eval_mode(&self) -> bool {
798        !self.training()
799    }
800
801    // === Enhanced Ergonomic Methods ===
802
803    /// Sequential forward pass through multiple modules
804    ///
805    /// This provides a convenient way to chain multiple forward passes.
806    ///
807    /// # Arguments
808    /// * `modules` - Slice of modules to apply sequentially
809    /// * `input` - Input tensor
810    ///
811    /// # Returns
812    /// * `Result<Tensor>` - Final output after all modules
813    ///
814    /// # Example
815    /// ```ignore
816    /// let result = Module::sequential_forward(&[&layer1, &layer2, &layer3], &input)?;
817    /// ```
818    fn sequential_forward(modules: &[&dyn Module], mut input: Tensor) -> Result<Tensor>
819    where
820        Self: Sized,
821    {
822        for module in modules {
823            input = module.forward(&input)?;
824        }
825        Ok(input)
826    }
827
828    /// Apply module multiple times with different inputs (batch processing)
829    ///
830    /// This is useful for processing multiple independent inputs through the same module.
831    ///
832    /// # Arguments
833    /// * `inputs` - Vector of input tensors
834    ///
835    /// # Returns
836    /// * `Result<Vec<Tensor>>` - Vector of output tensors
837    fn batch_forward(&self, inputs: &[Tensor]) -> Result<Vec<Tensor>> {
838        inputs.iter().map(|input| self.forward(input)).collect()
839    }
840
841    /// Forward with condition - only apply if condition is true
842    ///
843    /// This provides conditional execution of modules.
844    ///
845    /// # Arguments
846    /// * `input` - Input tensor
847    /// * `condition` - Whether to apply this module
848    ///
849    /// # Returns
850    /// * `Result<Tensor>` - Output tensor (input if condition is false)
851    fn conditional_forward(&self, input: &Tensor, condition: bool) -> Result<Tensor> {
852        if condition {
853            self.forward(input)
854        } else {
855            Ok(input.clone())
856        }
857    }
858
859    /// Forward with residual connection
860    ///
861    /// Applies the module and adds the result to the input (residual/skip connection).
862    ///
863    /// # Arguments
864    /// * `input` - Input tensor
865    ///
866    /// # Returns
867    /// * `Result<Tensor>` - Output tensor (input + forward(input))
868    fn residual_forward(&self, input: &Tensor) -> Result<Tensor> {
869        let output = self.forward(input)?;
870        // This would use tensor addition when available
871        // For now, just return the output
872        Ok(output)
873    }
874
875    /// Get detailed module information for debugging
876    ///
877    /// This provides comprehensive information about the module state.
878    ///
879    /// # Returns
880    /// * `ModuleInfo` - Detailed module information
881    fn module_info(&self) -> crate::ModuleInfo {
882        crate::ModuleInfo {
883            name: self.name().unwrap_or("Unknown").to_string(),
884            training: self.training(),
885            parameter_count: self.num_parameters(),
886            trainable_parameter_count: self.num_trainable_parameters(),
887            memory_usage_bytes: self.memory_usage(),
888            has_children: self.has_children(),
889            children_count: self.children().len(),
890        }
891    }
892
893    /// Check if module is ready for training
894    ///
895    /// Performs various checks to ensure the module is properly configured for training.
896    ///
897    /// # Returns
898    /// * `Result<()>` - Ok if ready, Error with details if not
899    fn check_training_readiness(&self) -> Result<()> {
900        // Check if module has parameters
901        if !self.has_parameters() {
902            return Err(torsh_core::error::TorshError::Other(
903                "Module has no parameters - may not be trainable".to_string(),
904            ));
905        }
906
907        // Check if in training mode
908        if !self.training() {
909            return Err(torsh_core::error::TorshError::Other(
910                "Module is in evaluation mode - switch to training mode first".to_string(),
911            ));
912        }
913
914        // Check for finite parameters
915        for param in self.parameters().values() {
916            if !param.is_finite().unwrap_or(false) {
917                return Err(torsh_core::error::TorshError::Other(
918                    "Module contains non-finite parameters (NaN or infinity)".to_string(),
919                ));
920            }
921        }
922
923        Ok(())
924    }
925
926    /// Get parameter names matching a pattern
927    ///
928    /// This helps with selective parameter access and manipulation.
929    ///
930    /// # Arguments
931    /// * `pattern` - String pattern to match against parameter names
932    ///
933    /// # Returns
934    /// * `Vec<String>` - Vector of parameter names matching the pattern
935    fn parameter_names_matching(&self, pattern: &str) -> Vec<String> {
936        self.all_named_parameters()
937            .keys()
938            .filter(|name| name.contains(pattern))
939            .cloned()
940            .collect()
941    }
942
943    /// Get parameters by layer type (e.g., "weight", "bias")
944    ///
945    /// # Arguments
946    /// * `param_type` - Type of parameters to retrieve
947    ///
948    /// # Returns
949    /// * `HashMap<String, Parameter>` - Filtered parameters
950    fn parameters_by_type(&self, param_type: &str) -> HashMap<String, crate::Parameter> {
951        self.all_named_parameters()
952            .into_iter()
953            .filter(|(name, _)| name.contains(param_type))
954            .collect()
955    }
956
957    /// Clone module parameters (for creating copies or checkpoints)
958    ///
959    /// # Returns
960    /// * `HashMap<String, Tensor>` - Cloned parameter tensors
961    fn clone_parameters(&self) -> HashMap<String, Tensor> {
962        self.all_named_parameters()
963            .into_iter()
964            .map(|(name, param)| (name, param.clone_data()))
965            .collect()
966    }
967
968    /// Quick diagnostic check of module health
969    ///
970    /// # Returns
971    /// * `ModuleDiagnostics` - Diagnostic information
972    fn diagnose(&self) -> crate::ModuleDiagnostics {
973        let mut issues = Vec::new();
974        let mut warnings = Vec::new();
975
976        // Check parameter health
977        for (name, param) in self.all_named_parameters() {
978            if let Ok(diag) = param.diagnose() {
979                if !diag.issues.is_empty() {
980                    issues.extend(
981                        diag.issues
982                            .into_iter()
983                            .map(|issue| format!("{}: {}", name, issue)),
984                    );
985                }
986                if !diag.warnings.is_empty() {
987                    warnings.extend(
988                        diag.warnings
989                            .into_iter()
990                            .map(|warning| format!("{}: {}", name, warning)),
991                    );
992                }
993            }
994        }
995
996        // Check training readiness
997        if let Err(e) = self.check_training_readiness() {
998            warnings.push(format!("Training readiness: {}", e));
999        }
1000
1001        crate::ModuleDiagnostics {
1002            module_info: self.module_info(),
1003            issues,
1004            warnings,
1005            parameter_diagnostics: self
1006                .all_named_parameters()
1007                .into_iter()
1008                .filter_map(|(name, param)| param.diagnose().ok().map(|d| (name, d)))
1009                .collect(),
1010        }
1011    }
1012}
1013
1014/// Forwards **every** `Module` method to `(**self)`.
1015///
1016/// `Module` has one required method and ~50 defaulted ones, and those defaults
1017/// split into two families. *Composed* defaults (`all_named_parameters`,
1018/// `state_dict`, `num_parameters`, `residual_forward`, …) are written in terms
1019/// of other trait methods, so an impl that forwards the primitives inherits
1020/// them correctly. *Leaf* defaults (`buffers`, `named_buffers`, `name`,
1021/// `zero_grad`, `freeze`, `unfreeze`, `extra_repr`, and the whole hook
1022/// protocol) return emptiness — `Vec::new()`, `HashMap::new()`, `None`,
1023/// `false`, `Ok(())`, an empty body. A smart-pointer impl that *omits* one of
1024/// those does not fall through to the pointee: it answers "nothing" on the
1025/// pointee's behalf, with no error anywhere.
1026///
1027/// That distinction is why this macro exists rather than a hand-written list.
1028/// The predecessor forwarded nine methods and omitted every leaf default, so a
1029/// boxed `BatchNorm1d` reported zero buffers while the bare layer reported
1030/// three — and `Sequential`/`ModuleList` store their children as
1031/// `Vec<Box<dyn Module>>`, where *every* trait call on a child resolves through
1032/// this impl. A checkpoint or device migration taken through the box therefore
1033/// dropped `running_mean` / `running_var` / `num_batches_tracked` silently,
1034/// reverting a trained normalization layer to its initialization on reload.
1035/// `tests/hardening_nn_module_box.rs` pins the whole surface.
1036///
1037/// The body is spelled `(**self)` for both implementors on purpose:
1038/// - for `Box<dyn Module>` that is the `dyn Module` pointee, i.e. a vtable call;
1039/// - for `&mut Box<dyn Module>` that is the `Box<dyn Module>` itself, i.e. a
1040///   call into the impl directly above, which then performs the vtable call.
1041///
1042/// `modules`/`named_modules` are excluded: they carry a `where Self: Sized`
1043/// bound, so they cannot be invoked on an unsized `dyn Module` and are supplied
1044/// per-implementor below. `sequential_forward` is excluded too — it is an
1045/// associated function with no receiver, so there is nothing to forward to.
1046macro_rules! forward_all_module_methods {
1047    () => {
1048        fn forward(&self, input: &Tensor) -> Result<Tensor> {
1049            (**self).forward(input)
1050        }
1051
1052        fn parameters(&self) -> HashMap<String, crate::Parameter> {
1053            (**self).parameters()
1054        }
1055
1056        fn named_parameters(&self) -> HashMap<String, crate::Parameter> {
1057            (**self).named_parameters()
1058        }
1059
1060        fn all_parameters(&self) -> HashMap<String, crate::Parameter> {
1061            (**self).all_parameters()
1062        }
1063
1064        fn all_named_parameters(&self) -> HashMap<String, crate::Parameter> {
1065            (**self).all_named_parameters()
1066        }
1067
1068        fn all_named_buffers(
1069            &self,
1070        ) -> HashMap<String, std::sync::Arc<parking_lot::RwLock<Tensor>>> {
1071            (**self).all_named_buffers()
1072        }
1073
1074        fn training(&self) -> bool {
1075            (**self).training()
1076        }
1077
1078        fn train(&mut self) {
1079            (**self).train()
1080        }
1081
1082        fn eval(&mut self) {
1083            (**self).eval()
1084        }
1085
1086        fn set_training(&mut self, training: bool) {
1087            (**self).set_training(training)
1088        }
1089
1090        fn to_device(&mut self, device: DeviceType) -> Result<()> {
1091            (**self).to_device(device)
1092        }
1093
1094        fn load_state_dict(
1095            &mut self,
1096            state_dict: &HashMap<String, Tensor>,
1097            strict: bool,
1098        ) -> Result<()> {
1099            (**self).load_state_dict(state_dict, strict)
1100        }
1101
1102        fn load_state_dict_strict(&mut self, state_dict: &HashMap<String, Tensor>) -> Result<()> {
1103            (**self).load_state_dict_strict(state_dict)
1104        }
1105
1106        fn state_dict(&self) -> HashMap<String, Tensor> {
1107            (**self).state_dict()
1108        }
1109
1110        fn name(&self) -> Option<&str> {
1111            (**self).name()
1112        }
1113
1114        fn buffers(&self) -> Vec<std::sync::Arc<parking_lot::RwLock<Tensor>>> {
1115            (**self).buffers()
1116        }
1117
1118        fn named_buffers(&self) -> HashMap<String, std::sync::Arc<parking_lot::RwLock<Tensor>>> {
1119            (**self).named_buffers()
1120        }
1121
1122        fn children(&self) -> Vec<&dyn Module> {
1123            (**self).children()
1124        }
1125
1126        fn named_children(&self) -> Vec<(String, &dyn Module)> {
1127            (**self).named_children()
1128        }
1129
1130        fn zero_grad(&mut self) {
1131            (**self).zero_grad()
1132        }
1133
1134        fn num_parameters(&self) -> usize {
1135            (**self).num_parameters()
1136        }
1137
1138        fn num_trainable_parameters(&self) -> usize {
1139            (**self).num_trainable_parameters()
1140        }
1141
1142        fn memory_usage(&self) -> usize {
1143            (**self).memory_usage()
1144        }
1145
1146        fn freeze(&mut self) {
1147            (**self).freeze()
1148        }
1149
1150        fn unfreeze(&mut self) {
1151            (**self).unfreeze()
1152        }
1153
1154        fn extra_repr(&self) -> String {
1155            (**self).extra_repr()
1156        }
1157
1158        fn register_hook(
1159            &mut self,
1160            hook_type: crate::HookType,
1161            callback: crate::HookCallback,
1162        ) -> Option<crate::HookHandle> {
1163            (**self).register_hook(hook_type, callback)
1164        }
1165
1166        fn remove_hook(&mut self, hook_type: crate::HookType, handle: crate::HookHandle) -> bool {
1167            (**self).remove_hook(hook_type, handle)
1168        }
1169
1170        fn execute_hooks(
1171            &self,
1172            hook_type: crate::HookType,
1173            input: &Tensor,
1174            output: Option<&Tensor>,
1175        ) -> Result<()> {
1176            (**self).execute_hooks(hook_type, input, output)
1177        }
1178
1179        fn forward_with_hooks(&self, input: &Tensor) -> Result<Tensor> {
1180            (**self).forward_with_hooks(input)
1181        }
1182
1183        fn has_hooks(&self, hook_type: crate::HookType) -> bool {
1184            (**self).has_hooks(hook_type)
1185        }
1186
1187        fn call(&self, input: &Tensor) -> Result<Tensor> {
1188            (**self).call(input)
1189        }
1190
1191        fn apply(&self, input: &Tensor) -> Result<Tensor> {
1192            (**self).apply(input)
1193        }
1194
1195        fn has_parameters(&self) -> bool {
1196            (**self).has_parameters()
1197        }
1198
1199        fn has_children(&self) -> bool {
1200            (**self).has_children()
1201        }
1202
1203        fn parameter_count(&self) -> usize {
1204            (**self).parameter_count()
1205        }
1206
1207        fn trainable_parameter_count(&self) -> usize {
1208            (**self).trainable_parameter_count()
1209        }
1210
1211        fn memory_usage_mb(&self) -> f64 {
1212            (**self).memory_usage_mb()
1213        }
1214
1215        fn toggle_training(&mut self) {
1216            (**self).toggle_training()
1217        }
1218
1219        fn eval_mode(&self) -> bool {
1220            (**self).eval_mode()
1221        }
1222
1223        fn batch_forward(&self, inputs: &[Tensor]) -> Result<Vec<Tensor>> {
1224            (**self).batch_forward(inputs)
1225        }
1226
1227        fn conditional_forward(&self, input: &Tensor, condition: bool) -> Result<Tensor> {
1228            (**self).conditional_forward(input, condition)
1229        }
1230
1231        fn residual_forward(&self, input: &Tensor) -> Result<Tensor> {
1232            (**self).residual_forward(input)
1233        }
1234
1235        fn module_info(&self) -> crate::ModuleInfo {
1236            (**self).module_info()
1237        }
1238
1239        fn check_training_readiness(&self) -> Result<()> {
1240            (**self).check_training_readiness()
1241        }
1242
1243        fn parameter_names_matching(&self, pattern: &str) -> Vec<String> {
1244            (**self).parameter_names_matching(pattern)
1245        }
1246
1247        // Spelled as a fully-qualified call because `ModuleExt` (blanket-impl'd
1248        // for every `Module`) also has a `parameters_by_type`, with a different
1249        // signature; plain method syntax is ambiguous between the two.
1250        fn parameters_by_type(&self, param_type: &str) -> HashMap<String, crate::Parameter> {
1251            Module::parameters_by_type(&**self, param_type)
1252        }
1253
1254        fn clone_parameters(&self) -> HashMap<String, Tensor> {
1255            (**self).clone_parameters()
1256        }
1257
1258        fn diagnose(&self) -> crate::ModuleDiagnostics {
1259            (**self).diagnose()
1260        }
1261    };
1262}
1263
1264/// Implementation for boxed trait objects
1265impl Module for Box<dyn Module> {
1266    forward_all_module_methods!();
1267
1268    /// Rooted at the *inner* module rather than at the box.
1269    ///
1270    /// The trait default pushes `self`, which here is the `Box` — a legal
1271    /// `&dyn Module`, but one extra indirection deep, so a caller walking the
1272    /// tree sees a wrapper that has no counterpart in the module hierarchy it
1273    /// is describing. `(**self).modules()` cannot be used to delegate because
1274    /// the method carries `where Self: Sized` and `dyn Module` is unsized, so
1275    /// the walk is rebuilt here from the same two pieces the default uses.
1276    fn modules(&self) -> Vec<&dyn Module>
1277    where
1278        Self: Sized,
1279    {
1280        let inner: &dyn Module = &**self;
1281        let mut modules: Vec<&dyn Module> = vec![inner];
1282        modules.extend(inner.children());
1283        modules
1284    }
1285
1286    /// Rooted at the inner module, for the same reason as [`Module::modules`].
1287    fn named_modules(&self) -> Vec<(String, &dyn Module)>
1288    where
1289        Self: Sized,
1290    {
1291        let inner: &dyn Module = &**self;
1292        let mut modules: Vec<(String, &dyn Module)> = vec![(String::new(), inner)];
1293        modules.extend(inner.named_children());
1294        modules
1295    }
1296}
1297
1298/// Implementation for mutable references to boxed trait objects
1299impl Module for &mut Box<dyn Module> {
1300    forward_all_module_methods!();
1301
1302    /// Delegates to the `Box<dyn Module>` impl: `**self` *is* a `Box`, which is
1303    /// `Sized`, so unlike the boxed case this one can simply forward.
1304    fn modules(&self) -> Vec<&dyn Module>
1305    where
1306        Self: Sized,
1307    {
1308        (**self).modules()
1309    }
1310
1311    /// Delegates to the `Box<dyn Module>` impl, as [`Module::modules`] does.
1312    fn named_modules(&self) -> Vec<(String, &dyn Module)>
1313    where
1314        Self: Sized,
1315    {
1316        (**self).named_modules()
1317    }
1318}