use openvm_cuda_common::{
copy::MemCopyH2D,
d_buffer::DeviceBuffer,
error::{CudaError, MemCopyError},
stream::GpuDeviceCtx,
};
use openvm_stark_backend::prover::{fractional_sumcheck_gkr::Frac, DeviceMultiStarkProvingKey};
use p3_field::PrimeCharacteristicRing;
use tracing::debug;
use crate::{
cuda::logup_zerocheck::{
logup_monomial_batched, precompute_lambda_combinations,
precompute_logup_denom_combinations, precompute_logup_numer_combinations,
zerocheck_monomial_batched, zerocheck_monomial_par_y_batched, BlockCtx, EvalCoreCtx,
LogupMonomialCommonCtx, LogupMonomialCtx, MonomialAirCtx,
},
error::KernelError,
gpu_backend::GenericGpuBackend,
hash_scheme::GpuHashScheme,
logup_zerocheck::{batch_mle::TraceCtx, block_ctxs::build_block_ctxs},
prelude::EF,
};
const THREADS_PER_BLOCK: u32 = 256;
pub(crate) fn trace_has_monomials<HS: GpuHashScheme>(
trace: &TraceCtx,
pk: &DeviceMultiStarkProvingKey<GenericGpuBackend<HS>>,
) -> bool {
trace.has_constraints
&& pk.per_air[trace.air_idx]
.other_data
.zerocheck_monomials
.as_ref()
.map(|m| m.num_monomials > 0)
.unwrap_or(false)
}
pub(crate) fn get_num_monomials<HS: GpuHashScheme>(
trace: &TraceCtx,
pk: &DeviceMultiStarkProvingKey<GenericGpuBackend<HS>>,
) -> u32 {
pk.per_air[trace.air_idx]
.other_data
.zerocheck_monomials
.as_ref()
.map(|m| m.num_monomials)
.unwrap_or(0)
}
pub(crate) fn get_zerocheck_rules_len<HS: GpuHashScheme>(
trace: &TraceCtx,
pk: &DeviceMultiStarkProvingKey<GenericGpuBackend<HS>>,
) -> usize {
pk.per_air[trace.air_idx]
.other_data
.zerocheck_mle
.inner
.d_rules
.len()
}
pub(crate) fn compute_lambda_combinations<HS: GpuHashScheme>(
pk: &DeviceMultiStarkProvingKey<GenericGpuBackend<HS>>,
air_idx: usize,
lambda_pows: &DeviceBuffer<EF>,
device_ctx: &GpuDeviceCtx,
) -> Result<DeviceBuffer<EF>, CudaError> {
let monomials = pk.per_air[air_idx]
.other_data
.zerocheck_monomials
.as_ref()
.expect("AIR must have monomials");
let mut buf =
DeviceBuffer::<EF>::with_capacity_on(monomials.num_monomials as usize, device_ctx);
unsafe {
precompute_lambda_combinations(
&mut buf,
monomials.d_headers.as_ptr(),
monomials.d_lambda_terms.as_ptr(),
lambda_pows,
monomials.num_monomials,
device_ctx.stream.as_raw(),
)?;
}
Ok(buf)
}
pub(crate) struct ZerocheckMonomialBatch<'a> {
traces: Vec<&'a TraceCtx>,
block_ctxs: DeviceBuffer<BlockCtx>,
air_ctxs: DeviceBuffer<MonomialAirCtx>,
air_offsets: DeviceBuffer<u32>,
device_ctx: GpuDeviceCtx,
}
impl<'a> ZerocheckMonomialBatch<'a> {
pub fn new<HS: GpuHashScheme>(
traces: impl IntoIterator<Item = &'a TraceCtx>,
pk: &DeviceMultiStarkProvingKey<GenericGpuBackend<HS>>,
lambda_combinations: &[&DeviceBuffer<EF>],
device_ctx: &GpuDeviceCtx,
) -> Result<Self, MemCopyError> {
let traces: Vec<_> = traces.into_iter().collect();
assert!(
!traces.is_empty(),
"ZerocheckMonomialBatch requires at least one trace"
);
assert_eq!(
traces.len(),
lambda_combinations.len(),
"lambda_combinations must have one buffer per trace"
);
let threads_per_block = THREADS_PER_BLOCK;
let (block_ctxs_h, air_offsets) = build_block_ctxs(traces.iter().map(|t| {
let monomials = pk.per_air[t.air_idx]
.other_data
.zerocheck_monomials
.as_ref()
.unwrap();
let mono_blocks = monomials.num_monomials.div_ceil(threads_per_block);
mono_blocks * t.num_y
}));
let air_ctxs_h: Vec<MonomialAirCtx> = traces
.iter()
.zip(lambda_combinations)
.map(|(t, lc)| {
let monomials = pk.per_air[t.air_idx]
.other_data
.zerocheck_monomials
.as_ref()
.unwrap();
let eval_ctx = EvalCoreCtx {
d_selectors: t.sels_ptr,
d_preprocessed: t.prep_ptr,
d_main: t.main_ptrs_dev.as_ptr(),
d_public: t.public_ptr,
};
MonomialAirCtx {
d_headers: monomials.d_headers.as_ptr(),
d_variables: monomials.d_variables.as_ptr(),
d_lambda_combinations: lc.as_ptr(),
num_monomials: monomials.num_monomials,
eval_ctx,
d_eq_xi: t.eq_xi_ptr,
num_y: t.num_y,
}
})
.collect();
let block_ctxs = block_ctxs_h.to_device_on(device_ctx)?;
let air_ctxs = air_ctxs_h.to_device_on(device_ctx)?;
let air_offsets = air_offsets.to_device_on(device_ctx)?;
debug!(
num_airs = traces.len(),
num_blocks = block_ctxs.len(),
"ZerocheckMonomialBatch created"
);
Ok(Self {
traces,
block_ctxs,
air_ctxs,
air_offsets,
device_ctx: device_ctx.clone(),
})
}
pub fn trace_indices(&self) -> impl Iterator<Item = usize> + '_ {
self.traces.iter().map(|t| t.trace_idx)
}
pub fn evaluate(&self, num_x: u32) -> Result<DeviceBuffer<EF>, KernelError> {
let num_blocks = self.block_ctxs.len();
let num_airs = self.air_ctxs.len();
debug!(
%num_blocks,
%num_x,
%num_airs,
"zerocheck_monomial_batched"
);
let mut tmp_sums =
DeviceBuffer::<EF>::with_capacity_on(num_blocks * num_x as usize, &self.device_ctx);
let mut output =
DeviceBuffer::<EF>::with_capacity_on(num_airs * num_x as usize, &self.device_ctx);
debug_assert_eq!(
self.air_offsets.len(),
num_airs + 1,
"air_offsets must have num_airs + 1 elements"
);
unsafe {
zerocheck_monomial_batched(
&mut tmp_sums,
&mut output,
&self.block_ctxs,
&self.air_ctxs,
&self.air_offsets,
num_blocks as u32,
num_x,
num_airs as u32,
THREADS_PER_BLOCK,
self.device_ctx.stream.as_raw(),
)?;
}
Ok(output)
}
}
const THREADS_PER_BLOCK_PAR_Y: u32 = 128;
const DEFAULT_MAX_MONOMIALS_PER_THREAD: u32 = 64;
const WAVES_TARGET: u32 = 4;
pub(crate) struct ZerocheckMonomialParYBatch<'a> {
traces: Vec<&'a TraceCtx>,
block_ctxs: DeviceBuffer<BlockCtx>,
air_ctxs: DeviceBuffer<MonomialAirCtx>,
air_offsets: DeviceBuffer<u32>,
num_blocks: u32,
chunk_size: u32,
device_ctx: GpuDeviceCtx,
}
impl<'a> ZerocheckMonomialParYBatch<'a> {
#[allow(clippy::too_many_arguments)]
pub fn new<HS: GpuHashScheme>(
traces: impl IntoIterator<Item = &'a TraceCtx>,
pk: &DeviceMultiStarkProvingKey<GenericGpuBackend<HS>>,
lambda_combinations: &[&DeviceBuffer<EF>],
sm_count: u32,
num_x: u32,
max_monomials_per_thread: Option<u32>,
device_ctx: &GpuDeviceCtx,
) -> Result<Self, MemCopyError> {
let traces: Vec<_> = traces.into_iter().collect();
assert!(
!traces.is_empty(),
"ZerocheckMonomialParYBatch requires at least one trace"
);
assert_eq!(
traces.len(),
lambda_combinations.len(),
"lambda_combinations must have one buffer per trace"
);
let threads_per_block = THREADS_PER_BLOCK_PAR_Y;
let max_mono_per_thread =
max_monomials_per_thread.unwrap_or(DEFAULT_MAX_MONOMIALS_PER_THREAD);
let mut per_air_info: Vec<(u32, u32)> = Vec::new(); let mut max_monomials = 0u32;
for t in traces.iter() {
let monomials = pk.per_air[t.air_idx]
.other_data
.zerocheck_monomials
.as_ref()
.expect("AIR with constraints must have monomials");
let y_blocks = t.num_y.div_ceil(threads_per_block);
max_monomials = max_monomials.max(monomials.num_monomials);
per_air_info.push((y_blocks, monomials.num_monomials));
}
let target_blocks = sm_count * WAVES_TARGET;
let mut chunk_size = max_mono_per_thread;
loop {
let total_blocks: u32 = per_air_info
.iter()
.map(|(y_blocks, num_mono)| {
let air_mono_chunks = num_mono.div_ceil(chunk_size);
y_blocks * air_mono_chunks
})
.sum();
if total_blocks * num_x >= target_blocks || chunk_size <= 1 {
break;
}
chunk_size = (chunk_size / 2).max(1);
}
let (block_ctxs_h, air_offsets) =
build_block_ctxs(per_air_info.iter().map(|(y_blocks, num_mono)| {
let air_mono_chunks = num_mono.div_ceil(chunk_size);
y_blocks * air_mono_chunks
}));
let num_blocks = block_ctxs_h.len() as u32;
let air_ctxs_h: Vec<MonomialAirCtx> = traces
.iter()
.zip(lambda_combinations)
.map(|(t, lc)| {
let monomials = pk.per_air[t.air_idx]
.other_data
.zerocheck_monomials
.as_ref()
.unwrap();
let eval_ctx = EvalCoreCtx {
d_selectors: t.sels_ptr,
d_preprocessed: t.prep_ptr,
d_main: t.main_ptrs_dev.as_ptr(),
d_public: t.public_ptr,
};
MonomialAirCtx {
d_headers: monomials.d_headers.as_ptr(),
d_variables: monomials.d_variables.as_ptr(),
d_lambda_combinations: lc.as_ptr(),
num_monomials: monomials.num_monomials,
eval_ctx,
d_eq_xi: t.eq_xi_ptr,
num_y: t.num_y,
}
})
.collect();
let block_ctxs = block_ctxs_h.to_device_on(device_ctx)?;
let air_ctxs = air_ctxs_h.to_device_on(device_ctx)?;
let air_offsets = air_offsets.to_device_on(device_ctx)?;
debug!(
num_airs = traces.len(),
num_blocks, chunk_size, max_monomials, "ZerocheckMonomialParYBatch created"
);
Ok(Self {
traces,
block_ctxs,
air_ctxs,
air_offsets,
num_blocks,
chunk_size,
device_ctx: device_ctx.clone(),
})
}
pub fn trace_indices(&self) -> impl Iterator<Item = usize> + '_ {
self.traces.iter().map(|t| t.trace_idx)
}
pub fn evaluate(&self, num_x: u32) -> Result<DeviceBuffer<EF>, KernelError> {
let num_airs = self.air_ctxs.len();
debug!(
num_blocks = %self.num_blocks,
%num_x,
%num_airs,
chunk_size = %self.chunk_size,
"zerocheck_monomial_par_y_batched"
);
let mut tmp_sums = DeviceBuffer::<EF>::with_capacity_on(
self.num_blocks as usize * num_x as usize,
&self.device_ctx,
);
let mut output =
DeviceBuffer::<EF>::with_capacity_on(num_airs * num_x as usize, &self.device_ctx);
debug_assert_eq!(
self.air_offsets.len(),
num_airs + 1,
"air_offsets must have num_airs + 1 elements"
);
unsafe {
zerocheck_monomial_par_y_batched(
&mut tmp_sums,
&mut output,
&self.block_ctxs,
&self.air_ctxs,
&self.air_offsets,
self.num_blocks,
num_x,
num_airs as u32,
self.chunk_size,
THREADS_PER_BLOCK_PAR_Y,
self.device_ctx.stream.as_raw(),
)?;
}
Ok(output)
}
}
pub struct LogupCombinations {
pub d_numer_combinations: DeviceBuffer<EF>,
pub d_denom_combinations: DeviceBuffer<EF>,
pub bus_term_sum: EF,
}
#[allow(clippy::too_many_arguments)]
pub(crate) fn compute_logup_combinations<HS: GpuHashScheme>(
pk: &DeviceMultiStarkProvingKey<GenericGpuBackend<HS>>,
air_idx: usize,
d_beta_pows: &DeviceBuffer<EF>,
d_eq_3bs: &DeviceBuffer<EF>,
eq_3bs_host: &[EF],
beta_pows_host: &[EF],
device_ctx: &GpuDeviceCtx,
) -> Result<LogupCombinations, CudaError> {
let monomials = pk.per_air[air_idx]
.other_data
.interaction_monomials
.as_ref()
.expect("AIR must have interaction monomials");
let stream = device_ctx.stream.as_raw();
let mut d_numer_combinations = if monomials.num_numer_monomials > 0 {
DeviceBuffer::<EF>::with_capacity_on(monomials.num_numer_monomials as usize, device_ctx)
} else {
DeviceBuffer::new()
};
if monomials.num_numer_monomials > 0 {
unsafe {
precompute_logup_numer_combinations(
&mut d_numer_combinations,
monomials.d_numer_headers.as_ptr(),
monomials.d_numer_terms.as_ptr(),
d_eq_3bs,
monomials.num_numer_monomials,
stream,
)?;
}
}
let mut d_denom_combinations = if monomials.num_denom_monomials > 0 {
DeviceBuffer::<EF>::with_capacity_on(monomials.num_denom_monomials as usize, device_ctx)
} else {
DeviceBuffer::new()
};
if monomials.num_denom_monomials > 0 {
unsafe {
precompute_logup_denom_combinations(
&mut d_denom_combinations,
monomials.d_denom_headers.as_ptr(),
monomials.d_denom_terms.as_ptr(),
d_beta_pows,
d_eq_3bs,
monomials.num_denom_monomials,
stream,
)?;
}
}
let interactions = &pk.per_air[air_idx].vk.symbolic_constraints.interactions;
debug_assert_eq!(
interactions.len(),
eq_3bs_host.len(),
"interaction count must match eq_3bs"
);
let mut bus_term_sum = EF::ZERO;
for (i, interaction) in interactions.iter().enumerate() {
let beta_len = beta_pows_host[interaction.message.len()];
let bus_idx = interaction.bus_index as u32;
bus_term_sum += beta_len * EF::from_u32(bus_idx + 1) * eq_3bs_host[i];
}
Ok(LogupCombinations {
d_numer_combinations,
d_denom_combinations,
bus_term_sum,
})
}
const THREADS_PER_BLOCK_LOGUP: u32 = 128;
pub(crate) struct LogupMonomialBatch<'a> {
traces: Vec<&'a TraceCtx>,
block_ctxs: DeviceBuffer<BlockCtx>,
common_ctxs: DeviceBuffer<LogupMonomialCommonCtx>,
numer_ctxs: DeviceBuffer<LogupMonomialCtx>,
denom_ctxs: DeviceBuffer<LogupMonomialCtx>,
air_offsets: DeviceBuffer<u32>,
num_blocks: u32,
device_ctx: GpuDeviceCtx,
}
impl<'a> LogupMonomialBatch<'a> {
pub fn new<HS: GpuHashScheme>(
traces: impl IntoIterator<Item = &'a TraceCtx>,
pk: &DeviceMultiStarkProvingKey<GenericGpuBackend<HS>>,
logup_combinations: &[&LogupCombinations],
device_ctx: &GpuDeviceCtx,
) -> Result<Self, MemCopyError> {
let traces: Vec<_> = traces.into_iter().collect();
assert!(
!traces.is_empty(),
"LogupMonomialBatch requires at least one trace"
);
assert_eq!(
traces.len(),
logup_combinations.len(),
"logup_combinations must have one entry per trace"
);
let threads_per_block = THREADS_PER_BLOCK_LOGUP;
let (block_ctxs_h, air_offsets) = build_block_ctxs(traces.iter().map(|t| {
let monomials = pk.per_air[t.air_idx]
.other_data
.interaction_monomials
.as_ref()
.unwrap();
let max_monomials = monomials
.num_numer_monomials
.max(monomials.num_denom_monomials);
let mono_blocks = max_monomials.div_ceil(threads_per_block).max(1);
t.num_y * mono_blocks
}));
let num_blocks = block_ctxs_h.len() as u32;
let common_ctxs_h: Vec<LogupMonomialCommonCtx> = traces
.iter()
.zip(logup_combinations)
.map(|(t, lc)| {
let monomials = pk.per_air[t.air_idx]
.other_data
.interaction_monomials
.as_ref()
.unwrap();
let max_monomials = monomials
.num_numer_monomials
.max(monomials.num_denom_monomials);
let mono_blocks = max_monomials.div_ceil(threads_per_block).max(1);
let eval_ctx = EvalCoreCtx {
d_selectors: t.sels_ptr,
d_preprocessed: t.prep_ptr,
d_main: t.main_ptrs_dev.as_ptr(),
d_public: t.public_ptr,
};
LogupMonomialCommonCtx {
eval_ctx,
d_eq_xi: t.eq_xi_ptr,
bus_term_sum: lc.bus_term_sum,
num_y: t.num_y,
mono_blocks,
}
})
.collect();
let numer_ctxs_h: Vec<LogupMonomialCtx> = traces
.iter()
.zip(logup_combinations)
.map(|(t, lc)| {
let monomials = pk.per_air[t.air_idx]
.other_data
.interaction_monomials
.as_ref()
.unwrap();
LogupMonomialCtx {
d_headers: monomials.d_numer_headers.as_ptr(),
d_variables: monomials.d_numer_variables.as_ptr(),
d_combinations: lc.d_numer_combinations.as_ptr(),
num_monomials: monomials.num_numer_monomials,
}
})
.collect();
let denom_ctxs_h: Vec<LogupMonomialCtx> = traces
.iter()
.zip(logup_combinations)
.map(|(t, lc)| {
let monomials = pk.per_air[t.air_idx]
.other_data
.interaction_monomials
.as_ref()
.unwrap();
LogupMonomialCtx {
d_headers: monomials.d_denom_headers.as_ptr(),
d_variables: monomials.d_denom_variables.as_ptr(),
d_combinations: lc.d_denom_combinations.as_ptr(),
num_monomials: monomials.num_denom_monomials,
}
})
.collect();
let block_ctxs = block_ctxs_h.to_device_on(device_ctx)?;
let common_ctxs = common_ctxs_h.to_device_on(device_ctx)?;
let numer_ctxs = numer_ctxs_h.to_device_on(device_ctx)?;
let denom_ctxs = denom_ctxs_h.to_device_on(device_ctx)?;
let air_offsets = air_offsets.to_device_on(device_ctx)?;
debug!(
num_airs = traces.len(),
num_blocks, "LogupMonomialBatch created"
);
Ok(Self {
traces,
block_ctxs,
common_ctxs,
numer_ctxs,
denom_ctxs,
air_offsets,
num_blocks,
device_ctx: device_ctx.clone(),
})
}
pub fn trace_indices(&self) -> impl Iterator<Item = usize> + '_ {
self.traces.iter().map(|t| t.trace_idx)
}
pub fn evaluate(&self, num_x: u32) -> Result<DeviceBuffer<Frac<EF>>, KernelError> {
let num_airs = self.common_ctxs.len();
debug!(
num_blocks = %self.num_blocks,
%num_x,
%num_airs,
"logup_monomial_batched"
);
let mut tmp_sums = DeviceBuffer::<Frac<EF>>::with_capacity_on(
self.num_blocks as usize * num_x as usize,
&self.device_ctx,
);
let mut output =
DeviceBuffer::<Frac<EF>>::with_capacity_on(num_airs * num_x as usize, &self.device_ctx);
debug_assert_eq!(
self.air_offsets.len(),
num_airs + 1,
"air_offsets must have num_airs + 1 elements"
);
unsafe {
logup_monomial_batched(
&mut tmp_sums,
&mut output,
&self.block_ctxs,
&self.common_ctxs,
&self.numer_ctxs,
&self.denom_ctxs,
&self.air_offsets,
self.num_blocks,
num_x,
num_airs as u32,
THREADS_PER_BLOCK_LOGUP,
self.device_ctx.stream.as_raw(),
)?;
}
Ok(output)
}
}