|
TDLS 0.4.0
Tiny Device-callable Linear Solvers
|
Compile-time knobs of the CooperativeLUpp solvers, carrying the defaults. More...
#include <config.hpp>
Public Attributes | |
| int | rows_per_thread = 1 |
| RowInterchange | row_interchange = RowInterchange::Logical |
| StructuralReal< T > | relative_pivot_threshold = T(0.1) |
| StructuralReal< T > | singular_floor = std::numeric_limits<T>::min() |
| bool | unroll_loops = true |
| MatrixLayout | layout = MatrixLayout::RowMajor |
Compile-time knobs of the CooperativeLUpp solvers, carrying the defaults.
| T | scalar type (float, double or long double) |
| MatrixLayout tdls::CooperativeLUppConfig< T >::layout = MatrixLayout::RowMajor |
Memory layout of the matrix, in both residency modes. The solver adapts its design to it, at compile time: it factors S, the stored matrix read column-major, and each thread holds rows of S, so that the threads of a group read consecutive addresses in both layouts, the access pattern the design of MAGMA rests on. Column-major, the convention of MAGMA: S = A, the threads hold rows of A, and the solver is MAGMA's kernel. Row-major, the TFEL convention and the default: S = A^T, the threads hold columns of A, the pivoting interchanges columns of A (partial pivoting of A^T, as stable), and the solve is the mirror of MAGMA's, through the transposed factors. A matrix stored row-major and its transpose stored column-major are thus the same array, factored the same way bit for bit, while the first solves A x = b and the second A^T x = b. The internal slice of a thread holds its vectors, in the chosen layout. The vector operands are one-dimensional and unaffected.
| StructuralReal<T> tdls::CooperativeLUppConfig< T >::relative_pivot_threshold = T(0.1) |
Relative threshold of the pivot choice, in (0, 1]. At each column, the row in place keeps the pivot when its magnitude reaches both this fraction of the largest magnitude of the column and the singularity floor. Otherwise the largest magnitude wins, the first one on ties. The multipliers stay bounded by the inverse of the threshold.
The default 0.1 is the classical value of threshold pivoting. It saves most row interchanges on matrices that pivot often, and the multipliers stay at most 10 in magnitude. A value of 1 is the partial pivoting of LAPACK, with the pivots of LAPACK and MAGMA, and the solvers then compile the test out. On matrices that never interchange, such as Newton Jacobians close to the identity, 1 thus saves the few percent the test costs. Values below 0.1 save few more interchanges, at a much larger price in stability. The threshold is relative: unlike the absolute oot_pivot_threshold of TiledLUpp, a value such as 1e-10 would practically disable the pivoting. The solvers enforce the range at compile time.
| RowInterchange tdls::CooperativeLUppConfig< T >::row_interchange = RowInterchange::Logical |
Row interchanges of the partial pivoting. Logical, the default, is the scheme of MAGMA: rows never move, and each thread tracks the position of its rows in the pivoted order. Physical is the scheme of LAPACK: rows are exchanged between the threads, so that the row at position k always lives in slot k / threads_per_system of thread k % threads_per_system. The owner of every row of U is then known in advance: the pivot search publishes one candidate per thread instead of one magnitude per row, and each step of the factorization takes two barriers instead of three. In exchange, a column whose pivot is not in place moves two whole rows through the workspace. Which scheme is faster depends on the device and on how often the matrices pivot. Both choose the same pivots and run the same operations. On a matrix they do not declare singular, their solutions are bitwise identical when no multiply-add is fused. GPU compilers fuse them by default, possibly differently in the two schemes: the solutions may then differ in the last bits. The factored matrix, the pivot array and the workspace size differ: see the solvers. Auto is rejected at compile time: it is the default of the blocks of ArrowheadLUpp only (ArrowheadLUppConfig::block_row_interchange).
| int tdls::CooperativeLUppConfig< T >::rows_per_thread = 1 |
Number of vectors of the matrix held by each thread of the group: rows of S, the stored matrix read column-major, that is columns of A under the row-major layout and rows of A under the column-major one (see layout). A system of dimension N is solved by ceil(N / rows_per_thread) threads, and each thread keeps its vectors in registers. This is the main performance axis of the solver: a larger value packs more systems per warp and wastes fewer lanes, at the price of more registers per thread. The default 1 is the original mapping of MAGMA, one row per thread. When N is not a multiple of rows_per_thread, the last slots hold phantom vectors, on one thread or more: the compile-time solver drops at compile time a slot where every thread holds a phantom vector and tests at run time a slot where only some do, and the runtime solver tests every vector at run time. A value reaching N gives one thread per system, the setting of sequential CPU code, and a larger value acts as N. Requires rows_per_thread >= 1.
| StructuralReal<T> tdls::CooperativeLUppConfig< T >::singular_floor = std::numeric_limits<T>::min() |
Singularity floor: the factorization is declared singular when the best pivot of a column falls below it. numeric_limits<T>::min() rejects only a zero or subnormal pivot, a genuine structural singularity, with the same criterion as the TiledLUpp solvers. It must be finite and positive, a zero floor letting a zero pivot through; the solver enforces both contracts at compile time.
| bool tdls::CooperativeLUppConfig< T >::unroll_loops = true |
Unroll policy, applied through a two-branch if constexpr (the pragma dialect itself lives in core/macros.hpp). The knob does not govern every loop on purpose: only the loops where offering the choice can noticeably change the performance, those indexing the register rows (the column sweeps of the factorization and of the substitutions, the loops along a row, the loads and stores). true: they carry a forced-unroll pragma, the guard that keeps the rows in registers on GPU backends, where a rolled loop indexes them dynamically and demotes them to slow local memory. false: no unroll pragma anywhere. On CPU, where a whole system held by one thread does not fit the registers anyway, false gives smaller code, faster builds and measured faster solves. The pivot search reads the workspace, not the registers, and carries no pragma in either branch; the compiler unrolls it on its own once the column sweep is unrolled. The runtime solver holds no register rows, carries no pragma and ignores this knob. Every TDLS family names this knob unroll_loops.