Skip to main content

Backends

This guide covers HETorch backends: the fake backend for development and future real backends for production.

Overview

Backends provide the actual implementation of HE operations. HETorch supports two types:

Backend TypePurposePerformanceUse Case
Fake BackendDevelopment, testingFast (no encryption)Development, debugging, testing
Real BackendProductionSlow (actual HE)Production deployment, benchmarking

Fake Backend

The fake backend simulates HE operations using PyTorch tensors without actual encryption.

Basic Usage

from hetorch import FakeBackend, CompilationContext, HEScheme, CKKSParameters

backend = FakeBackend()

context = CompilationContext(
scheme=HEScheme.CKKS,
params=CKKSParameters(...),
backend=backend
)

Configuration

backend = FakeBackend(
simulate_noise=False, # Enable noise simulation
initial_noise_budget=100.0, # Initial noise budget (bits)
warn_on_low_noise=False, # Warn when noise is low
noise_warning_threshold=20.0, # Warning threshold (bits)
noise_model=None # Custom noise model
)

Parameters

simulate_noise

Enable/disable noise budget tracking:

# No noise simulation (fast)
backend = FakeBackend(simulate_noise=False)

# With noise simulation (realistic)
backend = FakeBackend(simulate_noise=True)

When to use:

  • False: Fast testing, development
  • True: Validate bootstrapping needs, realistic testing

initial_noise_budget

Initial noise capacity in bits:

backend = FakeBackend(
simulate_noise=True,
initial_noise_budget=100.0 # 100 bits
)

Guidelines:

  • 100.0: Standard
  • 120.0: More capacity (conservative)
  • 80.0: Less capacity (aggressive)

warn_on_low_noise

Emit warnings when noise budget is low:

backend = FakeBackend(
simulate_noise=True,
warn_on_low_noise=True,
noise_warning_threshold=20.0
)

Output:

Warning: Low noise budget: 18.5 bits remaining

noise_model

Custom noise model for different scenarios:

from hetorch import NoiseModel

# Conservative model (more noise growth)
conservative_model = NoiseModel(
initial_noise_budget=100.0,
mult_noise_factor=3.0, # More noise from multiplication
add_noise_bits=2.0 # More noise from addition
)

backend = FakeBackend(
simulate_noise=True,
noise_model=conservative_model
)

Noise Simulation

How It Works

The fake backend tracks noise budget through operations:

backend = FakeBackend(simulate_noise=True, initial_noise_budget=100.0)

x = backend.encrypt(torch.tensor([1.0, 2.0, 3.0]))
print(f"After encrypt: {x.info.noise_budget:.2f} bits") # 100.00

y = backend.cadd(x, x)
print(f"After cadd: {y.info.noise_budget:.2f} bits") # 99.00 (-1.00)

z = backend.cmult(x, x)
print(f"After cmult: {z.info.noise_budget:.2f} bits") # 50.00 (-50.00)

w = backend.rescale(z)
print(f"After rescale: {w.info.noise_budget:.2f} bits") # 60.00 (+10.00)

v = backend.bootstrap(w)
print(f"After bootstrap: {v.info.noise_budget:.2f} bits") # 100.00 (reset)

Noise Growth by Operation

OperationNoise ImpactFormula
cadd-1.0 bitsmax(noise1, noise2) - 1.0
cmult-50%min(noise1, noise2) / 2.0
pmult-33%noise / 1.5
rotate-0.5 bitsnoise - 0.5
relinearize-0.3 bitsnoise - 0.3
rescale+10.0 bitsnoise + 10.0
bootstrapReset100.0 (initial budget)

Custom Noise Models

Create custom noise models for different scenarios:

from hetorch import NoiseModel

# Optimistic model (less noise growth)
optimistic = NoiseModel(
initial_noise_budget=100.0,
mult_noise_factor=1.5, # Less noise from mult
add_noise_bits=0.5, # Less noise from add
rotate_noise_bits=0.1, # Less noise from rotate
)

# Conservative model (more noise growth)
conservative = NoiseModel(
initial_noise_budget=100.0,
mult_noise_factor=3.0, # More noise from mult
add_noise_bits=2.0, # More noise from add
rotate_noise_bits=1.0, # More noise from rotate
)

# Use custom model
backend = FakeBackend(simulate_noise=True, noise_model=optimistic)

Supported Operations

