Matrix multiplication modulo small integers
- Cost
- For , one table lookup and one byte addition per product, with a reduction every steps; for , about word operations with , plus Strassen–Winograd above dimension 8192.
Moduli below 256 occur in coding theory, combinatorics and modular algorithms over small finite fields. Storing such residues in 64-bit words wastes most of the memory bandwidth, and multiplying them with 64-bit or floating-point instructions wastes most of the arithmetic width. nmod_mat_mul_u8 converts the entries to bytes (or bits) and uses methods that exploit their size. _nmod_mat_mul_u8 is the same multiplication on byte matrices, used directly by the gr contexts for nmod8. Throughout, is the modulus and , have dimensions and .
Byte kernel for #
If , every residue is below 16, so it can index a 16-byte table held in a vector register. Let be the table with for . For an entry of and a vector of 16 or 32 bytes of a row of , a byte shuffle instruction (vpshufb on x86-64 with AVX2, tbl on AArch64) computes all products at once. The multiplication is replaced by a lookup, and the products are already reduced.
The micro-kernel keeps a tile of entries of in byte accumulators. Each step adds the looked-up products of one entry of per row to one row of 64 bytes of .
Proposition 1. If each accumulator starts below and receives at most products before it is reduced, then it never exceeds 255.
Proof. Each product from the table is at most , so after steps the accumulator is at most .
The cadence is 254 for , 41 for , 20 for and 16 for . An accumulator byte is reduced with two more lookups, , followed by computed with byte wraparound, which is . A product of a byte would contribute up to to the accumulator, so the lookup also makes the cadence times longer than with multiplications.
Without a byte shuffle instruction, the portable code multiplies in 16-bit lanes and reduces every steps, which is still 291 steps at .
Above the residues no longer fit a single 16-byte table, and the lookups would have to be combined.
Larger moduli#
For , nmod_mat_mul_u8 uses single-precision floating point. By Proposition 1 of Multiplying matrices over Z/nZ, a block of columns of can be multiplied exactly by flint_sgemm with residues in ; . The blocks of the inner dimension are converted from bytes to floats, multiplied, and the exact block results are added in 32-bit integers, which are reduced modulo only before they could exceed and at the end. When several threads are available, and the whole product fits in one block, a single threaded sgemm on the whole matrices is used instead.
Strassen–Winograd on bytes#
Above the leaves, nmod_mat_mul_u8 applies Strassen–Winograd to the byte matrices, with byte additions modulo and temporaries in bytes, an eighth of the size of ulong temporaries. The recursion splits each dimension into and , extends short operands by zeros in the additions, and stops when a dimension is below 256 for and below 2048 for larger , whose sgemm leaves need large blocks to amortise the conversions. With several threads, is divided into a grid of tiles of at least entries, each computed by an independent recursion.
Bit-packed kernels for and #
For an entry is one bit, and 64 entries of a row are packed into a word, so that adding rows is an exclusive or of words. FLINT multiplies by the method of four Russians [ADKF1970], as in the M4RI library [ABH2010]:
Input: () and () over GF(2), rows packed into words; a parameter .
Output: .
For each group of consecutive rows of :
- build the table of all sums , , visiting the subsets in Gray code order, so that each entry is the previous one plus a single row;
- for each row of , read the bits as an index and add to row of .
Each table costs row additions and saves of the row additions per row of , so with the algorithm needs about word operations. FLINT builds eight tables at a time and absorbs them in one pass over , so that each row of is read and written once per 64 columns of .
For an entry is represented by two bits in two separate words (bit planes), with , and , so a pair of words holds 64 entries [BB2009]. Addition modulo 3 of two such pairs takes six bitwise operations, and negation exchanges the two planes, so subtraction costs the same as addition. The method of four Russians works as for GF(2), with tables of the combinations , , for (243 entries), two tables at a time, and an index formed from the base-3 digits of the entries of .
Above these leaves, both cases use Strassen–Winograd on the packed matrices, whose additions cost one (GF(2)) or six (GF(3)) operations per 64 entries. Because the leaves are so cheap, the recursion only starts when all dimensions are at least 8192 for and 16384 for , with at most 6 levels. The entries are packed directly from the ulong matrices, without an intermediate byte image. With several threads, the packed operands are shared, and is divided into a grid of tiles, splitting rows before columns since each extra strip of rows only costs rebuilding the tables. When has fewer than 64 () or 192 () rows, the tables do not pay off and the byte kernel is used instead.
Referenced from#
References#
- [ADKF1970] V. L. Arlazarov, E. A. Dinic, M. A. Kronrod and I. A. Faradžev, "On economical construction of the transitive closure of an oriented graph", Soviet Mathematics Doklady 11 (1970), 1209-1210
- [ABH2010] M. Albrecht, G. Bard and W. Hart, "Algorithm 898: Efficient multiplication of dense matrices over GF(2)", ACM Transactions on Mathematical Software 37:1 (2010), article 9, https://doi.org/10.1145/1644001.1644010
- [BB2009] T. J. Boothby and R. W. Bradshaw, "Bitslicing and the method of four Russians over larger finite fields", arXiv:0901.1413 (2009), https://arxiv.org/abs/0901.1413
The FLINT team, “Matrix multiplication modulo small integers,” FLINT: Fast Library for Number Theory, documentation, version 3.7.0-dev, 2026. https://flintlib.org/doc/algorithms/linear-algebra/small-modulus-matrix-multiplication.html (generated 2026-11-02).
@misc{flint-doc-algorithms-linear-algebra-small-modulus-matrix-multiplication,
author = {{The FLINT team}},
title = {{Matrix multiplication modulo small integers}},
howpublished = {FLINT: Fast Library for Number Theory, documentation, version 3.7.0-dev},
year = {2026},
url = {https://flintlib.org/doc/algorithms/linear-algebra/small-modulus-matrix-multiplication.html},
note = {Generated 2026-11-02}
}