Skip to content

Commit 2d91bb0

Browse files
nindanaotoclaude
andcommitted
Add tfhe-rs parameter alignment, PBS comparison benchmark, CI, and Apptainer
- include/params/tfhe-rs.hpp: update parameters to match tfhe-rs DEFAULT_PARAMETERS (boolean, 132-bit security): lvl0: n=805, α=5.86e-6 (binary keys) lvl1: k=3, N=512, l=2, Bgbit=10, binary keys, α=9.32e-10 lvl10param: t=5, basebit=3 lvl2: k=2, N=1024, l=2, Bgbit=10 (PARAMETERS_ERROR_PROB_2_POW_MINUS_165) lvl20param: t=5, basebit=3 Add AHlvl1param and AHlvl2param structs required by cloudkey. - include/mulfft.hpp: fix NTT dispatch for AHlvl1param when lvl1param uses T=uint64_t (tfhe-rs.hpp case). AHlvl1param shares the same ring as lvl1param (nbit=9, n=512) so it must use the lvl1 NTT tables, not the lvl2 tables (n=1024). - test/pbs_tfhers.cpp: new benchmark — 1000 HomNAND gates using lvl01param/lvl10param with USE_TFHE_RS parameters; outputs ms/gate and verifies correctness. - bench-tfhers/: standalone Rust crate that benchmarks tfhe-rs boolean NAND gate with DEFAULT_PARAMETERS using the same parameter set. Expects ../tfhe-rs/tfhe as a path dependency. - .github/workflows/compare-tfhers.yml: CI workflow on Ubuntu 24.04 that builds both sides and runs the comparison. - compare-tfhers.def: Apptainer definition (Ubuntu 24.04) for local reproducible benchmarking; copies TFHEpp source via %files, clones tfhe-rs in %post, builds both binaries, runs both in %runscript. - README.md: add "Comparison with tfhe-rs" section with parameter table, results (TFHEpp 6.66 ms vs tfhe-rs 5.997 ms per NAND gate), CPU info (AMD Ryzen 9 9950X3D), and reproduce instructions. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 162e040 commit 2d91bb0

9 files changed

Lines changed: 534 additions & 32 deletions

File tree

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
name: Compare with tfhe-rs
2+
3+
# Runs on every push and can also be triggered manually.
4+
on:
5+
push:
6+
workflow_dispatch:
7+
8+
jobs:
9+
compare-pbs:
10+
name: PBS benchmark (TFHEpp vs tfhe-rs) on Ubuntu 24.04
11+
runs-on: ubuntu-latest
12+
container:
13+
image: ubuntu:24.04
14+
15+
steps:
16+
# ── System dependencies ───────────────────────────────────────────────
17+
- name: Install system dependencies
18+
run: |
19+
apt-get update
20+
DEBIAN_FRONTEND=noninteractive apt-get install -y \
21+
build-essential g++ libomp-dev cmake ninja-build git \
22+
curl wget ca-certificates
23+
24+
# ── Rust toolchain (rustup) ───────────────────────────────────────────
25+
- name: Install Rust via rustup
26+
run: |
27+
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs \
28+
| sh -s -- -y --default-toolchain stable --no-modify-path
29+
echo "$HOME/.cargo/bin" >> "$GITHUB_PATH"
30+
31+
# ── Checkout TFHEpp ───────────────────────────────────────────────────
32+
- name: Checkout TFHEpp
33+
uses: actions/checkout@v4
34+
with:
35+
submodules: recursive
36+
37+
# ── Clone tfhe-rs alongside TFHEpp ───────────────────────────────────
38+
# Layout expected by bench-tfhers/Cargo.toml:
39+
# $GITHUB_WORKSPACE/ ← TFHEpp root
40+
# $GITHUB_WORKSPACE/tfhe-rs/ ← tfhe-rs clone
41+
- name: Clone tfhe-rs
42+
run: |
43+
git clone --depth 1 https://github.com/zama-ai/tfhe-rs.git tfhe-rs
44+
45+
# ── Build TFHEpp pbs_tfhers benchmark ────────────────────────────────
46+
- name: Build TFHEpp (USE_TFHE_RS, Release)
47+
run: |
48+
cmake . -B build -G Ninja \
49+
-DCMAKE_BUILD_TYPE=Release \
50+
-DUSE_TFHE_RS=ON \
51+
-DENABLE_TEST=ON
52+
cmake --build build --target pbs_tfhers
53+
54+
# ── Build tfhe-rs Rust benchmark ──────────────────────────────────────
55+
- name: Install pinned Rust toolchain (from tfhe-rs/rust-toolchain.toml)
56+
working-directory: bench-tfhers
57+
run: rustup show # triggers toolchain download from rust-toolchain.toml
58+
59+
- name: Build tfhe-rs benchmark (Release)
60+
working-directory: bench-tfhers
61+
run: cargo build --release
62+
63+
# ── Show machine info ─────────────────────────────────────────────────
64+
- name: Machine info
65+
run: |
66+
echo "=== CPU info ==="
67+
lscpu | grep -E "Model name|CPU\(s\)|Thread|MHz" || true
68+
echo "=== Memory ==="
69+
free -h || true
70+
71+
# ── Run benchmarks and compare ────────────────────────────────────────
72+
- name: Run TFHEpp pbs_tfhers
73+
run: |
74+
echo "=== TFHEpp NAND gate (USE_TFHE_RS parameters) ==="
75+
echo "Parameters: n=805, k=3, N=512, pbs_level=2, pbs_base_log=10"
76+
build/test/pbs_tfhers
77+
78+
- name: Run tfhe-rs benchmark
79+
run: |
80+
echo "=== tfhe-rs NAND gate (DEFAULT_PARAMETERS) ==="
81+
echo "Parameters: n=805, k=3, N=512, pbs_level=2, pbs_base_log=10"
82+
bench-tfhers/target/release/pbs-tfhers-bench

