Preserve linear_transform in Lattigo backend - #3315
Conversation
|
Rebased over #3313. The new changes are the secret-to-ckks and scheme-to-backend conversion passes, as well as the e2e tests and enabling it in the lattigo config. Interestingly: openfhe's linear_transform op is not part of their public API (and it only supports dense mode anyway, so it's less useful) so that backend is not enabled. |
mdgrs
left a comment
There was a problem hiding this comment.
I mostly live in the CKKS world, but this has broken a BGV test:
tests/Examples/common/cmux.mlir:1:1
|
|
||
| // logBabyStepGiantStepRatio | ||
| // For now default to 0. | ||
| int64_t logBSGSRatio = 0; |
There was a problem hiding this comment.
That is an input to the lattigo linear-transform op.
8da70e0 to
d55b94d
Compare
This change: - Implements the LWE-to-Lattigo lowering pattern for kernel.linear_transform to lattigo.ckks.linear_transform. - Enables has_kernel_linear_transform = 1 in Lattigo target config. - Fixes the Lattigo emitter to dynamically convert float32 diagonals to float64 in Go. - Resolves LevelQ and Scale matching issues in the emitter using dynamic ciphertext level instead of compile-time constants. - Aligns encoding slots with actual slot counts for CKKS bootstrapping. After this change, the e2e lattigo tests using linalg.matvec lower to use the linear_transform op. Still TODO is lowering a conv operator to an intermediate matvec op so it can be preserved as a linear_transform op, which is a bit of a larger change because it requires extracting the lowering for conv ops to occur before layout-propagation, and hard-coding the layout choice. PiperOrigin-RevId: 960437546
d55b94d to
8564c30
Compare
| return rewriter.notifyMatchFailure(op, | ||
| "input LWE type has no modulus chain"); | ||
| } | ||
| int64_t levelQ = |
There was a problem hiding this comment.
Should this be modulusChain.getCurrent()? the LattigoCKKSOps.td documentation says that what needs to be passed to the CKKSLinearTransformOp is the level at which the operation should be performed
There was a problem hiding this comment.
There seems to be a several places where a soft error (notifyMatchFailure) is used where I think hard emitOpError would be more appropriate/ give users clearer feedback. I annotated a few in LWEToLattigo's conversion pattern, but I think it also applies to the other patterns.
| if (failed(encoderResult)) { | ||
| return rewriter.notifyMatchFailure( | ||
| op, "CKKS encoder not found in function context"); | ||
| } |
There was a problem hiding this comment.
| if (failed(encoderResult)) { | |
| return rewriter.notifyMatchFailure( | |
| op, "CKKS encoder not found in function context"); | |
| } | |
| if (failed(encoderResult)) return encoderResult; |
It seems likegetContextualEvaluator already produces a hard emitOpError on failure, so the soft match failure here is probably impossible to reach
| if (failed(evaluatorResult)) { | ||
| return rewriter.notifyMatchFailure( | ||
| op, "CKKS evaluator not found in function context"); | ||
| } |
There was a problem hiding this comment.
| if (failed(evaluatorResult)) { | |
| return rewriter.notifyMatchFailure( | |
| op, "CKKS evaluator not found in function context"); | |
| } | |
| if (failed(evaluatorResult)) return evaluatorResult; |
Same as below
| // Extract level from input LWE ciphertext type | ||
| auto lweType = dyn_cast<lwe::LWECiphertextType>(op.getInput().getType()); | ||
| if (!lweType) { | ||
| return rewriter.notifyMatchFailure(op, "input is not LWE ciphertext"); | ||
| } | ||
| auto modulusChain = lweType.getModulusChain(); | ||
| if (!modulusChain) { | ||
| return rewriter.notifyMatchFailure(op, | ||
| "input LWE type has no modulus chain"); | ||
| } |
There was a problem hiding this comment.
There's a mismatch here between these being soft failures but the kernel.linear_transform op being declared illegal unconditionally.
If we somehow think it's important to allow plaintext-valued kernel.linear_transform operations to survive this pass, the op should be conditionally legal, otherwise I think these should be hard errors? (Otherwise, the user just gets a hard-to-understand failed-to-legalize error)
| auto outputLweType = | ||
| dyn_cast<lwe::LWECiphertextType>(op.getResult().getType()); | ||
| if (!outputLweType) { | ||
| return rewriter.notifyMatchFailure(op, "output is not LWE ciphertext"); | ||
| } | ||
| auto outputModulusChain = outputLweType.getModulusChain(); | ||
| if (!outputModulusChain) { | ||
| return rewriter.notifyMatchFailure( | ||
| op, "output LWE type has no modulus chain"); | ||
| } |
There was a problem hiding this comment.
Same as above, these seem like they should be hard emitOpError rather than notifyMatchFailure?
Preserve linear_transform in Lattigo backend
This change:
After this change, the e2e lattigo tests using linalg.matvec lower to use the linear_transform op.
Still TODO is lowering a conv operator to an intermediate matvec op so it can be preserved as a linear_transform op, which is a bit of a larger change because it requires extracting the lowering for conv ops to occur before layout-propagation, and hard-coding the layout choice.