Skip to content

Add a benchmark for CKKS FE functional bootstrapping - #1372

Merged
pascoec merged 3 commits into
devfrom
fefbt-benchmarks
Oct 20, 2026
Merged

pascoec merged 3 commits into
devfrom
fefbt-benchmarks

Conversation

@yspolyakov

@yspolyakov yspolyakov commented Oct 16, 2026 •

Copy link
Copy Markdown
Contributor

Benchmark for CKKS FE functional bootstrapping

Adds benchmark/src/ckks-fe-functional-bootstrapping.cpp, a benchmark for EvalFEFuncBootstrap. It covers continuous functions evaluated through their Fourier extensions, alongside the existing discrete benchmark ckks-functional-bootstrapping.cpp. No library code changes.

Functions

Function Interval
identity [-0.5, 0.5]
exp [-2, 2]
tanh [-4, 4]
sigmoid [-8, 8]
GELU (tanh approximation) [-8, 8]

The Fourier coefficients were generated with fefbt-python. For each scaling factor, the degree is the lowest at which FE functional bootstrapping reaches its noise floor. Within a depth step, a higher degree costs no extra level: degrees 6–13, 14–27 and 28–59 use 3, 4 and 5 levels.

Scaling factor identity exp tanh sigmoid gelu
59 bits 27 27 40 40 45
53 / 55 bits 27 27 40 40 40
29 bits, 1 level 5 5 8 8 10
29 bits, 6 levels 5 5 5 5 5

At 59 bits these degrees match or beat the tables in FE-functional-bootstrapping-ckks.cpp / ckksrns-fhe.h:

  • exp at degree 27 matches degree 29 at one level less.
  • identity at degree 27 gives 35.2 bits, against 34.5 at degree 25.
  • sigmoid degree 34 is about 1.5 bits below the floor.
  • the new gelu degree 45 is about 2.4 bits more accurate as an approximation than degree 44.

Configurations

All rows use SPARSE_ENCAPSULATED, all N/2 slots, and a first modulus one bit larger than the scaling factor, as FE functional bootstrapping requires. The ring dimension is the smallest one that keeps log(QP) within the 128-bit limits for sparse secrets: 767 / 1553 / 3104 bits for N = 2^15 / 2^16 / 2^17. The number of digits is the smallest that fits under that limit, which also keeps the keys small. The level budget was then tuned for runtime.

Scaling factor Technique Levels after bootstrapping N Level budget Digits log(QP)
29 FLEXIBLEAUTO 1 2^15 {3,2} 4 (identity, exp), 7 (others) 761 / 730
29 FLEXIBLEAUTO 6 2^15 {2,2} 24 757
59 FLEXIBLEAUTO, FIXEDMANUAL 1 2^16 {3,2} 8 (identity, exp), 12 (others) 1539 / 1538
55 (identity, exp), 53 (others) FLEXIBLEAUTO, FIXEDMANUAL 6 2^16 {2,2} 13 / 14 1551 / 1552
59 FLEXIBLEAUTO, FIXEDMANUAL 10 2^17 {4,3} 2 2968 / 3027

Notes:

  • 29 bits with 6 levels fits N = 2^15 only with a degree-5 series, level budget {2,2} and one modulus per digit. At degree 5, tanh, sigmoid and gelu lose about 1.2 bits.
  • 6 levels at N = 2^16: 59 bits cannot fit with 6 levels at N = 2^16, so these rows use the largest scaling factor that fits. tanh, sigmoid and gelu could reach 55 bits only with 27 digits (about 60 GB of rotation keys), so they use 53 bits with 14 digits.
  • Level budget: a 1-level SlotsToCoeffs ({3,1}, {4,1}) does not fit in memory, even at N = 2^15. A 2-level one ({4,2}, {5,2}) needs more rotation keys and was slower at N = 2^17.
  • Levels after bootstrapping follows the convention of the example: depth = GetFEFBTDepth + levels after bootstrapping. Because SlotsToCoeffs runs first, the output also keeps levelBudget[1] extra levels for the next bootstrap.

Precision

Mean error relative to the range of the function, in bits, measured at N = 2^15 with the same moduli:

Scaling factor FLEXIBLEAUTO FIXEDMANUAL
59 bits ~35.0 ~32
55 bits ~32.7 —
53 bits ~29.8 —
29 bits ~8.3 —

