Skip to main content

hermes_simd/
lib.rs

1//! High-performance, zero-overhead SIMD abstraction library.
2//!
3//! # Architecture
4//!
5//! `hermes-simd` is the public facade for the hermes-simd workspace:
6//!
7//! - [`hermes_simd_core`] — core abstractions, traits, views
8//! - [`hermes_simd_intrinsics`] — architecture-specific kernels
9//! - [`hermes_simd_macros`] — proc-macro code generation
10//!
11//! # Feature Flags
12//!
13//! | Feature | Description |
14//! |---------|-------------|
15//! | `std` (default) | Runtime CPU feature detection (`is_x86_feature_detected!`); without it dispatch uses compile-time `cfg!(target_feature)` only |
16//! | `mnemosyne-memory` (default) | Route `AlignedVec` allocation through the mnemosyne allocator |
17//! | `libnuma` | Linux NUMA affinity and residency probes via libnuma (links `-lnuma`); allocation still routes through Mnemosyne/the configured allocator |
18//!
19//! # Usage Examples
20//!
21//! **Dense sum (runtime dispatch):**
22//! ```rust
23//! use hermes_simd::sum;
24//! let data = vec![1.0f32; 1024];
25//! assert_eq!(sum(&data), 1024.0);
26//! ```
27//!
28//! **Masked dot product:**
29//! ```rust
30//! use hermes_simd::masked_dot;
31//! let a = vec![1.0f32, 2.0, 3.0, 4.0, 5.0];
32//! let b = vec![1.0f32; 5];
33//! let mask = vec![true, false, true, false, true];
34//! assert_eq!(masked_dot(&a, &b, &mask).unwrap(), 9.0); // 1+3+5
35//! ```
36
37#![cfg_attr(not(feature = "std"), no_std)]
38#![deny(missing_docs)]
39#![allow(
40    unused_unsafe,
41    clippy::too_many_arguments,
42    clippy::needless_range_loop,
43    clippy::assign_op_pattern,
44    clippy::manual_memcpy
45)]
46
47extern crate alloc;
48
49pub use hermes_simd_core::{
50    align::{Aligned, Alignment, Unaligned},
51    arch::SimdArch,
52    bitboard::{BitBoardKernel, BitBoardView},
53    // ComputeView extension
54    compute::{ComputeReduce, ComputeView},
55    cow::{ArchivedPacked4Cow, ArchivedSimdCow, Packed4CowResolver, SimdCow, SimdCowResolver},
56    current_numa_node,
57    execution::{ExecutionMode, Masked, Unmasked},
58    // Chunk iterators
59    iter::{SimdChunks, SimdChunksMut, ZipChunks, ZipChunksMut},
60    kernel::SimdKernel,
61    mask::BitMask,
62    refresh_numa_node,
63    scalar::{CastFrom, CastTo, FloatElement, Scalar as SimdScalar},
64    vec::AlignedVec,
65    verify_numa_locality,
66    view::{Mask, SimdError, SimdView, TileMatrixMultiply, TileView, Vector},
67    // Unary strategy ZSTs
68    Abs,
69    Add,
70    BitAnd,
71    BitOr,
72    BitXor,
73    Clamp,
74    Div,
75    Dot,
76    ElementOp,
77    Exclusive,
78    // Extended strategy ZSTs (v2)
79    FmaAdd,
80    Inclusive,
81    Mul,
82    Neg,
83    NumaBinding,
84    Popcount,
85    Product,
86    RecipSqrt,
87    // Operation strategy ZSTs and sealed traits — zero-cost, erased at monomorphization.
88    ReductionOp,
89    ScanAdd,
90    ScanMax,
91    ScanMin,
92    ScanMode,
93    ScanMul,
94    // Scan strategy ZSTs
95    ScanOp,
96    Sqrt,
97    Sub,
98    Sum,
99    UnaryOp,
100};
101
102// Re-export sparse types
103pub use hermes_simd_core::sparse::{
104    BlockedCoo,
105    BlockedCooData,
106    // Format-to-owned-storage mapping for Cow containers
107    CowFormat,
108    Csr,
109    CsrData,
110    DenseWithMask,
111    DenseWithMaskData,
112    OwnedBlockedCoo,
113    // Owned heap-backed sparse storage types
114    OwnedCsr,
115    OwnedDenseWithMask,
116    OwnedSellP,
117    SellP,
118    SellPData,
119    // Generic Clone-on-Write sparse container
120    SparseCow,
121    SparseFormat,
122    SparseOps,
123    SparseSpMv,
124    SparseView,
125    Validated,
126    ValidatedData,
127};
128
129// Re-export tiling
130pub use hermes_simd_core::tiling::{tiled_dot, tiled_gemv, TilingPolicy, TilingStrategy};
131
132// Re-export tensor views
133pub use hermes_simd_core::tensor::{ColMajor, RowMajor, TensorCow, TensorError, TensorView};
134
135// Re-export concrete ZST architecture markers
136pub use hermes_simd_intrinsics::{
137    Avx2, Avx512, AvxVnni, HybridSwarMagic, Hyperbola, KoggeStone, Magic, Neon, Scalar, SveArch,
138    Swar, SwarUtils,
139};
140
141#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
142pub use hermes_simd_intrinsics::{
143    AmxBatchSession, AmxBf16, AmxConfig, AmxInt8, AmxSession, AmxSessionError,
144};
145
146pub use eunomia::{
147    unpack_bf4_to_bf16, unpack_bf4_to_bf16_packed, unpack_bf8_to_bf16, unpack_f4_to_f32,
148    unpack_f4_to_f32_packed, unpack_f8_to_f32, Bf16, Bf4, Bf8, Packable4, Packed4Cow, Packed4Iter,
149    Packed4Slice, Packed4SliceMut, Packed4Vec, PackedBf4Cow, PackedBf4Slice, PackedBf4SliceMut,
150    PackedBf4Vec, PackedF4Cow, PackedF4Slice, PackedF4SliceMut, PackedF4Vec, F16, F32, F4, F64, F8,
151    I16, I32, I8,
152};
153
154// Re-export monomorphized vector register types and PreferredArch
155pub use hermes_simd_types::{
156    MaskBf16, MaskBf4, MaskBf8, MaskF16, MaskF32, MaskF4, MaskF64, MaskF8, MaskI16, MaskI32,
157    MaskI8, PreferredArch, ScalarBf16, ScalarBf4, ScalarBf8, ScalarF16, ScalarF32, ScalarF4,
158    ScalarF64, ScalarF8, ScalarI16, ScalarI32, ScalarI8, ScalarMaskF32, ScalarMaskF64, SimdBf16,
159    SimdBf4, SimdBf8, SimdF16, SimdF32, SimdF4, SimdF64, SimdF8, SimdI16, SimdI32, SimdI8,
160    SimdMaskBf16, SimdMaskBf4, SimdMaskBf8, SimdMaskF16, SimdMaskF32, SimdMaskF4, SimdMaskF64,
161    SimdMaskF8, SimdMaskI16, SimdMaskI32, SimdMaskI8, VectorBf16, VectorBf4, VectorBf8, VectorF16,
162    VectorF32, VectorF4, VectorF64, VectorF8, VectorI16, VectorI32, VectorI8,
163};
164
165#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
166pub use hermes_simd_types::{
167    Avx2Bf16, Avx2Bf4, Avx2Bf8, Avx2F16, Avx2F32, Avx2F4, Avx2F64, Avx2F8, Avx2I16, Avx2I32,
168    Avx2I8, Avx2MaskBf16, Avx2MaskF16, Avx2MaskF32, Avx2MaskF64, Avx512Bf16, Avx512Bf4, Avx512Bf8,
169    Avx512F16, Avx512F32, Avx512F4, Avx512F64, Avx512F8, Avx512I16, Avx512I32, Avx512I8,
170    Avx512MaskBf16, Avx512MaskF16, Avx512MaskF32, Avx512MaskF64,
171};
172
173#[cfg(target_arch = "aarch64")]
174pub use hermes_simd_types::{
175    NeonBf16, NeonBf4, NeonBf8, NeonF16, NeonF32, NeonF4, NeonF64, NeonF8, NeonI16, NeonI32,
176    NeonI8, NeonMaskBf16, NeonMaskF16, NeonMaskF32, NeonMaskF64,
177};
178
179/// Chess board attack generation kernels using bitboards and SWAR.
180pub mod attacks;
181/// Runtime CPU feature detection utilities.
182pub mod cpu;
183/// Dynamic dispatcher choosing optimal backends based on hardware/layout.
184pub mod dispatcher;
185
186/// Tiled matrix multiplication dispatch and kernel interfaces.
187pub mod tile_matmul;
188
189/// Runtime-dispatched SIMD abstractions and dynamic facade.
190pub mod dispatch;
191/// Explicit runtime target tokens and forced dispatch helpers.
192pub mod target;
193
194pub use attacks::{bishop_attacks, queen_attacks, rook_attacks};
195pub use cpu::{has_fma3, AmxSupport, Avx512Support, FmaSupport};
196pub use dispatcher::{AdaptiveDispatcher, DispatchDecision};
197pub use target::{dispatch_view_mut_to, dispatch_view_to, TargetId};
198pub use tile_matmul::{
199    dispatch_tile_matmul, gemm, unpack_int4, widen_I8_to_I16, widen_I8_to_I32, widen_i8_to_i16,
200    widen_i8_to_i32, TiledGemm,
201};
202
203// Re-export the generic dispatch operations. These monomorphize at call sites:
204// calling `sum::<f32>(data)` produces the f32 specialization.
205pub use dispatch::{
206    abs_max,
207    abs_sum,
208    argmax,
209    argmin,
210    axpy,
211    axpy_rows,
212    axpy_rows_batch,
213    dot,
214    elementwise_add,
215    elementwise_div,
216    elementwise_mul,
217    elementwise_sub,
218    gemv,
219    gemv_strided,
220    gemv_transpose,
221    gemv_transpose_strided,
222    interleaved_complex_dot,
223    interleaved_complex_dot_runtime,
224    interleaved_complex_mul_assign,
225    interleaved_complex_mul_assign_runtime,
226    masked_add,
227    masked_dot,
228    masked_sum,
229    max,
230    min,
231    ntt_butterfly_stage_u64,
232    // Generic free functions — the primary public API.
233    reduce_popcount,
234    reduce_popcount_and,
235    reduce_popcount_or,
236    reduce_popcount_xor,
237    scale,
238    spmv_bcoo,
239    // Sparse operations — generic entry points.
240    spmv_csr,
241    spmv_dense_masked,
242    spmv_sellp,
243    sum,
244    tiled_gemm,
245    // Core trait — sealed; implemented for f32 and f64.
246    SimdOps,
247};
248
249/// Target-specific, runtime-dispatched SIMD view wrapper.
250pub enum DispatchedView<'a, T, Align = Unaligned, Mode = Unmasked, Ref = &'a [T]>
251where
252    Align: hermes_simd_core::align::Alignment,
253    Mode: hermes_simd_core::execution::ExecutionMode,
254    Ref: core::ops::Deref<Target = [T]>,
255{
256    /// AVX-512 architecture target.
257    #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
258    Avx512(SimdView<'a, T, Avx512, Align, Mode, Ref>),
259    /// AVX2 architecture target.
260    #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
261    Avx2(SimdView<'a, T, Avx2, Align, Mode, Ref>),
262    /// NEON architecture target.
263    #[cfg(target_arch = "aarch64")]
264    Neon(SimdView<'a, T, Neon, Align, Mode, Ref>),
265    /// Fallback scalar target.
266    Scalar(SimdView<'a, T, Scalar, Align, Mode, Ref>),
267}
268
269/// Dispatches a shared slice into the best matching `DispatchedView` based on runtime CPU feature detection.
270#[inline]
271#[allow(unreachable_code)]
272pub fn dispatch_view<'a, T, Align>(
273    data: &'a [T],
274) -> Option<DispatchedView<'a, T, Align, Unmasked, &'a [T]>>
275where
276    T: FloatElement,
277    Align: Alignment,
278{
279    #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
280    {
281        #[cfg(feature = "std")]
282        {
283            if std::is_x86_feature_detected!("avx512f") {
284                return SimdView::<T, Avx512, Align, Unmasked, &'a [T]>::new(data)
285                    .map(DispatchedView::Avx512);
286            }
287            if std::is_x86_feature_detected!("avx2") && std::is_x86_feature_detected!("fma") {
288                return SimdView::<T, Avx2, Align, Unmasked, &'a [T]>::new(data)
289                    .map(DispatchedView::Avx2);
290            }
291        }
292        #[cfg(not(feature = "std"))]
293        {
294            if cfg!(target_feature = "avx512f") {
295                return SimdView::<T, Avx512, Align, Unmasked, &'a [T]>::new(data)
296                    .map(DispatchedView::Avx512);
297            }
298            if cfg!(target_feature = "avx2") && cfg!(target_feature = "fma") {
299                return SimdView::<T, Avx2, Align, Unmasked, &'a [T]>::new(data)
300                    .map(DispatchedView::Avx2);
301            }
302        }
303    }
304    #[cfg(target_arch = "aarch64")]
305    {
306        return SimdView::<T, Neon, Align, Unmasked, &'a [T]>::new(data).map(DispatchedView::Neon);
307    }
308    SimdView::<T, Scalar, Align, Unmasked, &'a [T]>::new(data).map(DispatchedView::Scalar)
309}
310
311/// Dispatches a mutable slice into the best matching `DispatchedView` based on runtime CPU feature detection.
312#[inline]
313#[allow(unreachable_code)]
314pub fn dispatch_view_mut<'a, T, Align>(
315    data: &'a mut [T],
316) -> Option<DispatchedView<'a, T, Align, Unmasked, &'a mut [T]>>
317where
318    T: FloatElement,
319    Align: Alignment,
320{
321    #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
322    {
323        #[cfg(feature = "std")]
324        {
325            if std::is_x86_feature_detected!("avx512f") {
326                return SimdView::<T, Avx512, Align, Unmasked, &'a mut [T]>::new_mut(data)
327                    .map(DispatchedView::Avx512);
328            }
329            if std::is_x86_feature_detected!("avx2") && std::is_x86_feature_detected!("fma") {
330                return SimdView::<T, Avx2, Align, Unmasked, &'a mut [T]>::new_mut(data)
331                    .map(DispatchedView::Avx2);
332            }
333        }
334        #[cfg(not(feature = "std"))]
335        {
336            if cfg!(target_feature = "avx512f") {
337                return SimdView::<T, Avx512, Align, Unmasked, &'a mut [T]>::new_mut(data)
338                    .map(DispatchedView::Avx512);
339            }
340            if cfg!(target_feature = "avx2") && cfg!(target_feature = "fma") {
341                return SimdView::<T, Avx2, Align, Unmasked, &'a mut [T]>::new_mut(data)
342                    .map(DispatchedView::Avx2);
343            }
344        }
345    }
346    #[cfg(target_arch = "aarch64")]
347    {
348        return SimdView::<T, Neon, Align, Unmasked, &'a mut [T]>::new_mut(data)
349            .map(DispatchedView::Neon);
350    }
351    SimdView::<T, Scalar, Align, Unmasked, &'a mut [T]>::new_mut(data).map(DispatchedView::Scalar)
352}
353
354/// Extension trait for `SimdCow` providing vector-register level operations.
355pub trait SimdCowExt<T: SimdScalar, Arch: SimdArch + SimdKernel<T>, Align: Alignment> {
356    /// In-place vector-level transformation.
357    ///
358    /// Promotes `self` to owned if borrowed (zero-copy upgrade), then applies
359    /// the function `f` elementwise to each SIMD vector chunk.
360    fn transform_vectors<F>(&mut self, f: F)
361    where
362        F: FnMut(Vector<T, Arch>) -> Vector<T, Arch>;
363}
364
365impl<'a, T, Arch, Align> SimdCowExt<T, Arch, Align> for SimdCow<'a, T, Arch, Align>
366where
367    T: SimdScalar,
368    Arch: SimdArch + SimdKernel<T>,
369    Align: Alignment,
370{
371    #[inline]
372    fn transform_vectors<F>(&mut self, mut f: F)
373    where
374        F: FnMut(Vector<T, Arch>) -> Vector<T, Arch>,
375    {
376        let owned_vec = self.to_mut();
377        let len = owned_vec.len();
378        let lane_count = Arch::LANE_COUNT;
379        let simd_len = (len / lane_count) * lane_count;
380        let slice = owned_vec.as_mut_slice();
381
382        let mut i = 0;
383        while i < simd_len {
384            unsafe {
385                let ptr = slice.as_mut_ptr().add(i);
386                let vec = if Align::IS_ALIGNED {
387                    Vector::load_aligned(ptr)
388                } else {
389                    Vector::load_unaligned(ptr)
390                };
391                let res = f(vec);
392                if Align::IS_ALIGNED {
393                    res.store_aligned(ptr);
394                } else {
395                    res.store_unaligned(ptr);
396                }
397            }
398            i += lane_count;
399        }
400
401        if simd_len < len {
402            // Tail buffer: sized conservatively at 64 elements (> any current
403            // SIMD lane width: SSE=4, AVX=8, AVX-512=16, SVE≤64).
404            // A debug assertion guards against future wider architectures.
405            debug_assert!(
406                lane_count <= 64,
407                "tail_buf[64] too small: lane_count={lane_count}"
408            );
409            let mut tail_buf = [core::mem::MaybeUninit::<T>::uninit(); 64];
410            let tail_len = len - simd_len;
411            unsafe {
412                for idx in 0..tail_len {
413                    tail_buf[idx].write(slice[simd_len + idx]);
414                }
415                for idx in tail_len..lane_count {
416                    tail_buf[idx].write(T::ZERO);
417                }
418                let vec = Vector::load_unaligned(tail_buf.as_ptr() as *const T);
419                let res = f(vec);
420                res.store_unaligned(tail_buf.as_mut_ptr() as *mut T);
421                for idx in 0..tail_len {
422                    slice[simd_len + idx] = tail_buf[idx].assume_init();
423                }
424            }
425        }
426    }
427}
428
429/// Extension trait for packed Clone-on-Write containers to support zero-copy unpacking directly into `SimdCow`.
430pub trait Packed4CowExt<'a, T: Packable4> {
431    /// Unpack packed elements directly to a `SimdCow` of wider precision with zero intermediate allocations.
432    fn unpack_to_cow<Arch, Align>(&self) -> SimdCow<'static, T::Unpacked, Arch, Align>
433    where
434        Arch: SimdArch,
435        Align: Alignment;
436}
437
438impl<'a, T: Packable4> Packed4CowExt<'a, T> for Packed4Cow<'a, T> {
439    #[inline]
440    fn unpack_to_cow<Arch, Align>(&self) -> SimdCow<'static, T::Unpacked, Arch, Align>
441    where
442        Arch: SimdArch,
443        Align: Alignment,
444    {
445        let len = self.len();
446        let mut dest = AlignedVec::with_capacity(len);
447        unsafe {
448            dest.set_len(len);
449        }
450        let view = self.as_view();
451        let n = view.len().min(dest.len());
452        let even_len = (n / 2) * 2;
453        T::unpack_slice_packed(
454            &view.as_packed_slice()[..even_len / 2],
455            &mut dest[..even_len],
456        );
457        if n % 2 != 0 {
458            if let Some(b) = view.get(n - 1) {
459                dest[n - 1] = T::unpack_single(b);
460            }
461        }
462        SimdCow::Owned(dest)
463    }
464}