blas-microbenchmark1.11.0-dev

What BLAS is

The BLAS in five minutes: what it is, its three levels, its four precisions, and what each routine computes.

blas-microbenchmark measures the routines of the BLAS. This page says what those are, enough to read a result: what a routine computes, what its letters mean, and why some reach a hundred GFLOP/s where others stay at a few.

The BLAS (Basic Linear Algebra Subprograms) is a standard set of vector and matrix operations, fixed in the 1970s and 1980s: add a multiple of one vector to another, multiply a matrix by a vector, multiply two matrices, solve a triangular system. Every numerical code that does linear algebra calls them, directly or through LAPACK, NumPy, Julia or a solver, and does not care who wrote them.

So many libraries implement the same interface, each tuned its own way: OpenBLAS, BLIS and AMD's AOCL-BLAS built on it, Intel's MKL, NVIDIA's NVPL, Arm's ArmPL, and Netlib's reference implementation, written for clarity rather than speed. The same program runs against any of them; how fast it runs depends on which. That is what these benchmarks measure (The BLAS libraries).

The benchmarks call the BLAS through CBLAS, its C interface: cblas_dgemm(...), with the storage order of the matrices as an argument (row-major, as C stores them, or column-major, as Fortran does; --layout).

The routines come in three levels, by what they work on. The level decides what limits them: the memory, or the arithmetic.

LevelWorks onExampleFLOPs a byte (double)Limited by
1vectorsdaxpy: y ← αx + y2n for 24n: 1/12memory bandwidth
2a matrix and vectorsdgemv: y ← αAx + βy2n² for 8n²: 1/4memory bandwidth
3matricesdgemm: C ← αAB + βC2n³ for 24n²: n/12arithmetic, once n is large

A level 1 or 2 routine reads each number once and does one or two operations with it: it waits for the memory, and its GB/s says more than its GFLOP/s. A level 3 routine uses each number n times: a good library keeps the operands in the caches and the vector units busy, and approaches the processor's peak. That is why the report draws a roofline for levels 1 and 2, and the peak for level 3 (The peak).

bmb_daxpy -v 1024:16777216
bmb_dgemm -m 256:2048

Run both: daxpy's rate falls as the vectors leave each cache, dgemm's rises with the size.

A routine's first letter is its precision:

LetterPrecisionBytes an element
ssingle, 32-bit real4
ddouble, 64-bit real8
csingle complex8
zdouble complex16

One routine mixes them: sbgemm, a GEMM whose A and B are in bfloat16 (2 bytes, 8 bits of significand) and whose C, and the sums, are in single precision, as machine-learning codes call it.

  • Single moves half the bytes of double and fits twice as many numbers in a vector register: it can run about twice as fast, at about 7 significant digits rather than 16.
  • Complex numbers are pairs of reals. A complex multiply-add is four multiplies and four adds, eight FLOPs on two numbers, where a real one is two FLOPs on one: four times the arithmetic on twice the data. A complex level 3 routine is therefore more compute-bound than its real counterpart.
  • Two letters name a routine that takes one precision and returns another: scasum sums a single complex vector into a single real, dznrm2 takes the norm of a double complex vector. i?amax returns an index: idamax.

The FLOPs and bytes the benchmarks count follow these conventions, so that a GFLOP/s is comparable across precisions and libraries (How a number is measured).

A routine is a precision and an operation, a family: dgemm is gemm in double. α and β are scalars, x and y vectors, A, B and C matrices; Aᵀ is A transposed, Aᴴ its conjugate transpose.

FamilyComputes
axpyy ← αx + y
scalx ← αx
copy, swapy ← x; x ↔ y (no arithmetic: only GB/s)
dotxᵀy (real); dotu xᵀy and dotc xᴴy (complex)
nrm2‖x‖₂, the Euclidean norm, without overflow
asumΣ|xᵢ| (for a complex x, |re| + |im|)
iamaxthe index of the largest |xᵢ| (no arithmetic counted)
rotapplies a plane rotation to the pairs (xᵢ, yᵢ)
axpbyy ← αx + βy: an extension, which not every library has