Each benchmark reports a precision counter and a logQP counter. A row whose log(QP) exceeds the limit for its ring dimension is skipped with an error.

Results

Time per EvalFEFuncBootstrap call, 4 iterations, measured on:

  • Intel Core i9-11900H @ 2.50 GHz (8 cores / 16 threads), 15 GB RAM
  • Ubuntu 22.04 on WSL2, GCC 11.4, Release build, OpenMP with 16 threads, NATIVE_SIZE=64, WITH_NATIVEOPT=OFF
Benchmark Time (s) Precision (bits) log(QP)
FEFBT/identity/FA/sf:29/levels:1/logN:15 2.53 8.56 761
FEFBT/exp/FA/sf:29/levels:1/logN:15 2.74 8.46 761
FEFBT/tanh/FA/sf:29/levels:1/logN:15 3.08 8.37 730
FEFBT/sigmoid/FA/sf:29/levels:1/logN:15 3.28 8.35 730
FEFBT/gelu/FA/sf:29/levels:1/logN:15 3.96 8.35 730

The 29-bit rows with 6 levels (N = 2^15, 24 digits) and all N = 2^16 / 2^17 rows need more than 15 GB of RAM. Results on a larger machine:

Running

make ckks-fe-functional-bootstrapping
./bin/benchmark/ckks-fe-functional-bootstrapping --benchmark_filter='logN:15'

Benchmarks EvalFEFuncBootstrap for identity, exp, tanh, sigmoid and GELU
(Fourier extensions generated with fefbt-python) with SPARSE_ENCAPSULATED
secrets and full packing:
- 29-bit FLEXIBLEAUTO with 1 and 6 levels after bootstrapping (N = 2^15)
- 59-bit FLEXIBLEAUTO/FIXEDMANUAL with 1 level (N = 2^16)
- 53/55-bit FLEXIBLEAUTO/FIXEDMANUAL with 6 levels (N = 2^16)
- 59-bit FLEXIBLEAUTO/FIXEDMANUAL with 10 levels (N = 2^17)

Each configuration uses the smallest ring dimension and number of digits
that keep log(QP) within 767 / 1553 / 3104 bits; the series degrees are the
lowest that reach the precision floor of the scaling factor. Each run
reports precision and log(QP) counters and is skipped if log(QP) exceeds
the limit.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@yspolyakov yspolyakov added this to the Release 1.6.2 milestone Oct 16, 2026
@yspolyakov
yspolyakov requested a review from pascoec October 16, 2026 19:18
@pascoec

pascoec commented Oct 17, 2026

Copy link
Copy Markdown
Collaborator

Measured on icelake (2× Intel Xeon Platinum 8360Y, 72 cores, all cores fixed at 2.4 GHz, turbo off, machine otherwise idle), PR head bd4bcedf built Release with GCC 13.3 (libgomp), OpenMP on, WITH_NATIVEOPT=OFF. Time per EvalFEFuncBootstrap call, mean of the benchmark's 4 iterations, wall clock. 1 thread: numactl --physcpubind=0 --preferred=0; 36 threads: numactl --cpunodebind=0,1 (one socket).

