Skip to main content

burn_core/module/param/
tensor.rs

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        // Parameters keep their configured training state separately from the effective tensor.
41        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    /// Create a new trainable parameter from a float tensor.
123    ///
124    /// # Warnings
125    ///
126    /// We strongly recommend using [Param::uninitialized] if you are using this method to
127    /// initialize parameters inside a module, since the tensor initialization will be lazy,
128    /// making the loading of weights more performant.
129    pub fn from_tensor(value: Tensor<D>) -> Self {
130        // A plain backend can't activate gradients immediately, so record the setting explicitly
131        // for a later transition to training.
132        let mut param =
133            Param::initialized(ParamId::new(), Parameter::set_require_grad(value, true));
134        param.is_active = true;
135        param
136    }
137
138    /// Create a new parameter from data.
139    pub fn from_data<T>(data: T, device: &Device) -> Self
140    where
141        T: Into<TensorData>,
142    {
143        let data: TensorData = data.into();
144        // A plain backend can't activate gradients immediately, so record the setting explicitly
145        // for a later transition to training.
146        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    /// Attach a custom or built-in reparameterization, replacing any existing one.
156    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        // Preserve whether the parameter was active, but reset the inner value's gradient state.
238        // `val()` folds any reparameterization into the base for inference.
239        //
240        // The param mapper crosses with it: it describes how the value relates to
241        // its *stored* form, which a change of backend does not alter. Dropping it
242        // makes `transform_for_save` the identity, so a record taken from a
243        // `valid()`ed module holds the in-memory shape rather than the checkpoint
244        // one — silently, for any layout that maps (a `Col` linear transposes).
245        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        // Keep the reparameterization structure and its parameters on the autodiff backend.
257        let reparameterization = self.reparameterization.take();
258        // Reinstate the parameter's training state.
259        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) // Don't support autodiff.
303    }
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) // Don't support autodiff.
359    }
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); // stateful
550
551        let param = param.train();
552        assert!(param.is_require_grad());
553        assert!(param.is_active); // stateful
554
555        let param = param.no_grad();
556        assert!(!param.is_require_grad());
557        assert!(!param.is_active); // stateful
558
559        let param = param.valid();
560        assert!(!param.is_require_grad()); // always
561        assert!(!param.is_active); // stateful
562
563        let param = param.train();
564        assert!(!param.is_require_grad());
565        assert!(!param.is_active); // stateful
566    }
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(&param), 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        // Initialize the captured base after registering the lazy mapper, then remove sharing.
689        // This exercises init_mapper's map_to_device fallback, not Module::to_device or fork.
690        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        // Map the effective LoRA value: (base + scale * A @ B) * 2 = (1 + 2) * 2 = 6.
698        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}