FLINT3.7.0-dev
Algorithm

Strassen–Winograd multiplication

Cost
O(dlog⁡27)≈O(d2.807)O(d^{\log_2 7}) \approx O(d^{2.807}) operations for d×dd \times d matrices; one level costs 7 products of half size and 15 additions of quarter-size blocks.

Strassen showed that two 2×22 \times 2 matrices over any ring can be multiplied with 7 multiplications instead of 8 [Str1969]. Since the entries can themselves be matrices, applying the scheme recursively multiplies d×dd \times d matrices in O(dlog⁡27)O(d^{\log_2 7}) ring operations. Winograd's variant [Win1971] needs 15 block additions instead of 18. The version described below is that of nmod_mat_mul_strassen, which is also used, in byte or bit-packed form, by nmod_mat_mul_u8.

Generic version#

gr_mat_mul_strassen performs one level of the Winograd form of Strassen's algorithm over any ring, in Bodrato's order of operations, computing the seven block products with gr_mat_mul and handling odd dimensions by peeling off the last row or column. Recursion therefore happens only through the ring's matrix multiplication method: a ring installs a method that calls gr_mat_mul_strassen above a cutoff and a base-case product below it, as the context nmod32 does on top of the 32-bit SIMD kernel of nmod_mat. The scheme needs no commutativity, so it also applies to matrices over noncommutative rings and to block matrices. The same seven-product formulas multiply the 2×22 \times 2 matrices of polynomials in the half-gcd algorithm of gr_poly (Half-GCD) once their entries have length 20 or more.

The formulas#

Split AA (m×km \times k), BB (k×nk \times n) and C=ABC = AB into 2×22 \times 2 blocks AijA_{ij}, BijB_{ij}, CijC_{ij} of compatible sizes. FLINT uses the sequence of Bodrato [Bodrato2010], which is equivalent to Winograd's and was designed so that the same operands serve for squaring. With

S=A12+A22−A21,T=B12+B22−B21,P1=(A12+A22)(B12+B22),P2=(A22−A21)(B22−B21),P3=S T,P4=(S−A11) B12,P5=A12B21,P6=A21(T−B11),P7=A11B11,\begin{aligned} S &= A_{12} + A_{22} - A_{21}, & T &= B_{12} + B_{22} - B_{21}, \\ P_1 &= (A_{12} + A_{22})(B_{12} + B_{22}), & P_2 &= (A_{22} - A_{21})(B_{22} - B_{21}), \\ P_3 &= S \, T, & P_4 &= (S - A_{11}) \, B_{12}, \\ P_5 &= A_{12} B_{21}, & P_6 &= A_{21} (T - B_{11}), \\ P_7 &= A_{11} B_{11}, \end{aligned}

the blocks of the product are

C11=P5+P7,C12=P3+P5−P4−P2,C21=P1−P3−P5−P6,C22=P1+P2−P3−P5.\begin{aligned} C_{11} &= P_5 + P_7, & C_{12} &= P_3 + P_5 - P_4 - P_2, \\ C_{21} &= P_1 - P_3 - P_5 - P_6, & C_{22} &= P_1 + P_2 - P_3 - P_5. \end{aligned}

Theorem 1. These formulas give C=ABC = AB over any ring, commutative or not.

Proof. C11=A11B11+A12B21C_{11} = A_{11} B_{11} + A_{12} B_{21} is immediate. Let U=A12+A22U = A_{12} + A_{22} and V=B12+B22V = B_{12} + B_{22}, so S=U−A21S = U - A_{21} and T=V−B21T = V - B_{21}. Then

P1−P3=UV−(U−A21)(V−B21)=UB21+A21V−A21B21.P_1 - P_3 = UV - (U - A_{21})(V - B_{21}) = U B_{21} + A_{21} V - A_{21} B_{21}.

