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