Skip to main content

burn_cubecl/kernel/index/
slice.rs

1use crate::{
2    kernel::utils::{address_type, shape_divmod},
3    ops::numeric::empty_device_dtype,
4    tensor::CubeTensor,
5};
6use burn_backend::cubecl::dtype_to_storage_type;
7use burn_backend::{Slice, TensorMetadata};
8use burn_std::{Metadata, SliceOps};
9use cubecl::{
10    calculate_cube_count_elemwise, intrinsic,
11    prelude::*,
12    std::{FastDivmod, tensor::layout::linear::LinearViewMut},
13};
14use std::ops::Range;
15
16/// Slice a jit tensor with a set of ranges
17pub fn slice(tensor: CubeTensor, indices: &[Range<usize>]) -> CubeTensor {
18    let tensor = crate::kernel::untile(tensor);
19    let mut dims = tensor.shape();
20    let mut offset_start = 0u64;
21    let mut offset_end = 0u64;
22
23    for i in 0..indices.len() {
24        offset_start += (tensor.meta.strides()[i] * indices[i].start) as u64;
25        offset_end += (tensor.meta.strides()[i] * (dims[i] - indices[i].end)) as u64;
26        dims[i] = indices[i].end - indices[i].start;
27    }
28
29    let offset_start = offset_start * tensor.dtype.size() as u64;
30    let offset_end = offset_end * tensor.dtype.size() as u64;
31
32    let memory_offset_alignment = tensor.client.properties().memory.alignment;
33
34    if offset_start.is_multiple_of(memory_offset_alignment)
35        && offset_end.is_multiple_of(memory_offset_alignment)
36    {
37        CubeTensor::new(
38            tensor.client.clone(),
39            tensor
40                .handle
41                .clone()
42                .offset_start(offset_start)
43                .offset_end(offset_end),
44            Metadata::new(dims, tensor.meta.strides.clone()),
45            tensor.device.clone(),
46            tensor.dtype,
47        )
48    } else {
49        let output = empty_device_dtype(
50            tensor.client.clone(),
51            tensor.device.clone(),
52            dims,
53            tensor.dtype,
54        );
55        slice_on_output(tensor, output, indices)
56    }
57}
58
59#[cube(launch_unchecked, address_type = "dynamic")]
60fn slice_kernel<E: Numeric>(
61    input: &Tensor<E>,
62    mut output: LinearViewMut<'_, E>,
63    out_shape: Sequence<FastDivmod<usize>>,
64    indices: Sequence<usize>,
65    #[define(E)] _dtype: ElemType,
66) {
67    if !output.is_in_bounds(ABSOLUTE_POS) {
68        terminate!();
69    }
70
71    let rank = comptime![out_shape.len()];
72    let mut offset_output = ABSOLUTE_POS;
73    let mut offset_input = 0;
74
75    #[unroll]
76    for i in 0..rank {
77        // Iterate in reverse to use divmod
78        let dim = rank - i - 1;
79
80        let range_start = indices[dim];
81        let (rem, offset_local) = out_shape[dim].div_mod(offset_output);
82        offset_output = rem;
83
84        let offset_local = offset_local + range_start;
85
86        offset_input += offset_local * input.stride(dim);
87    }
88
89    output.write(ABSOLUTE_POS, input[offset_input]);
90}
91
92pub(crate) fn slice_on_output(
93    tensor: CubeTensor,
94    output: CubeTensor,
95    indices: &[Range<usize>],
96) -> CubeTensor {
97    let ndims = tensor.meta.num_dims();
98    let mut indices_sequence = SequenceArg::<usize>::new();
99
100    for i in 0..ndims {
101        let start = indices.get(i).map(|index| index.start).unwrap_or(0);
102        indices_sequence.push(start);
103    }
104
105    let working_units = output.meta.num_elements();
106    let cube_dim = CubeDim::new(&tensor.client, working_units);
107    let cube_count = calculate_cube_count_elemwise(&tensor.client, working_units, cube_dim);
108    let dtype = tensor.dtype;
109
110    unsafe {
111        slice_kernel::launch_unchecked(
112            &output.client,
113            cube_count,
114            cube_dim,
115            address_type!(tensor, output),
116            tensor.into_tensor_arg(),
117            output.clone().into_linear_view(),
118            shape_divmod(&output),
119            indices_sequence,
120            dtype_to_storage_type(dtype),
121        )
122    };
123
124    output
125}
126
127/// Kernel for slicing with steps
128#[cube(launch_unchecked, address_type = "dynamic")]
129fn slice_with_steps_kernel<E: Numeric>(
130    input: &Tensor<E>,
131    mut output: LinearViewMut<'_, E>,
132    out_shape: Sequence<FastDivmod<usize>>,
133    starts: Sequence<usize>,
134    ends: Sequence<usize>,
135    steps: Sequence<i32>,
136    #[define(E)] _dtype: ElemType,
137) {
138    if !output.is_in_bounds(ABSOLUTE_POS) {
139        terminate!();
140    }
141
142    let rank = comptime![out_shape.len()];
143    let mut output_offset = ABSOLUTE_POS;
144    let mut input_offset = 0;
145
146    // Calculate the input offset based on output position and slice info
147    #[unroll]
148    for i in 0..rank {
149        // Iterate in reverse to use divmod
150        let dim = rank - i - 1;
151        let start = starts[dim];
152        let end = ends[dim];
153        let step = steps[dim];
154
155        let (rem, output_idx) = out_shape[dim].div_mod(output_offset);
156        output_offset = rem;
157
158        let input_idx = if step > 0 {
159            // Forward stepping
160            start + output_idx * (step as usize)
161        } else {
162            // Backward stepping - start from end-1
163            let abs_step = (-step) as usize;
164            let end_minus_1 = end - 1;
165            end_minus_1 - output_idx * abs_step
166        };
167
168        input_offset += input_idx * input.stride(dim);
169    }
170
171    output.write(ABSOLUTE_POS, input[input_offset]);
172}
173
174/// Slice a tensor with steps
175pub fn slice_with_steps(tensor: CubeTensor, slices: &[Slice]) -> CubeTensor {
176    // Check if all steps are 1 - if so, use the optimized regular slice
177    let all_steps_one = slices.iter().all(|info| info.step == 1);
178
179    if all_steps_one {
180        // Convert Slice to Range for step=1
181        let simple_ranges: Vec<Range<usize>> = slices
182            .iter()
183            .enumerate()
184            .map(|(i, slice)| slice.to_range(tensor.meta.shape()[i]))
185            .collect();
186        return slice(tensor, &simple_ranges);
187    }
188
189    // Calculate output shape
190    let shape_output = tensor.shape().slice(slices).unwrap();
191
192    // Create output tensor
193    let output = empty_device_dtype(
194        tensor.client.clone(),
195        tensor.device.clone(),
196        shape_output.clone(),
197        tensor.dtype,
198    );
199
200    // Prepare three separate sequences for kernel
201    let mut starts = SequenceArg::<usize>::new();
202    let mut ends = SequenceArg::<usize>::new();
203    let mut steps = SequenceArg::<i32>::new();
204
205    for (dim, slice) in slices.iter().enumerate() {
206        let range = slice.to_range(tensor.meta.shape()[dim]);
207        starts.push(range.start);
208        ends.push(range.end);
209        steps.push(slice.step as i32);
210    }
211
212    // Pad with default values if needed to match tensor dimensions
213    for dim in slices.len()..tensor.meta.num_dims() {
214        starts.push(0);
215        ends.push(tensor.meta.shape[dim]);
216        steps.push(1);
217    }
218
219    // Launch kernel
220    let working_units = shape_output.num_elements();
221    let cube_dim = CubeDim::new(&tensor.client, working_units);
222    let cube_count = calculate_cube_count_elemwise(&tensor.client, working_units, cube_dim);
223    let dtype = tensor.dtype;
224
225    unsafe {
226        slice_with_steps_kernel::launch_unchecked(
227            &output.client,
228            cube_count,
229            cube_dim,
230            address_type!(tensor, output),
231            tensor.into_tensor_arg(),
232            output.clone().into_linear_view(),
233            shape_divmod(&output),
234            starts,
235            ends,
236            steps,
237            dtype_to_storage_type(dtype),
238        );
239    }
240
241    output
242}
243
244/// This is annoying and we need to find a way to do this automatically at some point
245#[allow(unused)]
246#[cube]
247fn unwrap(value: u32) -> comptime_type!(u32) {
248    intrinsic!(|_| value.constant().unwrap().as_u32())
249}