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 apply_reparameterization(self, reparameterization: &dyn DynReparameterization) -> Self {
15 *reparameterization
16 .apply_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.lazy_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;
241 let mut param = Param::from_mapped_value(
242 self.id,
243 self.base().without_autodiff().set_require_grad(false),
244 self.param_mapper.clone(),
245 );
246 param.is_active = is_active;
247 param.reparameterization = self.reparameterization_dyn().map(|state| state.valid_dyn());
248 param
249 }
250
251 fn materialize(self) -> Self {
252 if self.reparameterization.is_none() {
253 return self;
254 }
255
256 let require_grad = self.base().is_require_grad();
259 let value = self.val().detach().set_require_grad(require_grad);
260 let is_active = self.is_active;
261 let mut param = Param::from_mapped_value(self.id, value, self.param_mapper);
262 param.is_active = is_active;
263 param
264 }
265
266 fn train(mut self) -> Self {
267 let reparameterization = self.reparameterization.take();
269 let is_active = self.is_active;
271 let tensor = Tensor::from_inner(self.val()).set_require_grad(is_active);
272 let mut base = Param::from_mapped_value(self.id, tensor, self.param_mapper);
273 base.is_active = is_active;
274 match reparameterization {
275 None => base,
276 Some(reparameterization) => {
277 base.with_dyn_reparameterization(Some(reparameterization.train_dyn()))
278 }
279 }
280 }
281}
282
283impl<const D: usize> ModuleDisplayDefault for Param<Tensor<D>> {
284 fn content(&self, content: Content) -> Option<Content> {
285 let id = if content.display_settings.show_param_id() {
286 format!(", id: {}", self.id)
287 } else {
288 "".to_string()
289 };
290 let string = format!(
291 "ParamTensor {{rank: {D}, shape: {:?}, kind: float{id}}}",
292 self.shape().as_slice()
293 );
294 content.add_formatted(&string).optional()
295 }
296}
297impl<const D: usize> ModuleDisplay for Param<Tensor<D>> {}
298
299impl<const D: usize> Module for Param<Tensor<D, Int>> {
300 fn materialize(self) -> Self {
301 self
302 }
303
304 fn visit<V: ModuleVisitor>(&self, visitor: &mut V) {
305 visitor.visit_int(self)
306 }
307
308 fn map<M: ModuleMapper>(self, mapper: &mut M) -> Self {
309 mapper.map_int(self)
310 }
311
312 fn to_device(self, device: &Device) -> Self {
313 self.map_to_device(device, |tensor| tensor.to_device(device))
314 }
315
316 fn fork(self, device: &Device) -> Self {
317 self.to_device(device) }
319
320 fn collect_devices(&self, mut devices: Vec<Device>) -> Vec<Device> {
321 let device = self.lazy_device();
322
323 if !devices.contains(&device) {
324 devices.push(device)
325 }
326
327 devices
328 }
329
330 fn valid(&self) -> Self {
331 Param::from_mapped_value(
332 self.id,
333 self.val().without_autodiff(),
334 self.param_mapper.clone(),
335 )
336 }
337
338 fn train(self) -> Self {
339 Param::from_mapped_value(self.id, Tensor::from_inner(self.val()), self.param_mapper)
340 }
341}
342
343impl<const D: usize> ModuleDisplayDefault for Param<Tensor<D, Int>> {
344 fn content(&self, content: Content) -> Option<Content> {
345 let id = if content.display_settings.show_param_id() {
346 format!(", id: {}", self.id)
347 } else {
348 "".to_string()
349 };
350 let string = format!(
351 "ParamTensor {{rank: {D}, shape: {:?}, kind: int{id}}}",
352 self.shape().as_slice()
353 );
354 content.add_formatted(&string).optional()
355 }
356}
357impl<const D: usize> ModuleDisplay for Param<Tensor<D, Int>> {}
358
359impl<const D: usize> Module for Param<Tensor<D, Bool>> {
360 fn materialize(self) -> Self {
361 self
362 }
363
364 fn visit<V: ModuleVisitor>(&self, visitor: &mut V) {
365 visitor.visit_bool(self)
366 }
367
368 fn map<M: ModuleMapper>(self, mapper: &mut M) -> Self {
369 mapper.map_bool(self)
370 }
371
372 fn to_device(self, device: &Device) -> Self {
373 self.map_to_device(device, |tensor| tensor.to_device(device))
374 }
375
376 fn fork(self, device: &Device) -> Self {
377 self.to_device(device) }
379
380 fn collect_devices(&self, mut devices: Vec<Device>) -> Vec<Device> {
381 let device = self.lazy_device();
382
383 if !devices.contains(&device) {
384 devices.push(device)
385 }
386
387 devices
388 }
389
390 fn valid(&self) -> Self {
391 Param::from_mapped_value(
392 self.id,
393 self.val().without_autodiff(),
394 self.param_mapper.clone(),
395 )
396 }
397
398 fn train(self) -> Self {
399 Param::from_mapped_value(self.id, Tensor::from_inner(self.val()), self.param_mapper)
400 }
401}
402
403impl<const D: usize> ModuleDisplayDefault for Param<Tensor<D, Bool>> {
404 fn content(&self, content: Content) -> Option<Content> {
405 let id = if content.display_settings.show_param_id() {
406 format!(", id: {}", self.id)
407 } else {
408 "".to_string()
409 };
410
411 let string = format!(
412 "ParamTensor {{rank: {D}, shape: {:?}, kind: bool{id}}}",
413 self.shape().as_slice()
414 );
415 content.add_formatted(&string).optional()
416 }
417}
418
419impl<const D: usize> ModuleDisplay for Param<Tensor<D, Bool>> {}
420
421#[cfg(all(test, feature = "std", feature = "autodiff"))]
422mod tests {
423 use super::*;
424 use crate as burn;
425 use crate::{
426 module::{LoraAdapter, Module},
427 test_device,
428 };
429 use burn_tensor::Distribution;
430
431 #[test]
432 fn validation_does_not_apply_reparameterizations() {
433 #[derive(Debug, Module)]
434 struct NeverApply {}
435
436 impl Reparameterization for NeverApply {
437 const NAME: &'static str = "never_apply";
438
439 fn apply<const D: usize>(&self, _base: Tensor<D>) -> Tensor<D> {
440 panic!("validation must not evaluate effective weights")
441 }
442 }
443
444 let device = test_device().autodiff();
445 let param = Param::from_tensor(Tensor::<2>::ones([2, 3], &device))
446 .with_reparameterization(NeverApply {});
447 let validation = param.valid().valid();
448 assert!(!validation.base().is_autodiff());
449 assert!(validation.reparameterization::<NeverApply>().is_some());
450 }
451
452 #[test]
453 fn materialize_detaches_factors_and_preserves_base_state_and_layout_mapping() {
454 let device = test_device().autodiff();
455 let a = Param::from_tensor(Tensor::<2>::ones([2, 1], &device));
456 let b = Param::from_tensor(Tensor::<2>::ones([1, 3], &device));
457 let param = Param::from_tensor(Tensor::<2>::ones([2, 3], &device))
458 .load_mapper(Tensor::transpose)
459 .save_mapper(Tensor::transpose)
460 .with_reparameterization(LoraAdapter {
461 a: a.clone(),
462 b: b.clone(),
463 scale: 2.0,
464 });
465
466 let merged = param.clone().materialize();
467 assert!(merged.adapter().is_none());
468 assert!(param.adapter().is_some());
469 assert_eq!(merged.id, param.id);
470 assert!(merged.base().is_require_grad());
471 let value = merged.base().into_data();
472 value.assert_eq(&TensorData::from([[3.0f32; 3]; 2]), true);
473 let saved = merged.transform_for_save().base().into_data();
474 saved.assert_eq(&TensorData::from([[3.0f32; 2]; 3]), true);
475 let loaded = merged
476 .clone()
477 .transform_for_load(Tensor::ones([3, 2], &device), merged.id);
478 assert_eq!(loaded.base().dims(), [2, 3]);
479
480 let grads = merged.val().sum().backward();
481 assert!(merged.base().grad(&grads).is_some());
482 assert!(param.base().grad(&grads).is_none());
483 assert!(a.val().grad(&grads).is_none());
484 assert!(b.val().grad(&grads).is_none());
485
486 let validation_merged = param.valid().materialize();
487 assert!(!validation_merged.base().is_autodiff());
488 assert!(validation_merged.train().base().is_require_grad());
489 let frozen_merged = param.freeze().materialize();
490 assert!(!frozen_merged.base().is_require_grad());
491 assert!(!frozen_merged.valid().train().base().is_require_grad());
492 }
493
494 #[test]
495 fn qlora_validation_and_records_preserve_packed_base_until_materialization() {
496 use crate::module::{Lora, QLora, Quantizer};
497 use burn_tensor::quantization::{Calibration, QuantValue};
498
499 let device = test_device().autodiff();
500 let scheme = device
501 .settings()
502 .quantization
503 .scheme
504 .with_value(QuantValue::Q8S);
505 let param = Param::from_tensor(Tensor::<2>::ones([4, 4], &device)).apply_qlora(QLora::new(
506 Lora::new(2, 4.0),
507 Quantizer::new(Calibration::MinMax, scheme),
508 ));
509 let inference = param.valid();
510 assert_eq!(inference.base().dtype(), param.base().dtype());
511 let dtype = inference.base().dtype();
512 assert!(matches!(dtype, burn_tensor::DType::QFloat(_)));
513 assert!(inference.adapter().is_some());
514 let restored = inference.clone().train();
515 assert!(!restored.base().is_require_grad());
516 let adapter = restored.adapter().unwrap();
517 assert!(adapter.a.val().is_require_grad());
518 assert!(adapter.b.val().is_require_grad());
519
520 let expected = inference.val().into_data();
521 let loaded = param.load_record(inference.clone().into_record());
522 assert_eq!(loaded.base().dtype(), inference.base().dtype());
523 assert!(loaded.adapter().is_some());
524 loaded.val().into_data().assert_eq(&expected, true);
525
526 let merged = inference.materialize().materialize();
527 assert!(merged.adapter().is_none());
528 assert!(merged.base().dtype().is_float());
529 assert!(!merged.base().is_autodiff());
530 merged.val().into_data().assert_eq(&expected, true);
531 let dense = Param::from_tensor(Tensor::<2>::zeros([4, 4], &test_device()));
532 let loaded = dense.load_record(merged.into_record());
533 loaded.val().into_data().assert_eq(&expected, true);
534 }
535
536 fn lazy_param_for_device_inspection<T: Parameter>(
537 device: &Device,
538 shape: [usize; 2],
539 ) -> Param<T> {
540 Param::uninitialized(
541 ParamId::new(),
542 |_, _| panic!("device inspection must not initialize parameters"),
543 device.clone(),
544 false,
545 shape.into(),
546 )
547 }
548
549 #[test]
550 fn devices_preserves_lazy_parameters_of_all_tensor_kinds() {
551 #[derive(Module, Debug)]
552 struct MixedParameters {
553 float: Param<Tensor<2>>,
554 int: Param<Tensor<2, Int>>,
555 bool: Param<Tensor<2, Bool>>,
556 initialized: Param<Tensor<2>>,
557 }
558
559 let device = test_device();
560 let model = MixedParameters {
561 float: lazy_param_for_device_inspection(&device, [2, 3]),
562 int: lazy_param_for_device_inspection(&device, [2, 3]),
563 bool: lazy_param_for_device_inspection(&device, [2, 3]),
564 initialized: Param::from_tensor(Tensor::ones([2, 3], &device)),
565 };
566 let clone = model.clone();
567
568 assert_eq!(model.devices(), alloc::vec![device.clone()]);
569 assert_eq!(clone.devices(), alloc::vec![device]);
570 assert!(!model.float.is_initialized());
571 assert!(!model.int.is_initialized());
572 assert!(!model.bool.is_initialized());
573 }
574
575 #[test]
576 fn devices_preserves_lazy_reparameterization_state() {
577 let device = test_device();
578 let param: Param<Tensor<2>> = lazy_param_for_device_inspection(&device, [2, 3]);
579
580 for base in [
582 param,
583 Param::from_tensor(Tensor::<2>::ones([2, 3], &device)),
584 ] {
585 let was_initialized = base.is_initialized();
586 let param = base.with_reparameterization(LoraAdapter {
587 a: lazy_param_for_device_inspection(&device, [2, 1]),
588 b: lazy_param_for_device_inspection(&device, [1, 3]),
589 scale: 1.0,
590 });
591
592 assert_eq!(param.devices(), alloc::vec![device.clone()]);
593 assert_eq!(param.is_initialized(), was_initialized);
594 let adapter = param.adapter().unwrap();
595 assert!(!adapter.a.is_initialized());
596 assert!(!adapter.b.is_initialized());
597 }
598 }
599
600 #[test]
601 fn devices_preserves_gradients_after_a_lazy_module_move() {
602 let device = test_device().autodiff();
603 let model = crate::test_utils::SimpleLinear {
604 weight: Param::uninitialized(
605 ParamId::new(),
606 |device, require_grad| Tensor::ones([1, 2], device).set_require_grad(require_grad),
607 device.clone(),
608 true,
609 [1, 2].into(),
610 ),
611 bias: Some(Param::uninitialized(
612 ParamId::new(),
613 |device, require_grad| Tensor::zeros([1], device).set_require_grad(require_grad),
614 device.clone(),
615 true,
616 [1].into(),
617 )),
618 };
619
620 assert_eq!(model.devices(), alloc::vec![device.clone()]);
621 assert!(!model.weight.is_initialized());
622 assert!(!model.bias.as_ref().unwrap().is_initialized());
623
624 let model = model.to_device(&device);
625 let output = Tensor::<2>::ones([1, 2], &device).matmul(model.weight.val().transpose())
626 + model.bias.as_ref().unwrap().val().unsqueeze();
627 let grads = output.sum().backward();
628
629 model
630 .weight
631 .grad(&grads)
632 .unwrap()
633 .into_data()
634 .assert_eq(&TensorData::from([[1.0f32, 1.0f32]]), false);
635 model
636 .bias
637 .as_ref()
638 .unwrap()
639 .grad(&grads)
640 .unwrap()
641 .into_data()
642 .assert_eq(&TensorData::from([1.0f32]), false);
643 }
644
645 #[test]
646 fn set_require_grad_updates_lazy_lifecycle_state() {
647 let device = test_device().autodiff();
648 let param: Param<Tensor<2>> = Param::uninitialized(
649 ParamId::new(),
650 |device, require_grad| Tensor::ones([2, 3], device).set_require_grad(require_grad),
651 device,
652 true,
653 [2, 3].into(),
654 );
655
656 let param = param.set_require_grad(false);
657
658 assert!(!param.is_initialized());
659 assert!(!param.is_active);
660 assert!(!param.val().is_require_grad());
661
662 let param = param.valid().train();
663
664 assert!(!param.is_require_grad());
665 assert!(!param.is_active);
666 }
667
668 #[test]
669 fn set_require_grad_on_a_plain_tensor_is_applied_by_train() {
670 let device = test_device();
671 let param = Param::initialized(
672 ParamId::new(),
673 Tensor::<2>::ones([2, 3], &device).set_require_grad(false),
674 )
675 .set_require_grad(true);
676
677 assert!(!param.is_require_grad());
678 assert!(param.is_active);
679
680 let param = param.train();
681
682 assert!(param.is_require_grad());
683 assert!(param.is_active);
684 }
685
686 #[test]
687 fn trainable_param_created_on_a_plain_device_is_applied_by_train() {
688 let device = test_device();
689 let param = Param::from_tensor(Tensor::<2>::ones([2, 3], &device));
690
691 assert!(!param.is_require_grad());
692 assert!(param.is_active);
693
694 let param = param.train();
695
696 assert!(param.is_require_grad());
697 assert!(param.is_active);
698 }
699
700 #[test]
701 fn lazy_activation_setting_on_a_plain_device_is_applied_by_train() {
702 let device = test_device();
703 let param: Param<Tensor<2>> = Param::uninitialized(
704 ParamId::new(),
705 |device, require_grad| Tensor::ones([2, 3], device).set_require_grad(require_grad),
706 device,
707 false,
708 [2, 3].into(),
709 )
710 .set_require_grad(true);
711
712 assert!(!param.is_initialized());
713 assert!(param.is_active);
714 assert!(!param.val().is_require_grad());
715
716 let param = param.train();
717
718 assert!(param.is_require_grad());
719 assert!(param.is_active);
720 }
721
722 #[test]
723 fn mapping_a_validation_param_preserves_what_train_restores() {
724 let device = test_device().autodiff();
725 let param = Param::from_tensor(Tensor::<2>::ones([2, 3], &device))
726 .valid()
727 .map(|tensor| tensor);
728
729 assert!(!param.is_require_grad());
730 assert!(param.is_active);
731
732 let param = param.train();
733
734 assert!(param.is_require_grad());
735 assert!(param.is_active);
736 }
737
738 #[test]
739 fn mapped_value_reconstruction_preserves_what_train_restores() {
740 let device = test_device().autodiff();
741 let valid = Param::from_tensor(Tensor::<2>::ones([2, 3], &device)).valid();
742 let (id, tensor, mapper) = valid.consume();
743
744 let param = Param::from_mapped_value(id, tensor, mapper);
745
746 assert!(!param.is_require_grad());
747 assert!(param.is_active);
748
749 let param = param.train();
750
751 assert!(param.is_require_grad());
752 assert!(param.is_active);
753 }
754
755 #[test]
756 fn loading_a_validation_param_preserves_what_train_restores() {
757 let device = test_device().autodiff();
758 let valid = Param::from_tensor(Tensor::<2>::ones([2, 3], &device)).valid();
759 let record = Tensor::<2>::zeros([2, 3], &test_device());
760
761 let param = valid.transform_for_load(record, ParamId::new());
762
763 assert!(!param.is_require_grad());
764 assert!(param.is_active);
765
766 let param = param.train();
767
768 assert!(param.is_require_grad());
769 assert!(param.is_active);
770 }
771
772 #[test]
773 fn test_param_require_grad_stateful() {
774 let device = test_device().autodiff();
775 let tensor = Tensor::<2>::ones([3, 3], &device).require_grad();
776
777 let param = Param::initialized(ParamId::new(), tensor);
778 assert!(param.is_require_grad());
779 assert!(param.is_active);
780
781 let param = param.valid();
782 assert!(!param.is_require_grad());
783 assert!(param.is_active); let param = param.train();
786 assert!(param.is_require_grad());
787 assert!(param.is_active); let param = param.no_grad();
790 assert!(!param.is_require_grad());
791 assert!(!param.is_active); let param = param.valid();
794 assert!(!param.is_require_grad()); assert!(!param.is_active); let param = param.train();
798 assert!(!param.is_require_grad());
799 assert!(!param.is_active); }
801
802 #[test]
803 fn a_lazy_param_with_an_init_mapper_trains_on_an_autodiff_device() {
804 let device = test_device().autodiff();
805 let param: Param<Tensor<2>> = Param::uninitialized(
806 ParamId::new(),
807 |device, require_grad| Tensor::ones([2, 3], device).set_require_grad(require_grad),
808 device,
809 true,
810 [2, 3].into(),
811 )
812 .init_mapper(|tensor| tensor.mul_scalar(2.0));
813
814 let value = param.val();
815 let grads = value.clone().sum().backward();
816
817 value
818 .into_data()
819 .assert_eq(&TensorData::from([[2.0f32; 3]; 2]), false);
820 param
821 .grad(&grads)
822 .expect("the mapped value is the leaf that receives the gradient")
823 .into_data()
824 .assert_eq(&TensorData::from([[1.0f32; 3]; 2]), false);
825 }
826
827 #[test]
828 fn counting_and_materializing_an_unadapted_lazy_param_leave_it_uninitialized() {
829 let param: Param<Tensor<2>> = Param::uninitialized(
830 ParamId::new(),
831 |device, require_grad| Tensor::ones([2, 3], device).set_require_grad(require_grad),
832 test_device(),
833 true,
834 [2, 3].into(),
835 );
836
837 assert_eq!(Module::num_params(¶m), 6);
838 assert!(!param.is_initialized());
839 let id = param.id;
840 let param = param.materialize().materialize();
841 assert_eq!(param.id, id);
842 assert!(!param.is_initialized());
843 }
844
845 #[test]
846 fn a_lazy_param_moved_then_loaded_never_initializes() {
847 let device = test_device();
848 let param: Param<Tensor<2>> = Param::uninitialized(
849 ParamId::new(),
850 |_, _| panic!("the moved parameter initialized before loading"),
851 device.clone(),
852 false,
853 [2, 3].into(),
854 );
855
856 let moved = param.to_device(&device.clone().autodiff());
857
858 assert!(!moved.is_initialized());
859 moved.transform_for_load(Tensor::ones([2, 3], &device), ParamId::new());
860 }
861
862 #[test]
863 fn a_moved_lazy_param_keeps_the_autodiff_context_it_was_built_with() {
864 let device = test_device();
865 let lazy_ones = |device: &Device| -> Param<Tensor<2>> {
866 Param::uninitialized(
867 ParamId::new(),
868 |device, require_grad| Tensor::ones([2, 3], device).set_require_grad(require_grad),
869 device.clone(),
870 true,
871 [2, 3].into(),
872 )
873 };
874
875 let onto_autodiff = lazy_ones(&device).to_device(&device.clone().autodiff());
876 let onto_plain = lazy_ones(&device.clone().autodiff()).fork(&device);
877
878 assert!(!onto_autodiff.lazy_device().is_autodiff());
879 assert!(onto_plain.lazy_device().is_autodiff());
880 }
881
882 #[test]
883 fn a_moved_param_keeps_its_reparameterization() {
884 let device = test_device();
885 let adapter = || LoraAdapter {
886 a: Param::from_tensor(Tensor::<2>::ones([3, 1], &device)),
887 b: Param::from_tensor(Tensor::<2>::zeros([1, 3], &device)),
888 scale: 1.0,
889 };
890 let lazy: Param<Tensor<2>> = Param::uninitialized(
891 ParamId::new(),
892 |device, _| Tensor::ones([3, 3], device),
893 device.clone(),
894 false,
895 [3, 3].into(),
896 )
897 .with_reparameterization(adapter());
898 let initialized = Param::from_tensor(Tensor::<2>::ones([3, 3], &device))
899 .with_reparameterization(adapter());
900
901 let target = device.clone().autodiff();
902
903 assert!(lazy.to_device(&target).adapter().is_some());
904 assert!(initialized.fork(&target).adapter().is_some());
905 }
906
907 #[test]
908 fn init_mapper_preserves_lora_after_a_clone_initializes_the_base() {
909 let device = test_device();
910 let param: Param<Tensor<2>> = Param::uninitialized(
911 ParamId::new(),
912 |device, _| Tensor::ones([3, 3], device),
913 device.clone(),
914 false,
915 [3, 3].into(),
916 )
917 .with_reparameterization(crate::module::LoraAdapter {
918 a: Param::from_tensor(Tensor::<2>::ones([3, 1], &device)),
919 b: Param::from_tensor(Tensor::<2>::ones([1, 3], &device)),
920 scale: 2.0,
921 });
922 let clone = param.clone();
923 assert!(!param.is_initialized());
924 let mapped = param.init_mapper(|value| value.mul_scalar(2.0));
925
926 clone
929 .val()
930 .into_data()
931 .assert_eq(&TensorData::from([[3.0f32; 3]; 3]), true);
932 drop(clone);
933 assert!(!mapped.is_initialized());
934
935 mapped
937 .val()
938 .into_data()
939 .assert_eq(&TensorData::from([[6.0f32; 3]; 3]), true);
940 }
941
942 #[test]
943 fn a_lazy_int_param_moved_never_initializes() {
944 let device = test_device();
945 let param: Param<Tensor<2, Int>> = Param::uninitialized(
946 ParamId::new(),
947 |_, _| panic!("the moved parameter initialized"),
948 device.clone(),
949 false,
950 [2, 3].into(),
951 );
952
953 let param = param.to_device(&device.clone().autodiff());
954
955 assert!(!param.is_initialized());
956 assert!(!param.lazy_device().is_autodiff());
957 }
958
959 #[test]
960 fn a_lazy_bool_param_moved_never_initializes() {
961 let device = test_device();
962 let param: Param<Tensor<2, Bool>> = Param::uninitialized(
963 ParamId::new(),
964 |_, _| panic!("the moved parameter initialized"),
965 device.clone(),
966 false,
967 [2, 3].into(),
968 );
969
970 let param = param.to_device(&device.clone().autodiff());
971
972 assert!(!param.is_initialized());
973 assert!(!param.lazy_device().is_autodiff());
974 }
975
976 #[test]
977 fn a_lazy_param_forked_keeps_its_gradient_requirement() {
978 let device = test_device();
979 let param: Param<Tensor<2>> = Param::uninitialized(
980 ParamId::new(),
981 |device, require_grad| Tensor::ones([2, 3], device).set_require_grad(require_grad),
982 device.clone().autodiff(),
983 true,
984 [2, 3].into(),
985 );
986
987 let param = param.fork(&device);
988
989 assert!(!param.is_initialized());
990 assert!(param.val().is_require_grad());
991 }
992
993 #[test]
994 fn a_lazy_param_with_an_init_mapper_follows_the_move() {
995 let device = test_device().autodiff();
996 let param: Param<Tensor<2>> = Param::uninitialized(
997 ParamId::new(),
998 |device, require_grad| Tensor::ones([2, 3], device).set_require_grad(require_grad),
999 device.clone(),
1000 true,
1001 [2, 3].into(),
1002 )
1003 .init_mapper(|tensor| tensor.mul_scalar(2.0));
1004
1005 let param = param.fork(&device);
1006
1007 assert!(!param.is_initialized());
1008 let value = param.val();
1009 assert!(value.device().is_autodiff());
1010 assert!(value.is_require_grad());
1011 }
1012
1013 #[test]
1014 fn a_lazy_param_shared_with_a_clone_initializes_before_moving() {
1015 let device = test_device();
1016 let param: Param<Tensor<2>> = Param::uninitialized(
1017 ParamId::new(),
1018 |device, _| Tensor::random([2, 3], Distribution::Default, device),
1019 device.clone(),
1020 false,
1021 [2, 3].into(),
1022 );
1023 let clone = param.clone();
1024
1025 let moved = param.to_device(&device.autodiff());
1026
1027 assert!(clone.is_initialized());
1028 moved
1029 .val()
1030 .into_data()
1031 .assert_eq(&clone.val().into_data(), true);
1032 }
1033}