hydroplane 0.1.0

Floating but fast: float-agnostic, ISPC-style SPMD/SIMD infrastructure — write one kernel generic over the scalar element (f32/f64/f16/bf16) and run it with runtime ISA dispatch.
Documentation

hydroplane

Float-agnostic, ISPC-style SPMD/SIMD infrastructure for Rust. Write one kernel, get nearly optimal code everywhere: the kernel is written once against a lane-agnostic context, and both the ISA width and the ILP unroll factor are raised for you, at compile time when the target is pinned, by runtime detection otherwise. Stable Rust, no_std-compatible, no SIMD-crate dependency. Hydroplane also works with GPU backends via rust-gpu and SPIR-V, enabling the same kernel code to run on graphics hardware. GPU functionality is still evolving and the hyper optimized performance promises cannot be made the same there as on the CPU backends.

use hydroplane::{Gang, kernel};

#[kernel]
fn dot<'a>(ctx: Gang, a: &'a [f32], b: &'a [f32]) -> f32 {
    ctx.zip_fold(a, b, 0.0, 0.0, ctx.splat(0.0), |acc, x, y| acc + x * y)
        .reduce_sum()
}

let s = dot(&a, &b); // dispatched: AVX-512 / AVX2 / SSE4 / NEON / SVE / ... picked per host

#[kernel] monomorphizes the body per backend and generates a dispatching wrapper (plus an _on companion for composing kernels on an already-selected backend). Besides the leading ctx: Gang argument, a kernel is no different from any other Rust function: ordinary parameters, borrows, generics, and return type, called like a plain fndot(&a, &b) above. Kernels never name an ISA. Elements are generic too: one body serves f32, f64, f16, bf16 (half types widen-compute-narrow unless the target has native lanes). Integers are supported as well: u32 and i32 are first-class kernel scalars and also run as companion lanes alongside float kernels for argmin-style index tracking and float bit manipulation via to_bits/from_bits.

What "raising" means

  • ISA raising. At run time the dispatcher picks the widest implemented backend the CPU supports; the SIMD ops are #[target_feature] bodies, so a generic x86 binary still uses AVX-512 on a host that has it. With --cfg hp_static_dispatch + a pinned target-cpu the ladder folds into one unconditional call at compile time.
  • ILP raising. Reductions and maps run K independent register chains to saturate the core's FP pipes. K is measured once per process by a startup sweep and baked into the monomorphized kernel as a compile-time constant (Unroll<S, K>); on pinned builds build.rs resolves it at build time instead. This is routinely worth more than lane width on latency-bound loops — see the case study.

Backends

target backends
x86-64 SSE4, AVX, AVX2, AVX-512 (+ AVX-512-FP16, AVX-512-BF16), Intel AMX (tiles)
aarch64 NEON (+ A32), SVE1/SVE2, SME1/SME2 (tiles)
riscv64 RVV
wasm32 simd128
GPU rust-gpu/SPIR-V subgroup backend (the scalar backend is the lowering target)
anything ScalarBackend, the always-available 1-lane oracle

Three layers: raw Backend ops → the ergonomic Gang/Varying surface (operator-overloaded registers, masks, chunking/tail combinators, SoA storage) → MatrixBackend/dense (register-blocked GEMM tiles; runtime-shaped BLAS-lite).

Benchmarks

Paired implementations of each workload — hydroplane #[kernel] (hp), hand-rolled wide SIMD with manual ILP (wide), and a plain scalar loop (scalar) — live in hydroplane-example. Apple M5 Pro, rustc 1.95, RUSTFLAGS="-C target-cpu=native", criterion --quick, n = 4096 f32 elements; correctness is asserted against the scalar oracle before timing.

RUSTFLAGS="-C target-cpu=native" cargo bench -p hydroplane-example --bench workloads
workload shape scalar hand wide hydroplane hp vs scalar hp vs wide
l2norm √Σx² reduction 1976.4 ns 351.4 ns 88.8 ns 22.26× 3.96×
asum Σ|x| reduction 1968.9 ns 294.1 ns 120.2 ns 16.39× 2.45×
dot Σ x·y reduction 1977.1 ns 176.5 ns 168.1 ns 11.76× 1.05×
l1dist Σ|x−y| reduction 1977.9 ns 311.1 ns 169.1 ns 11.70× 1.84×
cosine three dots + combine 2317.8 ns 426.6 ns 492.6 ns 4.71× 0.87×
polysum register-heavy poly reduction 2275.5 ns 584.5 ns 636.1 ns 3.58× 0.92×
double_polysum two fused poly reductions 4554.1 ns 1261.3 ns 1371.3 ns 3.32× 0.92×
mandelbrot divergent escape-time loop 113.24 µs 112.79 µs 47.12 µs 2.40× 2.39×
horner degree-8 poly, elementwise 965.8 ns 544.4 ns 630.8 ns 1.53× 0.86×
normalize SoA vec3 v/‖v‖ 870.3 ns 829.7 ns 823.4 ns 1.06× 1.01×
mat3_inverse batched 3×3 inverse 7423.3 ns 30194.8 ns 7327.3 ns 1.01× 4.12×
saxpy elementwise FMA (memory-bound) 172.8 ns 207.7 ns 175.3 ns 0.99× 1.18×
cmul streaming complex multiply 456.5 ns 588.3 ns 481.8 ns 0.95× 1.22×
transform batched 3×3 · vec3 3191.9 ns 3523.9 ns 3468.9 ns 0.92× 1.02×

For the full progression, scalar → portable widewide restructured for ILP → hydroplane (with raw NEON intrinsics as the ceiling reference) on one problem, with the mechanics of why each step pays (read CASE_STUDY.md ):

mandelbrot, 4096 pts scalar wide f32x4 wide + hand ILP raw NEON + ILP hydroplane
time 108.61 µs 146.60 µs 70.20 µs 41.89 µs 45.95 µs
lines / unsafe / ISAs 17 / none / — 32 / none / any ~50 / none / any ~40 / all / one 20 / none / all

Cargo features

feature
std (default) runtime CPU detection, Vec-based storage
alloc Soa/Cols storage without std
libm float math on bare-metal no_std
glam Vec3Wide/Mat3Wide/GangGlamExt wide-geometry helpers

f16/bf16 support (via half) is always on and re-exported from the crate root.

Build-time configuration

All opt-in via RUSTFLAGS="--cfg <name>":

cfg effect
hp_static_dispatch no runtime ladder; backend comes from target_feature alone. With a pinned target-cpu, dispatch folds to a direct call and build.rs also resolves the ILP unroll factor at compile time
hp_no_ilp compile out the multi-accumulator path (K = 1)
hp_no_avx512 / hp_no_avx floor x86 at AVX2 / SSE4
hp_no_sve / hp_no_sme / hp_neon_over_sve aarch64 backend trimming
hp_no_amx drop Intel AMX tiles
hp_no_apple_accelerate dense uses its own SIMD GEMM instead of Accelerate

Workspace

License: Apache-2.0.