use std::{
collections::BTreeSet,
sync::{Mutex, OnceLock},
};
use openvm_cuda_common::{
common::{device_reset_epoch, get_device},
d_buffer::DeviceBuffer,
stream::GpuDeviceCtx,
};
use crate::{cuda::ntt, prelude::F};
const MAX_LG_DOMAIN_SIZE: usize = ntt::MAX_CUDA_NTT_LOG_DOMAIN_SIZE as usize;
const LG_WINDOW_SIZE: usize = MAX_LG_DOMAIN_SIZE.div_ceil(5);
const WINDOW_SIZE: usize = 1 << LG_WINDOW_SIZE;
const WINDOW_NUM: usize = MAX_LG_DOMAIN_SIZE.div_ceil(LG_WINDOW_SIZE);
const RADIX_TWIDDLES_SIZE: usize = 32 + 64 + 128 + 256 + 512;
static INIT_FORWARD: OnceLock<Mutex<BTreeSet<(i32, u64)>>> = OnceLock::new();
static INIT_INVERSE: OnceLock<Mutex<BTreeSet<(i32, u64)>>> = OnceLock::new();
fn ensure_initialized(inverse: bool) -> Result<(), openvm_cuda_common::error::CudaError> {
let device_key = (get_device()?, device_reset_epoch());
let initialized = if inverse {
&INIT_INVERSE
} else {
&INIT_FORWARD
};
let initialized = initialized.get_or_init(|| Mutex::new(BTreeSet::new()));
let mut initialized = initialized.lock().unwrap();
if initialized.contains(&device_key) {
return Ok(());
}
{
let device_ctx = GpuDeviceCtx::for_device(device_key.0 as u32)?;
let partial_twiddles =
DeviceBuffer::<[F; WINDOW_SIZE]>::with_capacity_on(WINDOW_NUM, &device_ctx);
let twiddles = DeviceBuffer::<F>::with_capacity_on(RADIX_TWIDDLES_SIZE, &device_ctx);
unsafe {
ntt::generate_all_twiddles(&twiddles, inverse, device_ctx.stream.as_raw())?;
ntt::generate_partial_twiddles(&partial_twiddles, inverse, device_ctx.stream.as_raw())?;
}
}
initialized.insert(device_key);
Ok(())
}
struct NttImpl<'a> {
buffer: &'a DeviceBuffer<F>,
lg_domain_size: u32,
padded_poly_size: u32,
poly_count: u32,
is_intt: bool,
stage: u32,
device_ctx: GpuDeviceCtx,
}
impl<'a> NttImpl<'a> {
fn new(
buffer: &'a DeviceBuffer<F>,
lg_domain_size: u32,
padded_poly_size: u32,
poly_count: u32,
is_intt: bool,
device_ctx: &GpuDeviceCtx,
) -> Self {
ensure_initialized(is_intt).expect("failed to initialize CUDA NTT twiddle tables");
Self {
buffer,
lg_domain_size,
padded_poly_size,
poly_count,
is_intt,
stage: 0,
device_ctx: device_ctx.clone(),
}
}
fn step(&mut self, iterations: u32) {
assert!(iterations <= 10);
let radix = if iterations < 6 { 6 } else { iterations };
unsafe {
ntt::ct_mixed_radix_narrow(
self.buffer,
radix,
self.lg_domain_size,
self.stage,
iterations,
self.padded_poly_size,
self.poly_count,
self.is_intt,
self.device_ctx.stream.as_raw(),
)
.expect("failed to launch CUDA mixed-radix NTT step");
}
self.stage += iterations;
}
}
pub fn batch_ntt(
buffer: &DeviceBuffer<F>,
log_trace_height: u32,
log_blowup: u32,
width: u32,
bit_reverse: bool,
is_intt: bool,
device_ctx: &GpuDeviceCtx,
) {
if log_trace_height == 0 {
return;
}
assert!(
log_trace_height <= ntt::MAX_CUDA_NTT_LOG_DOMAIN_SIZE,
"CUDA batch_ntt supports log_trace_height <= {}",
ntt::MAX_CUDA_NTT_LOG_DOMAIN_SIZE
);
let padded_poly_size = 1 << (log_trace_height + log_blowup);
if bit_reverse {
unsafe {
ntt::bit_rev(
buffer,
buffer,
log_trace_height,
padded_poly_size,
width,
device_ctx.stream.as_raw(),
)
.expect("failed to launch CUDA bit-reversal permutation");
}
}
let mut _impl = NttImpl::new(
buffer,
log_trace_height,
padded_poly_size,
width,
is_intt,
device_ctx,
);
if log_trace_height <= 10 {
_impl.step(log_trace_height);
} else if log_trace_height <= 17 {
let step = log_trace_height / 2;
_impl.step(step + log_trace_height % 2);
_impl.step(step);
} else if log_trace_height <= ntt::MAX_CUDA_NTT_LOG_DOMAIN_SIZE {
let step = log_trace_height / 3;
let rem = log_trace_height % 3;
_impl.step(step);
_impl.step(step);
_impl.step(step + rem);
} else {
unreachable!("log_trace_height is bounded above");
}
}