README.md

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,56 @@ make
128128
./test/test-bootstrapping-fft-spqlios-fma
129129
```
130130

131+
# Comparison with tfhe-rs
132+
133+
TFHEpp can be benchmarked directly against [tfhe-rs](https://github.com/zama-ai/tfhe-rs) using
134+
identical parameters (`DEFAULT_PARAMETERS`, 132-bit security, boolean API).
135+
136+
## Parameter set
137+
138+
Both sides use exactly the same parameters:
139+
140+
| Parameter | Value | Description |
141+
|:---:|:---:|:---|
142+
| `n` | 805 | LWE dimension |
143+
| `k` | 3 | GLWE dimension |
144+
| `N` | 512 | Polynomial size |
145+
| `pbs_level` | 2 | PBS decomposition levels |
146+
| `pbs_base_log` | 10 | PBS decomposition log-base |
147+
| `ks_level` | 5 | Key-switch decomposition levels |
148+
| `ks_base_log` | 3 | Key-switch decomposition log-base |
149+
150+
On TFHEpp's side, enable these with `-DUSE_TFHE_RS=ON`.
151+
The operation benchmarked is one boolean NAND gate (linear combination + PBS + key switch).
152+
153+
## Results
154+
155+
Measured with `apptainer run compare-tfhers.sif` (Ubuntu 24.04, single-threaded).
156+
157+
**CPU**: AMD Ryzen 9 9950X3D 16-Core Processor (max 5752 MHz)
158+
159+
| Library | Time per NAND gate |
160+
|:---:|:---:|
161+
| **TFHEpp** (C++, SPQLIOS-AVX512, `-O3`) | **6.66 ms** |
162+
| **tfhe-rs** (Rust, `DEFAULT_PARAMETERS`) | **5.997 ms** |
163+
164+
## Reproducing locally (Apptainer)
165+
166+
Requires Apptainer and internet access to clone tfhe-rs and pull the Ubuntu 24.04 image.
167+
168+
```bash
169+
git clone --recurse-submodules https://github.com/virtualsecureplatform/TFHEpp
170+
cd TFHEpp
171+
apptainer build compare-tfhers.sif compare-tfhers.def
172+
apptainer run compare-tfhers.sif
173+
```
174+
175+
## CI
176+
177+
The comparison runs automatically on every push via
178+
[`.github/workflows/compare-tfhers.yml`](.github/workflows/compare-tfhers.yml)
179+
using an Ubuntu 24.04 container.
180+
131181
# Theory
132182

133183
Here is the slides (in japanese).

bench-tfhers/Cargo.toml

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
[package]
2+
name = "pbs-tfhers-bench"
3+
version = "0.1.0"
4+
edition = "2021"
5+
# Minimum Rust version required by tfhe-rs
6+
rust-version = "1.82"
7+
8+
# tfhe-rs is expected at ../tfhe-rs relative to the TFHEpp repository root.
9+
# The CI workflow and Apptainer definition both set up this layout:
10+
#
11+
# <workspace>/
12+
# ├── TFHEpp/ ← this repository
13+
# │ ├── bench-tfhers/ ← this crate
14+
# │ └── tfhe-rs/ ← tfhe-rs clone (git clone … TFHEpp/tfhe-rs)
15+
#
16+
# To build manually from the TFHEpp root:
17+
# git clone --depth 1 https://github.com/zama-ai/tfhe-rs.git tfhe-rs
18+
# cargo build --release --manifest-path bench-tfhers/Cargo.toml
19+
20+
[dependencies]
21+
tfhe = { path = "../tfhe-rs/tfhe", default-features = false, features = ["boolean"] }

bench-tfhers/rust-toolchain.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# Keep in sync with tfhe-rs/rust-toolchain.toml
2+
[toolchain]
3+
channel = "1.91.1"

bench-tfhers/src/main.rs

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
// pbs_tfhers — tfhe-rs boolean NAND gate benchmark for comparison with TFHEpp.
2+
//
3+
// Uses tfhe-rs DEFAULT_PARAMETERS which matches the updated TFHEpp tfhe-rs.hpp:
4+
// lwe_dimension : 805 (n)
5+
// glwe_dimension : 3 (k)
6+
// polynomial_size: 512 (N)
7+
// pbs_base_log : 10 (Bgbit)
8+
// pbs_level : 2 (l)
9+
// ks_base_log : 3 (basebit)
10+
// ks_level : 5 (t)
11+
// lwe_std_dev : 5.86e-6
12+
// glwe_std_dev : 9.32e-10
13+
// security : 132-bit, p-fail = 2^-64.344
14+
//
15+
// Outputs one line: "<ms-per-gate>ms"
16+
// Followed by "Passed" if correctness checks pass.
17+
//
18+
// Matching C++ benchmark: test/pbs_tfhers.cpp
19+
20+
use std::time::Instant;
21+
use tfhe::boolean::prelude::*;
22+
23+
fn main() {
24+
const NUM_TESTS: usize = 1000;
25+
26+
// Key generation uses DEFAULT_PARAMETERS internally.
27+
// This is the slow step (bootstrapping key generation); not timed.
28+
let (client_key, server_key) = gen_keys();
29+
30+
// Prepare random inputs
31+
let inputs_a: Vec<bool> = (0..NUM_TESTS).map(|i| i % 2 == 0).collect();
32+
let inputs_b: Vec<bool> = (0..NUM_TESTS).map(|i| (i / 2) % 2 == 0).collect();
33+
34+
let ct_a: Vec<Ciphertext> = inputs_a.iter().map(|&b| client_key.encrypt(b)).collect();
35+
let ct_b: Vec<Ciphertext> = inputs_b.iter().map(|&b| client_key.encrypt(b)).collect();
36+
37+
// Warmup: one gate to ensure JIT / cache effects are settled
38+
let _ = server_key.nand(&ct_a[0], &ct_b[0]);
39+
40+
// Timed benchmark
41+
let start = Instant::now();
42+
let results: Vec<Ciphertext> = ct_a
43+
.iter()
44+
.zip(ct_b.iter())
45+
.map(|(a, b)| server_key.nand(a, b))
46+
.collect();
47+
let elapsed = start.elapsed();
48+
49+
let ms_per_gate = elapsed.as_secs_f64() * 1000.0 / NUM_TESTS as f64;
50+
println!("{:.3}ms", ms_per_gate);
51+
52+
// Correctness check
53+
for (i, ct_res) in results.iter().enumerate() {
54+
let expected = !(inputs_a[i] && inputs_b[i]);
55+
let got = client_key.decrypt(ct_res);
56+
assert_eq!(
57+
got, expected,
58+
"NAND({}, {}) = {} but expected {}",
59+
inputs_a[i], inputs_b[i], got, expected
60+
);
61+
}
62+
println!("Passed");
63+
}

compare-tfhers.def

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
Bootstrap: docker
2+
From: ubuntu:24.04
3+
4+
# ============================================================================
5+
# compare-tfhers.def — Apptainer image for local TFHEpp vs tfhe-rs comparison
6+
#
7+
# Build (from the TFHEpp repository root, submodules must be initialised):
8+
# git submodule update --init --recursive # if not done yet
9+
# apptainer build compare-tfhers.sif compare-tfhers.def
10+
#
11+
# Run the benchmark:
12+
# apptainer run compare-tfhers.sif
13+
#
14+
# Both sides use identical parameters (tfhe-rs DEFAULT_PARAMETERS, 132-bit):
15+
# n=805, k=3, N=512, pbs_level=2, pbs_base_log=10,
16+
# ks_level=5, ks_base_log=3
17+
# ============================================================================
18+
19+
%labels
20+
Description TFHEpp vs tfhe-rs NAND-gate PBS comparison on Ubuntu 24.04
21+
22+
%environment
23+
export PATH="/root/.cargo/bin:${PATH}"
24+
export WORKSPACE="/opt/tfhe-compare"
25+
26+
# Copy the local TFHEpp source tree (including initialised submodules) into
27+
# the container at build time. Run `apptainer build` from the TFHEpp root.
28+
%files
29+
CMakeLists.txt /opt/tfhe-compare/TFHEpp/CMakeLists.txt
30+
include /opt/tfhe-compare/TFHEpp/include
31+
src /opt/tfhe-compare/TFHEpp/src
32+
test /opt/tfhe-compare/TFHEpp/test
33+
bench-tfhers /opt/tfhe-compare/TFHEpp/bench-tfhers
34+
thirdparties /opt/tfhe-compare/TFHEpp/thirdparties
35+
36+
%post
37+
export DEBIAN_FRONTEND=noninteractive
38+
export PATH="/root/.cargo/bin:${PATH}"
39+
export WORKSPACE="/opt/tfhe-compare"
40+
41+
# ── System packages ──────────────────────────────────────────────────────
42+
apt-get update
43+
apt-get upgrade -y
44+
apt-get install -y \
45+
build-essential g++ libomp-dev cmake ninja-build \
46+
git curl wget ca-certificates
47+
48+
# ── Rust toolchain (stable + let tfhe-rs pin its own version) ────────────
49+
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs \
50+
| sh -s -- -y --default-toolchain stable --no-modify-path
51+
52+
# ── Clone tfhe-rs alongside TFHEpp ───────────────────────────────────────
53+
# bench-tfhers/Cargo.toml expects: ../tfhe-rs/tfhe (relative to bench-tfhers/)
54+
cd "${WORKSPACE}/TFHEpp"
55+
git clone --depth 1 https://github.com/zama-ai/tfhe-rs.git tfhe-rs
56+
57+
# ── Build TFHEpp pbs_tfhers benchmark ────────────────────────────────────
58+
cmake . -B build -G Ninja \
59+
-DCMAKE_BUILD_TYPE=Release \
60+
-DUSE_TFHE_RS=ON \
61+
-DENABLE_TEST=ON
62+
cmake --build build --target pbs_tfhers
63+
64+
# ── Build tfhe-rs Rust benchmark ─────────────────────────────────────────
65+
cd bench-tfhers
66+
# Download the Rust toolchain pinned in rust-toolchain.toml
67+
rustup show
68+
cargo build --release
69+
cd ..
70+
71+
# Remove large intermediate artefacts to keep image size small
72+
rm -rf build/_deps build/CMakeFiles
73+
74+
%runscript
75+
export PATH="/root/.cargo/bin:${PATH}"
76+
TFHEPP_ROOT="/opt/tfhe-compare/TFHEpp"
77+
78+
echo "============================================================"
79+
echo " TFHEpp vs tfhe-rs — NAND gate bootstrap comparison"
80+
echo " Parameter set: tfhe-rs DEFAULT_PARAMETERS (132-bit security)"
81+
echo " n=805 k=3 N=512 pbs_level=2 pbs_base_log=10"
82+
echo " ks_level=5 ks_base_log=3"
83+
echo "============================================================"
84+
echo ""
85+
86+
echo "--- Machine info ---"
87+
lscpu 2>/dev/null | grep -E "Model name|CPU\(s\)|Thread|MHz" || true
88+
free -h 2>/dev/null || true
89+
echo ""
90+
91+
echo "--- TFHEpp (C++, USE_TFHE_RS) ---"
92+
"${TFHEPP_ROOT}/build/test/pbs_tfhers"
93+
echo ""
94+
95+
echo "--- tfhe-rs (Rust, DEFAULT_PARAMETERS) ---"
96+
"${TFHEPP_ROOT}/bench-tfhers/target/release/pbs-tfhers-bench"
97+
echo ""
98+
echo "Done."
99+
100+
%help
101+
Runs a NAND gate bootstrapping benchmark comparing TFHEpp and tfhe-rs
102+
using identical parameters (tfhe-rs DEFAULT_PARAMETERS, 132-bit security).
103+
104+
Prerequisites (on the build host):
105+
git submodule update --init --recursive
106+
107+
Build the image (requires internet access, run from TFHEpp root):
108+
apptainer build compare-tfhers.sif compare-tfhers.def
109+
110+
Run:
111+
apptainer run compare-tfhers.sif

include/mulfft.hpp

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,10 @@ constexpr uint64_t lvl1P = 1073707009;
6060
template <class P>
6161
inline void TwistNTT(Polynomial<P> &res, PolynomialNTT<P> &a)
6262
{
63-
if constexpr (std::is_same_v<P, lvl1param>)
63+
// AHlvl1param shares the same polynomial ring as lvl1param (same nbit/n),
64+
// so it uses the lvl1 NTT tables regardless of its Torus type.
65+
if constexpr (std::is_same_v<P, lvl1param> ||
66+
std::is_same_v<P, AHlvl1param>)
6467
#ifdef USE_HEXL
6568
{
6669
std::array<uint64_t, lvl1param::n> temp;
@@ -69,7 +72,7 @@ inline void TwistNTT(Polynomial<P> &res, PolynomialNTT<P> &a)
6972
for (int i = 0; i < lvl1param::n; i++) res[i] = (temp[i] << 32) / lvl1P;
7073
}
7174
#else
72-
cuHEpp::TwistNTT<typename lvl1param::T, lvl1param::nbit>(
75+
cuHEpp::TwistNTT<typename P::T, P::nbit>(
7376
res, a, (*ntttablelvl1)[0], (*ntttwistlvl1)[0]);
7477
#endif
7578
else if constexpr (std::is_same_v<typename P::T, uint64_t>)
@@ -125,7 +128,10 @@ inline void TwistFFTrescale(Polynomial<P> &res, const PolynomialInFD<P> &a)
125128
template <class P>
126129
inline void TwistINTT(PolynomialNTT<P> &res, const Polynomial<P> &a)
127130
{
128-
if constexpr (std::is_same_v<P, lvl1param>)
131+
// AHlvl1param shares the same polynomial ring as lvl1param (same nbit/n),
132+
// so it uses the lvl1 NTT tables regardless of its Torus type.
133+
if constexpr (std::is_same_v<P, lvl1param> ||
134+
std::is_same_v<P, AHlvl1param>)
129135
#ifdef USE_HEXL
130136
{
131137
std::array<uint64_t, lvl1param::n> temp;

0 commit comments

Comments
 (0)