Skip to main content

cubecl_core/frontend/container/tensor/
launch.rs

1use cubecl_ir::AddressType;
2use cubecl_runtime::server::CopyDescriptor;
3use cubecl_zspace::{MetadataError, Shape, Strides, Tiling};
4
5use crate::{
6    self as cubecl,
7    compute::{KernelBuilder, KernelLauncher},
8    frontend::container::{buffer_len::expand_buffer_length_native, slice},
9    prelude::*,
10};
11
12use super::Tensor;
13
14#[derive(CubeType, CubeLaunch, Clone, Copy)]
15#[expand(derive(Clone, Copy))]
16pub struct TensorMeta {
17    pub len: usize,
18    pub rank: usize,
19}
20
21/// Argument to be used for [tensors](Tensor) passed as arguments to kernels.
22#[derive(Debug)]
23pub enum TensorArg {
24    /// The tensor is passed with a tensor handle.
25    Handle {
26        /// The tensor handle.
27        handle: TensorBinding,
28    },
29    /// The tensor is aliasing another input tensor.
30    Alias {
31        /// The position of the input tensor.
32        input_pos: usize,
33        strides: Strides,
34        shape: Shape,
35    },
36}
37
38/// Tensor representation with a reference to the [server handle](cubecl_runtime::server::Handle),
39/// the strides and the shape.
40pub struct TensorBinding {
41    pub handle: cubecl_runtime::server::BufferBinding,
42    pub strides: Strides,
43    pub shape: Shape,
44    /// How many fragments each logical dim is stored as; untiled by default.
45    /// The shape and strides stay physical, as on [`Metadata`](cubecl_zspace::Metadata).
46    pub tiling: Tiling,
47}
48
49impl Clone for TensorBinding {
50    fn clone(&self) -> Self {
51        Self {
52            handle: self.handle.clone(),
53            strides: self.strides.clone(),
54            shape: self.shape.clone(),
55            tiling: self.tiling,
56        }
57    }
58}
59
60impl TensorBinding {
61    pub fn size(&self) -> usize {
62        self.shape.iter().product()
63    }
64
65    /// Address type required to fully index this tensor handle, assuming scalar access.
66    pub fn required_address_type(&self, elem_size: usize) -> AddressType {
67        AddressType::from_len(self.handle.size() as usize / elem_size)
68    }
69
70    /// This binding with `tiling` labelling its physical dims.
71    ///
72    /// # Errors
73    ///
74    /// When `tiling` does not describe this rank: see [`Tiling::logical_rank`].
75    pub fn with_tiling(mut self, tiling: Tiling) -> Result<Self, MetadataError> {
76        tiling.logical_rank(self.shape.rank())?;
77        self.tiling = tiling;
78        Ok(self)
79    }
80
81    /// An alias reaches its tensor through another argument's dims, which carry
82    /// no tiling of their own, so a tiled binding says so rather than aliasing
83    /// as if it were plain.
84    fn assert_untiled(&self, op: &str) {
85        assert!(
86            !self.tiling.is_tiled(),
87            "TensorBinding::{op} on a storage-tiled binding is not supported: {:?}",
88            self.tiling
89        );
90    }
91}
92
93impl core::fmt::Debug for TensorBinding {
94    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
95        writeln!(
96            f,
97            "TensorHandleRef {{ strides: {:?}, shape: {:?}, tiling: {:?} }}",
98            self.strides, self.shape, self.tiling
99        )
100    }
101}
102
103/// Compilation argument for a [tensor](Tensor).
104#[derive(Clone, PartialEq, Eq, Hash, Debug)]
105pub struct TensorCompilationArg {
106    pub meta: TensorMetaCompilationArg,
107    pub buffer: BufferCompilationArg,
108}
109
110impl<C: CubePrimitive> LaunchArg for Tensor<C> {
111    type RuntimeArg = TensorArg;
112    type CompilationArg = TensorCompilationArg;
113
114    fn register(arg: Self::RuntimeArg, launcher: &mut KernelLauncher) -> Self::CompilationArg {
115        let elem_size = launcher.with_scope(|scope| C::__expand_size(scope));
116        let vector_size = launcher.with_scope(|scope| C::__expand_vector_size(scope));
117        let len = arg.size() / vector_size;
118        let meta_arg = TensorMetaLaunch::new(len, arg.shape().len());
119        let buffer = match &arg {
120            TensorArg::Handle { .. } => BufferCompilationArg { inplace: None },
121            TensorArg::Alias { input_pos, .. } => BufferCompilationArg {
122                inplace: Some(*input_pos),
123            },
124        };
125        launcher.register_tensor(arg, elem_size);
126        let meta = TensorMeta::register(meta_arg, launcher);
127        TensorCompilationArg { meta, buffer }
128    }
129
130    fn expand(arg: &Self::CompilationArg, builder: &mut KernelBuilder) -> TensorExpand<C> {
131        let buffer = match arg.buffer.inplace {
132            Some(id) => builder.inplace(id),
133            None => builder.tensor(C::__expand_as_type(&builder.scope)),
134        };
135        let meta = TensorMeta::expand(&arg.meta, builder);
136        let scope = &builder.scope;
137        let len = expand_buffer_length_native(scope, buffer);
138        let buffer =
139            slice::from_raw_parts::<C>(scope, buffer, 0usize.into_expand(scope), len.into());
140        TensorExpand { meta, buffer }
141    }
142}
143
144impl<C: CubePrimitive> LaunchArg for OwnedTensor<C> {
145    type RuntimeArg = TensorArg;
146    type CompilationArg = TensorCompilationArg;
147
148    fn register(arg: Self::RuntimeArg, launcher: &mut KernelLauncher) -> Self::CompilationArg {
149        Tensor::<C>::register(arg, launcher)
150    }
151
152    fn expand(arg: &Self::CompilationArg, builder: &mut KernelBuilder) -> OwnedTensorExpand<C> {
153        let tensor = Tensor::<C>::expand(arg, builder);
154        OwnedTensorExpand {
155            meta: tensor.meta,
156            buffer: tensor.buffer.expand.into(),
157        }
158    }
159}
160
161impl TensorArg {
162    /// Create a new tensor argument specified with its vectorization factor.
163    ///
164    /// # Safety
165    ///
166    /// If you provide wrong strides or shapes, it might create undefined behavior caused by
167    /// out-of-bound reads and writes.
168    pub unsafe fn from_raw_parts(
169        handle: cubecl_runtime::server::Handle,
170        strides: Strides,
171        shape: Shape,
172    ) -> Self {
173        unsafe { Self::from_raw_parts_binding(handle.binding(), strides, shape) }
174    }
175
176    pub(crate) unsafe fn from_raw_parts_binding(
177        handle: cubecl_runtime::server::BufferBinding,
178        strides: Strides,
179        shape: Shape,
180    ) -> Self {
181        unsafe {
182            Self::Handle {
183                handle: TensorBinding::from_raw_parts_binding(handle, strides, shape),
184            }
185        }
186    }
187
188    /// Create an alias argument.
189    pub fn into_alias(self, position: usize) -> Self {
190        match self {
191            TensorArg::Handle { handle } => handle.into_alias(position),
192            alias @ TensorArg::Alias { .. } => alias,
193        }
194    }
195
196    pub fn size(&self) -> usize {
197        match self {
198            TensorArg::Handle { handle } => handle.size(),
199            TensorArg::Alias { shape, .. } => shape.iter().product(),
200        }
201    }
202
203    pub fn shape(&self) -> &[usize] {
204        match self {
205            TensorArg::Handle { handle } => &handle.shape,
206            TensorArg::Alias { shape, .. } => shape,
207        }
208    }
209
210    pub fn strides(&self) -> &[usize] {
211        match self {
212            TensorArg::Handle { handle } => &handle.strides,
213            TensorArg::Alias { strides, .. } => strides,
214        }
215    }
216}
217
218impl TensorArg {
219    pub fn into_buffer_arg(self) -> BufferArg {
220        match self {
221            TensorArg::Handle { handle } => {
222                let handle = unsafe {
223                    let size = handle.size();
224                    BufferBinding::from_raw_parts_binding(handle.handle, size)
225                };
226                BufferArg::Handle { handle }
227            }
228            TensorArg::Alias {
229                input_pos, shape, ..
230            } => BufferArg::Alias {
231                input_pos,
232                length: [shape.iter().product()],
233            },
234        }
235    }
236}
237
238impl TensorBinding {
239    /// Convert the handle into a [tensor argument](TensorArg).
240    pub fn into_tensor_arg(self) -> TensorArg {
241        TensorArg::Handle { handle: self }
242    }
243    /// Convert the handle into a [tensor argument](TensorArg).
244    pub fn into_alias(self, index: usize) -> TensorArg {
245        self.assert_untiled("into_alias");
246        TensorArg::Alias {
247            input_pos: index,
248            strides: self.strides,
249            shape: self.shape,
250        }
251    }
252    /// Convert the handle into a [tensor argument](TensorArg).
253    pub fn as_alias(&self, index: usize) -> TensorArg {
254        self.assert_untiled("as_alias");
255        TensorArg::Alias {
256            input_pos: index,
257            strides: self.strides.clone(),
258            shape: self.shape.clone(),
259        }
260    }
261    /// Convert the handle into a [buffer argument](BufferArg).
262    pub fn into_buffer_arg(self) -> BufferArg {
263        unsafe { BufferArg::from_raw_parts_binding(self.handle, self.shape.iter().product()) }
264    }
265
266    /// Create a handle from raw parts.
267    ///
268    /// # Safety
269    ///
270    /// If you provide wrong strides or shapes, it might create undefined behavior caused by
271    /// out-of-bounds reads and writes.
272    pub unsafe fn from_raw_parts(
273        handle: cubecl_runtime::server::Handle,
274        strides: Strides,
275        shape: Shape,
276    ) -> Self {
277        unsafe { Self::from_raw_parts_binding(handle.binding(), strides, shape) }
278    }
279
280    /// Create a handle from raw parts.
281    ///
282    /// # Safety
283    ///
284    /// If you provide wrong strides or shapes, it might create undefined behavior caused by
285    /// out-of-bounds reads and writes.
286    pub unsafe fn from_raw_parts_binding(
287        handle: cubecl_runtime::server::BufferBinding,
288        strides: Strides,
289        shape: Shape,
290    ) -> Self {
291        Self {
292            handle,
293            strides,
294            shape,
295            tiling: Tiling::UNTILED,
296        }
297    }
298
299    pub fn into_copy_descriptor(self, elem_size: usize) -> CopyDescriptor {
300        CopyDescriptor {
301            handle: self.handle,
302            shape: self.shape,
303            strides: self.strides,
304            elem_size,
305        }
306    }
307}