Skip to main content

cubecl_std/tensor/
handle.rs

1use cubecl_core::zspace::metadata::Metadata;
2use cubecl_core::{calculate_cube_count_elemwise, server::MemoryLayout};
3use cubecl_core::{prelude::*, server::CopyDescriptor};
4use cubecl_core::{server, zspace::strides};
5use cubecl_core::{
6    tensor_vector_size_parallel,
7    zspace::{Shape, Strides},
8};
9use cubecl_runtime::server::Handle;
10
11/// Tensor representation containing a [server handle](Handle) as well as basic tensor metadata.,
12pub struct TensorHandle {
13    /// The buffer where the data are stored.
14    pub handle: server::Handle,
15    pub metadata: Box<Metadata>,
16    /// The type used as storage.
17    pub dtype: ElemType,
18}
19
20impl core::fmt::Debug for TensorHandle {
21    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
22        f.write_fmt(format_args!(
23            "Tensor {{ shape: {:?}, strides: {:?}, dtype: {}}}",
24            self.shape(),
25            self.strides(),
26            self.dtype,
27        ))
28    }
29}
30
31impl Clone for TensorHandle {
32    fn clone(&self) -> Self {
33        Self {
34            handle: self.handle.clone(),
35            metadata: self.metadata.clone(),
36            dtype: self.dtype,
37        }
38    }
39}
40
41impl TensorHandle {
42    /// Create a new tensor.
43    pub fn new(
44        handle: server::Handle,
45        shape: impl Into<Shape>,
46        strides: impl Into<Strides>,
47        storage: impl Into<Type>,
48    ) -> Self {
49        Self {
50            handle,
51            metadata: Box::new(Metadata::new(shape, strides)),
52            dtype: storage.into().elem_type(),
53        }
54    }
55
56    /// A tensor over `metadata` as it is, tiling included: the constructor for a handle rebuilt
57    /// from another's metadata, where [`new`](Self::new) would rebuild an untiled one from the
58    /// shape and strides alone.
59    pub fn from_metadata(
60        handle: server::Handle,
61        metadata: Metadata,
62        storage: impl Into<Type>,
63    ) -> Self {
64        Self {
65            handle,
66            metadata: Box::new(metadata),
67            dtype: storage.into().elem_type(),
68        }
69    }
70
71    pub fn empty(client: &Client, shape: impl Into<Shape>, storage: impl Into<Type>) -> Self {
72        let storage = storage.into();
73        let shape: Shape = shape.into();
74        let elem_size = storage.elem_type().size();
75        let MemoryLayout {
76            memory: handle,
77            strides,
78        } = client.empty_tensor(shape.clone(), elem_size);
79
80        Self::new(handle, shape, strides, storage)
81    }
82
83    /// Create a new tensor with a contiguous memory layout.
84    pub fn new_contiguous(shape: impl Into<Shape>, handle: Handle, storage: ElemType) -> Self {
85        let shape = shape.into();
86        let strides = Self::contiguous_strides(&shape);
87
88        Self {
89            handle,
90            metadata: Box::new(Metadata::new(shape, strides)),
91            dtype: storage,
92        }
93    }
94
95    /// Check if the tensor is safe to mutate.
96    pub fn can_mut(&self) -> bool {
97        self.handle.can_mut()
98    }
99
100    pub fn binding(self) -> TensorBinding {
101        let Metadata {
102            shape,
103            strides,
104            tiling,
105        } = *self.metadata;
106        let mut binding = unsafe { TensorBinding::from_raw_parts(self.handle, strides, shape) };
107        // The metadata validated this tiling against this rank, and the binding
108        // carries the same dims, so it needs no second check.
109        binding.tiling = tiling;
110        binding
111    }
112
113    /// Return the reference to a tensor argument.
114    pub fn into_arg(self) -> TensorArg {
115        self.binding().into_tensor_arg()
116    }
117
118    pub fn into_copy_descriptor(self) -> CopyDescriptor {
119        CopyDescriptor {
120            handle: self.handle.binding(),
121            shape: self.metadata.shape,
122            strides: self.metadata.strides,
123            elem_size: self.dtype.size(),
124        }
125    }
126
127    pub fn required_address_type(&self) -> AddressType {
128        let len = self.handle.size() / self.dtype.size() as u64;
129        AddressType::from_len(len as usize)
130    }
131
132    pub fn shape(&self) -> &Shape {
133        self.metadata.shape()
134    }
135
136    pub fn strides(&self) -> &Strides {
137        self.metadata.strides()
138    }
139
140    fn contiguous_strides(shape: &[usize]) -> Strides {
141        let mut strides = strides![1; shape.len()];
142
143        let mut current = 1;
144        shape.iter().rev().enumerate().for_each(|(i, val)| {
145            strides[i] = current;
146            current *= val;
147        });
148        strides.reverse();
149        strides
150    }
151}
152impl TensorHandle {
153    pub fn zeros(client: &Client, shape: impl Into<Shape>, dtype: impl Into<Type>) -> Self {
154        let dtype = dtype.into();
155        let shape = shape.into();
156        let num_elements: usize = shape.iter().product();
157        let rank = shape.len();
158        let output = Self::empty(client, shape, dtype);
159        let dtype = dtype.elem_type();
160
161        let vector_size = tensor_vector_size_parallel(
162            client.io_optimized_vector_sizes(dtype.size()),
163            output.shape(),
164            output.strides(),
165            rank - 1,
166        );
167
168        let working_units = num_elements / vector_size as usize;
169        let cube_dim = CubeDim::new(client, working_units);
170        let cube_count = calculate_cube_count_elemwise(client, working_units, cube_dim);
171        let array_len = output.handle.size_in_used() as usize / dtype.size();
172
173        unsafe {
174            init::zeros_array::launch_unchecked(
175                client,
176                cube_count,
177                cube_dim,
178                output.required_address_type(),
179                vector_size,
180                BufferArg::from_raw_parts(output.handle.clone(), array_len),
181                dtype,
182            )
183        };
184
185        output
186    }
187}
188
189pub(crate) mod init {
190    use cubecl::prelude::*;
191    use cubecl_core::{self as cubecl};
192
193    #[cube(launch_unchecked, address_type = "dynamic")]
194    pub fn zeros_array<C: Numeric, N: Size>(
195        output: &mut [Vector<C, N>],
196        #[define(C)] _elem: ElemType,
197    ) {
198        if ABSOLUTE_POS < output.len() {
199            output[ABSOLUTE_POS] = Vector::cast_from(C::from_int(0));
200        }
201    }
202}