Skip to main content

luma_tensor/ops/
transfer.rs

1//! Cross-device transfer: move a tensor to another device, e.g. `Cpu` → `Cuda`.
2
3use std::any::TypeId;
4
5#[cfg(feature = "cuda")]
6use crate::Cuda;
7use crate::ops::construct::BytesDTypeKind;
8use crate::{Bool, Cpu, DTypeKind, Device, Float, Int, Tensor};
9
10// ============================================================================
11//    TransferDTypeKind: kind dispatch for `to_device`
12// ============================================================================
13
14/// Kind-level dispatch for `Tensor::to_device`.
15///
16/// Implemented for the three closed kinds ([`Float`], [`Int`], [`Bool`]) for
17/// every source/target device pair. The transfer is a bytes round-trip in
18/// logical order (`to_bytes` → `from_bytes`), so the result is contiguous.
19pub trait TransferDTypeKind<D: Device, D2: Device>: DTypeKind<D> + DTypeKind<D2> + BytesDTypeKind<D> + BytesDTypeKind<D2> {
20    fn transfer(src: &Tensor<D, Self>, device: &D2) -> crate::Result<Tensor<D2, Self>>;
21}
22
23impl<D: Device, D2: Device> TransferDTypeKind<D, D2> for Float {
24    fn transfer(src: &Tensor<D, Float>, device: &D2) -> crate::Result<Tensor<D2, Float>> {
25        let bytes = src.to_bytes()?;
26        let out = Tensor::from_bytes(bytes, src.shape().clone(), (device, src.dtype()))?;
27        // The autograd graph is single-device (`Op<D>` holds same-device
28        // inputs), so the transfer cannot record a node: the result is a fresh
29        // leaf. Preserve the trainability flag so a moved parameter keeps
30        // accumulating gradients.
31        out.set_requires_grad(src.requires_grad());
32        Ok(out)
33    }
34}
35
36impl<D: Device, D2: Device> TransferDTypeKind<D, D2> for Int {
37    fn transfer(src: &Tensor<D, Int>, device: &D2) -> crate::Result<Tensor<D2, Int>> {
38        let bytes = src.to_bytes()?;
39        Tensor::from_bytes(bytes, src.shape().clone(), (device, src.dtype()))
40    }
41}
42
43impl<D: Device, D2: Device> TransferDTypeKind<D, D2> for Bool {
44    fn transfer(src: &Tensor<D, Bool>, device: &D2) -> crate::Result<Tensor<D2, Bool>> {
45        let bytes = src.to_bytes()?;
46        Tensor::from_bytes(bytes, src.shape().clone(), (device, src.dtype()))
47    }
48}
49
50// ============================================================================
51//    Public API
52// ============================================================================
53
54impl<D: Device, K: DTypeKind<D>> Tensor<D, K> {
55    /// Move the tensor to another device.
56    ///
57    /// - Same device: returns this handle unchanged (shared `Arc`, O(1)) —
58    ///   generic code can call this unconditionally.
59    /// - Cross device: deep-copies the data (`to_bytes`/`from_bytes`), dtype
60    ///   unchanged, result contiguous. For `Float` tensors the autograd graph
61    ///   is severed (the result is a leaf) but `requires_grad` is preserved.
62    /// - Meta tensors (no storage) error with [`Error::MetaTensor`](crate::Error::MetaTensor).
63    pub fn to_device<D2: Device>(&self, device: &D2) -> crate::Result<Tensor<D2, K>>
64    where
65        K: TransferDTypeKind<D, D2>,
66    {
67        if TypeId::of::<D>() == TypeId::of::<D2>() {
68            // SAFETY: `TypeId` equality means `D` and `D2` are the same concrete
69            // type (`Device: 'static`), so these casts are type puns between
70            // identical types with identical layout — no aliasing or validity
71            // concerns beyond a plain clone.
72            let target: &D = unsafe { &*(device as *const D2 as *const D) };
73            if self.device().same_device(target) {
74                let p = self as *const Tensor<D, K> as *const Tensor<D2, K>;
75                return Ok(unsafe { (*p).clone() });
76            }
77        }
78        // Cross-device copy (covers `Cuda` → `Cuda` with different ordinals,
79        // which goes through the host — there is no DtoD primitive yet).
80        K::transfer(self, device)
81    }
82
83    /// Convenience for `to_device(&Cpu)`.
84    pub fn cpu(&self) -> crate::Result<Tensor<Cpu, K>>
85    where
86        K: TransferDTypeKind<D, Cpu>,
87    {
88        self.to_device(&Cpu)
89    }
90
91    /// Convenience for `to_device(&Cuda::new(ordinal))`.
92    #[cfg(feature = "cuda")]
93    pub fn cuda(&self, ordinal: usize) -> crate::Result<Tensor<Cuda, K>>
94    where
95        K: TransferDTypeKind<D, Cuda>,
96    {
97        self.to_device(&Cuda::new(ordinal)?)
98    }
99}