Skip to content

Record where a ciphertext argument starts on the modulus chain - #3357

Open
AlexanderViand wants to merge 1 commit into
mainfrom
alex/pass-down-attr
Open

Record where a ciphertext argument starts on the modulus chain#3357
AlexanderViand wants to merge 1 commit into
mainfrom
alex/pass-down-attr

Conversation

@AlexanderViand

Copy link
Copy Markdown
Collaborator

A ciphertext argument does not have to start at the top of the modulus chain. HEIR asks the client to encrypt at the level the computation actually begins at, which for a model whose first op is a linear transform can be well below the top. (e.g., func.func @foo(%0: !lwe.lwe_ciphertext<..., modulus_chain = <elements = <11 moduli>, current = 2>>, ...)

The lattigo lowering then gives every ciphertext the same opaque !lattigo.rlwe.ciphertext, and LevelAnalysis::setToEntryState joins every entry lattice to LevelState(0) (top of the chain).
As a result, the level analysis used by --lattigo-alloc-to-inplace is off, which causes incorrect (or, at least inefficient) buffer reuse.

This PR simply adds a lwe.entry_level_depth attribute to function arguments while still on the LWE types and uses that to determine the level at the ! lattigo.rlwe.ciphertext level.

A ciphertext argument does not have to start at the top of the modulus chain.
HEIR asks the client to encrypt at the level the computation actually begins
at, which for a model whose first op is a linear transform is well below the
top. On tcn the entry ciphertext is fresh but sits at level 2 of an
eleven-modulus chain:

  func.func @tcn(%0: !lwe.lwe_ciphertext<..., modulus_chain = <elements = <11
  moduli>, current = 2>>, ...)

The lattigo lowering then gives every ciphertext the same opaque
!lattigo.rlwe.ciphertext, and LevelAnalysis::setToEntryState joins every entry
lattice to LevelState(0) -- the top of the chain. Everything derived from that
argument is believed eight levels shallower than it is.

Nothing verifies that belief. lattigo-alloc-to-inplace is the only pass that
runs LevelAnalysis after this lowering, and it compares the believed level of a
candidate buffer against the believed level of an op's result to decide whether
reuse is safe, so a wrong belief costs reuse it should have taken or takes reuse
it should have refused. On tcn this changes 34 reuse decisions.

Record the starting level while the LWE type still carries it, and have
setToEntryState read it back. `current` indexes the module's Q chain, so the
depth is measured against the length of that chain from the scheme parameters
rather than against the type's own element list: a lowered LWE type can carry a
truncated view of the chain -- a fully consumed value in the same function
arrives as `elements = <1 modulus>, current = 0` -- and measuring against a
truncated list silently reports depth 0. Modules without scheme parameters
(hand-written test IR) still fall back to the type's own list.

Note this restores information the lowering drops; it is not what keeps the
backend from truncating a result. Lattigo resolves an in-place result to
min(operand levels, receiver level), so soundness there comes from the emitter
giving the receiver the operand's level, not from the level analysis being
right.
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.

1 participant