The fake backend implements all HE operations:

# Ciphertext-ciphertext operations
backend.cadd(ct1, ct2) # Addition
backend.cmult(ct1, ct2) # Multiplication
backend.rotate(ct, steps) # Rotation

# Plaintext-ciphertext operations
backend.padd(ct, pt) # Addition
backend.pmult(ct, pt) # Multiplication

# Scheme-specific operations
backend.rescale(ct) # Rescale (CKKS)
backend.relinearize(ct) # Relinearize
backend.bootstrap(ct) # Bootstrap

# Encryption/decryption
backend.encrypt(tensor) # Encrypt tensor
backend.decrypt(ct) # Decrypt ciphertext

Cost Model

The fake backend provides a simple cost model:

cost_model = backend.get_cost_model()

# Estimate latency (milliseconds)
latency = cost_model.estimate_latency("cmult", {})
print(f"cmult latency: {latency:.2f} ms") # 5.00 ms

# Estimate memory (bytes)
memory = cost_model.estimate_memory("cmult", {})
print(f"cmult memory: {memory} bytes") # 8192 bytes

# Estimate noise growth (bits)
noise = cost_model.estimate_noise_growth("cmult", {})
print(f"cmult noise: {noise:.2f} bits") # 50.00 bits

When to Use Fake Backend

Good for:

  • Development and testing
  • Pass development
  • Debugging compilation issues
  • Rapid iteration
  • CI/CD testing
  • Validating bootstrapping needs

Not good for:

  • Production deployment
  • Actual encrypted inference
  • Performance benchmarking
  • Security evaluation

Example

import torch
import torch.nn as nn
from hetorch import (
HEScheme,
CKKSParameters,
CompilationContext,
HETorchCompiler,
FakeBackend,
)
from hetorch.passes import PassPipeline
from hetorch.passes.builtin import (
InputPackingPass,
NonlinearToPolynomialPass,
RescalingInsertionPass,
)

# Create fake backend with noise simulation
backend = FakeBackend(
simulate_noise=True,
initial_noise_budget=100.0,
warn_on_low_noise=True,
noise_warning_threshold=20.0
)

# Create context
context = CompilationContext(
scheme=HEScheme.CKKS,
params=CKKSParameters(
poly_modulus_degree=8192,
coeff_modulus=[60, 40, 40, 60],
scale=2**40,
noise_budget=100.0
),
backend=backend
)

# Create pipeline
pipeline = PassPipeline([
InputPackingPass(),
NonlinearToPolynomialPass(),
RescalingInsertionPass(strategy="lazy"),
])

# Compile and execute
model = nn.Sequential(
nn.Linear(10, 5),
nn.ReLU(),
nn.Linear(5, 2)
)

compiler = HETorchCompiler(context, pipeline)
compiled_model = compiler.compile(model, torch.randn(1, 10))

# Execute
output = compiled_model(torch.randn(1, 10))
print(f"Output: {output}")

Real Backends (Future)

Real backends integrate with actual HE libraries for production deployment.

SEAL Backend (Future)

Microsoft SEAL integration:

from hetorch.backend import SEALBackend
import seal

# Create SEAL context
parms = seal.EncryptionParameters(seal.scheme_type.ckks)
parms.set_poly_modulus_degree(8192)
parms.set_coeff_modulus(seal.CoeffModulus.Create(8192, [60, 40, 40, 60]))
seal_context = seal.SEALContext(parms)

# Create backend
backend = SEALBackend(seal_context)

# Use with HETorch
context = CompilationContext(
scheme=HEScheme.CKKS,
params=CKKSParameters(...),
backend=backend
)

OpenFHE Backend (Future)

OpenFHE integration:

from hetorch.backend import OpenFHEBackend
import openfhe

# Create OpenFHE context
cc = openfhe.CryptoContextCKKS()
cc.SetPolyModulusDegree(8192)
# ... configure parameters

# Create backend
backend = OpenFHEBackend(cc)

# Use with HETorch
context = CompilationContext(
scheme=HEScheme.CKKS,
params=CKKSParameters(...),
backend=backend
)

TenSEAL Backend (Future)

TenSEAL integration:

from hetorch.backend import TenSEALBackend
import tenseal as ts

# Create TenSEAL context
ts_context = ts.context(
ts.SCHEME_TYPE.CKKS,
poly_modulus_degree=8192,
coeff_mod_bit_sizes=[60, 40, 40, 60]
)

