poulpyFully homomorphic encryption
GitHub Get started
Menu

Tune the workload you have.

Understand the cost drivers, select candidates, and measure the complete circuit.

For engineers with a working computation and parameter set · Source guide · 2394fa5

The short version

Start with a correct portable implementation. Select accelerated candidates for your target hardware, then compare them at the same input range, output accuracy, and security assumptions. The benchmark explorer reports 16-level CKKS bootstrapping measurements on an AMD Ryzen 9 9950X, with fixed presets and precision checks. These results were measured with Poulpy 0.8.3 and describe that workload and hardware.

The guidance below describes mechanisms and starting points. Record actual measurements using Measure a workload.

Poulpy’s backend APIs are hardware-independent, and a GPU backend is in active development. The build commands and Rayon diagnostics on this page exercise CPU implementations. For device execution, also account for data transfers, synchronization, batching, and device memory in the measurement boundary.

Key material and arithmetic

Key switching and external products read prepared evaluation-key material. Large keys can make memory traffic a substantial part of the cost. Measure prepared-key bytes as well as serialized size: they are different representations.

Coefficient-domain operations process polynomial limbs. At a fixed ciphertext width, larger base2k means fewer limbs, but the backend also changes the cost and storage of each transformed limb. Transform count alone does not determine runtime.

1. Arithmetic family

FFT64 uses a floating-point transform, while NTT4x30 and NTT3x42 use integer residue transforms. The integer families permit wider limbs in their supported configurations. An accelerated NTT backend may pack its residues differently from the reference implementation.

For small gate-level workloads, include FFT64 in the comparison. For leveled numerical work, include NTT4x30, and NTT3x42 if IFMA is available. Compare key preparation and the full circuit, not only an isolated multiplication. See Backends for types and supported hardware.

2. Backend

Compare the portable baseline with an accelerated implementation of the same arithmetic family first. Then compare arithmetic families. This helps separate hardware implementation effects from representation and parameter changes.

A wider instruction set is a candidate optimization, not a guarantee for every CPU or memory-bound workload. For CPU measurements, record the model, actual target flags, and thread count with each result.

3. Compilation options

Use release mode and the build requirements for your selected backend. The following settings apply to the CPU implementations:

BackendCrate featureTarget features
AVX2/FMAenable-avx+avx2,+fma
AVX-512enable-avx512f+avx512f
IFMAenable-ifma+avx512f,+avx512ifma,+avx512vl
NEONenable-neonAArch64 target

-C target-cpu=native selects the build machine’s instructions; binaries built that way may not run on another machine. The backend guide covers scheme integration and Rayon features.

From the pinned checkout, inspect CPU capabilities:

cargo test --locked -p poulpy-cpu-ref capabilities -- --ignored --nocapture

4. base2k

Choose a limb width within the backend’s supported bounds for the operation and shape. FFT64 commonly uses up to 19 bits; the NTT families support wider configurations up to 52 bits. Confirm the backend’s preconditions, particularly for accumulation and large transforms.

At fixed Torus width k, the limb count is ceil(k / base2k). Smaller limbs increase this count; larger limbs change product/noise bounds and may not suit every configuration. Re-evaluate accuracy and parameter assumptions when changing it.

5. dsize

dsize groups several limbs into one gadget digit. Changing it trades digit count, evaluation-key size, auxiliary precision, and evaluation work. Use the key-layout and noise constraints to select valid candidates, then measure them. There is no workload-independent best value established by this guide.

6. Threads

On CPUs, Rayon variants parallelize supported operations internally. Serial CPU backends can instead be used for independent operations scheduled by the application. Compare these strategies for the latency or throughput goal that matters to you.

More workers can stop helping when memory bandwidth, scheduling, or scratch reservations dominate. The useful pool width depends on the backend and parameter shape; it is not necessarily the CPU’s core count.

Setting the pool

let pool = rayon::ThreadPoolBuilder::new().num_threads(8).build()?;
pool.install(|| {
    // Poulpy calls use this pool; eight is an example, not a recommendation.
});

Measuring your own thread count

The pinned source supplies an ignored diagnostic for vector-matrix products, convolution, inverse transforms, and coefficient arithmetic. On a CPU supporting these flags:

RUSTFLAGS="-C target-cpu=native" cargo test --locked --release -p poulpy-cpu-avx512 \
  --features enable-avx512f,enable-ifma,enable-rayon tuning -- --ignored --nocapture

Run on an idle machine with the ring degree, limb count, and rank relevant to the application. Record repetition spread and any per-kernel worker caps. This diagnostic selects candidates; it does not replace measuring the full circuit, preparation, and memory footprint.

Keep commands, raw results, and the configuration record together so another developer can reproduce the recommendation.