Skip to content

Preserve linear_transform in Lattigo backend - #3315

Open
copybara-service[bot] wants to merge 1 commit into
mainfrom
test_960437546
Open

Preserve linear_transform in Lattigo backend#3315
copybara-service[bot] wants to merge 1 commit into
mainfrom
test_960437546

Conversation

@copybara-service

@copybara-service copybara-service Bot commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

Preserve linear_transform in Lattigo backend

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.

@j2kun

j2kun commented Aug 6, 2026

Copy link
Copy Markdown
Collaborator

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.

@j2kun
j2kun requested review from AlexanderViand and mdgrs August 6, 2026 23:11

@mdgrs mdgrs left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I mostly live in the CKKS world, but this has broken a BGV test:

tests/Examples/common/cmux.mlir:1:1

Comment thread lib/Dialect/Secret/Conversions/SecretToBGV/SecretToBGV.cpp
Comment thread lib/Dialect/Secret/Conversions/SecretToCKKS/SecretToCKKS.cpp Outdated

// logBabyStepGiantStepRatio
// For now default to 0.
int64_t logBSGSRatio = 0;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this used anywhere?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That is an input to the lattigo linear-transform op.

@copybara-service
copybara-service Bot force-pushed the test_960437546 branch 2 times, most recently from 8da70e0 to d55b94d Compare August 13, 2026 17:42
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
@j2kun
j2kun requested a review from mdgrs August 13, 2026 20:34
return rewriter.notifyMatchFailure(op,
"input LWE type has no modulus chain");
}
int64_t levelQ =

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

@AlexanderViand AlexanderViand left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment on lines +719 to +722
if (failed(encoderResult)) {
return rewriter.notifyMatchFailure(
op, "CKKS encoder not found in function context");
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
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

Comment on lines +711 to +714
if (failed(evaluatorResult)) {
return rewriter.notifyMatchFailure(
op, "CKKS evaluator not found in function context");
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if (failed(evaluatorResult)) {
return rewriter.notifyMatchFailure(
op, "CKKS evaluator not found in function context");
}
if (failed(evaluatorResult)) return evaluatorResult;

Same as below

Comment on lines +725 to +734
// 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");
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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)

Comment on lines +764 to +773
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");
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as above, these seem like they should be hard emitOpError rather than notifyMatchFailure?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants