This is an archive of the discontinued LLVM Phabricator instance.

[MLIR][Presburger] Add the related methods to support computing Matrix inverse
AbandonedPublic

Authored by kaitingwang on Sep 2 2022, 1:39 PM.

Details

Summary

Matrix inverse is useful when generating code from an affine schedule which must be full-rank (i.e. invertible).

Diff Detail

Event Timeline

kaitingwang created this revision.Sep 2 2022, 1:39 PM
Herald added a project: Restricted Project. · View Herald TranscriptSep 2 2022, 1:39 PM
kaitingwang requested review of this revision.Sep 2 2022, 1:39 PM
Groverkss requested changes to this revision.Sep 5 2022, 4:08 PM

Thank you for the patch!

Some comments on coding style to start off. I will put more comments on the algorithms used in a further review.

mlir/include/mlir/Analysis/Presburger/Matrix.h
48
48

Please mark this constructor as explicit.

185–196

Please use full stops at the end of sentences in comments.

mlir/lib/Analysis/Presburger/Matrix.cpp
25

vect[0] here can cause an out-of-bound access if vect is empty.

26

We prefer loops to be written with a pre-evaluated end condition and preincrement.

for (unsigned i = 0, e = vect.size(); i < e; ++i)
29–31

Braces can be dropped for single statement loops and if conditions:

for (unsigned j = 0; j < vect[0].size(); j++)
  at(i, j) = vect[i][j];
285

I don't think "quadratic" makes much sense. "square" seems to be a better term.

288–296

braces can be dropped for single statement if conditions and for loops.

300–310

Please use pre-increment for loops.

Also, the loop induction variables should be unsigned.

321–325

Braces can be dropped; use pre-evaluated for loop condition and pre-increment, please. Same for other loops as well.

331

quadratic -> square

363

This is better understood as "Matrix should be unimodular for integer inverse".

This revision now requires changes to proceed.Sep 5 2022, 4:08 PM
kaitingwang updated this revision to Diff 458294.EditedSep 6 2022, 3:26 PM

@Groverkss Thanks for the detailed review. gtest does show the algorithm is tending on the expensive side. There was a reason that we went the route of computing the determinant/cofactor as suppose to a method such as LU decomposition. I can find out the precise reason from our scheduler person and update the MR tomorrow. Hope the style better matches your expectation and thanks for catching the zero-row vector bug.

[ RUN      ] MatrixTest.determinant
[       OK ] MatrixTest.determinant (106 ms)
[ RUN      ] MatrixTest.transpose
[       OK ] MatrixTest.transpose (0 ms)
[ RUN      ] MatrixTest.cofactor
[       OK ] MatrixTest.cofactor (107 ms)
[ RUN      ] MatrixTest.inverse
[       OK ] MatrixTest.inverse (111 ms)
[ RUN      ] MatrixTest.toVector
[       OK ] MatrixTest.toVector (0 ms)
[ RUN      ] MatrixTest.Matrix
[       OK ] MatrixTest.Matrix (108 ms)
[----------] 13 tests from MatrixTest (432 ms total)
kaitingwang marked 7 inline comments as done.
Groverkss added a comment.EditedSep 7 2022, 3:33 AM

@Groverkss Thanks for the detailed review. gtest does show the algorithm is tending on the expensive side. There was a reason that we went the route of computing the determinant/cofactor as suppose to a method such as LU decomposition. I can find out the precise reason from our scheduler person and update the MR tomorrow. Hope the style better matches your expectation and thanks for catching the zero-row vector bug.

[ RUN      ] MatrixTest.determinant
[       OK ] MatrixTest.determinant (106 ms)
[ RUN      ] MatrixTest.transpose
[       OK ] MatrixTest.transpose (0 ms)
[ RUN      ] MatrixTest.cofactor
[       OK ] MatrixTest.cofactor (107 ms)
[ RUN      ] MatrixTest.inverse
[       OK ] MatrixTest.inverse (111 ms)
[ RUN      ] MatrixTest.toVector
[       OK ] MatrixTest.toVector (0 ms)
[ RUN      ] MatrixTest.Matrix
[       OK ] MatrixTest.Matrix (108 ms)
[----------] 13 tests from MatrixTest (432 ms total)

A simple reason I can see why the computation is expensive is that the determinant algorithm written is exponential, and each call makes a copy instead of creating a view. Fixing this would improve the performance quite a bit, but I would wait for the next round of review. Also, it would be helpful to know why you don't use LU decomposition (or Hermite normal form) before I can review the algorithm.

kaitingwang marked 5 inline comments as done.EditedSep 7 2022, 7:54 AM
[ RUN      ] MatrixTest.determinant
[       OK ] MatrixTest.determinant (106 ms)
[ RUN      ] MatrixTest.transpose
[       OK ] MatrixTest.transpose (0 ms)
[ RUN      ] MatrixTest.cofactor
[       OK ] MatrixTest.cofactor (107 ms)
[ RUN      ] MatrixTest.inverse
[       OK ] MatrixTest.inverse (111 ms)
[ RUN      ] MatrixTest.toVector
[       OK ] MatrixTest.toVector (0 ms)
[ RUN      ] MatrixTest.Matrix
[       OK ] MatrixTest.Matrix (108 ms)
[----------] 13 tests from MatrixTest (432 ms total)

A simple reason I can see why the computation is expensive is that the determinant algorithm written is exponential, and each call makes a copy instead of creating a view. Fixing this would improve the performance quite a bit, but I would wait for the next round of review. Also, it would be helpful to know why you don't use LU decomposition (or Hermite normal form) before I can review the algorithm.

We need to ensure/check that the determinant of the schedule matrix is always 1, -1 so that the inverse of the schedule can be non-fractional. This mostly stands from that we haven't figured out how to code generate for fractional schedule (which may require introducing if condition inside the newly generated loop to test for non-fraction, or use mod operator) We punted thinking about this to later. This leads us to pick (somewhat carelessly?) a method that implements finding out determinant.

mlir/lib/Analysis/Presburger/Matrix.cpp
25

Added a testcase for this.

From what I understand, you only need the integer inverse of a unimodular matrix. This is better accomplished by Hermite normal form.

The (column) hermite normal form for a matrix M is H = MU, such that U is unimodular, H is lower triangular, and the pivots in H are the max element in that column. Now, det(H) = 1 iff det(M) = 1, since det(U) = 1. det(H) = 1, actually forces H to be the identity matrix and U comes out to be inv(A). So you can simply find the hermite normal form, and use U as inv(A) if det(H) = 1 (since H is lower triangular this is just product of diagonals). Otherwise, M is not unimodular, and we cannot find an integer inverse.

We already have an algorithm to find the hermite normal form of a matrix upstream. I will refactor that algorithm to Matrix, and you can use that directly to get the inverse.

Groverkss added a comment.EditedSep 8 2022, 11:17 AM

I sent the refactor here: https://reviews.llvm.org/D133510

In pseudo-code, what you would need to do to get the inverse is:

h, u = m.hermiteNormalForm()
check if all diagonal elements of h are 1 --> unimodularity check for matrix
if yes, return u
if no, return nothing since integer inverse does not exist

The patch was landed. You can use Hermite decomp to do inverse here.

kaitingwang abandoned this revision.Nov 14 2022, 6:13 PM

The patch was landed. You can use Hermite decomp to do inverse here.

Thanks. I'll close this pull request.