Skip to content

OpenFheCkks backend fails running FFN with polynomial activations of degree >= 6 on 3D inputs #29

Description

@tsunami935

Summary

When running a feedforward network (FFN) with 3D input
and polynomial activation of degree >= 6 on OpenFheCkks backend,
there is a recursive terminate call.
This issue does not appear with SealCkks or LattigoCkks backend.

Running python -X faulthandler openfhe_nn_bug.py results in the following error:

Start prediction...
terminate called recursively
terminate called recursively
terminate called after throwing an instance of 'std::runtime_errorFatal Python error: '
Aborted

Thread 0x000070e9ccfff6c0terminate called recursively
 (most recent call first):
  File "terminate called recursively
~/.local/share/uv/python/cpython-3.11.15-linux-x86_64-gnu/lib/python3.11/threading.py", line 331 in wait
  File terminate called recursively
"~/.local/share/uv/python/cpython-3.11.15-linux-x86_64-gnu/lib/python3.11/threading.py", line 629 in wait
  File "~/helayers/.venv/lib/python3.11/site-packages/tqdm/_monitor.py", line 60 in run
  File "~/.local/share/uv/python/cpython-3.11.15-linux-x86_64-gnu/lib/python3.11/threading.py", line 1045 in _bootstrap_inner
  File "~/.local/share/uv/python/cpython-3.11.15-linux-x86_64-gnu/lib/python3.11/threading.py", line 1002 in _bootstrap

Thread 0x000070eb249b9740 (most recent call first):
  File "~/helayers/bugs/openfhe_nn_bug.py", line 78 in <module>
  File "<frozen runpy>", line 88 in _run_code
  File "<frozen runpy>", line 198 in _run_module_as_main
Aborted (core dumped)

Minimal Reproducible Code

import os
import time
import psutil

import torch
import torch.nn as nn
import torch.nn.functional as F

class FFN(nn.Module):
    def __init__(self, d, h, degree):
        super().__init__()
        self.l1 = nn.Linear(d, h, bias=False)
        self.degree = degree
        self.coefs = nn.Parameter(torch.ones(degree) / degree)
        self.l2 = nn.Linear(h, d, bias=False)

    def forward(self, x: torch.Tensor):
        x = self.l1(x)
        x = sum([self.coefs[i] * (x ** i) for i in range(self.degree)])
        x = self.l2(x)
        return x

# If using degree <= 5, then there's no problem
model = FFN(384, 384 * 4, 6).to(dtype=torch.float32)
model.eval()
output_path = "models/openfhe_nn_bug.onnx"
torch.onnx.export(
    model,  # model being run
    torch.randn((1,512,384)).to(dtype=torch.float32), 
    output_path, 
    opset_version=17,
    export_params=True, 
    external_data=False,
    input_names=["input"],  
    output_names=["output"],
)

#################################################################################

import numpy as np
import pyhelayers

# # There's no issues with any other context
# he_context = pyhelayers.LattigoContext()
# he_context = pyhelayers.SealCkksContext()
he_context = pyhelayers.OpenFheCkksContext()
print("Using backend: ", he_context.get_library_name())

hyper_params = pyhelayers.PlainModelHyperParams()
hyper_params.init_random_weights = False
hyper_params.min_rand_value = -0.1
hyper_params.max_rand_value = 0.1
hyper_params.sparse_rate = 0.5
hyper_params.verbose = True

np.random.seed(30)
input_file = [output_path]
input_sample = [(np.random.rand(1, 512, 384).astype(np.float64) - 0.5) * 1.0]

he_run_req = pyhelayers.HeRunRequirements()
he_run_req.set_he_context_options([he_context])
he_run_req.set_model_encrypted(False)
he_run_req.set_lazy_mode(pyhelayers.LazyMode.LAZY_ENCODING)

nn = pyhelayers.NeuralNet()
nn.encode(input_file, he_run_req, hyper_params) # Encode

model_io_encoder = pyhelayers.ModelIoEncoder(nn)
samples = pyhelayers.EncryptedData(he_context)
model_io_encoder.encode_encrypt(samples, input_sample)
print('Encrypted samples ready')

process = psutil.Process(os.getpid())
print("Memory consumption (GB): ", process.memory_info().rss / (1000*1000*1000))

predictions = pyhelayers.EncryptedData(he_context)
print("Start prediction...")
start = time.perf_counter()
nn.predict(predictions, samples) # We crash here.
duration=time.perf_counter() - start

print('Prediction done')
print('predict per sample:', duration, 's')

process = psutil.Process(os.getpid())
print("Memory consumption (GB): ", process.memory_info().rss / (1000*1000*1000))

plain_predictions = model_io_encoder.decrypt_decode_output(predictions)
print('Decrypted predictions ready')

process = psutil.Process(os.getpid())
print("Memory consumption (GB): ", process.memory_info().rss / (1000*1000*1000))

plain_predictions = plain_predictions.astype(np.float32)
print(plain_predictions)

process = psutil.Process(os.getpid())
print("Memory consumption (GB): ", process.memory_info().rss / (1000*1000*1000))

Environment Configuration:

  • python 3.11.15
  • pyhelayers==1.5.5.3
  • torch==2.11
  • onnx==1.19.0
  • onnx-ir==0.2.1
  • onnxscript==0.7.0

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions