Lower polynomial to mod_arith for add/sub/mul_scalar#995
Lower polynomial to mod_arith for add/sub/mul_scalar#995ZenithalHourlyRate wants to merge 1 commit intogoogle:mainfrom
Conversation
|
Turned out the lowering above is incorrect.
I agree that any signed integer can be coefficient, for the semantic of polynomial dialect (e.g. -1 or a number inside [mod, 2^31-1)). However we should keep some invariant internally. AFAIK the currently lowering (like ConvertAdd, ConvertMul) tries to keep the invariant that coefficients for polynomial are in (-mod, +mod), however, ConvertFromTensor does not keep the invariant as user may give any input and it did not remsi it. Or should we keep it in [-mod/2, +mod/2) (for L2/L-infinity norm/noise reason, commonly seen in FHE paper) In the definition of the polynomial dialect there should be a canonical form of the polynomial, like it has been mod coeffModulus and PolyModulus, then we can have a stable output for ToTensor (actually if it is in (-mod, +mod), we have two possible output for one number) and we can have safely lower to mod_arith in some way. I'm curious what other FHE libraries deal with this issue. |
What we’ve done in the past is to update the existing pipeline “in-place”, so we’d (a) create a pass |
|
Tried to keep the invariant inside FromTensor using mod_arith.reduce, leading to the following behavior if the modulus is large. |
|
https://mlir.llvm.org/docs/Dialects/PolynomialDialect/#ringattr I can not handle the case of coefficient modulus larger than the underlying type (e.g. i32). It has two cases.
Currently the documentation upstream does not define any constraint on this and it does not have a verifier for it. |
|
I think one complication is that MLIR does not specify any overflow semantics by default. So omitting a modulus that equals 2^32 when the type is i32 could result in lowerings that treat overflow differently to how we want. I thought I could handle that by allowing the type of the modulus to differ from the underlying type of the coefficients, and then lowerings could check for these edge cases. But since I'm not working for another month (on baby leave), I won't have the time to dive deep to recommend a fix. |
…or ringAttr (#111016) Currently the semantic of coefficientModulus is unclear and a lowering of it faces uncertainty, for example, google/heir#995 (comment) Also, it lacks a verifier which should conform to the definition in the document. This PR tries to further define the semantic of coefficientModulus and adds a verifier for it. Cc @j2kun for review and suggestions.
…or ringAttr (llvm#111016) Currently the semantic of coefficientModulus is unclear and a lowering of it faces uncertainty, for example, google/heir#995 (comment) Also, it lacks a verifier which should conform to the definition in the document. This PR tries to further define the semantic of coefficientModulus and adds a verifier for it. Cc @j2kun for review and suggestions.
7cab15e to
2efcd5d
Compare
2efcd5d to
b9f83f2
Compare
|
I think the lowering for add/sub/mul_scalar, along with auxiliary op from_tensor/constant are self-contained enough for review. #990 can be fixed by this PR. Each of mul, ntt, and intt is large enough for a separate PR, and including them all in this one would make the review process more burdensome. Things need discussion:
|
|
Awesome, thanks! Lots of good questions in your last post, so let me pick the easy one to answer xD
Since lots of libraries/HW targets have an explicit idea of polynomial subtraction, I think we can drop the upstream SubAsAdd pattern from the set of canonicalization patterns. If we don't want to have separate poly->(mod)arith lowerings for both add and sub, we can simply add the SubAsAdd pattern to the patternset of that lowering pass. |
|
Seems that I need to put |
Yeah, I think that's a good idea. Not many things use the tensor-valued versions of polynomial, but the lowerings from BG/CKKS make use of them as it's very convenient there. We made a (undocument, oops) decision at some point to only support "scalar-valued" polynomial ops in polynomial-to-x lowerings, and the elementwise-to-affine pass bridges the gap. |
b9f83f2 to
04f87f9
Compare
04f87f9 to
a692f80
Compare
|
Rebased to the current directory organization |
There was a problem hiding this comment.
Thanks for putting this together! I think the final output looks good, though I'm not a huge fan of how much "modular arithmetic" logic this lowering includes. For example, the pass should, imho, not care about things such as power-of-two moduli - worrying about how to efficiently realize modular arithmetic is exactly what ModArith was introduced for, so this logic should be moved to ModArithToArith.
However, I think working code beats no code, so I'd be in favor of merging this and creating an issue for refactoring this out. Would be interested to hear @inbelic 's opinion on this, too.
tests/Dialect/Polynomial/Conversions/heir_polynomial_to_llvm/to_mod_arith/lower_add.mlir
Show resolved
Hide resolved
lib/Dialect/Polynomial/Conversions/PolynomialToModArith/PolynomialToModArith.td
Outdated
Show resolved
Hide resolved
lib/Dialect/Polynomial/Conversions/PolynomialToModArith/PolynomialToModArith.cpp
Show resolved
Hide resolved
a692f80 to
384af07
Compare
|
I agree, I think we should be able to postpone any of the modulus based logic to a later lowering. I will set aside time this evening to do a full review. |
inbelic
left a comment
There was a problem hiding this comment.
Thanks for contributing this and lots of great work here!
Mostly nits and preferential comments.
lib/Dialect/Polynomial/Conversions/PolynomialToModArith/PolynomialToModArith.cpp
Outdated
Show resolved
Hide resolved
lib/Dialect/Polynomial/Conversions/PolynomialToModArith/PolynomialToModArith.td
Show resolved
Hide resolved
lib/Dialect/Polynomial/Conversions/PolynomialToModArith/PolynomialToModArith.cpp
Outdated
Show resolved
Hide resolved
| Value modArithReduceOp(ConvertCommon<Op> &c, ImplicitLocOpBuilder &b, | ||
| ConversionPatternRewriter &rewriter, Value v, | ||
| int64_t shape = 0) { | ||
| // why not c.resultTensorType here |
There was a problem hiding this comment.
I rephrased this comment so it is less confusing now.
| }; | ||
|
|
||
| template <typename Op, typename ModArithOp, typename ArithIOp, | ||
| typename ArithFOp> |
There was a problem hiding this comment.
Sorry, not up to date about adding different coefficient types for polynomials. Can we add tests for floating point?
There was a problem hiding this comment.
I will add it in the next round of push.
lib/Dialect/Polynomial/Conversions/PolynomialToModArith/PolynomialToModArith.cpp
Outdated
Show resolved
Hide resolved
| c.resultElementWidth > c.coefficientModulusWidth) { | ||
| v = b.create<mod_arith::ReduceOp>(resultType, v, c.coefficientModulus); | ||
| } | ||
| // else float or natural power of two modulus larger than underlying type. |
There was a problem hiding this comment.
Why don't we need the op if it is a float? Should we really reduce it but can't because it doesn't support floats at the moment?
There was a problem hiding this comment.
The polynomial dialect does not have definition for coefficientModulus when coeffcientType is a float
if the coefficient type is integral, whose coefficients are taken modulo some statically known modulus (coefficientModulus).
| } | ||
| } | ||
|
|
||
| // polynomial type related |
There was a problem hiding this comment.
We don't use some of the following variables outside of the above function. Do we anticipate using them in the future? Otherwise I think it would best to keep them local.
There was a problem hiding this comment.
for input* (e.g. inputTensor, inputElementWidth) it is indeed FromTensor specific and now moved to ConvertFromTensor. Some other unused variable are also eliminated, and I think the current variables are essential and may be used in the future.
lib/Dialect/Polynomial/Conversions/PolynomialToModArith/PolynomialToModArith.cpp
Outdated
Show resolved
Hide resolved
lib/Dialect/Polynomial/Conversions/PolynomialToModArith/PolynomialToModArith.td
Outdated
Show resolved
Hide resolved
384af07 to
9004489
Compare
ZenithalHourlyRate
left a comment
There was a problem hiding this comment.
Talking about moving some logic inside mod_arith. I think the handling of modArithReduceOp needs some change to mod_arith.reduce.
Note this will interpret x as a signed integer. It is required the bitwidth of q is smaller than that of x. The smaller requirement makes the lowering pass needs to manually extsi the input. Should we allow q (intepreted as unsigned) whose bitwidth is the same as x (signed). This semantic is wierd.
lib/Dialect/Polynomial/Conversions/PolynomialToModArith/PolynomialToModArith.td
Outdated
Show resolved
Hide resolved
lib/Dialect/Polynomial/Conversions/PolynomialToModArith/PolynomialToModArith.cpp
Outdated
Show resolved
Hide resolved
| } | ||
| } | ||
|
|
||
| // polynomial type related |
There was a problem hiding this comment.
for input* (e.g. inputTensor, inputElementWidth) it is indeed FromTensor specific and now moved to ConvertFromTensor. Some other unused variable are also eliminated, and I think the current variables are essential and may be used in the future.
| c.resultElementWidth > c.coefficientModulusWidth) { | ||
| v = b.create<mod_arith::ReduceOp>(resultType, v, c.coefficientModulus); | ||
| } | ||
| // else float or natural power of two modulus larger than underlying type. |
There was a problem hiding this comment.
The polynomial dialect does not have definition for coefficientModulus when coeffcientType is a float
if the coefficient type is integral, whose coefficients are taken modulo some statically known modulus (coefficientModulus).
lib/Dialect/Polynomial/Conversions/PolynomialToModArith/PolynomialToModArith.cpp
Outdated
Show resolved
Hide resolved
| typename ArithFOp> | ||
| Value modArithBinaryOp(ConvertCommon<Op> &c, ImplicitLocOpBuilder &b, Value lhs, | ||
| Value rhs) { | ||
| if (c.coefficientModulusIsNotNatural) { |
There was a problem hiding this comment.
I have now migrated the logic of natural modulus handling inside mod_arith. I agree with the idea of handling all the integer stuff in mod_arith. But I think handling float point inside mod_arith seems a violation of its name.
Also, the PolynomialToModArith code must contain some float point specific thing, like ConvertConstant where FloatAttr is explicitly asked.
| }; | ||
|
|
||
| template <typename Op, typename ModArithOp, typename ArithIOp, | ||
| typename ArithFOp> |
There was a problem hiding this comment.
I will add it in the next round of push.
| Value modArithReduceOp(ConvertCommon<Op> &c, ImplicitLocOpBuilder &b, | ||
| ConversionPatternRewriter &rewriter, Value v, | ||
| int64_t shape = 0) { | ||
| // why not c.resultTensorType here |
There was a problem hiding this comment.
I rephrased this comment so it is less confusing now.
lib/Dialect/Polynomial/Conversions/PolynomialToModArith/PolynomialToModArith.cpp
Outdated
Show resolved
Hide resolved
lib/Dialect/Polynomial/Conversions/PolynomialToModArith/PolynomialToModArith.cpp
Outdated
Show resolved
Hide resolved
I think it'd be a good idea to have a general |
9004489 to
c141b07
Compare
Now |
|
Oh no! I did not realize this PR was in flight when I was working on migrating the polynomial-to-standard pass to use |
I think most of them have been covered by your PR. One thing worth noting is that I used a |
This is a draft and not cleaned up yet (e.g. BUILD/Cmake), just demoing the concept of lowering polynomial to mod_arith. Some function like mod the polynomial (
PolynomialToStandard::generateOpImplementations) is not included yet.Related to #990 and #749
Should we add a separate pass for it (
polynomial-to-mod-arith, lots of duplicate code) and gradually replacepolynomial-to-standardor just placing it inside thepolynomial-to-standardpass?Extra note: with
mod-arith-to-ariththe result for the test case is elegant