EvalCompareSchemeSwitching fails with slots = N // 2

I am trying to use the Scheme Switching capability of OpenFHE to evaluate the Heaviside function in CKKS (y(x) = 1 if x>=0, 0 otherwise).

I am doing some tests, with this code:

import time
import csv
import sys
import numpy as np
from openfhe import *

def run_sign_eval(slots: int, csv_path: str = "results.csv"):
    N = 65536  # Mandatory because we need multDepth high
    multDepth = 18

    # -------------------------------
    # CKKS CRYPTOGRAPHIC CONTEXT
    # -------------------------------
    params = CCParamsCKKSRNS()
    params.SetSecurityLevel(HEStd_128_classic)
    params.SetMultiplicativeDepth(multDepth)
    params.SetScalingModSize(50)
    params.SetBatchSize(slots)
    params.SetRingDim(N)

    if get_native_int() != 128:
        params.SetScalingTechnique(ScalingTechnique.FLEXIBLEAUTO)
    else:
        params.SetScalingTechnique(ScalingTechnique.FIXEDAUTO)

    cc = GenCryptoContext(params)
    cc.Enable(PKESchemeFeature.PKE)
    cc.Enable(PKESchemeFeature.KEYSWITCH)
    cc.Enable(PKESchemeFeature.LEVELEDSHE)
    cc.Enable(PKESchemeFeature.ADVANCEDSHE)
    cc.Enable(PKESchemeFeature.SCHEMESWITCH)

    keys = cc.KeyGen()
    cc.EvalMultKeyGen(keys.secretKey)

    # -------------------------------
    # CKKS -> FHEW SCHEME SWITCHING SETUP
    # -------------------------------
    fhew_params = SchSwchParams()
    fhew_params.SetSecurityLevelCKKS(HEStd_128_classic)
    fhew_params.SetSecurityLevelFHEW(STD128)
    fhew_params.SetNumSlotsCKKS(slots)
    fhew_params.SetNumValues(slots)

    lwesk = cc.EvalSchemeSwitchingSetup(fhew_params)
    cc.EvalSchemeSwitchingKeyGen(keys, lwesk)

    pLWE = 0
    scaleSign = 1.0
    unit = True

    cc.EvalCompareSwitchPrecompute(pLWE, scaleSign, unit)

    # -------------------------------
    # ENCRYPT INPUTS
    # -------------------------------
    x = np.random.normal(0, 1, slots).tolist()
    pt_x = cc.MakeCKKSPackedPlaintext(x)
    pt_0 = cc.MakeCKKSPackedPlaintext([0.0] * slots)

    ct_x = cc.Encrypt(keys.publicKey, pt_x)
    ct_0 = cc.Encrypt(keys.publicKey, pt_0)

    # -------------------------------
    # RUN COMPARISON (MEASURE TIME)
    # -------------------------------
    numValues = slots
    numSlotsOut = numValues

    t0 = time.time()

    ct_cmp = cc.EvalCompareSchemeSwitching(
        ct_x,
        ct_0,
        numValues,
        numSlotsOut
    )

    elapsed = time.time() - t0

    # -------------------------------
    # APPEND TO CSV
    # -------------------------------
    with open(csv_path, "a", newline="") as f:
        writer = csv.writer(f)
        writer.writerow([slots, elapsed])

    print(f"[DONE] slots={slots} time={elapsed:.4f}s appended to {csv_path}")


# -------------------------------------
# CLI: python runnable_sign_eval.py 4096
# -------------------------------------
if __name__ == "__main__":
    if len(sys.argv) < 2:
        print("Usage: python runnable_sign_eval.py <slots> [csv_path]")
        sys.exit(1)

    slots = int(sys.argv[1])
    csv_path = sys.argv[2] if len(sys.argv) > 2 else "results.csv"

    run_sign_eval(slots, csv_path)

This works well with 16, 32, 64, …, 16384 as value for slot.
However, if I try with slot=32768 (which is exactly N // 2), I get this error:

RuntimeError: /root/openfhe-python-packager/build/openfhe-development/src/pke/include/cryptocontext.h:l.433:MakeCKKSPackedPlaintextInternal(): The size [33930] of the vector with values should not be greater than ringDim/2 [32768] if the scheme is CKKS

Am I getting something wrong? From what I’ve understood, each value in the CKKS ciphertext becomes a FHEW ciphertext, the function is evaluated with bootstrapping, and then the CKKS ciphertext is rebuilt.

To test the code, run it with:

OMP_NUM_THREADS=40 python3 runnable_sign_eval.py 16384 results.csv

Note that:

  1. I had to set OMP_NUM_THREADS otherwise with slots > 1024 the script spawns n_cores threads, resulting in a load overflow
  2. with slots=16384 you would need about 170GB of RAM.

EDIT
I forgot to specify the openfhe version: openfhe==1.4.2.0.24.4, Ubuntu 24.04.

Thank you for reporting this. I was able to recreate the bug in C++ (thankfully for much smaller parameters) and I have opened an issue to address it.

As a side remark for your application, the OpenFHE prototype of scheme switching is not really designed to work with such an enormous number of slots, because of how long it takes to perform the 32768 binfhe operations (this is also why the linear transform in EvalFHEWtoCKKS is done in a single level rather than in a recursive fashion consuming more levels like in CKKS bootstrapping and yields this enormous RAM consumption).

Thank you for your answer.

I was thinking about this because I have seen some works using this implementation to compute the ReLU function in DL network.

Can I ask you if there is a plan to support this operation with this very high number of slots, or if there is a mathematical/technological constraints that prevents a faster implementation?

The bottleneck is FHEW, which does not support SIMD instructions for a vector of inputs, and therefore the parallelism is limited by the number of threads on your machine.

In terms of practicality of the scheme switching implementation, either use sparse packing with fewer slots (so more ciphertexts but cheaper transformation, mentioned here) or implement the transformation via FFT (the way it is done for CKKS bootstrapping).

More promising are the recent methods for functional bootstrapping in CKKS: https://eprint.iacr.org/2024/1637.pdf, https://eprint.iacr.org/2024/1623.pdf. The latter is implemented in OpenFHE and we plan to expand it in the next release versions.

Thanks for your time, got it.
I will keep an eye on functional bootstrapping, which looks indeed promising for the PP-MDL use.