Help us shape the future of Go! Take the 2026 Go Developer Survey

floats

package module
v0.1.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jun 26, 2026 License: BSD-3-Clause Imports: 2 Imported by: 0

README

go-simd/floats

floats

CI coverage Go Reference

SIMD-accelerated reductions over []float32 and []float64 — the dot product, sum, min, max, Euclidean (L2) distance and cosine similarity that sit at the heart of vector search, embeddings and numerics. Pure Go: CGO_ENABLED=0, stable Go, no GOEXPERIMENT. The hot reduction loops are real FMA SIMD on all six 64-bit Go targets, assembly generated by go-asmgen — on a plain go build.

import "github.com/go-simd/floats"

sim := floats.CosineSimilarity(query, doc)        // []float64 relevance score
d   := floats.Float32Distance(embA, embB)         // []float32 L2 distance
dp  := floats.Dot(a, b)                            // a·b

API

For []float64 (top-level) and []float32 (the Float32* functions):

float64 float32 meaning
Dot(a, b) Float32Dot(a, b) a·b = Σ aᵢbᵢ
Sum(a) Float32Sum(a) Σ aᵢ
Min(a) / Max(a) Float32Min(a) / Float32Max(a) min / max element (NaN-propagating)
Distance(a, b) Float32Distance(a, b) ‖a−b‖₂ = √Σ(aᵢ−bᵢ)²
CosineSimilarity(a, b) Float32CosineSimilarity(a, b) a·b / (‖a‖₂‖b‖₂)

Dot, Distance and CosineSimilarity require len(a) == len(b) and panic otherwise; Min/Max panic on an empty slice; Sum of an empty slice is 0. The float32 dot/sum/squared-difference are accumulated in float32; cosine and distance take the final square root in float64 for accuracy.

Why this library

The Go SIMD-float landscape, compared:

dot/dist/cosine float32 amd64 arm64 ppc64le / s390x / riscv64 / loong64
gonum/floats dot, L2 only ✗ (float64 only) SSE ✗ ✗
viterin/vek ✓ ✓ AVX2+FMA ✗ ✗
kelindar/simd ✗ (sum/min/max only) ✓ AVX2 NEON ✗
go-simd/floats ✓ ✓ AVX2+FMA NEON ✓ (all four)

This is the only pure-Go float reduction library that ships the full vector-search API (dot / Euclidean / cosine) for both float32 and float64 on all six of Go's 64-bit SIMD architectures.

How the SIMD is built

Every kernel is an FMA-based vectorised reduction: a multi-lane accumulator folded with a fused multiply-add at the vector stride, then a horizontal reduce and a scalar tail.

arch ISA FMA op notes
amd64 AVX2 + FMA3 VFMADD231P{S,D} runtime-gated on cpu.X86.HasAVX2 && HasFMA; scalar fallback otherwise
arm64 NEON VFMLA 4 accumulators, ×4 unroll for ILP; baseline, no gate
riscv64 RVV VFMUL+VFREDOSUMVS length-agnostic VSETVLI stripmining; gated on cpu.RISCV64.HasV
s390x vector facility (big-endian) VFMADB float64 vectorised; float32 uses the scalar reference (no .SB ops in the assembler); z13 baseline, no gate
ppc64le VSX XVMADDADP/XVMADDASP the XV* ops are WORD-encoded (not in the released assembler); POWER8 baseline, no gate
loong64 LSX vfmadd.d/vfmadd.s LSX FP ops WORD-encoded; LA464 baseline, no gate