FamilyComputes
gemvy ← αAx + βy, or with Aᵀ or Aᴴ (--trans)
gbmvthe same with A a band matrix: only K diagonals each side of the main one are stored (-M is K)
symv, hemvy ← αAx + βy with A symmetric (real) or Hermitian (complex), one triangle stored (--uplo); spmv with that triangle packed, column after column, without the other half
gerA ← αxyᵀ + A, a rank-1 update; geru and gerc (xyᴴ) in complex
syr, herA ← αxxᵀ + A (xxᴴ), on one triangle; syr2, her2: αxyᵀ + αyxᵀ + A
trmvx ← Ax with A triangular (--uplo, --trans, --diag: a unit diagonal, not stored)
trsvsolves Ax = b for x, in place, A triangular; tpsv with A packed, tbsv with A banded

FamilyComputes
gemmC ← αAB + βC, each of A and B transposed or not (--trans NT, …). The routine most of linear algebra spends its time in. -K sets the inner dimension apart; --batch-count does many at once
symm, hemmC ← αAB + βC with A symmetric or Hermitian, on the left or the right (--side)
syrk, herkC ← αAAᵀ + βC (AAᴴ), on one triangle of C: a rank-k update
syr2k, her2kC ← αABᵀ + αBAᵀ + βC, the rank-2k update
trmmB ← αAB with A triangular, on either side
trsmsolves AX = αB for X, A triangular: the core of LU and Cholesky solves
gemmtC ← αAB + βC on one triangle of C (--uplo), for a product known to be symmetric: an extension
sbgemmgemm with A and B in bfloat16, C in single precision: an extension

The extensions are calls libraries add to the BLAS. Their benchmarks are built whatever the library; where it lacks the call, they measure nothing and say which call is missing.

Not every family exists in every precision: dot, ger, symv, syr, syr2 and spmv are real, their complex counterparts are dotu and dotc, geru and gerc, hemv, her, her2; and here rot, gbmv, tbsv and tpsv are measured in real precision only. The manual lists the 113 benchmarks by level, with what -m, -M and -K are for each, and Options the arguments each takes.

BLAS has no option for the kind of matrix: the structure is the routine's. A general matrix goes to gemm or gemv; the others each have their own routines, which read only the part that matters:

MatrixRoutinesWhat is used
Triangulartrmv, trsv, trmm, trsmone triangle (--uplo), its diagonal taken as ones with --diag U
Symmetric, Hermitiansymv, symm, syr, syr2, syrk, syr2k; hemv, hemm, her, her2, herk, her2kone triangle of the symmetric or Hermitian matrix (--uplo), the other being its mirror, conjugated for a Hermitian one; for syr, syr2, her and her2 that matrix is A, which they update; for syrk, syr2k, herk and her2k it is C, the triangle written
Bandgbmv, tbsvthe diagonals near the main one, in band storage: K each side for gbmv, K on one side for tbsv, which takes --uplo and --diag too (-M is K, the band's half-width)
Packedspmv, tpsvone triangle (--uplo; --diag for tpsv), stored without the other's place

rot applies a plane (Givens) rotation to two vectors: the rotation is given by its cosine and sine, not as a matrix. Sparse matrices, mostly zeros stored as lists of their non-zeros, are not in the dense BLAS these libraries implement: they have routines and libraries of their own (the Sparse BLAS, MKL's sparse routines, cuSPARSE, PETSc), and no benchmark here takes one.

For most dense calls the values barely change the speed: the same operations run in the same order whatever the operands hold. A library may still take shortcuts on some values, as the Reference BLAS does on a zero element, and subnormals, NaN and infinities slow some processors down. The benchmarks fill every operand themselves, the same for every library, with none of those (The operands).