Skip to main content

cubecl_core/
lib.rs

1#![no_std]
2
3#[cfg(feature = "std")]
4extern crate std;
5
6extern crate alloc;
7
8#[macro_use]
9extern crate derive_new;
10
11pub use cubecl_zspace as zspace;
12use cubecl_zspace::Shape;
13use cubecl_zspace::Strides;
14
15/// Cube Frontend Types.
16pub mod frontend;
17/// Input Output utilities.
18pub mod io;
19
20pub mod post_processing;
21
22/// Some future utilities that work across environments.
23pub use cubecl_common::future;
24
25use cubecl_ir::VectorSize;
26use cubecl_runtime::client::ComputeClient;
27pub use cubecl_runtime::memory_management::MemoryConfiguration;
28use cubecl_runtime::server::CubeCountSelection;
29pub use frontend::cmma;
30
31/// Cube Language Internal Representation.
32pub use cubecl_ir as ir;
33
34pub mod codegen;
35pub mod compute;
36pub mod prelude;
37
38mod pod;
39
40pub use codegen::*;
41pub use cubecl_runtime::runtime::*;
42pub use pod::*;
43
44pub use cubecl_macros::*;
45pub use cubecl_runtime::benchmark;
46pub use cubecl_runtime::client;
47pub use cubecl_runtime::compiler::{CompilationError, Compiler, CubeTask};
48pub use cubecl_runtime::memory_management::MemoryUsage;
49pub use cubecl_runtime::server;
50pub use cubecl_runtime::throughput;
51pub use cubecl_runtime::tune;
52
53use frontend::LaunchArg;
54
55pub use cubecl_common::*;
56
57pub use prelude::CubeCount;
58pub use prelude::{CubeDim, ExecutionMode};
59
60pub use num_traits;
61
62mod id;
63pub use id::*;
64
65// Private utils for macros
66#[doc(hidden)]
67pub mod __private {
68    pub use alloc::{format, vec};
69    pub use paste::paste;
70}
71
72pub use prelude::{Assign, IntoRuntime};
73
74/// Calculate the number of cubes required to execute an operation where one cube unit is
75/// assigned to one element.
76pub fn calculate_cube_count_elemwise<R: Runtime>(
77    client: &ComputeClient<R>,
78    num_elems: usize,
79    cube_dim: CubeDim,
80) -> CubeCount {
81    if num_elems == 0 {
82        return CubeCount::Static(0, 0, 0);
83    }
84    let num_cubes = num_elems.div_ceil(cube_dim.num_elems() as usize);
85    CubeCountSelection::new(client, num_cubes as u32).cube_count()
86}
87
88pub fn tensor_vectorization_factor(
89    factors: &[VectorSize],
90    shape: &Shape,
91    strides: &Strides,
92    dim: usize,
93) -> VectorSize {
94    tensor_vector_size_parallel(factors.iter().cloned(), shape, strides, dim)
95}
96pub fn tensor_vectorization(
97    factors: &[VectorSize],
98    shape: &Shape,
99    strides: &Strides,
100    dim: usize,
101) -> VectorSize {
102    tensor_vector_size_parallel(factors.iter().cloned(), shape, strides, dim)
103}
104
105#[derive(Debug, Clone)]
106pub enum VectorizationError {
107    AxisOutOfBounds,
108    StrideMismatch,
109    NoValidVectorization,
110}
111
112/// Find the maximum vector size usable for parallel vectorization along the given axis
113/// from the supported vector sizes or return 1 if vectorization is impossible.
114///
115/// This function is designed to never return a vector size above 1 by error,
116/// but doesn't guarantee to always return the actual maximum possible vector size.
117/// That is, it may be overly strict.
118///
119/// Currently, this checks that the stride of the axis is 1, that its shape is
120/// divisible by a candidate vector size and that every non-broadcast stride outside
121/// the axis is divisible by the vector size.
122/// The last condition ensures a vectorized read on `axis` stays contiguous in the
123/// source buffer as coordinates in other dimensions change.
124pub fn tensor_vector_size_parallel(
125    optimized_vector_sizes: impl Iterator<Item = VectorSize>,
126    shape: &Shape,
127    strides: &Strides,
128    axis: usize,
129) -> VectorSize {
130    try_tensor_vector_size_parallel(optimized_vector_sizes, shape, strides, axis).unwrap_or(1)
131}
132
133/// Like `try_tensor_vector_size_parallel` but does not assume 1 is supported
134pub fn try_tensor_vector_size_parallel(
135    supported_vector_sizes: impl Iterator<Item = VectorSize>,
136    shape: &Shape,
137    strides: &Strides,
138    axis: usize,
139) -> Result<VectorSize, VectorizationError> {
140    let stride = strides
141        .get(axis)
142        .ok_or(VectorizationError::AxisOutOfBounds)?;
143    if *stride != 1 {
144        return Err(VectorizationError::StrideMismatch);
145    }
146
147    let axis_shape = shape.get(axis).ok_or(VectorizationError::AxisOutOfBounds)?;
148
149    // Smallest non-zero stride among non-axis dims. Stride 0 is a broadcast and
150    // never contributes to the source offset, so it can be ignored. Every other
151    // dim can shift the source offset when its coord changes, so its stride must
152    // be a multiple of the vector size for vectorized reads to stay aligned.
153    // Unit-size dims are included for simplicity; they only cause false negatives
154    // (vectorization disabled) rather than incorrect output.
155    let next_stride = strides
156        .iter()
157        .enumerate()
158        .filter_map(|(i, &s)| (i != axis && s != 0).then_some(s))
159        .min()
160        .unwrap_or(0);
161
162    supported_vector_sizes
163        .filter(|&vector_size| axis_shape % vector_size == 0 && next_stride % vector_size == 0)
164        .max()
165        .ok_or(VectorizationError::NoValidVectorization)
166}
167
168/// Find the maximum vector size usable for perpendicular vectorization along the given axis
169/// from the supported vector sizes or return 1 if vectorization is impossible.
170///
171/// This function is designed to never return a vector size above 1 by error,
172/// but doesn't guarantee to always return the actual maximum possible vector size.
173/// That is, it may be overly strict.
174///
175/// Currently, this checks that the stride of the axis is divisible by a candidate vector size
176/// and that the product of all shapes of axes with smaller strides is equal to the stride of the axis.
177/// The second condition ensure that elements within the stride are contiguous.
178pub fn tensor_vector_size_perpendicular(
179    supported_vector_sizes: impl Iterator<Item = VectorSize>,
180    shape: &[usize],
181    strides: &[usize],
182    axis: usize,
183) -> VectorSize {
184    try_tensor_vector_sizes_perpendicular(supported_vector_sizes, shape, strides, axis).unwrap_or(1)
185}
186
187/// Like `tensor_vector_sizes_perpendicular` but does not assume 1 is supported
188pub fn try_tensor_vector_sizes_perpendicular(
189    supported_vector_sizes: impl Iterator<Item = VectorSize>,
190    shape: &[usize],
191    strides: &[usize],
192    axis: usize,
193) -> Result<VectorSize, VectorizationError> {
194    let axis_stride = strides
195        .get(axis)
196        .ok_or(VectorizationError::AxisOutOfBounds)?;
197
198    let prod_shape_axes_smaller_strides = strides
199        .iter()
200        .zip(shape.iter())
201        .filter(|(stride, _)| **stride < *axis_stride)
202        .map(|(_, shape)| shape)
203        .product::<usize>();
204
205    if *axis_stride != prod_shape_axes_smaller_strides {
206        return Err(VectorizationError::StrideMismatch);
207    }
208
209    supported_vector_sizes
210        .filter(|&vector_size| *axis_stride % vector_size == 0)
211        .max()
212        .ok_or(VectorizationError::NoValidVectorization)
213}
214
215/// Runtime arguments to launch a kernel.
216pub type RuntimeArg<T, R> = <T as LaunchArg>::RuntimeArg<R>;
217pub type ExpandType<T> = <T as crate::prelude::CubeType>::ExpandType;
218
219#[cfg(feature = "export_tests")]
220/// Tests only useful for runtimes.
221pub mod runtime_tests;
222
223#[cfg(test)]
224mod tests {
225    use super::*;
226
227    fn try_parallel(
228        sizes: &[VectorSize],
229        shape: &[usize],
230        strides: &[usize],
231        axis: usize,
232    ) -> Result<VectorSize, VectorizationError> {
233        try_tensor_vector_size_parallel(
234            sizes.iter().copied(),
235            &Shape::from(shape.iter().copied()),
236            &Strides::new(strides),
237            axis,
238        )
239    }
240
241    #[test]
242    fn parallel_contiguous_picks_max_vector_size() {
243        // Contiguous [1, 9, 4], vectorize along last dim (stride 1).
244        // Outer stride 4 is a multiple of 4, so vec_size = 4 is safe.
245        let v = try_parallel(&[1, 2, 4], &[1, 9, 4], &[36, 4, 1], 2).unwrap();
246        assert_eq!(v, 4);
247    }
248
249    #[test]
250    fn parallel_unfold_step_one_rejects_vectorization() {
251        // Unfold view produced by `unfold(1, 4, 1)` on a [1, 12] contiguous tensor:
252        // shape [1, 9, 4], strides [12, 1, 1]. The frame dim has stride 1, so each
253        // step in the frame coord shifts the source offset by 1 - not a multiple
254        // of any vec_size > 1, so vectorized reads would be unaligned and return
255        // the wrong data. Must fall back to vec_size = 1.
256        let v = try_parallel(&[1, 2, 4], &[1, 9, 4], &[12, 1, 1], 2).unwrap();
257        assert_eq!(v, 1);
258    }
259
260    #[test]
261    fn parallel_unfold_step_two_allows_vectorization() {
262        // Same unfold pattern but with step=2: strides [12, 2, 1]. Frame coord
263        // shifts source by 2 (still not a multiple of 4), so vec_size = 4 must
264        // be rejected - but vec_size = 2 is fine.
265        let v = try_parallel(&[1, 2, 4], &[1, 9, 4], &[12, 2, 1], 2).unwrap();
266        assert_eq!(v, 2);
267    }
268
269    #[test]
270    fn parallel_broadcast_dim_ignored() {
271        // Broadcast dim has stride 0; it never shifts the source offset, so
272        // it should not disqualify vectorization.
273        let v = try_parallel(&[1, 2, 4], &[1, 9, 4], &[0, 4, 1], 2).unwrap();
274        assert_eq!(v, 4);
275    }
276
277    #[test]
278    fn parallel_axis_stride_not_one_is_error() {
279        let err = try_parallel(&[1, 2, 4], &[1, 9, 4], &[36, 1, 4], 2).unwrap_err();
280        assert!(matches!(err, VectorizationError::StrideMismatch));
281    }
282}