Linear algebra for large matrices#
aether::linalg solves small systems inside a lane: one thread, the matrix in its registers, N <= 32.
aether::linalg::team is for the rest. A team of threads works on ONE matrix, which lives in scratch
you provide, so the size is bounded by memory (N up to 4096) and not by what a thread can hold.
#include <aether/aether.h>
using namespace aether::linalg::team;
constexpr std::size_t N = 512;
// a_in, b, x: N*N, N and N elements. f_a, piv, info: scratch you own.
Team tm; // host: rank 0, size 1
TeamLu<double, N> f{f_a, piv, info};
lu<N>(tm, f, a_in); // f_a may be a_in: factor in place
luSolve<N>(tm, f, b, x); // *info != 0 gives zeros in x
On the device the same code runs with Team tm{threadIdx.x, blockDim.x};, one block per matrix. Every
routine is collective: all threads of the team call it with the same arguments, and the results are
visible to the whole team on return.
What you can call#
Routine |
What it does |
|---|---|
|
LU with partial pivoting; solve |
|
Cholesky of the symmetric part; a factor you built; the solves |
|
eigenvalues ascending and eigenvectors, in the one-thread rotation order |
|
Householder QR over row tiles; least squares |
|
the ordered |
info means what it means in the per-lane family (LAPACK numbering for LU and Cholesky; a bit mask for
eigh; for qr the first column with a zero R diagonal). A failed factorisation makes every solve
built on it write zeros.
Results agree to a stated bound#
The team size is a performance knob, not a numerical one. Pivot searches break ties by lowest index,
substitution is column by column, and blockSum adds each entry’s rows serially in order, so no sum ever
crosses threads and the team size changes no arithmetic. The contract is a ULP bound, not bit identity: two
results of the same operation (another team size, the team of 1 on the host, the one-thread form where it
exists) agree to 50 N machine epsilon, relative to the largest entry. For blockSum the bound is
2 * count * eps times the largest sum of absolute products, where count is the number of rows added
into each entry. The reason is that a compiler may fuse a multiply-add at one call site and not at another.
The same compiled code run again gives the same bits.
LU here is right-looking where the one-thread form is Crout, so those two forms differ by rounding only.
Scratch#
All arrays are row-major, T is float or double, and k is the number of right-hand sides.
Routine |
Elements you provide |
Bytes for |
|---|---|---|
|
|
2.1 MB |
|
|
2.1 MB |
|
|
4.2 MB |
|
|
|
|
|
2.1 MB for |
qrWorkSize(m, cols) = cols * ceil(m / 64) + 1 (the tile is 64 rows, fixed, so a sum never depends on the
team size). The byte column is plain arithmetic, elements * sizeof(T).
Two worked sizes, again arithmetic: 130 matrices of N = 1800 need 3.4 GB for each plane of N*N
doubles, and one matrix of N = 800 needs 5 MB. Allocate for all the lanes you launch at once.
See also
Numerics contract for the order of arithmetic in the per-lane family, and Views and Items for the per-lane operands.