use std::sync::{Arc, OnceLock};
use num_bigint::BigInt;
use crate::batch::RnsBatch;
use crate::cpu::CpuBackend;
use crate::gpu::GpuBackend;
pub trait ArithmeticBackend: Send + Sync {
fn batch_add(&self, a: &RnsBatch, b: &RnsBatch) -> RnsBatch;
fn batch_sub(&self, a: &RnsBatch, b: &RnsBatch) -> RnsBatch;
fn batch_mul(&self, a: &RnsBatch, b: &RnsBatch) -> RnsBatch;
fn batch_crt(&self, batch: &RnsBatch) -> Vec<BigInt>;
fn name(&self) -> &'static str;
}
pub struct Executor {
cpu: Arc<CpuBackend>,
gpu: Option<Arc<GpuBackend>>,
pub gpu_threshold: usize,
}
impl Executor {
pub fn init() -> Self {
let cpu = Arc::new(CpuBackend::new());
let gpu = GpuBackend::try_init().ok().map(Arc::new);
match &gpu {
Some(g) => log::info!("adele-ring: GPU backend active ({})", g.adapter_name()),
None => log::info!("adele-ring: no GPU found, using CPU backend"),
}
Self {
cpu,
gpu,
gpu_threshold: 128,
}
}
pub fn has_gpu(&self) -> bool {
self.gpu.is_some()
}
pub fn cpu(&self) -> &CpuBackend {
&self.cpu
}
pub fn gpu(&self) -> Option<&GpuBackend> {
self.gpu.as_deref()
}
fn select(&self, batch_size: usize) -> &dyn ArithmeticBackend {
match &self.gpu {
Some(g) if batch_size >= self.gpu_threshold => g.as_ref(),
_ => self.cpu.as_ref(),
}
}
pub fn add(&self, a: &RnsBatch, b: &RnsBatch) -> RnsBatch {
self.select(a.batch_size).batch_add(a, b)
}
pub fn sub(&self, a: &RnsBatch, b: &RnsBatch) -> RnsBatch {
self.select(a.batch_size).batch_sub(a, b)
}
pub fn mul(&self, a: &RnsBatch, b: &RnsBatch) -> RnsBatch {
self.select(a.batch_size).batch_mul(a, b)
}
pub fn crt(&self, batch: &RnsBatch) -> Vec<BigInt> {
self.cpu.batch_crt(batch)
}
}
impl Default for Executor {
fn default() -> Self {
Self::init()
}
}
static EXECUTOR: OnceLock<Executor> = OnceLock::new();
pub fn executor() -> &'static Executor {
EXECUTOR.get_or_init(Executor::init)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::basis::Basis;
use crate::rns::RnsInt;
#[test]
fn executor_adds_batches() {
let b = Basis::standard();
let x = RnsBatch::from_rns_ints(&vec![RnsInt::from_i64(7, b.clone()); 200]);
let y = RnsBatch::from_rns_ints(&vec![RnsInt::from_i64(35, b.clone()); 200]);
let sum = executor().add(&x, &y);
for item in sum.to_rns_ints() {
assert_eq!(item.to_bigint(), BigInt::from(42));
}
}
#[test]
fn executor_subtracts_batches() {
let b = Basis::standard();
let x = RnsBatch::from_rns_ints(&vec![RnsInt::from_i64(35, b.clone()); 200]);
let y = RnsBatch::from_rns_ints(&vec![RnsInt::from_i64(42, b.clone()); 200]);
let diff = executor().sub(&x, &y);
for item in diff.to_rns_ints() {
assert_eq!(item.to_bigint(), BigInt::from(-7));
}
}
#[test]
fn cpu_gpu_identical() {
let exec = Executor::init();
if !exec.has_gpu() {
return;
}
let b = Basis::standard();
let x = RnsBatch::from_rns_ints(&vec![RnsInt::from_i64(123, b.clone()); 256]);
let y = RnsBatch::from_rns_ints(&vec![RnsInt::from_i64(456, b.clone()); 256]);
let cpu = exec.cpu().batch_add(&x, &y);
let gpu = exec.gpu().unwrap().batch_add(&x, &y);
assert_eq!(cpu.data, gpu.data);
}
}