scaling sf levels log N function 1 thread 36 threads speedup precision (bits) log(QP) peak memory
FLEXIBLEAUTO 29 1 15 identity 9.11 s 1.93 s 4.7× 8.6 761 7 GB
FLEXIBLEAUTO 29 1 15 exp 9.11 s 1.94 s 4.7× 8.5 761 7 GB
FLEXIBLEAUTO 29 1 15 tanh 11.3 s 2.23 s 5.1× 8.3 730 9 GB
FLEXIBLEAUTO 29 1 15 sigmoid 11.3 s 2.28 s 5.0× 8.3 730 9 GB
FLEXIBLEAUTO 29 1 15 gelu 11.4 s 2.25 s 5.1× 8.4 730 9 GB
FLEXIBLEAUTO 29 6 15 identity 36.7 s 5.96 s 6.2× 8.6 757 25 GB
FLEXIBLEAUTO 29 6 15 exp 36.9 s 5.92 s 6.2× 8.5 757 25 GB
FLEXIBLEAUTO 29 6 15 tanh 36.8 s 6.01 s 6.1× 7.0 757 25 GB
FLEXIBLEAUTO 29 6 15 sigmoid 36.7 s 5.95 s 6.2× 7.0 757 25 GB
FLEXIBLEAUTO 29 6 15 gelu 36.8 s 5.96 s 6.2× 7.0 757 25 GB
FLEXIBLEAUTO 59 1 16 identity 33.0 s 6.02 s 5.5× 35.2 1539 32 GB
FLEXIBLEAUTO 59 1 16 exp 32.9 s 6.04 s 5.4× 34.9 1539 32 GB
FLEXIBLEAUTO 59 1 16 tanh 40.8 s 6.86 s 5.9× 35.0 1538 44 GB
FLEXIBLEAUTO 59 1 16 sigmoid 40.8 s 6.86 s 5.9× 35.0 1538 44 GB
FLEXIBLEAUTO 59 1 16 gelu 41.2 s 7.08 s 5.8× 35.1 1538 44 GB
FIXEDMANUAL 59 1 16 identity 30.7 s 5.61 s 5.5× 32.3 1539 32 GB
FIXEDMANUAL 59 1 16 exp 30.8 s 5.61 s 5.5× 31.5 1539 32 GB
FIXEDMANUAL 59 1 16 tanh 38.0 s 6.32 s 6.0× 31.6 1538 44 GB
FIXEDMANUAL 59 1 16 sigmoid 37.9 s 6.36 s 6.0× 31.6 1538 44 GB
FIXEDMANUAL 59 1 16 gelu 38.3 s 6.46 s 5.9× 31.9 1538 44 GB
FLEXIBLEAUTO 55 6 16 identity 79.6 s 11.6 s 6.9× 33.0 1551 63 GB
FLEXIBLEAUTO 55 6 16 exp 78.6 s 11.7 s 6.7× 32.9 1551 63 GB
FLEXIBLEAUTO 53 6 16 tanh 86.7 s 12.4 s 7.0× 29.8 1552 69 GB
FLEXIBLEAUTO 53 6 16 sigmoid 85.9 s 12.4 s 6.9× 29.8 1552 68 GB
FLEXIBLEAUTO 53 6 16 gelu 85.9 s 12.6 s 6.8× 29.7 1552 69 GB
FIXEDMANUAL 55 6 16 identity 75.3 s 11.4 s 6.6× 28.5 1551 63 GB
FIXEDMANUAL 55 6 16 exp 75.3 s 11.3 s 6.7× 27.2 1551 63 GB
FIXEDMANUAL 53 6 16 tanh 81.7 s 11.8 s 6.9× 26.8 1552 69 GB
FIXEDMANUAL 53 6 16 sigmoid 81.8 s 11.8 s 6.9× 26.7 1552 69 GB
FIXEDMANUAL 53 6 16 gelu 81.7 s 11.8 s 6.9× 27.3 1552 69 GB
FLEXIBLEAUTO 59 10 17 identity 90.5 s 14.8 s 6.1× 34.0 2968 38 GB
FLEXIBLEAUTO 59 10 17 exp 90.5 s 14.7 s 6.2× 33.9 2968 38 GB
FLEXIBLEAUTO 59 10 17 tanh 98.0 s 15.4 s 6.4× 33.9 3027 39 GB
FLEXIBLEAUTO 59 10 17 sigmoid 97.9 s 15.5 s 6.3× 34.0 3027 39 GB
FLEXIBLEAUTO 59 10 17 gelu 100 s 15.7 s 6.4× 33.9 3027 39 GB
FIXEDMANUAL 59 10 17 identity 81.1 s 13.6 s 6.0× 31.0 2968 38 GB
FIXEDMANUAL 59 10 17 exp 81.1 s 13.5 s 6.0× 29.2 2968 38 GB
FIXEDMANUAL 59 10 17 tanh 86.0 s 14.1 s 6.1× 29.4 3027 39 GB
FIXEDMANUAL 59 10 17 sigmoid 86.0 s 14.1 s 6.1× 29.4 3027 39 GB
FIXEDMANUAL 59 10 17 gelu 87.4 s 14.2 s 6.2× 30.1 3027 39 GB

