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