Hardware Acceleration
Side-channel claim limit. Nothing on this page claims constant-time behaviour or side-channel resistance for any target: the side-channel harness covers nothing (finding A48) and no measurement exists. The Rust and C cores are designed for constant time and unmeasured; managed runtimes (JIT, GC, bounds checks) make no timing claim. Where this page names constant-time or side-channel techniques, in prose or in code, they are design intent or recommendations, not measured properties.
Release scope. Under the owner’s rule of 2026-09-24 the public release carries portable implementations only: the Rust source release, the one published so far, has no SIMD, assembly, GPU or CPU-feature-detection path. The backends on this page live in this source tree outside that release. In Rust they are separate, unpublished crates that an application must opt into (for Falcon, metamui_falcon512_simd::install() before the first Falcon operation; AVX-512 and Metal also need the crate’s avx512 / metal features). In the C tree, the Falcon library compiles the kernels the compiler supports and picks one with a CPUID check at run time.
No performance claim is made for any backend on this page.
Dispatch Hierarchy
Where a dispatcher is in use (the opt-in Rust metamui-falcon512-simd crate, the C Falcon library), it checks backends in this order and selects the first one available. The order is a design choice, not a measured ranking:
Metal GPU (macOS, Apple Silicon, batch >= 100)
|
v
AVX-512BW (Intel Ice Lake 2019+, AMD Zen 4 2022+)
|
v
AVX-2 (Intel Haswell 2013+, AMD Excavator 2015+)
|
v
ARM NEON (all AArch64 processors)
|
v
Portable (scalar fallback -- always available)
For GPU backends (Metal, CUDA), the dispatch only activates for batch operations where the overhead of GPU dispatch is amortized across many items. Single-polynomial operations always use CPU SIMD.
Backend Summary
| Backend | Architecture | Register Width | Coefficients per Op |
|---|---|---|---|
| AVX-512 | x86-64 | 512-bit | 32 x uint16 |
| AVX-2 | x86-64 | 256-bit | 16 x uint16 |
| ARM NEON | AArch64 | 128-bit | 8 x uint16 |
| Apple Metal | Apple Silicon GPU | Threadgroup | 1024 coefficients |
| NVIDIA CUDA | NVIDIA GPU | Warp (32 threads) | Configurable |
| ARM SVE2 | AArch64 (experimental) | 128-2048 bit | Variable |
| Portable | Any | 64-bit | 1 x uint16 |
Algorithm Coverage
Backends present in the source tree, per algorithm (none of them is in the public release):
| Algorithm | AVX-512 | AVX-2 | NEON | Metal | CUDA |
|---|---|---|---|---|---|
| Falcon-512/1024 | NTT, FFT, polynomial ops | NTT, FFT, polynomial ops | NTT, FFT, polynomial ops | NTT, pointwise mul, batch verify | – |
| BLAKE3 | Batch block compression | 8-block batch compression | Block compression | Chunk processing, tree merge | Batch hashing, tree hashing |
| ML-KEM | – | NTT, poly ops, sampling | NTT, poly ops, sampling | – | – |
| HAETAE | – | Packing, polyfix, polymat, FFT, poly, NTT (ASM) | – | – | – |
| SMAUG-T | – | – | – | Poly add/sub/mul, NTT, matrix-vector, Barrett reduce | Poly add/sub/mul, NTT, matrix-vector, Karatsuba |
Falcon SIMD Operations
The Falcon dispatch covers both NTT-domain (integer mod q=12289) and FFT-domain (floating-point complex) operations:
- NTT domain:
ntt_forward,ntt_inverse,poly_pointwise - FFT domain:
poly_add_fft,poly_sub_fft,poly_mul_fft,poly_muladj_fft,poly_adj_fft,poly_inv_fft,poly_norm_fft,poly_split_fft,poly_merge_fft - Fused operations:
poly_muladd_fft(c = acc + ab),poly_mulsub_fft(c = acc - ab)
Constant-Time Behaviour (not claimed)
All SIMD paths use branchless masking throughout: conditional operations use arithmetic masks ((cond - 1) patterns) rather than branches. This is design intent for constant-time execution; it is unmeasured and no constant-time claim is made (A48). It applies to:
- Montgomery reduction in NTT kernels
- Modular addition/subtraction (
addmod,submod) - Butterfly operations in NTT and FFT transforms
- Signature norm checking
Portable Fallback
Every accelerated operation has a portable scalar counterpart that the backend is meant to match byte for byte. In the public Rust crates the portable path is the only one. Where a dispatcher is in use and finds no SIMD or GPU backend, it uses the portable implementation.