/home/noah/src/trueno/src/vector/mod.rs
Line | Count | Source |
1 | | //! Vector type with multi-backend support |
2 | | //! |
3 | | //! This module provides the core `Vector<T>` type with SIMD-optimized operations |
4 | | //! across multiple backends (Scalar, SSE2, AVX2, AVX-512, NEON, WASM SIMD). |
5 | | //! |
6 | | //! GPU thresholds intentionally set to usize::MAX to disable GPU for element-wise ops. |
7 | | //! See docs/performance-analysis.md - GPU is 2-65,000x SLOWER than scalar for these ops. |
8 | | |
9 | | #![allow(clippy::absurd_extreme_comparisons)] |
10 | | |
11 | | // Submodules |
12 | | pub mod dispatch; |
13 | | mod ops; |
14 | | |
15 | | // Tests (~10K lines extracted for TDG compliance) |
16 | | #[cfg(test)] |
17 | | mod tests; |
18 | | |
19 | | use crate::{Backend, Result, TruenoError}; |
20 | | |
21 | | /// High-performance vector with multi-backend support |
22 | | /// |
23 | | /// # Examples |
24 | | /// |
25 | | /// ``` |
26 | | /// use trueno::Vector; |
27 | | /// |
28 | | /// let a = Vector::from_slice(&[1.0, 2.0, 3.0]); |
29 | | /// let b = Vector::from_slice(&[4.0, 5.0, 6.0]); |
30 | | /// let result = a.add(&b).unwrap(); |
31 | | /// |
32 | | /// assert_eq!(result.as_slice(), &[5.0, 7.0, 9.0]); |
33 | | /// ``` |
34 | | #[derive(Debug, Clone, PartialEq)] |
35 | | pub struct Vector<T> { |
36 | | data: Vec<T>, |
37 | | backend: Backend, |
38 | | } |
39 | | |
40 | | impl<T> Vector<T> |
41 | | where |
42 | | T: Clone, |
43 | | { |
44 | | /// Create vector from slice using auto-selected optimal backend |
45 | | /// |
46 | | /// # Performance |
47 | | /// |
48 | | /// Auto-selects the best available backend at creation time based on: |
49 | | /// - CPU feature detection (AVX-512 > AVX2 > AVX > SSE2) |
50 | | /// - Vector size (GPU for large workloads) |
51 | | /// - Platform availability (NEON on ARM, WASM SIMD in browser) |
52 | | /// |
53 | | /// # Examples |
54 | | /// |
55 | | /// ``` |
56 | | /// use trueno::Vector; |
57 | | /// |
58 | | /// let v = Vector::from_slice(&[1.0, 2.0, 3.0, 4.0]); |
59 | | /// assert_eq!(v.len(), 4); |
60 | | /// ``` |
61 | 247k | pub fn from_slice(data: &[T]) -> Self { |
62 | 247k | Self { |
63 | 247k | data: data.to_vec(), |
64 | 247k | backend: crate::select_best_available_backend(), |
65 | 247k | } |
66 | 247k | } |
67 | | |
68 | | /// Create vector from an existing Vec (takes ownership, no copy) |
69 | | /// |
70 | | /// This is more efficient than `from_slice` when you already have a Vec |
71 | | /// and don't need to keep it, as it avoids an extra allocation and copy. |
72 | | /// |
73 | | /// # Examples |
74 | | /// |
75 | | /// ``` |
76 | | /// use trueno::Vector; |
77 | | /// |
78 | | /// let data = vec![1.0, 2.0, 3.0]; |
79 | | /// let v = Vector::from_vec(data); |
80 | | /// assert_eq!(v.len(), 3); |
81 | | /// ``` |
82 | 10.7k | pub fn from_vec(data: Vec<T>) -> Self { |
83 | 10.7k | Self { |
84 | 10.7k | data, |
85 | 10.7k | backend: crate::select_best_available_backend(), |
86 | 10.7k | } |
87 | 10.7k | } |
88 | | |
89 | | /// Create vector with specific backend (for benchmarking or testing) |
90 | | /// |
91 | | /// # Examples |
92 | | /// |
93 | | /// ``` |
94 | | /// use trueno::{Vector, Backend}; |
95 | | /// |
96 | | /// let v = Vector::from_slice_with_backend(&[1.0, 2.0], Backend::Scalar); |
97 | | /// assert_eq!(v.len(), 2); |
98 | | /// ``` |
99 | 0 | pub fn from_slice_with_backend(data: &[T], backend: Backend) -> Self { |
100 | 0 | let resolved_backend = match backend { |
101 | 0 | Backend::Auto => crate::select_best_available_backend(), |
102 | 0 | _ => backend, |
103 | | }; |
104 | | |
105 | 0 | Self { |
106 | 0 | data: data.to_vec(), |
107 | 0 | backend: resolved_backend, |
108 | 0 | } |
109 | 0 | } |
110 | | } |
111 | | |
112 | | impl Vector<f32> { |
113 | | /// Create vector with specified alignment for optimal SIMD performance |
114 | | /// |
115 | | /// This method attempts to create a vector with memory aligned to the specified byte boundary. |
116 | | /// Note: Rust's Vec allocator may already provide sufficient alignment for most use cases. |
117 | | /// This method validates the alignment requirement but uses standard Vec allocation. |
118 | | /// |
119 | | /// # Arguments |
120 | | /// |
121 | | /// * `size` - Number of elements to allocate |
122 | | /// * `backend` - Backend to use for operations |
123 | | /// * `alignment` - Requested alignment in bytes (must be power of 2: 16, 32, 64) |
124 | | /// |
125 | | /// # Recommended Alignments |
126 | | /// |
127 | | /// - SSE2: 16 bytes (128-bit) |
128 | | /// - AVX2: 32 bytes (256-bit) |
129 | | /// - AVX-512: 64 bytes (512-bit) |
130 | | /// |
131 | | /// # Note on Implementation |
132 | | /// |
133 | | /// Currently uses Rust's default Vec allocator, which typically provides 16-byte alignment |
134 | | /// on modern systems. Custom allocators for specific alignments will be added in future versions. |
135 | | /// |
136 | | /// # Examples |
137 | | /// |
138 | | /// ``` |
139 | | /// use trueno::{Vector, Backend}; |
140 | | /// |
141 | | /// // Create vector with requested 16-byte alignment |
142 | | /// let v = Vector::with_alignment(100, Backend::SSE2, 16).unwrap(); |
143 | | /// assert_eq!(v.len(), 100); |
144 | | /// ``` |
145 | | /// |
146 | | /// # Errors |
147 | | /// |
148 | | /// Returns `TruenoError::InvalidInput` if alignment is not a power of 2. |
149 | 0 | pub fn with_alignment(size: usize, backend: Backend, alignment: usize) -> Result<Self> { |
150 | | // Validate alignment is power of 2 |
151 | 0 | if alignment == 0 || (alignment & (alignment - 1)) != 0 { |
152 | 0 | return Err(TruenoError::InvalidInput(format!( |
153 | 0 | "Alignment must be power of 2, got {}", |
154 | 0 | alignment |
155 | 0 | ))); |
156 | 0 | } |
157 | | |
158 | | // Resolve backend |
159 | 0 | let resolved_backend = match backend { |
160 | 0 | Backend::Auto => crate::select_best_available_backend(), |
161 | 0 | _ => backend, |
162 | | }; |
163 | | |
164 | | // For now, use standard Vec allocation which typically provides good alignment |
165 | | // Future enhancement: use custom allocator for guaranteed alignment > 16 bytes |
166 | 0 | let data = vec![0.0f32; size]; |
167 | | |
168 | | // Verify actual alignment (for informational purposes) |
169 | 0 | let ptr = data.as_ptr() as usize; |
170 | 0 | let actual_alignment = ptr & !(ptr - 1); // Find lowest set bit |
171 | | |
172 | | // Log warning if alignment requirement not met (for future enhancement) |
173 | 0 | if alignment > actual_alignment { |
174 | 0 | // Note: This is not an error, just informational |
175 | 0 | // The unaligned loads in SSE2 (_mm_loadu_ps) will still work correctly |
176 | 0 | eprintln!( |
177 | 0 | "Note: Requested {}-byte alignment, got {}-byte alignment. Using unaligned loads.", |
178 | 0 | alignment, actual_alignment |
179 | 0 | ); |
180 | 0 | } |
181 | | |
182 | 0 | Ok(Self { |
183 | 0 | data, |
184 | 0 | backend: resolved_backend, |
185 | 0 | }) |
186 | 0 | } |
187 | | } |
188 | | |
189 | | impl<T> Vector<T> |
190 | | where |
191 | | T: Clone, |
192 | | { |
193 | | /// Get underlying data as slice |
194 | | /// |
195 | | /// # Examples |
196 | | /// |
197 | | /// ``` |
198 | | /// use trueno::Vector; |
199 | | /// |
200 | | /// let v = Vector::from_slice(&[1.0, 2.0, 3.0]); |
201 | | /// assert_eq!(v.as_slice(), &[1.0, 2.0, 3.0]); |
202 | | /// ``` |
203 | 10.6k | pub fn as_slice(&self) -> &[T] { |
204 | 10.6k | &self.data |
205 | 10.6k | } |
206 | | |
207 | | /// Get vector length |
208 | | /// |
209 | | /// # Examples |
210 | | /// |
211 | | /// ``` |
212 | | /// use trueno::Vector; |
213 | | /// |
214 | | /// let v = Vector::from_slice(&[1.0, 2.0, 3.0, 4.0, 5.0]); |
215 | | /// assert_eq!(v.len(), 5); |
216 | | /// ``` |
217 | 452k | pub fn len(&self) -> usize { |
218 | 452k | self.data.len() |
219 | 452k | } |
220 | | |
221 | | /// Check if vector is empty |
222 | | /// |
223 | | /// # Examples |
224 | | /// |
225 | | /// ``` |
226 | | /// use trueno::Vector; |
227 | | /// |
228 | | /// let v1: Vector<f32> = Vector::from_slice(&[]); |
229 | | /// assert!(v1.is_empty()); |
230 | | /// |
231 | | /// let v2 = Vector::from_slice(&[1.0]); |
232 | | /// assert!(!v2.is_empty()); |
233 | | /// ``` |
234 | 0 | pub fn is_empty(&self) -> bool { |
235 | 0 | self.data.is_empty() |
236 | 0 | } |
237 | | |
238 | | /// Get the backend being used |
239 | 0 | pub fn backend(&self) -> Backend { |
240 | 0 | self.backend |
241 | 0 | } |
242 | | } |
243 | | |
244 | | // Note: Vector<f32> operations have been moved to submodules in ops/: |
245 | | // - ops/normalization.rs: zscore, minmax_normalize, layer_norm, layer_norm_simple, normalize |
246 | | // - ops/norms.rs: norm_l1, norm_l2, norm_linf |
247 | | // - ops/transforms.rs: abs, clamp, clip, lerp, sqrt, recip, pow |
248 | | // - ops/arithmetic.rs: add, sub, mul, div, scale, fma |
249 | | // - ops/reductions.rs: dot, sum, max, min, argmax, argmin, mean, variance, stddev, covariance, correlation |
250 | | // - ops/activations.rs: relu, sigmoid, gelu, etc. |
251 | | // - ops/transcendental.rs: exp, log, sin, cos, etc. |
252 | | // - ops/rounding.rs: floor, ceil, round, trunc, etc. |
253 | | |