@pascoec pascoec linked an issue Oct 17, 2026 that may be closed by this pull request
Re-tune three configuration groups (36-thread times on a Xeon 8360Y):
- 29-bit, six levels: N = 2^15 {2,2} dnum 24 -> N = 2^16 {5,4} dnum 2,
  36-42% faster, a third of the memory; the full-degree tanh, sigmoid
  and GELU series now fit (+1.3 bits).
- six levels: N = 2^16 {2,2} dnum 13-14 at 53/55 bits -> N = 2^17 {5,4}
  dnum 4 at 59 bits, 8-17% faster, half the memory, 1-4 bits more
  precise.
- 59-bit, ten levels, N = 2^17: {4,3} dnum 2 -> {5,4} dnum 3, 14-18%
  faster, less memory.

The one-level rows are unchanged: N = 2^17 is 29-46% slower per
ciphertext for them. Update the configuration comment accordingly.
@pascoec

pascoec commented Oct 19, 2026

Copy link
Copy Markdown
Collaborator

Pushed updated benchmark configurations for three groups, plus the context-release fix. All changes keep log(QP) within the 128-bit limits for sparse secrets (767 / 1553 / 3104 bits at N = 2^15 / 2^16 / 2^17).

group before after 36 threads 1 thread peak memory
29-bit, six levels N = 2^15, {2,2}, dnum 24, degree-5 series N = 2^16, {5,4}, dnum 2, full-degree series 36–42% faster 38–43% faster 24.6 → 9 GB
six levels, 53/55-bit N = 2^16, {2,2}, dnum 13–14 N = 2^17, 59-bit, {5,4}, dnum 4 8–17% faster 11–20% faster 63–69 → 32 GB
59-bit, ten levels N = 2^17, {4,3}, dnum 2 N = 2^17, {5,4}, dnum 3 14–18% faster 8–11% faster 38–39 → 32 GB

Precision is the same or better in every row: +1.3 bits for tanh, sigmoid and GELU in the 29-bit six-level group (the full-degree series now fit), +1 to +4 bits for the six-level 59-bit group, and unchanged to +0.7 bits for the ten-level group. In the six-level 59-bit group GELU uses the degree-45 series, like the other 59-bit rows. The degree-5 series are no longer used but are kept in the file.

Why they are faster. Near the security limit, the smaller ring dimension forces a {2,2} budget and a very large dnum, and that costs more than doubling N. In the ten-level rows, the deeper {5,4} budget does 13% fewer key switches per call (641 → 555 for identity, 661 → 575 for tanh), and dnum 3 shrinks the auxiliary modulus from 17 to 12 primes.

On the dnum > 8 suggestion. It holds when the chain is deep: both six-level groups are faster at 2N with a small dnum, with less memory and twice the slots. It does not hold per ciphertext for the one-level 59-bit rows (dnum 8 and 12). Their fastest N = 2^17 configuration ({5,4}, dnum 3; also tried {5,4} with dnum 2 and 4, {4,4}, {5,5}, {6,4}) is 29–46% slower per ciphertext, although about a third cheaper per value with twice the slots. Those rows, and the 29-bit one-level rows, are unchanged.

Memory fix (separate commit). Each case now ends with CryptoContextFactory<DCRTPoly>::ReleaseAllContexts(). Previously every context and its bootstrapping precomputations stayed in the factory, and a full single-process run was killed after 27 cases on a 125 GB machine. Now all 42 cases complete in one process, peaking at 42 GB.

Method. Xeon Platinum 8360Y (2 × 36 cores, fixed 2.4 GHz, turbo off), GCC 13.3 Release with OpenMP, WITH_NATIVEOPT=OFF. Times are per EvalFEFuncBootstrap call, mean of 4 iterations, one process per case. 36 threads ran on one socket and 1 thread on CPU 0. Candidates were searched at 36 threads over ring dimension, level budgets {3,2} to {6,4} and dnum 2–5, and compared back to back against the original configuration.

@pascoec pascoec self-assigned this Oct 19, 2026
@pascoec
pascoec merged commit d4320e9 into dev Oct 20, 2026
20 checks passed
@pascoec
pascoec deleted the fefbt-benchmarks branch October 20, 2026 21:54
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add a benchmark for Fourier-Extension CKKS functional bootstrapping

2 participants