1use super::reparameterization_dyn::{self, DynReparameterization};
2use super::{Param, ParamId, Parameter, ParameterValue, Reparameterization};
3use crate::module::{
4 Content, Module, ModuleDisplay, ModuleDisplayDefault, ModuleMapper, ModuleVisitor,
5};
6use alloc::{boxed::Box, format, string::ToString, vec::Vec};
7use burn_tensor::{Bool, Device, Float, Int, Tensor, TensorData};
8
9impl<const D: usize> super::sealed::Sealed for Tensor<D, Float> {
10 fn is_active(&self) -> bool {
11 Tensor::is_require_grad(self)
12 }
13
14 fn materialize(self, reparameterization: &dyn DynReparameterization) -> Self {
15 *reparameterization
16 .materialize_dyn(Box::new(self))
17 .downcast::<Tensor<D>>()
18 .expect("Reparameterization should preserve tensor rank")
19 }
20}
21impl<const D: usize> super::sealed::Sealed for Tensor<D, Int> {
22 fn is_active(&self) -> bool {
23 false
24 }
25}
26impl<const D: usize> super::sealed::Sealed for Tensor<D, Bool> {
27 fn is_active(&self) -> bool {
28 false
29 }
30}
31
32impl<const D: usize> ParameterValue for Tensor<D, Float> {}
33
34impl<const D: usize> Parameter for Tensor<D, Float> {
35 fn is_require_grad(&self) -> bool {
36 Tensor::is_require_grad(self)
37 }
38
39 fn set_require_grad(self, require_grad: bool) -> Self {
40 if require_grad && !self.is_autodiff() {
42 self
43 } else {
44 Tensor::set_require_grad(self, require_grad)
45 }
46 }
47
48 fn device(&self) -> Device {
49 Tensor::device(self)
50 }
51
52 fn shape(&self) -> burn_std::Shape {
53 Tensor::shape(self)
54 }
55
56 fn load_to_device(self, device: &Device) -> Self {
57 if self.device() != *device {
58 Tensor::to_device(self, device).detach()
59 } else {
60 self
61 }
62 }
63}
64
65impl<const D: usize> ParameterValue for Tensor<D, Int> {}
66
67impl<const D: usize> Parameter for Tensor<D, Int> {
68 fn is_require_grad(&self) -> bool {
69 false
70 }
71
72 fn set_require_grad(self, _require_grad: bool) -> Self {
73 self
74 }
75
76 fn device(&self) -> Device {
77 Tensor::device(self)
78 }
79
80 fn shape(&self) -> burn_std::Shape {
81 Tensor::shape(self)
82 }
83
84 fn load_to_device(self, device: &Device) -> Self {
85 if self.device() != *device {
86 Tensor::to_device(self, device)
87 } else {
88 self
89 }
90 }
91}
92
93impl<const D: usize> ParameterValue for Tensor<D, Bool> {}
94
95impl<const D: usize> Parameter for Tensor<D, Bool> {
96 fn is_require_grad(&self) -> bool {
97 false
98 }
99
100 fn set_require_grad(self, _require_grad: bool) -> Self {
101 self
102 }
103
104 fn device(&self) -> Device {
105 Tensor::device(self)
106 }
107
108 fn shape(&self) -> burn_std::Shape {
109 Tensor::shape(self)
110 }
111
112 fn load_to_device(self, device: &Device) -> Self {
113 if self.device() != *device {
114 Tensor::to_device(self, device)
115 } else {
116 self
117 }
118 }
119}
120
121impl<const D: usize> Param<Tensor<D>> {
122 pub fn from_tensor(value: Tensor<D>) -> Self {
130 let mut param =
133 Param::initialized(ParamId::new(), Parameter::set_require_grad(value, true));
134 param.is_active = true;
135 param
136 }
137
138 pub fn from_data<T>(data: T, device: &Device) -> Self
140 where
141 T: Into<TensorData>,
142 {
143 let data: TensorData = data.into();
144 device.memory_persistent_allocations(data, |data| {
147 let value = Tensor::from_data(data, device);
148 let mut param =
149 Param::initialized(ParamId::new(), Parameter::set_require_grad(value, true));
150 param.is_active = true;
151 param
152 })
153 }
154
155 pub(crate) fn with_reparameterization<R>(mut self, reparameterization: R) -> Self
157 where
158 R: Reparameterization,
159 {
160 self.reparameterization = Some(reparameterization_dyn::boxed::<R, D>(reparameterization));
161 self
162 }
163}
164
165impl<const D: usize> Module for Param<Tensor<D>> {
166 fn visit<V: ModuleVisitor>(&self, visitor: &mut V) {
167 match self.reparameterization_dyn() {
168 None => visitor.visit_float(self),
169 Some(reparameterization) => {
170 visitor.visit_float(&self.without_reparameterization());
171 visitor.enter_module(reparameterization.name(), "Reparameterization");
172 reparameterization_dyn::visit(reparameterization, visitor);
173 visitor.exit_module(reparameterization.name(), "Reparameterization");
174 }
175 }
176 }
177
178 fn map<M: ModuleMapper>(mut self, mapper: &mut M) -> Self {
179 match self.reparameterization.take() {
180 None => mapper.map_float(self),
181 Some(reparameterization) => {
182 let base = mapper.map_float(self);
183 mapper.enter_module(reparameterization.name(), "Reparameterization");
184 let reparameterization = reparameterization_dyn::map(reparameterization, mapper);
185 mapper.exit_module(reparameterization.name(), "Reparameterization");
186 base.with_dyn_reparameterization(Some(reparameterization))
187 }
188 }
189 }
190
191 fn to_device(mut self, device: &Device) -> Self {
192 let reparameterization = self.reparameterization.take();
193 let base = self.map_to_device(device, |tensor| tensor.to_device(device));
194 match reparameterization {
195 None => base,
196 Some(reparameterization) => {
197 base.with_dyn_reparameterization(Some(reparameterization.to_device_dyn(device)))
198 }
199 }
200 }
201
202 fn fork(mut self, device: &Device) -> Self {
203 let reparameterization = self.reparameterization.take();
204 let base = self.map_to_device(device, |tensor| {
205 let is_require_grad = tensor.is_require_grad();
206 let mut tensor = tensor.to_device(device).detach();
207
208 if is_require_grad {
209 tensor = tensor.require_grad();
210 }
211
212 tensor
213 });
214 match reparameterization {
215 None => base,
216 Some(reparameterization) => {
217 base.with_dyn_reparameterization(Some(reparameterization.fork_dyn(device)))
218 }
219 }
220 }
221
222 fn collect_devices(&self, mut devices: Vec<Device>) -> Vec<Device> {
223 let device = self.base().device();
224
225 if !devices.contains(&device) {
226 devices.push(device)
227 }
228
229 if let Some(reparameterization) = self.reparameterization_dyn() {
230 devices = reparameterization.collect_devices_dyn(devices);
231 }
232
233 devices
234 }
235
236 fn valid(&self) -> Self {
237 let is_active = self.is_active;
246 let mut param = Param::from_mapped_value(
247 self.id,
248 self.val().without_autodiff().set_require_grad(false),
249 self.param_mapper.clone(),
250 );
251 param.is_active = is_active;
252 param
253 }
254
255 fn train(mut self) -> Self {
256 let reparameterization = self.reparameterization.take();
258 let is_active = self.is_active;
260 let tensor = Tensor::from_inner(self.val()).set_require_grad(is_active);
261 let mut base = Param::from_mapped_value(self.id, tensor, self.param_mapper);
262 base.is_active = is_active;
263 match reparameterization {
264 None => base,
265 Some(reparameterization) => {
266 base.with_dyn_reparameterization(Some(reparameterization.train_dyn()))
267 }
268 }
269 }
270}
271
272impl<const D: usize> ModuleDisplayDefault for Param<Tensor<D>> {
273 fn content(&self, content: Content) -> Option<Content> {
274 let id = if content.display_settings.show_param_id() {
275 format!(", id: {}", self.id)
276 } else {
277 "".to_string()
278 };
279 let string = format!(
280 "ParamTensor {{rank: {D}, shape: {:?}, kind: float{id}}}",
281 self.shape().as_slice()
282 );
283 content.add_formatted(&string).optional()
284 }
285}
286impl<const D: usize> ModuleDisplay for Param<Tensor<D>> {}
287
288impl<const D: usize> Module for Param<Tensor<D, Int>> {
289 fn visit<V: ModuleVisitor>(&self, visitor: &mut V) {
290 visitor.visit_int(self)
291 }
292
293 fn map<M: ModuleMapper>(self, mapper: &mut M) -> Self {
294 mapper.map_int(self)
295 }
296
297 fn to_device(self, device: &Device) -> Self {
298 self.map_to_device(device, |tensor| tensor.to_device(device))
299 }
300
301 fn fork(self, device: &Device) -> Self {
302 self.to_device(device) }
304
305 fn collect_devices(&self, mut devices: Vec<Device>) -> Vec<Device> {
306 let device = self.val().device();
307
308 if !devices.contains(&device) {
309 devices.push(device)
310 }
311
312 devices
313 }
314
315 fn valid(&self) -> Self {
316 Param::from_mapped_value(
317 self.id,
318 self.val().without_autodiff(),
319 self.param_mapper.clone(),
320 )
321 }
322
323 fn train(self) -> Self {
324 Param::from_mapped_value(self.id, Tensor::from_inner(self.val()), self.param_mapper)
325 }
326}
327
328impl<const D: usize> ModuleDisplayDefault for Param<Tensor<D, Int>> {
329 fn content(&self, content: Content) -> Option<Content> {
330 let id = if content.display_settings.show_param_id() {
331 format!(", id: {}", self.id)
332 } else {
333 "".to_string()
334 };
335 let string = format!(
336 "ParamTensor {{rank: {D}, shape: {:?}, kind: int{id}}}",
337 self.shape().as_slice()
338 );
339 content.add_formatted(&string).optional()
340 }
341}
342impl<const D: usize> ModuleDisplay for Param<Tensor<D, Int>> {}
343
344impl<const D: usize> Module for Param<Tensor<D, Bool>> {
345 fn visit<V: ModuleVisitor>(&self, visitor: &mut V) {
346 visitor.visit_bool(self)
347 }
348
349 fn map<M: ModuleMapper>(self, mapper: &mut M) -> Self {
350 mapper.map_bool(self)
351 }
352
353 fn to_device(self, device: &Device) -> Self {
354 self.map_to_device(device, |tensor| tensor.to_device(device))
355 }
356
357 fn fork(self, device: &Device) -> Self {
358 self.to_device(device) }
360
361 fn collect_devices(&self, mut devices: Vec<Device>) -> Vec<Device> {
362 let device = self.val().device();
363
364 if !devices.contains(&device) {
365 devices.push(device)
366 }
367
368 devices
369 }
370
371 fn valid(&self) -> Self {
372 Param::from_mapped_value(
373 self.id,
374 self.val().without_autodiff(),
375 self.param_mapper.clone(),
376 )
377 }
378
379 fn train(self) -> Self {
380 Param::from_mapped_value(self.id, Tensor::from_inner(self.val()), self.param_mapper)
381 }
382}
383
384impl<const D: usize> ModuleDisplayDefault for Param<Tensor<D, Bool>> {
385 fn content(&self, content: Content) -> Option<Content> {
386 let id = if content.display_settings.show_param_id() {
387 format!(", id: {}", self.id)
388 } else {
389 "".to_string()
390 };
391
392 let string = format!(
393 "ParamTensor {{rank: {D}, shape: {:?}, kind: bool{id}}}",
394 self.shape().as_slice()
395 );
396 content.add_formatted(&string).optional()
397 }
398}
399
400impl<const D: usize> ModuleDisplay for Param<Tensor<D, Bool>> {}
401
402#[cfg(all(test, feature = "std", feature = "autodiff"))]
403mod tests {
404 use super::*;
405 use crate::{
406 module::{LoraAdapter, Module},
407 test_device,
408 };
409 use burn_tensor::Distribution;
410
411 #[test]
412 fn set_require_grad_updates_lazy_lifecycle_state() {
413 let device = test_device().autodiff();
414 let param: Param<Tensor<2>> = Param::uninitialized(
415 ParamId::new(),
416 |device, require_grad| Tensor::ones([2, 3], device).set_require_grad(require_grad),
417 device,
418 true,
419 [2, 3].into(),
420 );
421
422 let param = param.set_require_grad(false);
423
424 assert!(!param.is_initialized());
425 assert!(!param.is_active);
426 assert!(!param.val().is_require_grad());
427
428 let param = param.valid().train();
429
430 assert!(!param.is_require_grad());
431 assert!(!param.is_active);
432 }
433
434 #[test]
435 fn set_require_grad_on_a_plain_tensor_is_applied_by_train() {
436 let device = test_device();
437 let param = Param::initialized(
438 ParamId::new(),
439 Tensor::<2>::ones([2, 3], &device).set_require_grad(false),
440 )
441 .set_require_grad(true);
442
443 assert!(!param.is_require_grad());
444 assert!(param.is_active);
445
446 let param = param.train();
447
448 assert!(param.is_require_grad());
449 assert!(param.is_active);
450 }
451
452 #[test]
453 fn trainable_param_created_on_a_plain_device_is_applied_by_train() {
454 let device = test_device();
455 let param = Param::from_tensor(Tensor::<2>::ones([2, 3], &device));
456
457 assert!(!param.is_require_grad());
458 assert!(param.is_active);
459
460 let param = param.train();
461
462 assert!(param.is_require_grad());
463 assert!(param.is_active);
464 }
465
466 #[test]
467 fn lazy_activation_setting_on_a_plain_device_is_applied_by_train() {
468 let device = test_device();
469 let param: Param<Tensor<2>> = Param::uninitialized(
470 ParamId::new(),
471 |device, require_grad| Tensor::ones([2, 3], device).set_require_grad(require_grad),
472 device,
473 false,
474 [2, 3].into(),
475 )
476 .set_require_grad(true);
477
478 assert!(!param.is_initialized());
479 assert!(param.is_active);
480 assert!(!param.val().is_require_grad());
481
482 let param = param.train();
483
484 assert!(param.is_require_grad());
485 assert!(param.is_active);
486 }
487
488 #[test]
489 fn mapping_a_validation_param_preserves_what_train_restores() {
490 let device = test_device().autodiff();
491 let param = Param::from_tensor(Tensor::<2>::ones([2, 3], &device))
492 .valid()
493 .map(|tensor| tensor);
494
495 assert!(!param.is_require_grad());
496 assert!(param.is_active);
497
498 let param = param.train();
499
500 assert!(param.is_require_grad());
501 assert!(param.is_active);
502 }
503
504 #[test]
505 fn mapped_value_reconstruction_preserves_what_train_restores() {
506 let device = test_device().autodiff();
507 let valid = Param::from_tensor(Tensor::<2>::ones([2, 3], &device)).valid();
508 let (id, tensor, mapper) = valid.consume();
509
510 let param = Param::from_mapped_value(id, tensor, mapper);
511
512 assert!(!param.is_require_grad());
513 assert!(param.is_active);
514
515 let param = param.train();
516
517 assert!(param.is_require_grad());
518 assert!(param.is_active);
519 }
520
521 #[test]
522 fn loading_a_validation_param_preserves_what_train_restores() {
523 let device = test_device().autodiff();
524 let valid = Param::from_tensor(Tensor::<2>::ones([2, 3], &device)).valid();
525 let record = Tensor::<2>::zeros([2, 3], &test_device());
526
527 let param = valid.transform_for_load(record, ParamId::new());
528
529 assert!(!param.is_require_grad());
530 assert!(param.is_active);
531
532 let param = param.train();
533
534 assert!(param.is_require_grad());
535 assert!(param.is_active);
536 }
537
538 #[test]
539 fn test_param_require_grad_stateful() {
540 let device = test_device().autodiff();
541 let tensor = Tensor::<2>::ones([3, 3], &device).require_grad();
542
543 let param = Param::initialized(ParamId::new(), tensor);
544 assert!(param.is_require_grad());
545 assert!(param.is_active);
546
547 let param = param.valid();
548 assert!(!param.is_require_grad());
549 assert!(param.is_active); let param = param.train();
552 assert!(param.is_require_grad());
553 assert!(param.is_active); let param = param.no_grad();
556 assert!(!param.is_require_grad());
557 assert!(!param.is_active); let param = param.valid();
560 assert!(!param.is_require_grad()); assert!(!param.is_active); let param = param.train();
564 assert!(!param.is_require_grad());
565 assert!(!param.is_active); }
567
568 #[test]
569 fn a_lazy_param_with_an_init_mapper_trains_on_an_autodiff_device() {
570 let device = test_device().autodiff();
571 let param: Param<Tensor<2>> = Param::uninitialized(
572 ParamId::new(),
573 |device, require_grad| Tensor::ones([2, 3], device).set_require_grad(require_grad),
574 device,
575 true,
576 [2, 3].into(),
577 )
578 .init_mapper(|tensor| tensor.mul_scalar(2.0));
579
580 let value = param.val();
581 let grads = value.clone().sum().backward();
582
583 value
584 .into_data()
585 .assert_eq(&TensorData::from([[2.0f32; 3]; 2]), false);
586 param
587 .grad(&grads)
588 .expect("the mapped value is the leaf that receives the gradient")
589 .into_data()
590 .assert_eq(&TensorData::from([[1.0f32; 3]; 2]), false);
591 }
592
593 #[test]
594 fn counting_a_lazy_param_leaves_it_uninitialized() {
595 let param: Param<Tensor<2>> = Param::uninitialized(
596 ParamId::new(),
597 |device, require_grad| Tensor::ones([2, 3], device).set_require_grad(require_grad),
598 test_device(),
599 true,
600 [2, 3].into(),
601 );
602
603 assert_eq!(Module::num_params(¶m), 6);
604 assert!(!param.is_initialized());
605 }
606
607 #[test]
608 fn a_lazy_param_moved_then_loaded_never_initializes() {
609 let device = test_device();
610 let param: Param<Tensor<2>> = Param::uninitialized(
611 ParamId::new(),
612 |_, _| panic!("the moved parameter initialized before loading"),
613 device.clone(),
614 false,
615 [2, 3].into(),
616 );
617
618 let moved = param.to_device(&device.clone().autodiff());
619
620 assert!(!moved.is_initialized());
621 moved.transform_for_load(Tensor::ones([2, 3], &device), ParamId::new());
622 }
623
624 #[test]
625 fn a_moved_lazy_param_keeps_the_autodiff_context_it_was_built_with() {
626 let device = test_device();
627 let lazy_ones = |device: &Device| -> Param<Tensor<2>> {
628 Param::uninitialized(
629 ParamId::new(),
630 |device, require_grad| Tensor::ones([2, 3], device).set_require_grad(require_grad),
631 device.clone(),
632 true,
633 [2, 3].into(),
634 )
635 };
636
637 let onto_autodiff = lazy_ones(&device).to_device(&device.clone().autodiff());
638 let onto_plain = lazy_ones(&device.clone().autodiff()).fork(&device);
639
640 assert!(!onto_autodiff.lazy_device().is_autodiff());
641 assert!(onto_plain.lazy_device().is_autodiff());
642 }
643
644 #[test]
645 fn a_moved_param_keeps_its_reparameterization() {
646 let device = test_device();
647 let adapter = || LoraAdapter {
648 a: Param::from_tensor(Tensor::<2>::ones([3, 1], &device)),
649 b: Param::from_tensor(Tensor::<2>::zeros([1, 3], &device)),
650 scale: 1.0,
651 };
652 let lazy: Param<Tensor<2>> = Param::uninitialized(
653 ParamId::new(),
654 |device, _| Tensor::ones([3, 3], device),
655 device.clone(),
656 false,
657 [3, 3].into(),
658 )
659 .with_reparameterization(adapter());
660 let initialized = Param::from_tensor(Tensor::<2>::ones([3, 3], &device))
661 .with_reparameterization(adapter());
662
663 let target = device.clone().autodiff();
664
665 assert!(lazy.to_device(&target).adapter().is_some());
666 assert!(initialized.fork(&target).adapter().is_some());
667 }
668
669 #[test]
670 fn init_mapper_preserves_lora_after_a_clone_initializes_the_base() {
671 let device = test_device();
672 let param: Param<Tensor<2>> = Param::uninitialized(
673 ParamId::new(),
674 |device, _| Tensor::ones([3, 3], device),
675 device.clone(),
676 false,
677 [3, 3].into(),
678 )
679 .with_reparameterization(crate::module::LoraAdapter {
680 a: Param::from_tensor(Tensor::<2>::ones([3, 1], &device)),
681 b: Param::from_tensor(Tensor::<2>::ones([1, 3], &device)),
682 scale: 2.0,
683 });
684 let clone = param.clone();
685 assert!(!param.is_initialized());
686 let mapped = param.init_mapper(|value| value.mul_scalar(2.0));
687
688 clone
691 .val()
692 .into_data()
693 .assert_eq(&TensorData::from([[3.0f32; 3]; 3]), true);
694 drop(clone);
695 assert!(!mapped.is_initialized());
696
697 mapped
699 .val()
700 .into_data()
701 .assert_eq(&TensorData::from([[6.0f32; 3]; 3]), true);
702 }
703
704 #[test]
705 fn a_lazy_int_param_moved_never_initializes() {
706 let device = test_device();
707 let param: Param<Tensor<2, Int>> = Param::uninitialized(
708 ParamId::new(),
709 |_, _| panic!("the moved parameter initialized"),
710 device.clone(),
711 false,
712 [2, 3].into(),
713 );
714
715 let param = param.to_device(&device.clone().autodiff());
716
717 assert!(!param.is_initialized());
718 assert!(!param.lazy_device().is_autodiff());
719 }
720
721 #[test]
722 fn a_lazy_bool_param_moved_never_initializes() {
723 let device = test_device();
724 let param: Param<Tensor<2, Bool>> = Param::uninitialized(
725 ParamId::new(),
726 |_, _| panic!("the moved parameter initialized"),
727 device.clone(),
728 false,
729 [2, 3].into(),
730 );
731
732 let param = param.to_device(&device.clone().autodiff());
733
734 assert!(!param.is_initialized());
735 assert!(!param.lazy_device().is_autodiff());
736 }
737
738 #[test]
739 fn a_lazy_param_forked_keeps_its_gradient_requirement() {
740 let device = test_device();
741 let param: Param<Tensor<2>> = Param::uninitialized(
742 ParamId::new(),
743 |device, require_grad| Tensor::ones([2, 3], device).set_require_grad(require_grad),
744 device.clone().autodiff(),
745 true,
746 [2, 3].into(),
747 );
748
749 let param = param.fork(&device);
750
751 assert!(!param.is_initialized());
752 assert!(param.val().is_require_grad());
753 }
754
755 #[test]
756 fn a_lazy_param_with_an_init_mapper_follows_the_move() {
757 let device = test_device().autodiff();
758 let param: Param<Tensor<2>> = Param::uninitialized(
759 ParamId::new(),
760 |device, require_grad| Tensor::ones([2, 3], device).set_require_grad(require_grad),
761 device.clone(),
762 true,
763 [2, 3].into(),
764 )
765 .init_mapper(|tensor| tensor.mul_scalar(2.0));
766
767 let param = param.fork(&device);
768
769 assert!(!param.is_initialized());
770 let value = param.val();
771 assert!(value.device().is_autodiff());
772 assert!(value.is_require_grad());
773 }
774
775 #[test]
776 fn a_lazy_param_shared_with_a_clone_initializes_before_moving() {
777 let device = test_device();
778 let param: Param<Tensor<2>> = Param::uninitialized(
779 ParamId::new(),
780 |device, _| Tensor::random([2, 3], Distribution::Default, device),
781 device.clone(),
782 false,
783 [2, 3].into(),
784 );
785 let clone = param.clone();
786
787 let moved = param.to_device(&device.autodiff());
788
789 assert!(clone.is_initialized());
790 moved
791 .val()
792 .into_data()
793 .assert_eq(&clone.val().into_data(), true);
794 }
795}