Documentation
¶
Overview ¶
Package simdblas is a BLAS implementation for gonum, backed by github.com/sebishogun/simd.
Gonum's own BLAS is pure Go with SSE2-only assembly on amd64 and none at all on arm64. This replaces the routines where a vector unit helps, reaching AVX-512, NEON, SVE2, RVV, VSX, VX and LASX — with no cgo, because the kernels are compiled ahead of time and committed as assembly.
Install it once, at startup:
import (
"gonum.org/v1/gonum/blas/blas64"
"github.com/sebishogun/simdblas"
)
func init() {
blas64.Use(simdblas.Implementation{})
blas32.Use(simdblas.Implementation{})
}
Everything above BLAS — gonum's mat, stat and optimize — then runs on it without further change.
What is accelerated ¶
The routines where whole-slice vector work pays: the Level 1 set, Dgemv and Dgemm, in both precisions. Everything else is inherited from gonum.Implementation and behaves exactly as it did.
Acceleration also requires unit strides and, for the matrix routines, no transpose, alpha of 1, beta of 0 and natural leading dimensions. Anything else falls through to gonum, so the answer is always a correct BLAS answer and never a wrong fast one. See Implementation for why that is the whole design.
Results differ from gonum's, and that is the point ¶
BLAS does not specify bit-exact results, and no two implementations agree. Reductions here use a fixed sixteen-accumulator tree, gonum's use a four-way unrolled loop, so a dot product of 4096 elements typically differs in the last unit in the last place.
The difference worth knowing runs the other way. Gonum's answer depends on what it was compiled for: its amd64 assembly and its pure-Go fallback do not agree with each other, so the same program gives different results on x86 and on arm64. This implementation gives the same bits on every architecture and every instruction set, because the accumulation order is fixed rather than following the vector width. If you need a result that reproduces across machines, that is the reason to use this.
Index ¶
- type Implementation
- func (impl Implementation) Dasum(n int, x []float64, incX int) float64
- func (impl Implementation) Daxpy(n int, alpha float64, x []float64, incX int, y []float64, incY int)
- func (impl Implementation) Ddot(n int, x []float64, incX int, y []float64, incY int) float64
- func (impl Implementation) Dgemm(tA, tB blas.Transpose, m, n, k int, alpha float64, a []float64, lda int, ...)
- func (impl Implementation) Dgemv(tA blas.Transpose, m, n int, alpha float64, a []float64, lda int, x []float64, ...)
- func (impl Implementation) Dnrm2(n int, x []float64, incX int) float64
- func (impl Implementation) Dscal(n int, alpha float64, x []float64, incX int)
- func (impl Implementation) Sasum(n int, x []float32, incX int) float32
- func (impl Implementation) Saxpy(n int, alpha float32, x []float32, incX int, y []float32, incY int)
- func (impl Implementation) Sdot(n int, x []float32, incX int, y []float32, incY int) float32
- func (impl Implementation) Sgemm(tA, tB blas.Transpose, m, n, k int, alpha float32, a []float32, lda int, ...)
- func (impl Implementation) Sgemv(tA blas.Transpose, m, n int, alpha float32, a []float32, lda int, x []float32, ...)
- func (impl Implementation) Snrm2(n int, x []float32, incX int) float32
- func (impl Implementation) Sscal(n int, alpha float32, x []float32, incX int)
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type Implementation ¶
type Implementation struct{ gonum.Implementation }
Implementation is a drop-in for gonum's BLAS.
It embeds gonum.Implementation rather than reimplementing the interface, so every routine exists and is correct from the first line, and each accelerated one is an override of a working method rather than a new one that has to be got right. The 60-odd routines nobody has measured a win for are gonum's, unchanged.
Each override checks whether its fast path applies and delegates to the embedded method when it does not — including for invalid arguments, so the panics a caller relies on come from gonum and read exactly as they always have.
func (Implementation) Dasum ¶
func (impl Implementation) Dasum(n int, x []float64, incX int) float64
func (Implementation) Daxpy ¶
func (impl Implementation) Daxpy(n int, alpha float64, x []float64, incX int, y []float64, incY int)
Daxpy is y += alpha*x, the operation BLAS calls axpy and this library calls AddScaled. One pass over both slices rather than a scale followed by an add.
func (Implementation) Dnrm2 ¶
func (impl Implementation) Dnrm2(n int, x []float64, incX int) float64
Dnrm2 is the one routine here that cannot simply be handed to the vector unit, and the reason is in the BLAS specification rather than in the kernel.
nrm2 is *defined* to avoid spurious overflow and underflow: gonum computes it by scaling to the largest element first, so a vector of 1e200s returns 8.48e200 rather than +Inf. The sum of squares overflows there, and flushes to zero for a vector of 1e-200s. Swapping the backend must not turn a finite result into an infinity.
Checking the magnitudes first works and costs a whole extra pass — measured at 483us against 54us for a million elements, which spends most of the win to buy an answer that is almost always already correct. So instead the fast path runs and its own failure is detected: the sum of squares overflowing produces +Inf, and underflowing produces a zero that a non-empty vector can only otherwise reach by being all zeros. Both are exactly the cases gonum's scaled algorithm exists for, and both then go to it.
The common path pays one comparison.
func (Implementation) Dscal ¶
func (impl Implementation) Dscal(n int, alpha float64, x []float64, incX int)
func (Implementation) Sasum ¶
func (impl Implementation) Sasum(n int, x []float32, incX int) float32