Diagnose a value-state mismatch
When an operation rejects a value—or a lower-level experiment produces wrong results—compare value state in a fixed order. Do not begin by changing kernels or disabling validation.
Prerequisites
Retain the inputs, configuration provenance, key relation, and first failing operation. For a Compile path, also retain the Program and pass reports that assigned the relevant value facts.
1. Reduce to one deterministic operation
Build the smallest reproducer with:
- a fixed preset and indexed device;
- deterministic input values and seed;
- one named operation;
- decrypt/cleartext comparison immediately afterward;
- state printed before and after;
- no graph, distributed execution, cache, or multi-stream overlap.
First establish whether the same operation and supplied materials are correct in a direct single-device execution.
2. Compare configuration provenance and device
Check:
the value and keys were produced with parameters compatible with engine.config
all operands for one ordinary operation share one device
the installed native extension supports that device type
ring dimension matches2
3
4
Runtime values do not carry a configuration identifier. Parameter provenance is application state, so a mistaken cross-configuration combination may produce an incorrect result instead of a FHElium mismatch exception.
A loaded value may be on CPU by default. torch.get_default_device() controls factory placement, while existing values retain their own placement. Move a value with .to(...), or pass device to a boundary operation after loading.
3. Compare structure
Inspect:
concrete value type
tensor dtype and dimensionality (ndim)
component count
limb count
ring dimension
prime_ids length and order2
3
4
5
6
Two tensors can have equal shape but different parameter provenance or row identity. A partial-limb view does not contain the complete active-row layout merely because its other metadata is valid.
4. Compare arithmetic state
Use this order:
- depth;
- active
prime_ids; - plaintext representation, where applicable;
- modulus basis (
QorQP); - polynomial domain (
coefficientorntt); - residue representation (
standardormontgomery); - scale;
- component count.
Typical diagnoses:
| Symptom | Likely mismatch |
|---|---|
Fresh ciphertext rejected by multiply | Still coefficient domain or not rescaled/prepared |
| Same-depth addition has incompatible semantics | Caller did not match prime IDs, scale, polynomial domain, or modulus basis before dispatch |
| Q value rejected by key-switch path | Key/value basis or active rows incompatible |
| Three-component value rejected | Operation requires two components or relinearization |
MaximumDepthError | No remaining public depth transition is available |
5. Check stored key state and the external key relation
For key-requiring operations, verify:
- caller-recorded key parameter provenance;
- Q/QP prime layout;
- NTT/residue representation;
- key type;
- rotation key's normalized signed step;
- whether the key is installed on the local engine/device.
- whether the application supplied a key with the required ciphertext, source-secret, and destination-secret relation.
Do not substitute a same-shaped key from another parameter set, step, or externally supplied key provenance.
6. Inspect the operation requirements
List the required input and output state. For example:
multiply:
input: two compatible 2-component Q NTT/Montgomery ciphertexts
output: 3-component Q NTT/Montgomery ciphertext
relinearize:
input: compatible 3-component ciphertext + relin key
output: 2-component coefficient ciphertext2
3
4
5
6
7
Use the API docstring and focused tests as the source of truth.
7. Add transitions back one at a time
Once the single operation passes, restore the original chain incrementally:
For operations whose intermediate representation is not directly meaningful to decrypt, first reach a legal decryptable checkpoint or compare against a trusted single-GPU reference path.
8. Reintroduce execution mechanisms last
Add in this order:
- in-place variants;
- prepared plaintext or NTT reuse;
- multi-stream copies;
- CUDA Graph replay;
- distributed transport/partition;
- residency/prefetch policy.
At each step, preserve the same oracle, seed, depth checkpoints, and error threshold.
9. If the problem reaches native code
Capture:
- source commit and build/wheel origin;
- operator schema and generated wrapper status;
- shape/dtype/device and mutation or aliasing semantics;
- depth-specific row start/stop and prime IDs;
- singleton/last-depth/Q-vs-QP cases;
- synchronized CUDA error location;
- smallest
logNand NTT backend that reproduce the issue.
Do not treat an asynchronous error reported at a later call as proof that the later call caused it.
Verify the outcome
The reduced operation should produce the expected decoded message with the intended state transition. Use Compile preparation diagnosis when the issue is a missing fact or binding before execution rather than a materialized operand mismatch.