1use crate::compat::*;
2use crate::module::Module;
3use crate::state::{get_state, State};
4use hodu_core::{error::HoduResult, scalar::Scalar, tensor::Tensor, types::DType};
5
6#[derive(Module, Clone)]
7pub struct BatchNorm1D {
8 num_features: u32,
9 eps: Scalar,
10 momentum: Scalar,
11 affine: bool,
12 gamma: Option<Tensor>,
13 beta: Option<Tensor>,
14 running_mean: RefCell<Tensor>,
15 running_var: RefCell<Tensor>,
16}
17
18impl BatchNorm1D {
19 pub fn new(
20 num_features: u32,
21 eps: impl Into<Scalar>,
22 momentum: impl Into<Scalar>,
23 affine: bool,
24 dtype: DType,
25 ) -> HoduResult<Self> {
26 let eps = eps.into();
27 let momentum = momentum.into();
28
29 let (gamma, beta) = if affine {
30 let gamma = Tensor::ones([num_features], dtype)?;
31 gamma.set_requires_grad(true)?;
32
33 let beta = Tensor::zeros([num_features], dtype)?;
34 beta.set_requires_grad(true)?;
35
36 (Some(gamma), Some(beta))
37 } else {
38 (None, None)
39 };
40
41 let running_mean = RefCell::new(Tensor::zeros([num_features], dtype)?);
42 let running_var = RefCell::new(Tensor::ones([num_features], dtype)?);
43
44 Ok(Self {
45 num_features,
46 eps,
47 momentum,
48 affine,
49 gamma,
50 beta,
51 running_mean,
52 running_var,
53 })
54 }
55
56 pub fn forward(&self, input: &Tensor) -> HoduResult<Tensor> {
57 let input_shape = input.shape();
58
59 if get_state() == State::Training {
60 let mean = if input_shape.ndim() == 2 {
62 input.mean(&[0], true)?
64 } else if input_shape.ndim() == 3 {
65 input.mean(&[0, 2], true)?
67 } else {
68 return Err(hodu_core::error::HoduError::InternalError(format!(
69 "BatchNorm1d expects 2D [N, C] or 3D [N, C, L] input, got {}D",
70 input_shape.ndim()
71 )));
72 };
73
74 let var = if input_shape.ndim() == 2 {
75 let centered = input.sub(&mean)?;
76 let squared = centered.mul(¢ered)?;
77 squared.mean(&[0], true)?
78 } else {
79 let centered = input.sub(&mean)?;
80 let squared = centered.mul(¢ered)?;
81 squared.mean(&[0, 2], true)?
82 };
83
84 {
88 let mut rm = self.running_mean.borrow_mut();
89 let mut rv = self.running_var.borrow_mut();
90
91 let momentum_typed = self.momentum.to_dtype(rm.dtype());
92 let one_minus_momentum = Scalar::from_f32(1.0, rm.dtype()) - momentum_typed;
93
94 let mean_squeezed = mean.reshape([self.num_features])?;
96 let var_squeezed = var.reshape([self.num_features])?;
97
98 let new_mean = rm
99 .mul_scalar(momentum_typed)?
100 .add(&mean_squeezed.mul_scalar(one_minus_momentum)?)?;
101 let new_var = rv
102 .mul_scalar(momentum_typed)?
103 .add(&var_squeezed.mul_scalar(one_minus_momentum)?)?;
104
105 *rm = new_mean;
106 *rv = new_var;
107 }
108
109 let normalized = self.normalize(input, &mean, &var)?;
111
112 Ok(normalized)
113 } else {
114 let mean = self.running_mean.borrow();
116 let var = self.running_var.borrow();
117
118 let mean = if input_shape.ndim() == 2 {
120 mean.reshape([1, self.num_features])?
121 } else {
122 mean.reshape([1, self.num_features, 1])?
123 };
124
125 let var = if input_shape.ndim() == 2 {
126 var.reshape([1, self.num_features])?
127 } else {
128 var.reshape([1, self.num_features, 1])?
129 };
130
131 let normalized = self.normalize(input, &mean, &var)?;
132
133 Ok(normalized)
134 }
135 }
136
137 fn normalize(&self, input: &Tensor, mean: &Tensor, var: &Tensor) -> HoduResult<Tensor> {
138 let input_shape = input.shape();
139
140 let centered = input.sub(mean)?;
142 let eps_typed = self.eps.to_dtype(input.dtype());
143 let std = var.add_scalar(eps_typed)?.sqrt()?;
144 let normalized = centered.div(&std)?;
145
146 if self.affine {
148 let gamma = self.gamma.as_ref().unwrap();
149 let beta = self.beta.as_ref().unwrap();
150
151 let gamma_reshaped = if input_shape.ndim() == 2 {
152 gamma.reshape([1, self.num_features])?
153 } else {
154 gamma.reshape([1, self.num_features, 1])?
155 };
156
157 let beta_reshaped = if input_shape.ndim() == 2 {
158 beta.reshape([1, self.num_features])?
159 } else {
160 beta.reshape([1, self.num_features, 1])?
161 };
162
163 let scaled = normalized.mul(&gamma_reshaped)?;
164 scaled.add(&beta_reshaped)
165 } else {
166 Ok(normalized)
167 }
168 }
169
170 pub fn parameters(&mut self) -> Vec<&mut Tensor> {
171 let mut params = Vec::new();
172 if self.affine {
173 if let Some(ref mut gamma) = self.gamma {
174 params.push(gamma);
175 }
176 if let Some(ref mut beta) = self.beta {
177 params.push(beta);
178 }
179 }
180 params
181 }
182}
183
184#[derive(Module, Clone)]
185pub struct BatchNorm2D {
186 num_features: u32,
187 eps: Scalar,
188 momentum: Scalar,
189 affine: bool,
190 gamma: Option<Tensor>,
191 beta: Option<Tensor>,
192 running_mean: RefCell<Tensor>,
193 running_var: RefCell<Tensor>,
194}
195
196impl BatchNorm2D {
197 pub fn new(
198 num_features: u32,
199 eps: impl Into<Scalar>,
200 momentum: impl Into<Scalar>,
201 affine: bool,
202 dtype: DType,
203 ) -> HoduResult<Self> {
204 let eps = eps.into();
205 let momentum = momentum.into();
206
207 let (gamma, beta) = if affine {
208 let gamma = Tensor::ones([num_features], dtype)?;
209 gamma.set_requires_grad(true)?;
210
211 let beta = Tensor::zeros([num_features], dtype)?;
212 beta.set_requires_grad(true)?;
213
214 (Some(gamma), Some(beta))
215 } else {
216 (None, None)
217 };
218
219 let running_mean = RefCell::new(Tensor::zeros([num_features], dtype)?);
220 let running_var = RefCell::new(Tensor::ones([num_features], dtype)?);
221
222 Ok(Self {
223 num_features,
224 eps,
225 momentum,
226 affine,
227 gamma,
228 beta,
229 running_mean,
230 running_var,
231 })
232 }
233
234 pub fn forward(&self, input: &Tensor) -> HoduResult<Tensor> {
235 let input_shape = input.shape();
236
237 if input_shape.ndim() != 4 {
238 return Err(hodu_core::error::HoduError::InternalError(format!(
239 "BatchNorm2d expects 4D input [N, C, H, W], got {}D",
240 input_shape.ndim()
241 )));
242 }
243
244 if get_state() == State::Training {
245 let mean = input.mean(&[0, 2, 3], true)?;
248 let centered = input.sub(&mean)?;
249 let squared = centered.mul(¢ered)?;
250 let var = squared.mean(&[0, 2, 3], true)?;
251
252 {
254 let mut rm = self.running_mean.borrow_mut();
255 let mut rv = self.running_var.borrow_mut();
256
257 let momentum_typed = self.momentum.to_dtype(rm.dtype());
258 let one_minus_momentum = Scalar::from_f32(1.0, rm.dtype()) - momentum_typed;
259
260 let mean_squeezed = mean.reshape([self.num_features])?;
262 let var_squeezed = var.reshape([self.num_features])?;
263
264 let new_mean = rm
265 .mul_scalar(momentum_typed)?
266 .add(&mean_squeezed.mul_scalar(one_minus_momentum)?)?;
267 let new_var = rv
268 .mul_scalar(momentum_typed)?
269 .add(&var_squeezed.mul_scalar(one_minus_momentum)?)?;
270
271 *rm = new_mean;
272 *rv = new_var;
273 }
274
275 let normalized = self.normalize(input, &mean, &var)?;
276 Ok(normalized)
277 } else {
278 let mean = self.running_mean.borrow();
280 let var = self.running_var.borrow();
281
282 let mean = mean.reshape([1, self.num_features, 1, 1])?;
283 let var = var.reshape([1, self.num_features, 1, 1])?;
284
285 let normalized = self.normalize(input, &mean, &var)?;
286 Ok(normalized)
287 }
288 }
289
290 fn normalize(&self, input: &Tensor, mean: &Tensor, var: &Tensor) -> HoduResult<Tensor> {
291 let centered = input.sub(mean)?;
293 let eps_typed = self.eps.to_dtype(input.dtype());
294 let std = var.add_scalar(eps_typed)?.sqrt()?;
295 let normalized = centered.div(&std)?;
296
297 if self.affine {
299 let gamma = self.gamma.as_ref().unwrap();
300 let beta = self.beta.as_ref().unwrap();
301
302 let gamma_reshaped = gamma.reshape([1, self.num_features, 1, 1])?;
303 let beta_reshaped = beta.reshape([1, self.num_features, 1, 1])?;
304
305 let scaled = normalized.mul(&gamma_reshaped)?;
306 scaled.add(&beta_reshaped)
307 } else {
308 Ok(normalized)
309 }
310 }
311
312 pub fn parameters(&mut self) -> Vec<&mut Tensor> {
313 let mut params = Vec::new();
314 if self.affine {
315 if let Some(ref mut gamma) = self.gamma {
316 params.push(gamma);
317 }
318 if let Some(ref mut beta) = self.beta {
319 params.push(beta);
320 }
321 }
322 params
323 }
324}
325
326#[derive(Module, Clone)]
327pub struct BatchNorm3D {
328 num_features: u32,
329 eps: Scalar,
330 momentum: Scalar,
331 affine: bool,
332 gamma: Option<Tensor>,
333 beta: Option<Tensor>,
334 running_mean: RefCell<Tensor>,
335 running_var: RefCell<Tensor>,
336}
337
338impl BatchNorm3D {
339 pub fn new(
340 num_features: u32,
341 eps: impl Into<Scalar>,
342 momentum: impl Into<Scalar>,
343 affine: bool,
344 dtype: DType,
345 ) -> HoduResult<Self> {
346 let eps = eps.into();
347 let momentum = momentum.into();
348
349 let (gamma, beta) = if affine {
350 let gamma = Tensor::ones([num_features], dtype)?;
351 gamma.set_requires_grad(true)?;
352
353 let beta = Tensor::zeros([num_features], dtype)?;
354 beta.set_requires_grad(true)?;
355
356 (Some(gamma), Some(beta))
357 } else {
358 (None, None)
359 };
360
361 let running_mean = RefCell::new(Tensor::zeros([num_features], dtype)?);
362 let running_var = RefCell::new(Tensor::ones([num_features], dtype)?);
363
364 Ok(Self {
365 num_features,
366 eps,
367 momentum,
368 affine,
369 gamma,
370 beta,
371 running_mean,
372 running_var,
373 })
374 }
375
376 pub fn forward(&self, input: &Tensor) -> HoduResult<Tensor> {
377 let input_shape = input.shape();
378
379 if input_shape.ndim() != 5 {
380 return Err(hodu_core::error::HoduError::InternalError(format!(
381 "BatchNorm3D expects 5D input [N, C, D, H, W], got {}D",
382 input_shape.ndim()
383 )));
384 }
385
386 if get_state() == State::Training {
387 let mean = input.mean(&[0, 2, 3, 4], true)?;
390 let centered = input.sub(&mean)?;
391 let squared = centered.mul(¢ered)?;
392 let var = squared.mean(&[0, 2, 3, 4], true)?;
393
394 {
396 let mut rm = self.running_mean.borrow_mut();
397 let mut rv = self.running_var.borrow_mut();
398
399 let momentum_typed = self.momentum.to_dtype(rm.dtype());
400 let one_minus_momentum = Scalar::from_f32(1.0, rm.dtype()) - momentum_typed;
401
402 let mean_squeezed = mean.reshape([self.num_features])?;
404 let var_squeezed = var.reshape([self.num_features])?;
405
406 let new_mean = rm
407 .mul_scalar(momentum_typed)?
408 .add(&mean_squeezed.mul_scalar(one_minus_momentum)?)?;
409 let new_var = rv
410 .mul_scalar(momentum_typed)?
411 .add(&var_squeezed.mul_scalar(one_minus_momentum)?)?;
412
413 *rm = new_mean;
414 *rv = new_var;
415 }
416
417 let normalized = self.normalize(input, &mean, &var)?;
418 Ok(normalized)
419 } else {
420 let mean = self.running_mean.borrow();
422 let var = self.running_var.borrow();
423
424 let mean = mean.reshape([1, self.num_features, 1, 1, 1])?;
425 let var = var.reshape([1, self.num_features, 1, 1, 1])?;
426
427 let normalized = self.normalize(input, &mean, &var)?;
428 Ok(normalized)
429 }
430 }
431
432 fn normalize(&self, input: &Tensor, mean: &Tensor, var: &Tensor) -> HoduResult<Tensor> {
433 let centered = input.sub(mean)?;
435 let eps_typed = self.eps.to_dtype(input.dtype());
436 let std = var.add_scalar(eps_typed)?.sqrt()?;
437 let normalized = centered.div(&std)?;
438
439 if self.affine {
441 let gamma = self.gamma.as_ref().unwrap();
442 let beta = self.beta.as_ref().unwrap();
443
444 let gamma_reshaped = gamma.reshape([1, self.num_features, 1, 1, 1])?;
445 let beta_reshaped = beta.reshape([1, self.num_features, 1, 1, 1])?;
446
447 let scaled = normalized.mul(&gamma_reshaped)?;
448 scaled.add(&beta_reshaped)
449 } else {
450 Ok(normalized)
451 }
452 }
453
454 pub fn parameters(&mut self) -> Vec<&mut Tensor> {
455 let mut params = Vec::new();
456 if self.affine {
457 if let Some(ref mut gamma) = self.gamma {
458 params.push(gamma);
459 }
460 if let Some(ref mut beta) = self.beta {
461 params.push(beta);
462 }
463 }
464 params
465 }
466}
467
468#[derive(Module, Clone)]
469pub struct LayerNorm {
470 normalized_shape: Vec<u32>,
471 eps: Scalar,
472 elementwise_affine: bool,
473 gamma: Option<Tensor>,
474 beta: Option<Tensor>,
475}
476
477impl LayerNorm {
478 pub fn new(
479 normalized_shape: Vec<u32>,
480 eps: impl Into<Scalar>,
481 elementwise_affine: bool,
482 dtype: DType,
483 ) -> HoduResult<Self> {
484 let eps = eps.into();
485
486 let (gamma, beta) = if elementwise_affine {
487 let gamma = Tensor::ones(&normalized_shape, dtype)?;
488 gamma.set_requires_grad(true)?;
489
490 let beta = Tensor::zeros(&normalized_shape, dtype)?;
491 beta.set_requires_grad(true)?;
492
493 (Some(gamma), Some(beta))
494 } else {
495 (None, None)
496 };
497
498 Ok(Self {
499 normalized_shape,
500 eps,
501 elementwise_affine,
502 gamma,
503 beta,
504 })
505 }
506
507 pub fn forward(&self, input: &Tensor) -> HoduResult<Tensor> {
508 let input_shape = input.shape();
509 let input_rank = input_shape.ndim();
510 let norm_rank = self.normalized_shape.len() as u32;
511
512 if input_rank < norm_rank {
514 return Err(hodu_core::error::HoduError::InternalError(format!(
515 "Input rank {} is less than normalized_shape rank {}",
516 input_rank, norm_rank
517 )));
518 }
519
520 for i in 0..norm_rank {
521 if input_shape[input_rank - norm_rank + i] != self.normalized_shape[i as usize] {
522 return Err(hodu_core::error::HoduError::InternalError(format!(
523 "Input shape mismatch at dimension {}",
524 i
525 )));
526 }
527 }
528
529 let axes: Vec<u32> = (0..norm_rank).map(|i| input_rank - norm_rank + i).collect();
531
532 let mean = input.mean(&axes, true)?;
534 let centered = input.sub(&mean)?;
535 let squared = centered.mul(¢ered)?;
536 let var = squared.mean(&axes, true)?;
537
538 let eps_typed = self.eps.to_dtype(input.dtype());
540 let std = var.add_scalar(eps_typed)?.sqrt()?;
541 let normalized = centered.div(&std)?;
542
543 if self.elementwise_affine {
545 let gamma = self.gamma.as_ref().unwrap();
546 let beta = self.beta.as_ref().unwrap();
547
548 let scaled = normalized.mul(gamma)?;
549 scaled.add(beta)
550 } else {
551 Ok(normalized)
552 }
553 }
554
555 pub fn parameters(&mut self) -> Vec<&mut Tensor> {
556 let mut params = Vec::new();
557 if self.elementwise_affine {
558 if let Some(ref mut gamma) = self.gamma {
559 params.push(gamma);
560 }
561 if let Some(ref mut beta) = self.beta {
562 params.push(beta);
563 }
564 }
565 params
566 }
567}