The .s files are committed; regenerate with go run *_gen.go (the generators are build-tagged //go:build ignore). All six are validated in CI — amd64/arm64 natively, the other four under QEMU (tonistiigi/binfmt) — at 100 % test coverage, with differential tests and FuzzDot/FuzzDistance against the scalar reference on every architecture.

Floating-point determinism

A SIMD reduction does not add its terms left-to-right: it keeps several independent accumulator lanes (for instruction-level parallelism) and folds them at the end, so its rounding differs from a naïve scalar loop. For dot / distance / cosine, bit-exactness against a naïve loop is neither meaningful nor required — correctness to a few ULP is.

So the reference the tests and fuzzers check against (dotLanes, sumSqDiffLanes, …) reduces in the same lane-blocked order the kernels use, and each kernel is required to match it to a tight, documented relative tolerance (1e-12 for float64, 1e-5 for float32) on every architecture. The differential tests and fuzzers enforce this, making the results deterministic and consistent across arches.

Performance

Apple M-series (arm64, native), go test -bench, ns/op (lower is better); simd is this library, naive is the textbook left-to-right Go loop (which the Go compiler already turns into a pipelined scalar FMADD loop):

op n=8 n=64 n=512 n=4096
Dot float64 simd 7.6 14.3 71.7 539
Dot float64 naive 2.4 18.5 152 1075
Float32Dot simd 9.5 11.7 39.4 276

The SIMD dot product is ~2× the compiler's scalar loop on medium/large vectors (≈57 GB/s float64, ≈59 GB/s float32 at n=512), and loses only on tiny inputs (n≤8) where the unroll/setup overhead dominates — the regime where you would not reach for SIMD anyway.

Honesty note on the other arches. amd64's AVX2+FMA kernel is validated for correctness on real x86 (an AVX2/FMA-capable VM) but the native-hardware throughput numbers are pending (the CI/dev x86 runner used here is TCG-emulated, so its timings are not representative). ppc64le, s390x, riscv64 and loong64 are QEMU-validated for correctness; native-hardware performance numbers are pending access to that hardware.

License

BSD-3-Clause. © The go-simd authors.

Documentation

Overview

Package floats provides SIMD-accelerated reductions over float32 and float64 slices — the dot product, sum, min, max, Euclidean (L2) distance and cosine similarity that sit at the heart of vector search, embeddings and numerics. It is pure Go (CGO_ENABLED=0, stable Go, no GOEXPERIMENT): the hot reduction loops are SIMD assembly generated by go-asmgen on all six of Go's 64-bit SIMD targets — amd64 (AVX2+FMA), arm64 (NEON FMLA), ppc64le (VSX), s390x (vector facility), riscv64 (RVV) and loong64 (LSX) — with a portable scalar fallback everywhere else, all on a plain `go build`.

Operations

For both []float32 (the Float32 functions) and []float64:

Dot(a, b)              a·b = Σ aᵢbᵢ
Sum(a)                 Σ aᵢ
Min(a) / Max(a)        the minimum / maximum element
Distance(a, b)         ‖a−b‖₂ = √Σ(aᵢ−bᵢ)²
CosineSimilarity(a, b) a·b / (‖a‖₂‖b‖₂)

Dot, Distance and CosineSimilarity require len(a) == len(b) and panic otherwise. Min and Max panic on an empty slice. Sum of an empty slice is 0.

Floating-point determinism

A SIMD reduction does not add its terms left-to-right: it keeps several independent accumulator lanes (for instruction-level parallelism) and folds them together at the end, so the rounding differs from a naïve scalar loop. To stay deterministic and architecture-independent, the scalar reference used internally and by the tests (sumLanes / dotLanes / …) reduces in the *same* lane-blocked order the kernels use (laneCount lanes), and the SIMD result is required to agree with it to a tight, documented ULP bound across every architecture (the differential tests and FuzzDot/FuzzDistance enforce this). For the similarity use case (cosine / Euclidean distance) bit-exact equality with a naïve scalar loop is neither required nor meaningful; correctness to a few ULP is.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func CosineSimilarity

func CosineSimilarity(a, b []float64) float64

CosineSimilarity returns a·b / (‖a‖₂‖b‖₂), a value in [-1, 1] measuring the angle between a and b — the standard relevance score for embedding vectors. It panics if len(a) != len(b). If either vector has zero magnitude the result is NaN (0/0), matching the mathematical definition.

func Distance

func Distance(a, b []float64) float64

Distance returns the Euclidean (L2) distance ‖a−b‖₂ = √Σ(aᵢ−bᵢ)². It panics if len(a) != len(b).

func Dot

func Dot(a, b []float64) float64

Dot returns the dot product a·b = Σ aᵢbᵢ. It panics if len(a) != len(b).

func Float32CosineSimilarity

func Float32CosineSimilarity(a, b []float32) float32

Float32CosineSimilarity returns a·b / (‖a‖₂‖b‖₂) over float32 slices — the relevance score for float32 embedding vectors. It panics if len(a) != len(b). If either vector has zero magnitude the result is NaN.

func Float32Distance

func Float32Distance(a, b []float32) float32

Float32Distance returns the Euclidean (L2) distance ‖a−b‖₂ over float32 slices. The squared-difference sum is accumulated in float32 and the square root taken in float64 for accuracy. It panics if len(a) != len(b).

func Float32Dot

func Float32Dot(a, b []float32) float32

Float32Dot returns the dot product a·b = Σ aᵢbᵢ over float32 slices, accumulated in float32. It panics if len(a) != len(b).

func Float32Max

func Float32Max(a []float32) float32

Float32Max returns the largest element of a. It panics if a is empty. If a contains a NaN the result is NaN.

func Float32Min

func Float32Min(a []float32) float32

Float32Min returns the smallest element of a. It panics if a is empty. If a contains a NaN the result is NaN.

func Float32Sum

func Float32Sum(a []float32) float32

Float32Sum returns Σ aᵢ over a float32 slice, accumulated in float32. The sum of an empty slice is 0.

func Max

func Max(a []float64) float64

Max returns the largest element of a. It panics if a is empty. If a contains a NaN the result is NaN.

func Min

func Min(a []float64) float64

Min returns the smallest element of a. It panics if a is empty. If a contains a NaN the result is NaN.

func Sum

func Sum(a []float64) float64

Sum returns Σ aᵢ. The sum of an empty slice is 0.

Types

This section is empty.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL