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 Type | Purpose | Performance | Use Case |
|---|---|---|---|
| Fake Backend | Development, testing | Fast (no encryption) | Development, debugging, testing |
| Real Backend | Production | Slow (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, developmentTrue: 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
| Operation | Noise Impact | Formula |
|---|---|---|
cadd | -1.0 bits | max(noise1, noise2) - 1.0 |
cmult | -50% | min(noise1, noise2) / 2.0 |
pmult | -33% | noise / 1.5 |
rotate | -0.5 bits | noise - 0.5 |
relinearize | -0.3 bits | noise - 0.3 |
rescale | +10.0 bits | noise + 10.0 |
bootstrap | Reset | 100.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
| Phase | Backend | Why |
|---|---|---|
| Development | Fake | Fast iteration, no HE overhead |
| Testing | Fake with noise | Validate bootstrapping, realistic testing |
| Staging | Real | Test actual HE performance |
| Production | Real | Actual encrypted inference |
Backend Comparison
| Feature | Fake Backend | Real Backend |
|---|---|---|
| Speed | Fast (no encryption) | Slow (actual HE) |
| Accuracy | Exact (PyTorch) | Approximate (HE noise) |
| Noise Simulation | Optional | Actual noise |
| Security | None | Full HE security |
| Use Case | Development | Production |
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:
- Fake backend uses exact PyTorch arithmetic
- Real backend has actual HE noise
- Polynomial approximations may differ
Solution: Use noise simulation to predict differences:
backend = FakeBackend(simulate_noise=True)
Next Steps
- Compilation Workflow: Learn the compilation process
- Custom Backends: Implement your own backend
- Examples: Complete working examples