# Create backend
backend = TenSEALBackend(ts_context)

# Use with HETorch
context = CompilationContext(
scheme=HEScheme.CKKS,
params=CKKSParameters(...),
backend=backend
)

Backend Interface

All backends implement the HEBackend interface:

from hetorch.backend import HEBackend

class HEBackend(ABC):
@abstractmethod
def get_supported_operations(self) -> List[str]:
"""Return list of supported operations"""
pass

@abstractmethod
def get_cost_model(self) -> CostModel:
"""Return cost model for this backend"""
pass

@abstractmethod
def cadd(self, ct1, ct2):
"""Ciphertext addition"""
pass

@abstractmethod
def cmult(self, ct1, ct2):
"""Ciphertext multiplication"""
pass

@abstractmethod
def rotate(self, ct, steps):
"""Ciphertext rotation"""
pass

# ... more operations

Implementing Custom Backends

See Custom Backends for details on implementing your own backend.


Choosing a Backend

Development vs Production

PhaseBackendWhy
DevelopmentFakeFast iteration, no HE overhead
TestingFake with noiseValidate bootstrapping, realistic testing
StagingRealTest actual HE performance
ProductionRealActual encrypted inference

Backend Comparison

FeatureFake BackendReal Backend
SpeedFast (no encryption)Slow (actual HE)
AccuracyExact (PyTorch)Approximate (HE noise)
Noise SimulationOptionalActual noise
SecurityNoneFull HE security
Use CaseDevelopmentProduction

Best Practices

1. Develop with Fake Backend

Start with fake backend for rapid iteration:

# Development
backend = FakeBackend(simulate_noise=False) # Fast

# Testing
backend = FakeBackend(simulate_noise=True) # Realistic

# Production (future)
backend = SEALBackend(...) # Actual HE

2. Enable Noise Simulation for Validation

Use noise simulation to validate bootstrapping:

backend = FakeBackend(
simulate_noise=True,
warn_on_low_noise=True,
noise_warning_threshold=20.0
)

# Compile with bootstrapping
pipeline = PassPipeline([
InputPackingPass(),
NonlinearToPolynomialPass(),
RescalingInsertionPass(strategy="lazy"),
BootstrappingInsertionPass(level_threshold=15.0),
])

3. Use Custom Noise Models for Tuning

Experiment with different noise models:

# Conservative (more bootstraps)
conservative = NoiseModel(mult_noise_factor=3.0)
backend = FakeBackend(simulate_noise=True, noise_model=conservative)

# Optimistic (fewer bootstraps)
optimistic = NoiseModel(mult_noise_factor=1.5)
backend = FakeBackend(simulate_noise=True, noise_model=optimistic)

4. Profile Before Switching to Real Backend

Use cost analysis with fake backend:

from hetorch.passes.builtin import CostAnalysisPass

pipeline = PassPipeline([
# ... transformation passes
CostAnalysisPass(verbose=True),
])

# Analyze before deploying to real backend
compiled_model = compiler.compile(model, example_input)
analysis = compiled_model.meta.get('cost_analysis')
print(f"Estimated latency: {analysis.estimated_latency:.2f} ms")

Troubleshooting

Noise Budget Exhausted

Symptom: Warnings about low noise budget

Solution: Add bootstrapping or reduce depth:

# Add bootstrapping
pipeline = PassPipeline([
InputPackingPass(),
NonlinearToPolynomialPass(),
RescalingInsertionPass(strategy="lazy"),
BootstrappingInsertionPass(level_threshold=20.0), # Bootstrap earlier
])

# Or reduce polynomial degree
pipeline = PassPipeline([
InputPackingPass(),
NonlinearToPolynomialPass(degree=6), # Lower degree = less depth
RescalingInsertionPass(strategy="lazy"),
])

Fake Backend Too Slow

Symptom: Fake backend execution is slow

Solution: Disable noise simulation:

# Fast fake backend
backend = FakeBackend(simulate_noise=False)

Differences Between Fake and Real

Symptom: Results differ between fake and real backends

Causes:

  1. Fake backend uses exact PyTorch arithmetic
  2. Real backend has actual HE noise
  3. Polynomial approximations may differ

Solution: Use noise simulation to predict differences:

backend = FakeBackend(simulate_noise=True)

Next Steps