Skip to main content

ruda_kernel/dsl/
mod.rs

1pub use ruda_core::tensor as zspace;
2use ruda_core::tensor::Shape;
3use ruda_core::tensor::Strides;
4
5/// Ruda Frontend Types.
6pub mod frontend;
7/// Input Output utilities.
8pub mod io;
9
10pub mod post_processing;
11
12/// Some future utilities that work across environments.
13pub use ruda_core::future;
14
15use ruda_core::ir::VectorSize;
16use ruda::runtime::client::ComputeClient;
17pub use ruda::runtime::memory_management::MemoryConfiguration;
18use ruda::runtime::server::RudaCountSelection;
19pub use frontend::cmma;
20
21/// Ruda Language Internal Representation.
22pub use ruda_core::ir as ir;
23
24pub mod codegen;
25pub mod compute;
26pub mod prelude;
27
28mod pod;
29
30pub use codegen::*;
31pub use ruda::runtime::backend::*;
32pub use pod::*;
33
34pub use ruda_kernel_macros::{
35    AutotuneKey, RudaLaunch, RudaType, RudaTypeMut, comment, comptime,
36    comptime_type, ruda, derive_ruda_comptime, derive_expand, intrinsic, terminate,
37};
38
39pub use ruda::runtime::benchmark;
40pub use ruda::runtime::client;
41pub use ruda::runtime::compiler::{CompilationError, Compiler, RudaTask};
42pub use ruda::runtime::memory_management::MemoryUsage;
43pub use ruda::runtime::server;
44pub use ruda::runtime::tune;
45
46use frontend::LaunchArg;
47
48pub use ruda_core::*;
49
50pub use prelude::RudaCount;
51pub use prelude::{RudaDim, ExecutionMode};
52
53pub use num_traits;
54
55mod id;
56pub use id::*;
57
58// Private utils for macros
59#[doc(hidden)]
60pub mod __private {
61    pub use alloc::{format, vec};
62    pub use paste::paste;
63}
64
65pub use prelude::{Assign, IntoRuntime};
66
67/// Calculate the number of rudas required to execute an operation where one ruda unit is
68/// assigned to one element.
69pub fn calculate_ruda_count_elemwise<R: Runtime>(
70    client: &ComputeClient<R>,
71    num_elems: usize,
72    ruda_dim: RudaDim,
73) -> RudaCount {
74    if num_elems == 0 {
75        return RudaCount::Static(0, 0, 0);
76    }
77    let num_rudas = num_elems.div_ceil(ruda_dim.num_elems() as usize);
78    RudaCountSelection::new(client, num_rudas as u32).ruda_count()
79}
80
81pub fn tensor_vectorization_factor(
82    factors: &[VectorSize],
83    shape: &Shape,
84    strides: &Strides,
85    dim: usize,
86) -> VectorSize {
87    tensor_vector_size_parallel(factors.iter().cloned(), shape, strides, dim)
88}
89pub fn tensor_vectorization(
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}
97
98#[derive(Debug, Clone)]
99pub enum VectorizationError {
100    AxisOutOfBounds,
101    StrideMismatch,
102    NoValidVectorization,
103}
104
105/// Find the maximum vector size usable for parallel vectorization along the given axis
106/// from the supported vector sizes or return 1 if vectorization is impossible.
107///
108/// This function is designed to never return a vector size above 1 by error,
109/// but doesn't guarantee to always return the actual maximum possible vector size.
110/// That is, it may be overly strict.
111///
112/// Currently, this checks that the stride of the axis is 1, that its shape is
113/// divisible by a candidate vector size and that every non-broadcast stride outside
114/// the axis is divisible by the vector size.
115/// The last condition ensures a vectorized read on `axis` stays contiguous in the
116/// source buffer as coordinates in other dimensions change.
117pub fn tensor_vector_size_parallel(
118    optimized_vector_sizes: impl Iterator<Item = VectorSize>,
119    shape: &Shape,
120    strides: &Strides,
121    axis: usize,
122) -> VectorSize {
123    try_tensor_vector_size_parallel(optimized_vector_sizes, shape, strides, axis).unwrap_or(1)
124}
125
126/// Like `try_tensor_vector_size_parallel` but does not assume 1 is supported
127pub fn try_tensor_vector_size_parallel(
128    supported_vector_sizes: impl Iterator<Item = VectorSize>,
129    shape: &Shape,
130    strides: &Strides,
131    axis: usize,
132) -> Result<VectorSize, VectorizationError> {
133    let stride = strides
134        .get(axis)
135        .ok_or(VectorizationError::AxisOutOfBounds)?;
136    if *stride != 1 {
137        return Err(VectorizationError::StrideMismatch);
138    }
139
140    let axis_shape = shape.get(axis).ok_or(VectorizationError::AxisOutOfBounds)?;
141
142    // Check all non-axis strides. Stride 0 is a broadcast and
143    // never contributes to the source offset, so it can be ignored. Every other
144    // dim can shift the source offset when its coord changes, so its stride must
145    // be a multiple of the vector size for vectorized reads to stay aligned.
146    // Unit-size dims are included for simplicity; they only cause false negatives
147    // (vectorization disabled) rather than incorrect output.
148    supported_vector_sizes
149        .filter(|&vector_size| {
150            vector_size != 0
151                && axis_shape % vector_size == 0
152                && strides
153                    .iter()
154                    .enumerate()
155                    .all(|(i, &stride)| i == axis || stride % vector_size == 0)
156        })
157        .max()
158        .ok_or(VectorizationError::NoValidVectorization)
159}
160
161/// Find the maximum vector size usable for perpendicular vectorization along the given axis
162/// from the supported vector sizes or return 1 if vectorization is impossible.
163///
164/// This function is designed to never return a vector size above 1 by error,
165/// but doesn't guarantee to always return the actual maximum possible vector size.
166/// That is, it may be overly strict.
167///
168/// Checks that smaller-stride axes form a contiguous span ending at the axis stride,
169/// and that the axis stride and every larger stride are divisible by the candidate vector size.
170pub fn tensor_vector_size_perpendicular(
171    supported_vector_sizes: impl Iterator<Item = VectorSize>,
172    shape: &[usize],
173    strides: &[usize],
174    axis: usize,
175) -> VectorSize {
176    try_tensor_vector_sizes_perpendicular(supported_vector_sizes, shape, strides, axis).unwrap_or(1)
177}
178
179/// Like `tensor_vector_sizes_perpendicular` but does not assume 1 is supported
180pub fn try_tensor_vector_sizes_perpendicular(
181    supported_vector_sizes: impl Iterator<Item = VectorSize>,
182    shape: &[usize],
183    strides: &[usize],
184    axis: usize,
185) -> Result<VectorSize, VectorizationError> {
186    let axis_stride = *strides
187        .get(axis)
188        .ok_or(VectorizationError::AxisOutOfBounds)?;
189    shape.get(axis).ok_or(VectorizationError::AxisOutOfBounds)?;
190    if shape.len() != strides.len() || axis_stride == 0 {
191        return Err(VectorizationError::StrideMismatch);
192    }
193
194    let mut inner_axes = strides
195        .iter()
196        .zip(shape.iter())
197        .filter_map(|(&stride, &size)| {
198            (stride < axis_stride && size != 1).then_some((stride, size))
199        })
200        .collect::<alloc::vec::Vec<_>>();
201    inner_axes.sort_unstable_by_key(|&(stride, _)| stride);
202
203    let mut span = 1usize;
204    for (stride, size) in inner_axes {
205        if stride != span {
206            return Err(VectorizationError::StrideMismatch);
207        }
208        span = span.checked_mul(size).ok_or(VectorizationError::StrideMismatch)?;
209    }
210    if axis_stride != span {
211        return Err(VectorizationError::StrideMismatch);
212    }
213
214    supported_vector_sizes
215        .filter(|&vector_size| {
216            vector_size != 0
217                && axis_stride % vector_size == 0
218                && strides.iter().all(|&stride| stride < axis_stride || stride % vector_size == 0)
219        })
220        .max()
221        .ok_or(VectorizationError::NoValidVectorization)
222}
223
224/// Runtime arguments to launch a kernel.
225pub type RuntimeArg<T, R> = <T as LaunchArg>::RuntimeArg<R>;
226pub type ExpandType<T> = <T as crate::dsl::prelude::RudaType>::ExpandType;
227
228#[cfg(feature = "frontend-tests")]
229/// Tests only useful for runtimes.
230pub mod runtime_tests;
231
232#[cfg(test)]
233mod tests {
234    use super::*;
235
236    fn try_parallel(
237        sizes: &[VectorSize],
238        shape: &[usize],
239        strides: &[usize],
240        axis: usize,
241    ) -> Result<VectorSize, VectorizationError> {
242        try_tensor_vector_size_parallel(
243            sizes.iter().copied(),
244            &Shape::from(shape.iter().copied()),
245            &Strides::new(strides),
246            axis,
247        )
248    }
249
250    #[test]
251    fn parallel_contiguous_picks_max_vector_size() {
252        // Contiguous [1, 9, 4], vectorize along last dim (stride 1).
253        // Outer stride 4 is a multiple of 4, so vec_size = 4 is safe.
254        let v = try_parallel(&[1, 2, 4], &[1, 9, 4], &[36, 4, 1], 2).unwrap();
255        assert_eq!(v, 4);
256    }
257
258    #[test]
259    fn parallel_unfold_step_one_rejects_vectorization() {
260        // Unfold view produced by `unfold(1, 4, 1)` on a [1, 12] contiguous tensor:
261        // shape [1, 9, 4], strides [12, 1, 1]. The frame dim has stride 1, so each
262        // step in the frame coord shifts the source offset by 1 - not a multiple
263        // of any vec_size > 1, so vectorized reads would be unaligned and return
264        // the wrong data. Must fall back to vec_size = 1.
265        let v = try_parallel(&[1, 2, 4], &[1, 9, 4], &[12, 1, 1], 2).unwrap();
266        assert_eq!(v, 1);
267    }
268
269    #[test]
270    fn parallel_unfold_step_two_allows_vectorization() {
271        // Same unfold pattern but with step=2: strides [12, 2, 1]. Frame coord
272        // shifts source by 2 (still not a multiple of 4), so vec_size = 4 must
273        // be rejected - but vec_size = 2 is fine.
274        let v = try_parallel(&[1, 2, 4], &[1, 9, 4], &[12, 2, 1], 2).unwrap();
275        assert_eq!(v, 2);
276    }
277
278    #[test]
279    fn parallel_broadcast_dim_ignored() {
280        // Broadcast dim has stride 0; it never shifts the source offset, so
281        // it should not disqualify vectorization.
282        let v = try_parallel(&[1, 2, 4], &[1, 9, 4], &[0, 4, 1], 2).unwrap();
283        assert_eq!(v, 4);
284    }
285
286    #[test]
287    fn parallel_axis_stride_not_one_is_error() {
288        let err = try_parallel(&[1, 2, 4], &[1, 9, 4], &[36, 1, 4], 2).unwrap_err();
289        assert!(matches!(err, VectorizationError::StrideMismatch));
290    }
291}
292
293pub use crate::{unexpanded, expand_error, expand_assert, size, define, debug_print, debug_print_expand, define_scalar, define_size};
294
295#[cfg(feature = "frontend-tests")]
296pub use crate::{testgen_all_reduce, testgen_assign, testgen_atomic_untyped, testgen_atomic_int, testgen_atomic_float, testgen_barrier, testgen_binary, testgen_binary_untyped, testgen_branch, testgen_cluster, testgen_cmma, testgen_comparison, testgen_const_match, testgen_constants, testgen_debug, testgen_different_rank, testgen_enums, testgen_file, testgen_index, testgen_launch, testgen_launch_untyped, testgen_metadata, testgen_minifloat, testgen_all, testgen_float, testgen_int, testgen_uint, testgen_untyped, as_bytes, as_type, testgen_numeric, testgen_plane, testgen_properties, testgen_saturating_uint, testgen_saturating_int, testgen_sequence, testgen_slice, testgen_stream, testgen_sync_plane, testgen_tensor_indexing, testgen_tensormap, testgen_to_client, testgen_topology, testgen_unary, testgen_unary_int, testgen_unroll, testgen_vector};
297
298pub mod lowering;