Subtracting P6=A21V−A21B21−A21B11P_6 = A_{21} V - A_{21} B_{21} - A_{21} B_{11} and P5=A12B21P_5 = A_{12} B_{21} leaves A22B21+A21B11=C21A_{22} B_{21} + A_{21} B_{11} = C_{21}. Adding P2−P5=(A22−A21)(B22−B21)−A12B21P_2 - P_5 = (A_{22} - A_{21})(B_{22} - B_{21}) - A_{12} B_{21} to P1−P3P_1 - P_3 instead gives, after cancellation, A21B12+A22B22=C22A_{21} B_{12} + A_{22} B_{22} = C_{22}. Finally P3−P4=ST−SB12+A11B12=S(B22−B21)+A11B12P_3 - P_4 = S T - S B_{12} + A_{11} B_{12} = S (B_{22} - B_{21}) + A_{11} B_{12} and S(B22−B21)=A12(B22−B21)+P2S (B_{22} - B_{21}) = A_{12} (B_{22} - B_{21}) + P_2, so P3−P4−P2+P5=A11B12+A12B22=C12P_3 - P_4 - P_2 + P_5 = A_{11} B_{12} + A_{12} B_{22} = C_{12}. No step exchanges the two factors of a product, so commutativity is not used.

The operands of the products need 8 additions (UU, VV, A22−A21A_{22} - A_{21}, B22−B21B_{22} - B_{21}, SS, TT, S−A11S - A_{11}, T−B11T - B_{11}) and the results 7 more, 15 in all.

Schedule and memory#

The implementation follows the memory-efficient schedule of Dumas, Pernet and Zhou [DPZ2009]: the products are written into the blocks of CC as they are computed, and only two temporaries are needed, X1X_1 of the size of A11A_{11} (or C11C_{11}, whichever is wider) and X2X_2 of the size of B11B_{11}. With X1,X2X_1, X_2 and the four blocks of CC as storage:

Algorithm 1: Strassen–Winograd, one level
  1. X1←A22+A12X_1 \gets A_{22} + A_{12}, X2←B22+B12X_2 \gets B_{22} + B_{12}, C21←X1X2C_{21} \gets X_1 X_2 (P1P_1).
  2. X1←A22−A21X_1 \gets A_{22} - A_{21}, X2←B22−B21X_2 \gets B_{22} - B_{21}, C22←X1X2C_{22} \gets X_1 X_2 (P2P_2).
  3. X1←X1+A12X_1 \gets X_1 + A_{12}, X2←X2+B12X_2 \gets X_2 + B_{12}, C11←X1X2C_{11} \gets X_1 X_2 (P3P_3).
  4. X1←X1−A11X_1 \gets X_1 - A_{11}, C12←X1B12C_{12} \gets X_1 B_{12} (P4P_4).
  5. X1←A12B21X_1 \gets A_{12} B_{21} (P5P_5), C11←C11+X1C_{11} \gets C_{11} + X_1.
  6. C12←C12+C22C_{12} \gets C_{12} + C_{22}, C12←C11−C12C_{12} \gets C_{11} - C_{12} (final).
  7. C11←C21−C11C_{11} \gets C_{21} - C_{11} (P1−P3−P5P_1 - P_3 - P_5).
  8. X2←X2−B11X_2 \gets X_2 - B_{11}, C21←A21X2C_{21} \gets A_{21} X_2 (P6P_6).
  9. C21←C11−C21C_{21} \gets C_{11} - C_{21}, C22←C22+C11C_{22} \gets C_{22} + C_{11} (final).
  10. C11←A11B11C_{11} \gets A_{11} B_{11} (P7P_7), C11←X1+C11C_{11} \gets X_1 + C_{11} (final).

The 7 products are computed by nmod_mat_mul, so they use the fast kernels or recurse into Strassen–Winograd again, and CC must not be aliased with AA or BB. If any dimension is at most 4, nmod_mat_mul_strassen calls nmod_mat_mul directly.

Odd dimensions#

FLINT splits a dimension dd into h=⌈d/2⌉h = \lceil d/2 \rceil for the top or left blocks and d−h∈{h−1,h}d - h \in \{h - 1, h\} for the bottom or right blocks. Conceptually, the short blocks are padded with one zero row or column, so that all blocks have the same size and Theorem 1 applies. Padding AA with a zero row and a zero column (and BB accordingly) pads ABAB with zeros, so the padded product contains CC as its top left block. Nothing is actually padded:

  • the temporaries X1X_1, X2X_2 have the full padded size, and the sums of blocks that form them copy the extra row or column of the larger operand and write zeros beyond;
  • a product with a short block of AA or BB as an operand uses the real dimensions of that block;
  • the blocks C12C_{12}, C21C_{21} and C22C_{22} of the real CC lack the padding, while the schedule stores intermediate products in them.

