use std::sync::Arc;
use getset::Getters;
use openvm_cuda_common::{
copy::{MemCopyD2D, MemCopyH2D},
d_buffer::DeviceBuffer,
error::CudaError,
stream::{cudaStream_t, GpuDeviceCtx},
};
use openvm_stark_backend::prover::MatrixDimensions;
use p3_field::PrimeCharacteristicRing;
use crate::{
base::DeviceMatrix,
cuda::{
batch_ntt_small::{batch_ntt_small, validate_gpu_l_skip},
mle_interpolate::{
mle_interpolate_fused_2d, mle_interpolate_shared_2d, mle_interpolate_stage_2d,
MLE_SHARED_TILE_LOG_SIZE,
},
poly::{
eq_hypercube_interleaved_stage_ext, eq_hypercube_nonoverlapping_stage_ext,
eq_hypercube_stage_ext, mobius_eq_hypercube_stage_ext,
},
sumcheck::fold_mle_column,
LOG_WARP_SIZE,
},
prelude::{EF, F},
KernelError,
};
#[derive(derive_new::new, Getters)]
pub struct PleMatrix<F> {
#[getset(get = "pub")]
pub(crate) mixed: DeviceBuffer<F>,
height: usize,
width: usize,
}
impl<F> MatrixDimensions for PleMatrix<F> {
fn width(&self) -> usize {
self.width
}
fn height(&self) -> usize {
self.height
}
}
impl PleMatrix<F> {
pub fn from_evals(
l_skip: usize,
evals: DeviceBuffer<F>,
height: usize,
width: usize,
device_ctx: &GpuDeviceCtx,
) -> Self {
validate_gpu_l_skip(l_skip).expect("GPU PleMatrix requires l_skip <= 9");
let mut mixed = evals;
if l_skip > 0 {
let num_uni_poly = width * (height >> l_skip);
unsafe {
batch_ntt_small(
&mut mixed,
l_skip,
num_uni_poly,
true,
device_ctx.stream.as_raw(),
)
.unwrap();
}
}
Self {
mixed,
height,
width,
}
}
pub fn to_evals(
&self,
l_skip: usize,
device_ctx: &GpuDeviceCtx,
) -> Result<DeviceMatrix<F>, KernelError> {
validate_gpu_l_skip(l_skip)?;
let width = self.width();
let height = self.height();
let mut evals = self.mixed.device_copy_on(device_ctx)?;
if l_skip > 0 {
let num_uni_poly = width * (height >> l_skip);
unsafe {
batch_ntt_small(
&mut evals,
l_skip,
num_uni_poly,
false,
device_ctx.stream.as_raw(),
)
.unwrap();
}
}
Ok(DeviceMatrix::new(Arc::new(evals), height, width))
}
}
pub fn mle_evals_to_coeffs_inplace(
evals: &mut DeviceBuffer<F>,
n: usize,
device_ctx: &GpuDeviceCtx,
) -> Result<(), CudaError> {
if n == 0 {
return Ok(());
}
debug_assert!(evals.len().is_multiple_of(1 << n));
let width = evals.len() >> n;
unsafe {
mle_interpolate_stages(
evals.as_mut_ptr(),
width,
1 << n,
0,
0,
n as u32 - 1,
true,
false,
device_ctx.stream.as_raw(),
)
}
}
#[allow(clippy::too_many_arguments)]
pub unsafe fn mle_interpolate_stages(
buffer: *mut F,
width: usize,
padded_height: u32,
log_blowup: u32,
start_log_step: u32,
end_log_step: u32,
is_eval_to_coeff: bool,
right_pad: bool,
stream: cudaStream_t,
) -> Result<(), CudaError> {
if start_log_step > end_log_step {
return Ok(());
}
let mut current_log_step = start_log_step;
let warp_end = end_log_step.min(LOG_WARP_SIZE as u32 - 1);
let warp_stages = warp_end.saturating_sub(current_log_step) + 1;
if current_log_step < LOG_WARP_SIZE as u32 && warp_stages >= 2 {
let num_stages = warp_stages;
let start_step = 1u32 << current_log_step;
mle_interpolate_fused_2d(
buffer,
width,
padded_height,
log_blowup,
start_step,
num_stages,
is_eval_to_coeff,
right_pad,
stream,
)?;
current_log_step = warp_end + 1;
}
if current_log_step > end_log_step {
return Ok(());
}
if current_log_step < MLE_SHARED_TILE_LOG_SIZE {
let shared_end = end_log_step.min(MLE_SHARED_TILE_LOG_SIZE - 1);
mle_interpolate_shared_2d(
buffer,
width,
padded_height,
log_blowup,
current_log_step,
shared_end,
is_eval_to_coeff,
right_pad,
stream,
)?;
current_log_step = shared_end + 1;
}
assert!(
current_log_step > end_log_step || !right_pad,
"bit_reversed mode not supported for log_step >= MLE_SHARED_TILE_LOG_SIZE"
);
let height = padded_height >> log_blowup;
while current_log_step <= end_log_step {
let step = 1u32 << current_log_step;
mle_interpolate_stage_2d(
buffer,
width,
height,
padded_height,
step,
is_eval_to_coeff,
stream,
)?;
current_log_step += 1;
}
Ok(())
}
pub unsafe fn evals_eq_hypercube(
out: &mut DeviceBuffer<EF>,
xs: &[EF],
device_ctx: &GpuDeviceCtx,
) -> Result<(), KernelError> {
let n = xs.len();
assert!(out.len() >= 1 << n);
[EF::ONE]
.copy_to_on(out, device_ctx)
.map_err(KernelError::MemCopy)?;
for (i, &x_i) in xs.iter().enumerate() {
let step = 1 << i;
eq_hypercube_stage_ext(out.as_mut_ptr(), x_i, step, device_ctx.stream.as_raw())
.map_err(KernelError::Kernel)?;
}
Ok(())
}
pub unsafe fn evals_mobius_eq_hypercube(
out: &mut DeviceBuffer<EF>,
omega: &[EF],
device_ctx: &GpuDeviceCtx,
) -> Result<(), KernelError> {
let n = omega.len();
assert!(out.len() >= 1 << n);
[EF::ONE]
.copy_to_on(out, device_ctx)
.map_err(KernelError::MemCopy)?;
for (i, &omega_i) in omega.iter().enumerate() {
let step = 1 << i;
mobius_eq_hypercube_stage_ext(out.as_mut_ptr(), omega_i, step, device_ctx.stream.as_raw())
.map_err(KernelError::Kernel)?;
}
Ok(())
}
#[derive(Getters)]
pub struct EqEvalSegments<F> {
#[getset(get = "pub")]
pub(crate) buffer: DeviceBuffer<F>,
#[getset(get_copy = "pub")]
max_n: usize,
}
impl<F> EqEvalSegments<F> {
pub fn get_ptr(&self, n: usize) -> *const F {
assert!(n <= self.max_n);
unsafe { self.buffer.as_ptr().add(1 << n) }
}
pub unsafe fn from_raw_parts(buffer: DeviceBuffer<F>, max_n: usize) -> Self {
Self { buffer, max_n }
}
}
impl EqEvalSegments<EF> {
pub fn new(x: &[EF], device_ctx: &GpuDeviceCtx) -> Result<Self, KernelError> {
let max_n = x.len();
let mut buffer = DeviceBuffer::with_capacity_on(2 << max_n, device_ctx);
[EF::ZERO, EF::ONE]
.copy_to_on(&mut buffer, device_ctx)
.map_err(KernelError::MemCopy)?;
for (i, &x_i) in x.iter().enumerate() {
let step = 1 << i;
unsafe {
let dst = buffer.as_mut_ptr().add(2 * step);
let src = buffer.as_ptr().add(step);
eq_hypercube_nonoverlapping_stage_ext(
dst,
src,
x_i,
step as u32,
device_ctx.stream.as_raw(),
)
.map_err(KernelError::Kernel)?;
}
}
Ok(Self { buffer, max_n })
}
}
#[derive(Getters)]
pub struct EqEvalLayers<F> {
pub layers: Vec<Arc<DeviceBuffer<F>>>,
}
impl<F> EqEvalLayers<F> {
pub fn get_ptr(&self, n: usize) -> *const F {
debug_assert_eq!(self.layers[n].len(), 1 << n);
self.layers[n].as_ptr()
}
}
impl EqEvalLayers<EF> {
pub(crate) fn one_layer(
device_ctx: &GpuDeviceCtx,
) -> Result<Arc<DeviceBuffer<EF>>, KernelError> {
[EF::ONE]
.to_device_on(device_ctx)
.map(Arc::new)
.map_err(KernelError::MemCopy)
}
pub fn new_rev<'a>(
n: usize,
x: impl IntoIterator<Item = &'a EF>,
device_ctx: &GpuDeviceCtx,
) -> Result<Self, KernelError> {
Self::new_rev_with_one(n, x, Self::one_layer(device_ctx)?, device_ctx)
}
pub(crate) fn new_rev_with_one<'a>(
n: usize,
x: impl IntoIterator<Item = &'a EF>,
layer_0: Arc<DeviceBuffer<EF>>,
device_ctx: &GpuDeviceCtx,
) -> Result<Self, KernelError> {
let mut layers = Vec::with_capacity(n + 1);
layers.push(layer_0);
for (i, &x_i) in x.into_iter().enumerate() {
let step = 1 << i;
let buffer = DeviceBuffer::with_capacity_on(2 * step, device_ctx);
unsafe {
let dst = buffer.as_mut_ptr();
let src = layers.last().unwrap().as_ptr();
eq_hypercube_interleaved_stage_ext(
dst,
src,
x_i,
step as u32,
device_ctx.stream.as_raw(),
)
.map_err(KernelError::Kernel)?;
}
layers.push(Arc::new(buffer));
}
Ok(Self { layers })
}
pub fn new<'a>(
n: usize,
x: impl IntoIterator<Item = &'a EF>,
device_ctx: &GpuDeviceCtx,
) -> Result<Self, KernelError> {
Self::new_with_one(n, x, Self::one_layer(device_ctx)?, device_ctx)
}
pub(crate) fn new_with_one<'a>(
n: usize,
x: impl IntoIterator<Item = &'a EF>,
layer_0: Arc<DeviceBuffer<EF>>,
device_ctx: &GpuDeviceCtx,
) -> Result<Self, KernelError> {
let mut layers = Vec::with_capacity(n + 1);
layers.push(layer_0);
for (i, &x_i) in x.into_iter().enumerate() {
let step = 1 << i;
let buffer = DeviceBuffer::with_capacity_on(2 * step, device_ctx);
unsafe {
let dst = buffer.as_mut_ptr();
let src = layers.last().unwrap().as_ptr();
eq_hypercube_nonoverlapping_stage_ext(
dst,
src,
x_i,
step as u32,
device_ctx.stream.as_raw(),
)
.map_err(KernelError::Kernel)?;
}
layers.push(Arc::new(buffer));
}
Ok(Self { layers })
}
}
pub struct SqrtHyperBuffer {
pub low: DeviceBuffer<EF>,
pub high: DeviceBuffer<EF>,
pub low_capacity: usize,
pub size: usize,
}
impl SqrtHyperBuffer {
pub fn from_xi(xi: &[EF], device_ctx: &GpuDeviceCtx) -> Result<Self, KernelError> {
let low = {
let mut res = DeviceBuffer::with_capacity_on(1 << (xi.len() / 2), device_ctx);
unsafe { evals_eq_hypercube(&mut res, &xi[..xi.len() / 2], device_ctx)? };
res
};
let high = {
let mut res = DeviceBuffer::with_capacity_on(1 << xi.len().div_ceil(2), device_ctx);
unsafe { evals_eq_hypercube(&mut res, &xi[xi.len() / 2..], device_ctx)? };
res
};
Ok(Self {
low,
high,
low_capacity: 1 << (xi.len() / 2),
size: 1 << xi.len(),
})
}
pub fn fold_columns(&mut self, r: EF, device_ctx: &GpuDeviceCtx) -> Result<(), CudaError> {
assert!(self.size > 1);
if self.size > self.low_capacity {
unsafe {
fold_mle_column(
&mut self.high,
self.size / self.low_capacity,
r,
device_ctx.stream.as_raw(),
)?;
}
} else {
unsafe {
fold_mle_column(&mut self.low, self.size, r, device_ctx.stream.as_raw())?;
}
};
self.size /= 2;
Ok(())
}
}
pub struct SqrtEqLayers {
pub low: EqEvalLayers<EF>,
pub high: EqEvalLayers<EF>,
}
impl SqrtEqLayers {
pub fn from_xi(xi: &[EF], device_ctx: &GpuDeviceCtx) -> Result<Self, KernelError> {
let n = xi.len();
let low_n = n / 2;
let high_n = n - low_n;
let layer_0 = EqEvalLayers::one_layer(device_ctx)?;
let low = EqEvalLayers::new_with_one(
low_n,
xi[high_n..].iter().rev(),
Arc::clone(&layer_0),
device_ctx,
)?;
let high =
EqEvalLayers::new_with_one(high_n, xi[..high_n].iter().rev(), layer_0, device_ctx)?;
Ok(Self { low, high })
}
pub fn max_n(&self) -> usize {
self.low_n() + self.high_n()
}
pub fn low_n(&self) -> usize {
self.low.layers.len() - 1
}
pub fn high_n(&self) -> usize {
self.high.layers.len() - 1
}
pub fn drop_layer(&mut self) {
if self.high.layers.len() > 1 {
self.high.layers.pop();
} else if self.low.layers.len() > 1 {
self.low.layers.pop();
}
}
}