Skip to content

pke/cryptocontext: expose auto-facade hooks for Make*Plaintext + Eval…#36

Open
podschwadt wants to merge 1 commit into
nb_mainfrom
fix/auto_facade
Open

pke/cryptocontext: expose auto-facade hooks for Make*Plaintext + Eval…#36
podschwadt wants to merge 1 commit into
nb_mainfrom
fix/auto_facade

Conversation

@podschwadt
Copy link
Copy Markdown
Collaborator

…*(ct, pt) replay shortcuts

Add three pieces of plumbing the niobium-client auto-facade needs to transparently record/replay programs that build plaintexts at runtime (e.g. mask plaintexts for slot-replication, running-sum coefficients):

  1. niobium_auto::on_make_plaintext(pt) hook fired at the tail of every MakePackedPlaintext / MakeCKKSPackedPlaintext (both overloads). The weak default is a no-op so existing programs linking only libnbfhetch see no behavioral change. The strong override in libniobium_client_autofacade tags the plaintext as an input under a deterministic auto-name on record, and rehydrates it from disk on replay so the FHETCH simulator has its polynomial values as live-in.

    In replay mode the hook fires with a null pt (the existing "Make*Plaintext returns nullptr in replay" shortcut is preserved because the encoding work is unnecessary — the recorded values are already on disk). The auto-name counter still advances so record and replay agree on pt_0 / pt_1 / ... ordering.

  2. Replay early-returns in the 8 Eval*(ct, pt) wrappers (EvalAdd, EvalAddInPlace, EvalAddMutable, EvalSub, EvalSubInPlace, EvalSubMutable, EvalMult, EvalMultMutable). NiobiumAutoScheme already overrides every one of these with if (g_replay_mode) return dummy(ct); — it never dereferences pt. But TypeCheck and plaintext->SetFormat(EVALUATION) in the wrapper run above the scheme proxy and would null-deref on the shortcut pt. The early return dispatches straight to GetScheme() in replay so the proxy gets to do its job. Mirrors the EvalAdd(ct, ct) pattern where the proxy naturally short-circuits via the scheme dispatch.

  3. !g_replay_mode guard on the evalKeyVec.size() check inside Relinearize / RelinearizeInPlace / EvalMultAndRelinearize. Dummy ciphertexts carry inflated NumberCiphertextElements() that would falsely trip the threshold; the scheme proxy returns dummy(ct) without consulting evalKeyVec anyway.

All three pieces are gated on OPENFHE_CPROBES, so non-instrumented builds are unaffected. Programs that build with CPROBES but don't link the auto-facade strong override get weak no-op behavior, identical to prior semantics.

…*(ct, pt) replay shortcuts

Add three pieces of plumbing the niobium-client auto-facade needs to
transparently record/replay programs that build plaintexts at runtime
(e.g. mask plaintexts for slot-replication, running-sum coefficients):

1. niobium_auto::on_make_plaintext(pt) hook fired at the tail of every
   MakePackedPlaintext / MakeCKKSPackedPlaintext (both overloads). The
   weak default is a no-op so existing programs linking only
   libnbfhetch see no behavioral change. The strong override in
   libniobium_client_autofacade tags the plaintext as an input under
   a deterministic auto-name on record, and rehydrates it from disk on
   replay so the FHETCH simulator has its polynomial values as live-in.

   In replay mode the hook fires with a null pt (the existing
   "Make*Plaintext returns nullptr in replay" shortcut is preserved
   because the encoding work is unnecessary — the recorded values are
   already on disk). The auto-name counter still advances so record and
   replay agree on pt_0 / pt_1 / ... ordering.

2. Replay early-returns in the 8 Eval*(ct, pt) wrappers (EvalAdd,
   EvalAddInPlace, EvalAddMutable, EvalSub, EvalSubInPlace,
   EvalSubMutable, EvalMult, EvalMultMutable). NiobiumAutoScheme
   already overrides every one of these with `if (g_replay_mode)
   return dummy(ct);` — it never dereferences pt. But TypeCheck and
   plaintext->SetFormat(EVALUATION) in the wrapper run *above* the
   scheme proxy and would null-deref on the shortcut pt. The early
   return dispatches straight to GetScheme() in replay so the proxy
   gets to do its job. Mirrors the EvalAdd(ct, ct) pattern where the
   proxy naturally short-circuits via the scheme dispatch.

3. !g_replay_mode guard on the evalKeyVec.size() check inside
   Relinearize / RelinearizeInPlace / EvalMultAndRelinearize. Dummy
   ciphertexts carry inflated NumberCiphertextElements() that would
   falsely trip the threshold; the scheme proxy returns dummy(ct)
   without consulting evalKeyVec anyway.

All three pieces are gated on OPENFHE_CPROBES, so non-instrumented
builds are unaffected. Programs that build with CPROBES but don't link
the auto-facade strong override get weak no-op behavior, identical to
prior semantics.
Copy link
Copy Markdown

@ryanorendorff ryanorendorff left a comment

Choose a reason for hiding this comment

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

Ideally clean up the // comments because they seem to pull in a bunch of ideas unrelated to the actual PR.

// from NiobiumAutoScheme carry an inflated NumberCiphertextElements
// that would falsely trip the threshold. The scheme proxy returns
// dummy(ct) without ever consulting evalKeyVec.
if (!g_replay_mode && evalKeyVec.size() < (ciphertext->NumberCiphertextElements() - 2))
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

There are a few places here that don't have the macro guard (OPENFHE_CPROBES) that I assume brings g_replay_mode into scope; ideally this is consistent so we can disable the probes easily at build time.

Comment on lines +1210 to +1214
if (g_replay_mode) {
Plaintext null_pt;
niobium_auto::on_make_plaintext(null_pt);
return null_pt;
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Not sure I love this reuse of null as a sentinel value. Also this if statement pattern is repeated, could be factored out.


const auto& evalKeyVec = CryptoContextImpl<Element>::GetEvalMultKeyVector(ciphertext->GetKeyTag());
if (evalKeyVec.size() < (ciphertext->NumberCiphertextElements() - 2))
if (!g_replay_mode && evalKeyVec.size() < (ciphertext->NumberCiphertextElements() - 2))
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Here as well for missing OPENFHE_CPROBES check.

const auto& evalKeyVec = CryptoContextImpl<Element>::GetEvalMultKeyVector(ciphertext1->GetKeyTag());

if (evalKeyVec.size() <
if (!g_replay_mode && evalKeyVec.size() <
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

And here

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Also since we are skipping checks in a similar manner this should ideally also be abstracted. Otherwise when we look at this code again it will look very odd to skip a necessary check.

// data — required for replay of any op that consumes the plaintext.
// In DORMANT mode (explicit niobium::compiler() SDK in use), the
// override stays a no-op to avoid duplicate-tagging the manual flow.
void on_make_plaintext(lbcrypto::Plaintext& pt);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Ideally we don't have a symbol we require a downstream TU to provide to link openfhe. You could add a no-op default here.

I am also unsure what the definition of this is supposed to be.

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.

2 participants