Proposition 2. With the schedule of Algorithm 1, the entries missing from C12C_{12}, C21C_{21}, C22C_{22} are never needed for an entry of CC.

Proof. If nn is odd, C12C_{12} and C22C_{22} lack their last column. The final C11=P5+P7C_{11} = P_5 + P_7 and C21=P1−P3−P5−P6C_{21} = P_1 - P_3 - P_5 - P_6 do not read C12C_{12} or C22C_{22}, and the final C12C_{12} and C22C_{22} only need their own columns, so the missing column is not used. If mm is odd, C21C_{21} and C22C_{22} lack their last row. The last rows of the values held there (P1P_1, P2P_2, P6P_6 and the partial sums of step 9) only feed the last rows of the final C21C_{21} and C22C_{22}, which do not exist, except for P2P_2, which is added to C12C_{12} in step 6. But P2=(A22−A21)(B22−B21)P_2 = (A_{22} - A_{21})(B_{22} - B_{21}) has a zero last row, because the padded A21A_{21} and A22A_{22} both have a zero last row. If kk is odd, only the temporaries and the inner dimension of P5=A12B21P_5 = A_{12} B_{21} are affected, and both are handled exactly as stated above.

So odd dimensions cost nothing beyond the padded additions, which touch the same entries as ordinary ones. Earlier versions of FLINT peeled off the last row or column and corrected the result with matrix-vector products, a technique known as dynamic peeling [HLJJTG1996].

Complexity#

Let T(d)T(d) be the cost for d×dd \times d matrices when the recursion goes down to dimension d0d_0. Then T(d)=7T(⌈d/2⌉)+15⌈d/2⌉2T(d) = 7 T(\lceil d/2 \rceil) + 15 \lceil d/2 \rceil^2, so T(d)=O(dlog⁡27)T(d) = O(d^{\log_2 7}). One level replaces 8 products by 7, saving one eighth of the multiplication work, at the cost of 15 additions of (d/2)2(d/2)^2 entries, each of which reads and writes memory. The level pays off when a product of dimension d/2d/2 costs more than about 15 additions of that size. With the classical algorithm, which forms one dot product per entry, this happens around dimension 200 to 400 in FLINT. With the SIMD kernels, which run at a much higher rate, the crossover FLINT_NMOD_MAT_MUL_SIMD_STRASSEN_CUTOFF is between 256 and 800, depending on the processor. The byte kernels of nmod_mat_mul_u8 recurse from dimension 256 (N≤16N \le 16) or 2048 (17≤N≤25517 \le N \le 255), and the bit-packed kernels for N=2N = 2 and N=3N = 3 from 8192 and 16384, since their leaves are much cheaper relative to an addition.

Numerical stability, the main concern with Strassen's algorithm in floating point, plays no role over Z/NZ\mathbb{Z}/N\mathbb{Z}, where all operations are exact.

Referenced from#

References#

  • [Str1969] V. Strassen, "Gaussian elimination is not optimal", Numerische Mathematik 13 (1969), 354-356, https://doi.org/10.1007/BF02165411
  • [Win1971] S. Winograd, "On multiplication of 2 × 2 matrices", Linear Algebra and its Applications 4 (1971), 381-388, https://doi.org/10.1016/0024-3795(71)90009-7
  • [Bodrato2010] Bodrato, Marco : A Strassen-like Matrix Multiplication Suited for Squaring and Higher Power Computation. Proceedings of the ISSAC 2010 München, Germany, 25-28 July, 2010
  • [DPZ2009] J.-G. Dumas, C. Pernet and W. Zhou, "Memory efficient scheduling of Strassen-Winograd's matrix multiplication algorithm", Proceedings of the 2009 International Symposium on Symbolic and Algebraic Computation (ISSAC) (2009), 135-143, https://doi.org/10.1145/1576702.1576713
  • [HLJJTG1996] S. Huss-Lederman, E. M. Jacobson, J. R. Johnson, A. Tsao and T. Turnbull, "Implementation of Strassen's algorithm for matrix multiplication", Proceedings of the 1996 ACM/IEEE Conference on Supercomputing (1996)
Cite this page

The FLINT team, “Strassen–Winograd multiplication,” FLINT: Fast Library for Number Theory, documentation, version 3.7.0-dev, 2026. https://flintlib.org/doc/algorithms/linear-algebra/strassen-winograd.html (generated 2026-11-02).