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