1use crate::CubeRuntime;
2use crate::kernel::{NumericUnaryOp, NumericUnaryOpFamily, launch_unary_numeric};
3use burn_backend::cubecl::{dtype_to_elem_type, dtype_to_storage_type};
4use burn_backend::quantization::QuantScheme;
5use burn_backend::{DType, Shape, TensorMetadata};
6use burn_std::{Metadata, strides, tensor::is_contiguous};
7use cubecl::server::Handle;
8use cubecl::std::tensor::TensorHandle;
9use cubecl::{client::ComputeClient, std::tensor::layout::linear::LinearViewLaunch};
10use cubecl::{frontend::Numeric, std::tensor::layout::linear::LinearViewLayoutLaunch};
11use cubecl::{
12 prelude::{TensorBinding, *},
13 std::tensor::layout::linear::LinearViewLayout,
14};
15use std::marker::PhantomData;
16
17use super::QParams;
18
19pub struct CubeTensor<R: CubeRuntime> {
21 pub client: ComputeClient<R>,
23 pub handle: Handle,
25 pub meta: Box<Metadata>,
27 pub device: R::Device,
29 pub dtype: DType,
31 pub qparams: Option<QParams>,
33}
34
35impl<R: CubeRuntime> From<CubeTensor<R>> for TensorHandle<R> {
36 fn from(val: CubeTensor<R>) -> Self {
37 TensorHandle::new(
38 val.handle.clone(),
39 val.meta.shape().clone(),
40 val.meta.strides().clone(),
41 dtype_to_storage_type(val.dtype),
42 )
43 }
44}
45
46impl<R: CubeRuntime> cubecl::tune::AutotuneOutput for CubeTensor<R> {
47 #[cfg(feature = "autotune-checks")]
48 fn check_equivalence(&self, other: Self) {
49 use crate::ops::into_data_sync;
50 use burn_backend::Tolerance;
51
52 let expected = into_data_sync::<R>(self.clone());
53 let actual = into_data_sync::<R>(other);
54 expected.assert_approx_eq::<f32>(&actual, Tolerance::permissive());
55 }
56}
57
58impl<R> core::fmt::Debug for CubeTensor<R>
70where
71 R: CubeRuntime,
72{
73 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
74 f.write_fmt(format_args!(
75 "CubeTensor {{ shape: {:?}, device: {:?}, strides: {:?}, elem: {}, runtime: {}}}",
76 self.meta.shape(),
77 self.device,
78 self.meta.strides(),
79 self.dtype.name(),
80 R::name(&self.client),
81 ))
82 }
83}
84
85impl<R> Clone for CubeTensor<R>
86where
87 R: CubeRuntime,
88{
89 fn clone(&self) -> Self {
90 Self {
91 client: self.client.clone(),
92 handle: self.handle.clone(),
93 meta: self.meta.clone(),
94 device: self.device.clone(),
95 dtype: self.dtype,
96 qparams: self.qparams.clone(),
97 }
98 }
99}
100
101impl<R: CubeRuntime> TensorMetadata for CubeTensor<R> {
102 type Device = R::CubeDevice;
103 fn dtype(&self) -> DType {
104 self.dtype
105 }
106
107 fn shape(&self) -> Shape {
108 self.meta.shape().clone()
109 }
110
111 fn rank(&self) -> usize {
112 self.meta.rank()
113 }
114
115 fn device(&self) -> Self::Device {
116 self.device.clone()
117 }
118
119 fn can_mut(&self) -> bool {
120 self.handle.can_mut()
121 }
122}
123
124impl<R> CubeTensor<R>
125where
126 R: CubeRuntime,
127{
128 pub fn new(
130 client: ComputeClient<R>,
131 handle: Handle,
132 metadata: Metadata,
133 device: R::Device,
134 dtype: DType,
135 ) -> Self {
136 CubeTensor {
137 client,
138 handle,
139 meta: Box::new(metadata),
140 device,
141 dtype,
142 qparams: None,
143 }
144 }
145
146 pub fn new_contiguous(
148 client: ComputeClient<R>,
149 device: R::Device,
150 shape: Shape,
151 handle: Handle,
152 dtype: DType,
153 ) -> Self {
154 let ndims = shape.num_dims();
155 let mut strides = strides![0; ndims];
156 let mut current = 1;
157
158 shape.iter().enumerate().rev().for_each(|(index, val)| {
159 strides[index] = current;
160 current *= val;
161 });
162
163 Self {
164 client,
165 handle,
166 meta: Box::new(Metadata::new(shape, strides)),
167 device,
168 dtype,
169 qparams: None,
170 }
171 }
172
173 pub fn to_client(&mut self, client: ComputeClient<R>, device: R::Device) -> Self {
175 let desc = self.handle.clone().copy_descriptor(
176 self.meta.shape().clone(),
177 self.meta.strides().clone(),
178 self.elem_size(),
179 );
180 let handle = self
181 .client
182 .to_client_tensor(desc, &client, dtype_to_elem_type(self.dtype));
183
184 Self {
185 client,
186 handle,
187 meta: Box::new(Metadata::new(self.shape(), self.meta.strides().clone())),
188 device,
189 dtype: self.dtype,
190 qparams: self.qparams.clone(),
191 }
192 }
193
194 pub fn binding(self) -> TensorBinding<R> {
196 TensorBinding {
197 handle: self.handle.binding(),
198 strides: self.meta.strides,
199 shape: self.meta.shape,
200 runtime: PhantomData,
201 }
202 }
203
204 pub fn elem_size(&self) -> usize {
206 self.dtype.size()
207 }
208
209 pub fn into_tensor_arg(self) -> TensorArg<R> {
211 self.binding().into_tensor_arg()
212 }
213
214 pub fn into_buffer_arg(self) -> BufferArg<R> {
216 self.into_tensor_arg().into_buffer_arg()
217 }
218
219 pub fn as_tensor_alias(&self, input_pos: usize) -> TensorArg<R> {
221 TensorArg::Alias {
222 input_pos,
223 strides: self.meta.strides().clone(),
224 shape: self.meta.shape().clone(),
225 }
226 }
227
228 pub fn into_linear_view(self) -> LinearViewLaunch<R> {
230 let layout = LinearViewLayoutLaunch::new();
231 let buffer = self.into_tensor_arg();
232 LinearViewLaunch::new_tensor::<LinearViewLayout>(buffer, layout)
233 }
234
235 pub fn as_linear_view_alias(&self, input_pos: usize) -> LinearViewLaunch<R> {
237 let layout = LinearViewLayoutLaunch::new();
238 let buffer = self.as_tensor_alias(input_pos);
239 LinearViewLaunch::new_tensor::<LinearViewLayout>(buffer, layout)
240 }
241
242 pub fn into_linear_view_like(self, reference: &Self) -> LinearViewLaunch<R> {
244 let layout = LinearViewLayoutLaunch::from_reference_shape(reference.shape());
245 let buffer = self.into_tensor_arg();
246 LinearViewLaunch::new_tensor::<LinearViewLayout>(buffer, layout)
247 }
248
249 pub fn required_address_type(&self) -> AddressType {
251 match self.try_scheme() {
252 Some(scheme) => {
253 let len = self.handle.size() as usize * 8 / scheme.size_bits_value();
254 AddressType::from_len(len)
255 }
256 None => AddressType::from_len(self.handle.size() as usize / self.dtype.size()),
257 }
258 }
259
260 pub fn try_scheme(&self) -> Option<&QuantScheme> {
262 match &self.dtype {
263 DType::QFloat(scheme) => Some(scheme),
264 _ => None,
265 }
266 }
267
268 pub(crate) fn can_mut_broadcast(&self, rhs: &Self) -> bool {
269 if !self.handle.can_mut() || !self.is_nonoverlapping() {
270 return false;
271 }
272 let ndims = self.meta.num_dims();
273
274 for i in 0..ndims {
275 let shape_lhs = self.meta.shape()[i];
276 let shape_rhs = rhs.meta.shape()[i];
277
278 if shape_lhs < shape_rhs {
280 return false;
281 }
282 }
283
284 true
285 }
286
287 pub fn copy(&self) -> Self {
289 struct Copy;
290
291 #[cube]
292 impl<T: Numeric, N: Size> NumericUnaryOp<T, N> for Copy {
293 type Options = ();
294
295 fn execute(input: Vector<T, N>, _options: &Self::Options) -> Vector<T, N> {
296 input
297 }
298 }
299
300 impl NumericUnaryOpFamily for Copy {
301 type Options = ();
302 type Unary<T: Numeric, N: Size> = Self;
303 }
304
305 let tensor = self.clone();
306 launch_unary_numeric::<R, Copy, _>(tensor, |_| ())
307 }
308
309 pub fn can_mut(&self) -> bool {
311 self.handle.can_mut()
312 }
313
314 pub fn assert_is_on_same_device(&self, other: &Self) {
316 if self.device != other.device {
317 panic!(
318 "Both tensors should be on the same device {:?} != {:?}",
319 self.device, other.device
320 );
321 }
322 }
323
324 pub fn is_contiguous(&self) -> bool {
331 is_contiguous(self.meta.shape(), self.meta.strides())
332 }
333
334 pub fn is_contiguous_buffer(&self) -> bool {
337 self.meta.shape().num_elements() * self.dtype.size() == self.handle.size() as usize
338 }
339
340 pub fn is_nonoverlapping(&self) -> bool {
342 let shape = self.meta.shape();
343 let strides = self.meta.strides();
344
345 if strides.contains(&0) {
346 return false;
347 }
348 let rank = self.rank();
349 if rank > 1 {
350 let mut dims = shape.iter().zip(strides.iter()).collect::<Vec<_>>();
351 dims.sort_by_key(|(_, stride)| **stride);
352
353 let mut max_offset = 0;
354 for (shape, stride) in dims.into_iter() {
355 if *stride <= max_offset && *shape != 1 {
356 return false;
357 }
358
359 max_offset += (*shape - 1) * *stride;
360 }
361 }
362 true
363 }
364}
365
366#[cfg(test)]
367mod tests {
368 use super::*;
369
370 #[test]
371 fn is_contiguous_non_increasing() {
372 assert!(is_contiguous(&[3, 1], &[1, 1]));
373 }
374
375 #[test]
376 fn is_contiguous_basic() {
377 assert!(is_contiguous(&[32, 32], &[32, 1]));
378 }
379
380 #[test]
381 fn is_contiguous_permuted() {
382 assert!(!is_contiguous(&[32, 32], &[1, 32]));
383 }
384
385 #[test]
386 fn is_contiguous_slice() {
387 assert!(!is_contiguous(&[32, 1, 64], &[32, 64, 1]));
388 }
389
390 #[test]
391 fn is_contiguous_4d_positive() {
392 assert!(is_contiguous(&[8, 256, 32, 32], &[262144, 1024, 32, 1]));
393 }
394
395 #[test]
396 fn is_contiguous_4d_negative() {
397 assert!(!is_contiguous(&[256, 8, 32, 32], &[1024, 262144, 32, 1]));
398 }
399
400 #[test]
402 fn is_contiguous_4d_unit_shape() {
403 assert!(!is_contiguous(&[1, 1, 1, 9], &[72, 1, 72, 8]));
404 }
405}