#[cfg(feature = "cuda-runtime")]
use cudarc::driver::{CudaSlice, CudaStream, LaunchConfig, LaunchAsync};
#[cfg(feature = "cuda-runtime")]
use super::cuda_executor::{CudaFftError, CudaFftExecutor, get_cuda_executor, get_executor_for_device};
#[cfg(feature = "cuda-runtime")]
use super::optimizations::{CudaGraph, get_pinned_pool_u32};
#[cfg(feature = "cuda-runtime")]
use std::sync::Arc;
#[cfg(feature = "cuda-runtime")]
use super::fft::{compute_itwiddle_dbls_cpu, compute_twiddle_dbls_cpu};
#[cfg(feature = "cuda-runtime")]
pub struct GpuProofPipeline {
poly_data: Vec<CudaSlice<u32>>,
itwiddles: CudaSlice<u32>,
twiddles: CudaSlice<u32>,
twiddle_offsets: CudaSlice<u32>,
log_size: u32,
itwiddles_cpu: Vec<Vec<u32>>,
twiddles_cpu: Vec<Vec<u32>>,
executor: Arc<CudaFftExecutor>,
device_id: usize,
fft_graph: Option<CudaGraph>,
ifft_graph: Option<CudaGraph>,
use_graphs: bool,
}
#[cfg(feature = "cuda-runtime")]
unsafe impl Send for GpuProofPipeline {}
#[cfg(feature = "cuda-runtime")]
unsafe impl Sync for GpuProofPipeline {}
#[cfg(feature = "cuda-runtime")]
impl GpuProofPipeline {
pub fn new(log_size: u32) -> Result<Self, CudaFftError> {
Self::new_on_device(log_size, 0)
}
pub fn new_on_device(log_size: u32, device_id: usize) -> Result<Self, CudaFftError> {
let executor = get_executor_for_device(device_id)?;
let itwiddles_cpu = compute_itwiddle_dbls_cpu(log_size);
let twiddles_cpu = compute_twiddle_dbls_cpu(log_size);
let flat_itwiddles: Vec<u32> = itwiddles_cpu.iter().flatten().copied().collect();
let flat_twiddles: Vec<u32> = twiddles_cpu.iter().flatten().copied().collect();
let itwiddles = executor.device.htod_sync_copy(&flat_itwiddles)
.map_err(|e| CudaFftError::MemoryAllocation(format!("{:?}", e)))?;
let twiddles = executor.device.htod_sync_copy(&flat_twiddles)
.map_err(|e| CudaFftError::MemoryAllocation(format!("{:?}", e)))?;
let mut offsets: Vec<u32> = Vec::new();
let mut offset = 0u32;
for tw in &itwiddles_cpu {
offsets.push(offset);
offset += tw.len() as u32;
}
let twiddle_offsets = executor.device.htod_sync_copy(&offsets)
.map_err(|e| CudaFftError::MemoryAllocation(format!("{:?}", e)))?;
tracing::info!("Created GPU pipeline on device {} with log_size {}", device_id, log_size);
Ok(Self {
poly_data: Vec::new(),
itwiddles,
twiddles,
twiddle_offsets,
log_size,
itwiddles_cpu,
twiddles_cpu,
executor,
device_id,
fft_graph: None,
ifft_graph: None,
use_graphs: true, })
}
pub fn set_use_graphs(&mut self, enabled: bool) {
self.use_graphs = enabled;
if !enabled {
self.fft_graph = None;
self.ifft_graph = None;
}
}
pub fn uses_graphs(&self) -> bool {
self.use_graphs
}
pub fn capture_fft_graph(&mut self) -> Result<(), CudaFftError> {
let mut graph = CudaGraph::new(self.executor.device.clone())?;
graph.begin_capture()?;
let n = 1usize << self.log_size;
let dummy_data: Vec<u32> = vec![0u32; n];
let mut d_dummy = self.executor.device.htod_sync_copy(&dummy_data)
.map_err(|e| CudaFftError::MemoryAllocation(format!("{:?}", e)))?;
self.execute_fft_kernels(&mut d_dummy, false)?;
graph.end_capture()?;
self.fft_graph = Some(graph);
tracing::info!("Captured FFT graph for log_size={}", self.log_size);
Ok(())
}
pub fn capture_ifft_graph(&mut self) -> Result<(), CudaFftError> {
let mut graph = CudaGraph::new(self.executor.device.clone())?;
graph.begin_capture()?;
let n = 1usize << self.log_size;
let dummy_data: Vec<u32> = vec![0u32; n];
let mut d_dummy = self.executor.device.htod_sync_copy(&dummy_data)
.map_err(|e| CudaFftError::MemoryAllocation(format!("{:?}", e)))?;
self.execute_ifft_kernels(&mut d_dummy)?;
graph.end_capture()?;
self.ifft_graph = Some(graph);
tracing::info!("Captured IFFT graph for log_size={}", self.log_size);
Ok(())
}
fn execute_fft_kernels(&self, data: &mut CudaSlice<u32>, sync: bool) -> Result<(), CudaFftError> {
let block_size = 256u32;
let num_layers = self.twiddles_cpu.len();
let mut twiddle_offsets: Vec<usize> = Vec::new();
let mut offset = 0usize;
for tw in &self.twiddles_cpu {
twiddle_offsets.push(offset);
offset += tw.len();
}
for layer in (0..num_layers).rev() {
let n_twiddles = self.twiddles_cpu[layer].len() as u32;
let butterflies_per_twiddle = 1u32 << layer;
let total_butterflies = n_twiddles * butterflies_per_twiddle;
let grid_size = (total_butterflies + block_size - 1) / block_size;
let twiddle_offset = twiddle_offsets[layer];
let cfg = LaunchConfig {
grid_dim: (grid_size, 1, 1),
block_dim: (block_size, 1, 1),
shared_mem_bytes: 0,
};
let twiddle_view = self.twiddles.slice(twiddle_offset..);
unsafe {
self.executor.kernels.fft_layer.clone().launch(
cfg,
(&mut *data, &twiddle_view, layer as u32, self.log_size, n_twiddles),
).map_err(|e| CudaFftError::KernelExecution(format!("{:?}", e)))?;
}
}
if sync {
self.executor.device.synchronize()
.map_err(|e| CudaFftError::KernelExecution(format!("Sync failed: {:?}", e)))?;
}
Ok(())
}
fn execute_ifft_kernels(&self, data: &mut CudaSlice<u32>) -> Result<(), CudaFftError> {
let block_size = 256u32;
let num_layers = self.itwiddles_cpu.len();
let mut twiddle_offsets: Vec<usize> = Vec::new();
let mut offset = 0usize;
for tw in &self.itwiddles_cpu {
twiddle_offsets.push(offset);
offset += tw.len();
}
for layer in 0..num_layers {
let n_twiddles = self.itwiddles_cpu[layer].len() as u32;
let butterflies_per_twiddle = 1u32 << layer;
let total_butterflies = n_twiddles * butterflies_per_twiddle;
let grid_size = (total_butterflies + block_size - 1) / block_size;
let twiddle_offset = twiddle_offsets[layer];
let cfg = LaunchConfig {
grid_dim: (grid_size, 1, 1),
block_dim: (block_size, 1, 1),
shared_mem_bytes: 0,
};
let twiddle_view = self.itwiddles.slice(twiddle_offset..);
unsafe {
self.executor.kernels.ifft_layer.clone().launch(
cfg,
(&mut *data, &twiddle_view, layer as u32, self.log_size, n_twiddles),
).map_err(|e| CudaFftError::KernelExecution(format!("{:?}", e)))?;
}
}
Ok(())
}
pub fn device_id(&self) -> usize {
self.device_id
}
#[inline]
pub fn executor(&self) -> &Arc<CudaFftExecutor> {
&self.executor
}
#[allow(dead_code)]
#[inline]
fn get_executor(&self) -> &CudaFftExecutor {
&self.executor
}
pub fn upload_polynomial(&mut self, data: &[u32]) -> Result<usize, CudaFftError> {
let n = 1usize << self.log_size;
if data.len() != n {
return Err(CudaFftError::InvalidSize(
format!("Expected {} elements, got {}", n, data.len())
));
}
let executor = self.executor.clone();
let d_data = executor.device.htod_sync_copy(data)
.map_err(|e| CudaFftError::MemoryAllocation(format!("{:?}", e)))?;
let idx = self.poly_data.len();
self.poly_data.push(d_data);
Ok(idx)
}
pub fn upload_polynomials_bulk<'a>(
&mut self,
polynomials: impl Iterator<Item = &'a [u32]>,
) -> Result<usize, CudaFftError> {
let n = 1usize << self.log_size;
let executor = self.executor.clone();
let polys: Vec<&[u32]> = polynomials.collect();
let num_polys = polys.len();
if num_polys == 0 {
return Ok(0);
}
for (i, poly) in polys.iter().enumerate() {
if poly.len() != n {
return Err(CudaFftError::InvalidSize(
format!("Polynomial {} has {} elements, expected {}", i, poly.len(), n)
));
}
}
for poly in &polys {
let d_poly = executor.device.htod_sync_copy(poly)
.map_err(|e| CudaFftError::MemoryAllocation(format!("{:?}", e)))?;
self.poly_data.push(d_poly);
}
Ok(num_polys)
}
pub fn upload_polynomial_pinned(&mut self, data: &[u32]) -> Result<usize, CudaFftError> {
let n = 1usize << self.log_size;
if data.len() != n {
return Err(CudaFftError::InvalidSize(
format!("Expected {} elements, got {}", n, data.len())
));
}
let pool = get_pinned_pool_u32();
let pinned = pool.acquire_with_data(data)?;
let executor = self.executor.clone();
let mut d_data = unsafe {
executor.device.alloc::<u32>(n)
}.map_err(|e| CudaFftError::MemoryAllocation(format!("{:?}", e)))?;
executor.device.htod_sync_copy_into(pinned.as_slice(), &mut d_data)
.map_err(|e| CudaFftError::MemoryTransfer(format!("{:?}", e)))?;
let idx = self.poly_data.len();
self.poly_data.push(d_data);
Ok(idx)
}
pub fn upload_polynomials_bulk_pinned<'a>(
&mut self,
polynomials: impl Iterator<Item = &'a [u32]>,
) -> Result<usize, CudaFftError> {
let n = 1usize << self.log_size;
let executor = self.executor.clone();
let pool = get_pinned_pool_u32();
let polys: Vec<&[u32]> = polynomials.collect();
let num_polys = polys.len();
if num_polys == 0 {
return Ok(0);
}
for (i, poly) in polys.iter().enumerate() {
if poly.len() != n {
return Err(CudaFftError::InvalidSize(
format!("Polynomial {} has {} elements, expected {}", i, poly.len(), n)
));
}
}
for poly in &polys {
let pinned = pool.acquire_with_data(poly)?;
let mut d_poly = unsafe {
executor.device.alloc::<u32>(n)
}.map_err(|e| CudaFftError::MemoryAllocation(format!("{:?}", e)))?;
executor.device.htod_sync_copy_into(pinned.as_slice(), &mut d_poly)
.map_err(|e| CudaFftError::MemoryTransfer(format!("{:?}", e)))?;
self.poly_data.push(d_poly);
}
Ok(num_polys)
}
pub fn download_polynomial_pinned(&self, poly_idx: usize) -> Result<Vec<u32>, CudaFftError> {
if poly_idx >= self.poly_data.len() {
return Err(CudaFftError::InvalidSize(
format!("Invalid polynomial index: {}", poly_idx)
));
}
let n = 1usize << self.log_size;
let executor = self.executor.clone();
let pool = get_pinned_pool_u32();
let mut pinned = pool.acquire(n)?;
executor.device.dtoh_sync_copy_into(&self.poly_data[poly_idx], pinned.as_mut_slice())
.map_err(|e| CudaFftError::MemoryTransfer(format!("{:?}", e)))?;
let result = pinned.as_slice().to_vec();
Ok(result)
}
pub fn download_polynomials_bulk(&self) -> Result<Vec<Vec<u32>>, CudaFftError> {
let n = 1usize << self.log_size;
let num_polys = self.poly_data.len();
if num_polys == 0 {
return Ok(Vec::new());
}
let executor = self.executor.clone();
let mut results = Vec::with_capacity(num_polys);
for d_poly in &self.poly_data {
let mut data = vec![0u32; n];
executor.device.dtoh_sync_copy_into(d_poly, &mut data)
.map_err(|e| CudaFftError::MemoryTransfer(format!("{:?}", e)))?;
results.push(data);
}
Ok(results)
}
pub fn ifft(&mut self, poly_idx: usize) -> Result<(), CudaFftError> {
if poly_idx >= self.poly_data.len() {
return Err(CudaFftError::InvalidSize(
format!("Invalid polynomial index: {}", poly_idx)
));
}
let executor = self.executor.clone();
executor.execute_ifft_on_device(
&mut self.poly_data[poly_idx],
&self.itwiddles,
&self.twiddle_offsets,
&self.itwiddles_cpu,
self.log_size,
)
}
pub fn fft(&mut self, poly_idx: usize) -> Result<(), CudaFftError> {
if poly_idx >= self.poly_data.len() {
return Err(CudaFftError::InvalidSize(
format!("Invalid polynomial index: {}", poly_idx)
));
}
if self.use_graphs {
if let Some(ref graph) = self.fft_graph {
if graph.is_ready() {
graph.launch()?;
graph.synchronize()?;
return Ok(());
}
}
}
self.fft_direct(poly_idx)
}
pub fn fft_direct(&mut self, poly_idx: usize) -> Result<(), CudaFftError> {
if poly_idx >= self.poly_data.len() {
return Err(CudaFftError::InvalidSize(
format!("Invalid polynomial index: {}", poly_idx)
));
}
let executor = self.executor.clone();
let block_size = 256u32;
let num_layers = self.twiddles_cpu.len();
let mut twiddle_offsets: Vec<usize> = Vec::new();
let mut offset = 0usize;
for tw in &self.twiddles_cpu {
twiddle_offsets.push(offset);
offset += tw.len();
}
for layer in (0..num_layers).rev() {
let n_twiddles = self.twiddles_cpu[layer].len() as u32;
let butterflies_per_twiddle = 1u32 << layer;
let total_butterflies = n_twiddles * butterflies_per_twiddle;
let grid_size = (total_butterflies + block_size - 1) / block_size;
let twiddle_offset = twiddle_offsets[layer];
let cfg = LaunchConfig {
grid_dim: (grid_size, 1, 1),
block_dim: (block_size, 1, 1),
shared_mem_bytes: 0,
};
let twiddle_view = self.twiddles.slice(twiddle_offset..);
unsafe {
executor.kernels.fft_layer.clone().launch(
cfg,
(&mut self.poly_data[poly_idx], &twiddle_view, layer as u32, self.log_size, n_twiddles),
).map_err(|e| CudaFftError::KernelExecution(format!("{:?}", e)))?;
}
}
executor.device.synchronize()
.map_err(|e| CudaFftError::KernelExecution(format!("Sync failed: {:?}", e)))?;
Ok(())
}
pub fn download_polynomial(&self, poly_idx: usize) -> Result<Vec<u32>, CudaFftError> {
if poly_idx >= self.poly_data.len() {
return Err(CudaFftError::InvalidSize(
format!("Invalid polynomial index: {}", poly_idx)
));
}
let executor = self.executor.clone();
let n = 1usize << self.log_size;
let mut result = vec![0u32; n];
executor.device.dtoh_sync_copy_into(&self.poly_data[poly_idx], &mut result)
.map_err(|e| CudaFftError::MemoryTransfer(format!("{:?}", e)))?;
Ok(result)
}
pub fn num_polynomials(&self) -> usize {
self.poly_data.len()
}
pub fn log_size(&self) -> u32 {
self.log_size
}
pub fn clear_polynomials(&mut self) {
self.poly_data.clear();
}
pub fn take_all_poly_data(&mut self) -> Vec<CudaSlice<u32>> {
std::mem::take(&mut self.poly_data)
}
pub fn device(&self) -> &Arc<cudarc::driver::CudaDevice> {
&self.executor.device
}
pub fn sync(&self) -> Result<(), CudaFftError> {
let executor = self.executor.clone();
executor.device.synchronize()
.map_err(|e| CudaFftError::KernelExecution(format!("Sync failed: {:?}", e)))
}
pub fn denormalize(&mut self, poly_idx: usize, denorm_factor: u32) -> Result<(), CudaFftError> {
if poly_idx >= self.poly_data.len() {
return Err(CudaFftError::InvalidSize(
format!("Invalid polynomial index: {}", poly_idx)
));
}
let n = 1u32 << self.log_size;
let mut data = self.poly_data.swap_remove(poly_idx);
let result = self.executor.clone().execute_denormalize_on_device(
&mut data,
denorm_factor,
n,
);
if poly_idx < self.poly_data.len() {
self.poly_data.push(data);
let last = self.poly_data.len() - 1;
self.poly_data.swap(poly_idx, last);
} else {
self.poly_data.push(data);
}
result
}
pub fn ifft_with_denormalize(&mut self, poly_idx: usize) -> Result<(), CudaFftError> {
self.ifft(poly_idx)?;
use crate::core::fields::m31::BaseField;
let denorm = BaseField::from(1u32 << self.log_size).inverse();
self.denormalize(poly_idx, denorm.0)
}
pub fn ifft_with_denormalize_batch(&mut self, poly_indices: &[usize]) -> Result<(), CudaFftError> {
if poly_indices.is_empty() {
return Ok(());
}
for &idx in poly_indices {
if idx >= self.poly_data.len() {
return Err(CudaFftError::InvalidSize(
format!("Invalid polynomial index: {}", idx)
));
}
}
let executor = self.executor.clone();
let block_size = 256u32;
let num_layers = self.itwiddles_cpu.len();
let n = 1u32 << self.log_size;
let mut twiddle_offsets_cpu: Vec<u32> = Vec::new();
let mut offset = 0u32;
for tw in &self.itwiddles_cpu {
twiddle_offsets_cpu.push(offset);
offset += tw.len() as u32;
}
const SHMEM_ELEMENTS: u32 = 1024;
const SHMEM_LOG_ELEMENTS: u32 = 10;
const SHMEM_BLOCK_SIZE: u32 = 256;
let shared_mem_layers = std::cmp::min(self.log_size, SHMEM_LOG_ELEMENTS);
let use_shmem = shared_mem_layers > 0 && n >= SHMEM_ELEMENTS;
if use_shmem {
let num_blocks = n / SHMEM_ELEMENTS;
let cfg = LaunchConfig {
grid_dim: (num_blocks, 1, 1),
block_dim: (SHMEM_BLOCK_SIZE, 1, 1),
shared_mem_bytes: SHMEM_ELEMENTS * 4,
};
for &idx in poly_indices {
unsafe {
executor.kernels.ifft_shared_mem.clone().launch(
cfg,
(
&mut self.poly_data[idx],
&self.itwiddles,
&self.twiddle_offsets,
shared_mem_layers,
self.log_size,
),
).map_err(|e| CudaFftError::KernelExecution(format!("Batch IFFT shmem: {:?}", e)))?;
}
}
}
let start_layer = if use_shmem { shared_mem_layers as usize } else { 0 };
for layer in start_layer..num_layers {
let n_twiddles = self.itwiddles_cpu[layer].len() as u32;
let butterflies_per_twiddle = 1u32 << layer;
let total_butterflies = n_twiddles * butterflies_per_twiddle;
let grid_size = (total_butterflies + block_size - 1) / block_size;
let twiddle_offset = twiddle_offsets_cpu[layer] as usize;
let cfg = LaunchConfig {
grid_dim: (grid_size, 1, 1),
block_dim: (block_size, 1, 1),
shared_mem_bytes: 0,
};
let twiddle_view = self.itwiddles.slice(twiddle_offset..);
for &idx in poly_indices {
unsafe {
executor.kernels.ifft_layer.clone().launch(
cfg,
(&mut self.poly_data[idx], &twiddle_view, layer as u32, self.log_size, n_twiddles),
).map_err(|e| CudaFftError::KernelExecution(format!("Batch IFFT layer {}: {:?}", layer, e)))?;
}
}
}
use crate::core::fields::m31::BaseField;
let denorm = BaseField::from(1u32 << self.log_size).inverse();
let denorm_factor = denorm.0;
if n >= 1024 && n % 4 == 0 {
let grid_size = (n / 4 + block_size - 1) / block_size;
let cfg = LaunchConfig {
grid_dim: (grid_size, 1, 1),
block_dim: (block_size, 1, 1),
shared_mem_bytes: 0,
};
for &idx in poly_indices {
unsafe {
executor.kernels.denormalize_vec4.clone().launch(
cfg,
(&mut self.poly_data[idx], denorm_factor, n),
).map_err(|e| CudaFftError::KernelExecution(format!("Batch denorm: {:?}", e)))?;
}
}
} else {
let grid_size = (n + block_size - 1) / block_size;
let cfg = LaunchConfig {
grid_dim: (grid_size, 1, 1),
block_dim: (block_size, 1, 1),
shared_mem_bytes: 0,
};
for &idx in poly_indices {
unsafe {
executor.kernels.denormalize.clone().launch(
cfg,
(&mut self.poly_data[idx], denorm_factor, n),
).map_err(|e| CudaFftError::KernelExecution(format!("Batch denorm: {:?}", e)))?;
}
}
}
executor.device.synchronize()
.map_err(|e| CudaFftError::KernelExecution(format!("Batch IFFT sync: {:?}", e)))?;
Ok(())
}
pub fn fft_batch(&mut self, poly_indices: &[usize]) -> Result<(), CudaFftError> {
if poly_indices.is_empty() {
return Ok(());
}
for &idx in poly_indices {
if idx >= self.poly_data.len() {
return Err(CudaFftError::InvalidSize(
format!("Invalid polynomial index: {}", idx)
));
}
}
let executor = self.executor.clone();
let block_size = 256u32;
let num_layers = self.twiddles_cpu.len();
let mut twiddle_offsets: Vec<usize> = Vec::new();
let mut offset = 0usize;
for tw in &self.twiddles_cpu {
twiddle_offsets.push(offset);
offset += tw.len();
}
for layer in (0..num_layers).rev() {
let n_twiddles = self.twiddles_cpu[layer].len() as u32;
let butterflies_per_twiddle = 1u32 << layer;
let total_butterflies = n_twiddles * butterflies_per_twiddle;
let grid_size = (total_butterflies + block_size - 1) / block_size;
let twiddle_offset = twiddle_offsets[layer];
let cfg = LaunchConfig {
grid_dim: (grid_size, 1, 1),
block_dim: (block_size, 1, 1),
shared_mem_bytes: 0,
};
let twiddle_view = self.twiddles.slice(twiddle_offset..);
for &idx in poly_indices {
unsafe {
executor.kernels.fft_layer.clone().launch(
cfg,
(&mut self.poly_data[idx], &twiddle_view, layer as u32, self.log_size, n_twiddles),
).map_err(|e| CudaFftError::KernelExecution(format!("Batch FFT layer {}: {:?}", layer, e)))?;
}
}
}
executor.device.synchronize()
.map_err(|e| CudaFftError::KernelExecution(format!("Batch FFT sync: {:?}", e)))?;
Ok(())
}
pub fn fri_fold_line(
&mut self,
input_idx: usize,
itwiddles: &[u32],
alpha: &[u32; 4],
) -> Result<usize, CudaFftError> {
if input_idx >= self.poly_data.len() {
return Err(CudaFftError::InvalidSize(
format!("Invalid polynomial index: {}", input_idx)
));
}
let executor = self.executor.clone();
let n = 1usize << self.log_size;
let n_output = n / 2;
let d_itwiddles = executor.device.htod_sync_copy(itwiddles)
.map_err(|e| CudaFftError::MemoryAllocation(format!("{:?}", e)))?;
let d_alpha = executor.device.htod_sync_copy(alpha)
.map_err(|e| CudaFftError::MemoryAllocation(format!("{:?}", e)))?;
let mut d_output = unsafe {
executor.device.alloc::<u32>(n_output * 4)
}.map_err(|e| CudaFftError::MemoryAllocation(format!("{:?}", e)))?;
let block_size = 256u32;
let grid_size = ((n_output as u32) + block_size - 1) / block_size;
let log_n = n.ilog2();
let cfg = LaunchConfig {
grid_dim: (grid_size, 1, 1),
block_dim: (block_size, 1, 1),
shared_mem_bytes: 32, };
unsafe {
executor.kernels.fold_line.clone().launch(
cfg,
(&mut d_output, &self.poly_data[input_idx], &d_itwiddles, &d_alpha, n as u32, log_n),
).map_err(|e| CudaFftError::KernelExecution(format!("{:?}", e)))?;
}
executor.device.synchronize()
.map_err(|e| CudaFftError::KernelExecution(format!("Sync failed: {:?}", e)))?;
let output_idx = self.poly_data.len();
self.poly_data.push(d_output);
Ok(output_idx)
}
pub fn fri_fold_line_gpu(
&mut self,
input_idx: usize,
d_itwiddles: &CudaSlice<u32>,
d_alpha: &CudaSlice<u32>,
current_n: usize,
) -> Result<usize, CudaFftError> {
if input_idx >= self.poly_data.len() {
return Err(CudaFftError::InvalidSize(
format!("Invalid polynomial index: {}", input_idx)
));
}
let executor = self.executor.clone();
let n_output = current_n / 2;
let mut d_output = unsafe {
executor.device.alloc::<u32>(n_output * 4)
}.map_err(|e| CudaFftError::MemoryAllocation(format!("{:?}", e)))?;
let block_size = 256u32;
let grid_size = ((n_output as u32) + block_size - 1) / block_size;
let log_n = current_n.ilog2();
let cfg = LaunchConfig {
grid_dim: (grid_size, 1, 1),
block_dim: (block_size, 1, 1),
shared_mem_bytes: 32,
};
unsafe {
executor.kernels.fold_line.clone().launch(
cfg,
(&mut d_output, &self.poly_data[input_idx], d_itwiddles, d_alpha, current_n as u32, log_n),
).map_err(|e| CudaFftError::KernelExecution(format!("{:?}", e)))?;
}
let output_idx = self.poly_data.len();
self.poly_data.push(d_output);
Ok(output_idx)
}
pub fn fri_fold_circle_into_line(
&mut self,
dst_idx: usize,
src_idx: usize,
itwiddles: &[u32],
alpha: &[u32; 4],
) -> Result<(), CudaFftError> {
if dst_idx >= self.poly_data.len() || src_idx >= self.poly_data.len() {
return Err(CudaFftError::InvalidSize(
format!("Invalid polynomial indices: dst={}, src={}", dst_idx, src_idx)
));
}
if dst_idx == src_idx {
return Err(CudaFftError::InvalidSize(
"dst_idx and src_idx must be different".into()
));
}
let n = 1usize << self.log_size;
let n_dst = n / 2;
let (d_itwiddles, d_alpha) = {
let executor = self.executor.clone();
let d_itwiddles = executor.device.htod_sync_copy(itwiddles)
.map_err(|e| CudaFftError::MemoryAllocation(format!("{:?}", e)))?;
let d_alpha = executor.device.htod_sync_copy(alpha)
.map_err(|e| CudaFftError::MemoryAllocation(format!("{:?}", e)))?;
(d_itwiddles, d_alpha)
};
let block_size = 256u32;
let grid_size = ((n_dst as u32) + block_size - 1) / block_size;
let log_n = n.ilog2();
let cfg = LaunchConfig {
grid_dim: (grid_size, 1, 1),
block_dim: (block_size, 1, 1),
shared_mem_bytes: 32,
};
let (dst_slice, src_slice) = if dst_idx < src_idx {
let (left, right) = self.poly_data.split_at_mut(src_idx);
(&mut left[dst_idx], &right[0])
} else {
let (left, right) = self.poly_data.split_at_mut(dst_idx);
(&mut right[0], &left[src_idx])
};
{
let executor = self.executor.clone();
unsafe {
executor.kernels.fold_circle_into_line.clone().launch(
cfg,
(dst_slice, src_slice, &d_itwiddles, &d_alpha, n as u32, log_n),
).map_err(|e| CudaFftError::KernelExecution(format!("{:?}", e)))?;
}
executor.device.synchronize()
.map_err(|e| CudaFftError::KernelExecution(format!("Sync failed: {:?}", e)))?;
}
Ok(())
}
pub fn fri_fold_multi_layer(
&mut self,
input_idx: usize,
all_itwiddles: &[Vec<u32>], alpha: &[u32; 4],
num_layers: usize,
) -> Result<usize, CudaFftError> {
if input_idx >= self.poly_data.len() {
return Err(CudaFftError::InvalidSize(
format!("Invalid polynomial index: {}", input_idx)
));
}
if all_itwiddles.len() < num_layers {
return Err(CudaFftError::InvalidSize(
format!("Not enough twiddles: have {}, need {}", all_itwiddles.len(), num_layers)
));
}
let d_alpha = {
let executor = self.executor.clone();
executor.device.htod_sync_copy(alpha)
.map_err(|e| CudaFftError::MemoryAllocation(format!("{:?}", e)))?
};
let mut current_idx = input_idx;
let mut current_n = 1usize << self.log_size;
for layer in 0..num_layers {
let d_itwiddles = {
let executor = self.executor.clone();
executor.device.htod_sync_copy(&all_itwiddles[layer])
.map_err(|e| CudaFftError::MemoryAllocation(format!("{:?}", e)))?
};
current_idx = self.fri_fold_line_gpu(current_idx, &d_itwiddles, &d_alpha, current_n)?;
current_n /= 2;
}
{
let executor = self.executor.clone();
executor.device.synchronize()
.map_err(|e| CudaFftError::KernelExecution(format!("Sync failed: {:?}", e)))?;
}
Ok(current_idx)
}
pub fn accumulate_quotients(
&mut self,
column_indices: &[usize],
line_coeffs: &[[u32; 12]],
denom_inv: &[u32],
batch_sizes: &[usize],
col_indices: &[usize],
n_points: usize,
) -> Result<usize, CudaFftError> {
let executor = self.executor.clone();
let n_columns = column_indices.len();
let col_size = 1usize << self.log_size;
let total_elements = n_columns * col_size;
let mut d_columns = unsafe {
executor.device.alloc::<u32>(total_elements)
}.map_err(|e| CudaFftError::MemoryAllocation(format!("{:?}", e)))?;
let block_size = 256u32;
for (i, &idx) in column_indices.iter().enumerate() {
if idx >= self.poly_data.len() {
return Err(CudaFftError::InvalidSize(
format!("Invalid column index: {}", idx)
));
}
let dst_offset = (i * col_size) as u32;
let n_elements = col_size as u32;
let grid_size = (n_elements + block_size - 1) / block_size;
let cfg = LaunchConfig {
grid_dim: (grid_size, 1, 1),
block_dim: (block_size, 1, 1),
shared_mem_bytes: 0,
};
unsafe {
executor.kernels.copy_column.clone().launch(
cfg,
(
&mut d_columns,
&self.poly_data[idx],
dst_offset,
n_elements,
),
).map_err(|e| CudaFftError::KernelExecution(format!("copy_column: {:?}", e)))?;
}
}
let flat_line_coeffs: Vec<u32> = line_coeffs.iter()
.flat_map(|coeffs| coeffs.iter().copied())
.collect();
let batch_sizes_u32: Vec<u32> = batch_sizes.iter().map(|&s| s as u32).collect();
let col_indices_u32: Vec<u32> = col_indices.iter().map(|&i| i as u32).collect();
let n_batches = batch_sizes.len();
let d_line_coeffs = executor.device.htod_sync_copy(&flat_line_coeffs)
.map_err(|e| CudaFftError::MemoryAllocation(format!("{:?}", e)))?;
let d_denom_inv = executor.device.htod_sync_copy(denom_inv)
.map_err(|e| CudaFftError::MemoryAllocation(format!("{:?}", e)))?;
let d_batch_sizes = executor.device.htod_sync_copy(&batch_sizes_u32)
.map_err(|e| CudaFftError::MemoryAllocation(format!("{:?}", e)))?;
let d_col_indices = executor.device.htod_sync_copy(&col_indices_u32)
.map_err(|e| CudaFftError::MemoryAllocation(format!("{:?}", e)))?;
let mut d_output = unsafe {
executor.device.alloc::<u32>(n_points * 4)
}.map_err(|e| CudaFftError::MemoryAllocation(format!("{:?}", e)))?;
let block_size = 256u32;
let grid_size = ((n_points as u32) + block_size - 1) / block_size;
let cfg = LaunchConfig {
grid_dim: (grid_size, 1, 1),
block_dim: (block_size, 1, 1),
shared_mem_bytes: 0,
};
unsafe {
executor.kernels.accumulate_quotients.clone().launch(
cfg,
(
&mut d_output,
&d_columns,
&d_line_coeffs,
&d_denom_inv,
&d_batch_sizes,
&d_col_indices,
n_batches as u32,
n_points as u32,
n_columns as u32,
),
).map_err(|e| CudaFftError::KernelExecution(format!("{:?}", e)))?;
}
executor.device.synchronize()
.map_err(|e| CudaFftError::KernelExecution(format!("Sync failed: {:?}", e)))?;
let output_idx = self.poly_data.len();
self.poly_data.push(d_output);
Ok(output_idx)
}
pub fn merkle_hash(
&self,
column_indices: &[usize],
n_hashes: usize,
) -> Result<Vec<u8>, CudaFftError> {
let executor = self.executor.clone();
let n_columns = column_indices.len();
let n = 1usize << self.log_size;
for &idx in column_indices {
if idx >= self.poly_data.len() {
return Err(CudaFftError::InvalidSize(
format!("Invalid column index: {}", idx)
));
}
}
let mut d_columns = unsafe {
executor.device.alloc::<u32>(n_columns * n)
}.map_err(|e| CudaFftError::MemoryAllocation(format!("{:?}", e)))?;
for (i, &idx) in column_indices.iter().enumerate() {
executor.device.dtod_copy(
&self.poly_data[idx],
&mut d_columns.slice_mut(i * n..(i + 1) * n),
).map_err(|e| CudaFftError::MemoryTransfer(format!("{:?}", e)))?;
}
let mut d_output = unsafe {
executor.device.alloc::<u8>(n_hashes * 32)
}.map_err(|e| CudaFftError::MemoryAllocation(format!("{:?}", e)))?;
let block_size = 256u32;
let grid_size = ((n_hashes as u32) + block_size - 1) / block_size;
let cfg = LaunchConfig {
grid_dim: (grid_size, 1, 1),
block_dim: (block_size, 1, 1),
shared_mem_bytes: 0,
};
let dummy_prev = unsafe {
executor.device.alloc::<u8>(1)
}.map_err(|e| CudaFftError::MemoryAllocation(format!("{:?}", e)))?;
unsafe {
executor.kernels.merkle_layer.clone().launch(
cfg,
(
&mut d_output,
&d_columns,
&dummy_prev,
n_columns as u32,
n_hashes as u32,
0u32, ),
).map_err(|e| CudaFftError::KernelExecution(format!("{:?}", e)))?;
}
executor.device.synchronize()
.map_err(|e| CudaFftError::KernelExecution(format!("Sync failed: {:?}", e)))?;
let mut output = vec![0u8; n_hashes * 32];
executor.device.dtoh_sync_copy_into(&d_output, &mut output)
.map_err(|e| CudaFftError::MemoryTransfer(format!("{:?}", e)))?;
Ok(output)
}
pub fn merkle_tree_full(
&self,
column_indices: &[usize],
n_leaves: usize,
) -> Result<[u8; 32], CudaFftError> {
let executor = self.executor.clone();
let n_columns = column_indices.len();
let n = 1usize << self.log_size;
for &idx in column_indices {
if idx >= self.poly_data.len() {
return Err(CudaFftError::InvalidSize(
format!("Invalid column index: {}", idx)
));
}
}
let mut d_columns = unsafe {
executor.device.alloc::<u32>(n_columns * n)
}.map_err(|e| CudaFftError::MemoryAllocation(format!("{:?}", e)))?;
for (i, &idx) in column_indices.iter().enumerate() {
executor.device.dtod_copy(
&self.poly_data[idx],
&mut d_columns.slice_mut(i * n..(i + 1) * n),
).map_err(|e| CudaFftError::MemoryTransfer(format!("{:?}", e)))?;
}
let max_layer_size = n_leaves * 32;
let mut d_layer_a = unsafe {
executor.device.alloc::<u8>(max_layer_size)
}.map_err(|e| CudaFftError::MemoryAllocation(format!("{:?}", e)))?;
let mut d_layer_b = unsafe {
executor.device.alloc::<u8>(max_layer_size)
}.map_err(|e| CudaFftError::MemoryAllocation(format!("{:?}", e)))?;
let dummy_cols = unsafe {
executor.device.alloc::<u32>(1)
}.map_err(|e| CudaFftError::MemoryAllocation(format!("{:?}", e)))?;
let block_size = 256u32;
let grid_size = ((n_leaves as u32) + block_size - 1) / block_size;
let cfg = LaunchConfig {
grid_dim: (grid_size, 1, 1),
block_dim: (block_size, 1, 1),
shared_mem_bytes: 0,
};
let dummy_prev = unsafe {
executor.device.alloc::<u8>(1)
}.map_err(|e| CudaFftError::MemoryAllocation(format!("{:?}", e)))?;
unsafe {
executor.kernels.merkle_layer.clone().launch(
cfg,
(
&mut d_layer_a,
&d_columns,
&dummy_prev,
n_columns as u32,
n_leaves as u32,
0u32, ),
).map_err(|e| CudaFftError::KernelExecution(format!("{:?}", e)))?;
}
let mut current_size = n_leaves;
let mut use_a = true;
while current_size > 1 {
let next_size = current_size / 2;
let grid_size = ((next_size as u32) + block_size - 1) / block_size;
let cfg = LaunchConfig {
grid_dim: (grid_size, 1, 1),
block_dim: (block_size, 1, 1),
shared_mem_bytes: 0,
};
if use_a {
unsafe {
executor.kernels.merkle_layer.clone().launch(
cfg,
(
&mut d_layer_b,
&dummy_cols,
&d_layer_a,
0u32, next_size as u32,
1u32, ),
).map_err(|e| CudaFftError::KernelExecution(format!("{:?}", e)))?;
}
} else {
unsafe {
executor.kernels.merkle_layer.clone().launch(
cfg,
(
&mut d_layer_a,
&dummy_cols,
&d_layer_b,
0u32, next_size as u32,
1u32, ),
).map_err(|e| CudaFftError::KernelExecution(format!("{:?}", e)))?;
}
}
current_size = next_size;
use_a = !use_a;
}
executor.device.synchronize()
.map_err(|e| CudaFftError::KernelExecution(format!("Sync failed: {:?}", e)))?;
let mut root = [0u8; 32];
let root_buffer = if use_a { &d_layer_a } else { &d_layer_b };
executor.device.dtoh_sync_copy_into(&root_buffer.slice(0..32), &mut root)
.map_err(|e| CudaFftError::MemoryTransfer(format!("{:?}", e)))?;
Ok(root)
}
pub fn merkle_tree_layer(&self, prev_layer: &[u8]) -> Result<Vec<u8>, CudaFftError> {
let executor = self.executor.clone();
let n_prev = prev_layer.len() / 32;
let n_output = n_prev / 2;
if n_output == 0 {
return Err(CudaFftError::InvalidSize(
"Previous layer must have at least 2 hashes".into()
));
}
let d_prev = executor.device.htod_sync_copy(prev_layer)
.map_err(|e| CudaFftError::MemoryAllocation(format!("{:?}", e)))?;
let mut d_output = unsafe {
executor.device.alloc::<u8>(n_output * 32)
}.map_err(|e| CudaFftError::MemoryAllocation(format!("{:?}", e)))?;
let block_size = 256u32;
let grid_size = ((n_output as u32) + block_size - 1) / block_size;
let cfg = LaunchConfig {
grid_dim: (grid_size, 1, 1),
block_dim: (block_size, 1, 1),
shared_mem_bytes: 0,
};
let dummy_cols = unsafe {
executor.device.alloc::<u32>(1)
}.map_err(|e| CudaFftError::MemoryAllocation(format!("{:?}", e)))?;
unsafe {
executor.kernels.merkle_layer.clone().launch(
cfg,
(
&mut d_output,
&dummy_cols,
&d_prev,
0u32, n_output as u32,
1u32, ),
).map_err(|e| CudaFftError::KernelExecution(format!("{:?}", e)))?;
}
executor.device.synchronize()
.map_err(|e| CudaFftError::KernelExecution(format!("Sync failed: {:?}", e)))?;
let mut output = vec![0u8; n_output * 32];
executor.device.dtoh_sync_copy_into(&d_output, &mut output)
.map_err(|e| CudaFftError::MemoryTransfer(format!("{:?}", e)))?;
Ok(output)
}
pub fn poly_slice(&self, idx: usize) -> &CudaSlice<u32> {
&self.poly_data[idx]
}
pub fn push_external_poly(&mut self, data: CudaSlice<u32>) -> usize {
let idx = self.poly_data.len();
self.poly_data.push(data);
idx
}
}
#[cfg(feature = "cuda-runtime")]
pub fn benchmark_proof_pipeline(
log_size: u32,
num_polynomials: usize,
num_fft_rounds: usize,
) -> Result<PipelineBenchmarkResult, CudaFftError> {
use std::time::Instant;
let n = 1usize << log_size;
let test_data: Vec<Vec<u32>> = (0..num_polynomials)
.map(|p| {
(0..n)
.map(|i| ((i * 7 + p * 13 + 17) as u32) % ((1 << 31) - 1))
.collect()
})
.collect();
let setup_start = Instant::now();
let mut pipeline = GpuProofPipeline::new(log_size)?;
let setup_time = setup_start.elapsed();
let upload_start = Instant::now();
for data in &test_data {
pipeline.upload_polynomial(data)?;
}
pipeline.sync()?;
let upload_time = upload_start.elapsed();
let compute_start = Instant::now();
for _round in 0..num_fft_rounds {
for poly_idx in 0..num_polynomials {
pipeline.ifft(poly_idx)?;
}
for poly_idx in 0..num_polynomials {
pipeline.fft(poly_idx)?;
}
}
pipeline.sync()?;
let compute_time = compute_start.elapsed();
let download_start = Instant::now();
let mut _results = Vec::new();
for poly_idx in 0..num_polynomials {
_results.push(pipeline.download_polynomial(poly_idx)?);
}
let download_time = download_start.elapsed();
let total_time = setup_time + upload_time + compute_time + download_time;
let total_ffts = num_polynomials * num_fft_rounds * 2;
Ok(PipelineBenchmarkResult {
log_size,
num_polynomials,
num_fft_rounds,
total_ffts,
setup_time,
upload_time,
compute_time,
download_time,
total_time,
})
}
#[cfg(feature = "cuda-runtime")]
pub fn benchmark_full_proof_pipeline(
log_size: u32,
num_polynomials: usize,
num_fri_layers: usize,
) -> Result<FullProofBenchmarkResult, CudaFftError> {
use std::time::Instant;
let n = 1usize << log_size;
let test_data: Vec<Vec<u32>> = (0..num_polynomials)
.map(|p| {
(0..n)
.map(|i| ((i * 7 + p * 13 + 17) as u32) % ((1 << 31) - 1))
.collect()
})
.collect();
let _itwiddles: Vec<u32> = (0..n/2)
.map(|i| ((i * 11 + 3) as u32) % ((1 << 31) - 1))
.collect();
let _alpha: [u32; 4] = [12345, 67890, 11111, 22222];
let setup_start = Instant::now();
let mut pipeline = GpuProofPipeline::new(log_size)?;
let setup_time = setup_start.elapsed();
let upload_start = Instant::now();
for data in &test_data {
pipeline.upload_polynomial(data)?;
}
pipeline.sync()?;
let upload_time = upload_start.elapsed();
let fft_start = Instant::now();
for poly_idx in 0..num_polynomials {
pipeline.ifft(poly_idx)?;
pipeline.fft(poly_idx)?;
}
pipeline.sync()?;
let fft_time = fft_start.elapsed();
let fri_start = Instant::now();
let alpha: [u32; 4] = [12345, 67890, 11111, 22222];
let mut all_itwiddles: Vec<Vec<u32>> = Vec::new();
let mut current_size = n;
for _ in 0..num_fri_layers.min(log_size as usize - 4) {
let n_twiddles = current_size / 2;
let layer_twiddles: Vec<u32> = (0..n_twiddles)
.map(|i| ((i as u64 * 31337) % 0x7FFFFFFF) as u32)
.collect();
all_itwiddles.push(layer_twiddles);
current_size /= 2;
}
if !all_itwiddles.is_empty() {
let _folded_idx = pipeline.fri_fold_multi_layer(
0,
&all_itwiddles,
&alpha,
all_itwiddles.len(),
)?;
}
pipeline.sync()?;
let fri_time = fri_start.elapsed();
let merkle_start = Instant::now();
let column_indices: Vec<usize> = (0..num_polynomials).collect();
let n_leaves = n / 2; let _merkle_root = pipeline.merkle_tree_full(&column_indices, n_leaves)?;
let merkle_time = merkle_start.elapsed();
let download_start = Instant::now();
let _result = pipeline.download_polynomial(0)?;
let download_time = download_start.elapsed();
let total_time = setup_time + upload_time + fft_time + fri_time + merkle_time + download_time;
let compute_time = fft_time + fri_time + merkle_time;
Ok(FullProofBenchmarkResult {
log_size,
num_polynomials,
num_fri_layers,
setup_time,
upload_time,
fft_time,
fri_time,
merkle_time,
download_time,
total_time,
compute_time,
})
}
#[derive(Debug)]
pub struct FullProofBenchmarkResult {
pub log_size: u32,
pub num_polynomials: usize,
pub num_fri_layers: usize,
pub setup_time: std::time::Duration,
pub upload_time: std::time::Duration,
pub fft_time: std::time::Duration,
pub fri_time: std::time::Duration,
pub merkle_time: std::time::Duration,
pub download_time: std::time::Duration,
pub total_time: std::time::Duration,
pub compute_time: std::time::Duration,
}
impl FullProofBenchmarkResult {
pub fn transfer_overhead_percent(&self) -> f64 {
let transfer = self.upload_time + self.download_time;
transfer.as_secs_f64() / self.total_time.as_secs_f64() * 100.0
}
pub fn compute_percent(&self) -> f64 {
self.compute_time.as_secs_f64() / self.total_time.as_secs_f64() * 100.0
}
}
impl std::fmt::Display for FullProofBenchmarkResult {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
writeln!(f, "Full GPU Proof Pipeline Results")?;
writeln!(f, "================================")?;
writeln!(f, " Polynomial size: 2^{} = {} elements", self.log_size, 1usize << self.log_size)?;
writeln!(f, " Polynomials: {}", self.num_polynomials)?;
writeln!(f, " FRI layers: {}", self.num_fri_layers)?;
writeln!(f)?;
writeln!(f, "Timing Breakdown:")?;
writeln!(f, " Setup: {:?}", self.setup_time)?;
writeln!(f, " Upload: {:?}", self.upload_time)?;
writeln!(f, " FFT (commit): {:?}", self.fft_time)?;
writeln!(f, " FRI folding: {:?}", self.fri_time)?;
writeln!(f, " Merkle hashing: {:?}", self.merkle_time)?;
writeln!(f, " Download: {:?}", self.download_time)?;
writeln!(f, " Total: {:?}", self.total_time)?;
writeln!(f)?;
writeln!(f, "Performance:")?;
writeln!(f, " Transfer overhead: {:.1}%", self.transfer_overhead_percent())?;
writeln!(f, " Compute time: {:.1}%", self.compute_percent())?;
Ok(())
}
}
#[derive(Debug)]
pub struct PipelineBenchmarkResult {
pub log_size: u32,
pub num_polynomials: usize,
pub num_fft_rounds: usize,
pub total_ffts: usize,
pub setup_time: std::time::Duration,
pub upload_time: std::time::Duration,
pub compute_time: std::time::Duration,
pub download_time: std::time::Duration,
pub total_time: std::time::Duration,
}
impl PipelineBenchmarkResult {
pub fn time_per_fft(&self) -> std::time::Duration {
self.compute_time / self.total_ffts as u32
}
pub fn transfer_overhead_percent(&self) -> f64 {
let transfer = self.upload_time + self.download_time;
transfer.as_secs_f64() / self.total_time.as_secs_f64() * 100.0
}
pub fn compute_percent(&self) -> f64 {
self.compute_time.as_secs_f64() / self.total_time.as_secs_f64() * 100.0
}
}
impl std::fmt::Display for PipelineBenchmarkResult {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
writeln!(f, "GPU Pipeline Benchmark Results")?;
writeln!(f, "===============================")?;
writeln!(f, " Polynomial size: 2^{} = {} elements", self.log_size, 1usize << self.log_size)?;
writeln!(f, " Polynomials: {}", self.num_polynomials)?;
writeln!(f, " FFT rounds: {}", self.num_fft_rounds)?;
writeln!(f, " Total FFTs: {}", self.total_ffts)?;
writeln!(f)?;
writeln!(f, "Timing Breakdown:")?;
writeln!(f, " Setup: {:?}", self.setup_time)?;
writeln!(f, " Upload: {:?}", self.upload_time)?;
writeln!(f, " Compute: {:?}", self.compute_time)?;
writeln!(f, " Download: {:?}", self.download_time)?;
writeln!(f, " Total: {:?}", self.total_time)?;
writeln!(f)?;
writeln!(f, "Performance:")?;
writeln!(f, " Time per FFT: {:?}", self.time_per_fft())?;
writeln!(f, " Transfer overhead: {:.1}%", self.transfer_overhead_percent())?;
writeln!(f, " Compute time: {:.1}%", self.compute_percent())?;
Ok(())
}
}
#[cfg(feature = "cuda-runtime")]
pub struct BatchProofProcessor {
pipelines: Vec<GpuProofPipeline>,
}
#[cfg(feature = "cuda-runtime")]
impl BatchProofProcessor {
pub fn new(log_size: u32, num_proofs: usize) -> Result<Self, CudaFftError> {
let mut pipelines = Vec::with_capacity(num_proofs);
for _ in 0..num_proofs {
pipelines.push(GpuProofPipeline::new(log_size)?);
}
Ok(Self { pipelines })
}
pub fn upload_polynomial(&mut self, proof_idx: usize, data: &[u32]) -> Result<usize, CudaFftError> {
if proof_idx >= self.pipelines.len() {
return Err(CudaFftError::InvalidSize(
format!("Invalid proof index: {}", proof_idx)
));
}
self.pipelines[proof_idx].upload_polynomial(data)
}
pub fn batch_ifft(&mut self, poly_idx: usize) -> Result<(), CudaFftError> {
for pipeline in &mut self.pipelines {
pipeline.ifft(poly_idx)?;
}
Ok(())
}
pub fn batch_fft(&mut self, poly_idx: usize) -> Result<(), CudaFftError> {
for pipeline in &mut self.pipelines {
pipeline.fft(poly_idx)?;
}
Ok(())
}
pub fn sync(&self) -> Result<(), CudaFftError> {
for pipeline in &self.pipelines {
pipeline.sync()?;
}
Ok(())
}
pub fn num_proofs(&self) -> usize {
self.pipelines.len()
}
pub fn pipeline(&self, idx: usize) -> Option<&GpuProofPipeline> {
self.pipelines.get(idx)
}
pub fn pipeline_mut(&mut self, idx: usize) -> Option<&mut GpuProofPipeline> {
self.pipelines.get_mut(idx)
}
}
#[cfg(feature = "cuda-runtime")]
pub fn benchmark_large_proof(
log_size: u32,
num_polynomials: usize,
num_fft_rounds: usize,
) -> Result<LargeProofBenchmarkResult, CudaFftError> {
use std::time::Instant;
let n = 1usize << log_size;
let test_data: Vec<Vec<u32>> = (0..num_polynomials)
.map(|p| {
(0..n)
.map(|i| ((i * 7 + p * 13 + 17) as u32) % ((1 << 31) - 1))
.collect()
})
.collect();
let setup_start = Instant::now();
let mut pipeline = GpuProofPipeline::new(log_size)?;
let setup_time = setup_start.elapsed();
let upload_start = Instant::now();
for data in &test_data {
pipeline.upload_polynomial(data)?;
}
pipeline.sync()?;
let upload_time = upload_start.elapsed();
let compute_start = Instant::now();
for _round in 0..num_fft_rounds {
for poly_idx in 0..num_polynomials {
pipeline.ifft(poly_idx)?;
}
for poly_idx in 0..num_polynomials {
pipeline.fft(poly_idx)?;
}
}
pipeline.sync()?;
let compute_time = compute_start.elapsed();
let download_start = Instant::now();
let mut _results = Vec::new();
for poly_idx in 0..num_polynomials {
_results.push(pipeline.download_polynomial(poly_idx)?);
}
let download_time = download_start.elapsed();
let total_time = setup_time + upload_time + compute_time + download_time;
let total_ffts = num_polynomials * num_fft_rounds * 2;
let elements_processed = (n * total_ffts) as u64;
let throughput_gflops = (elements_processed as f64 * log_size as f64 * 5.0) /
compute_time.as_secs_f64() / 1e9;
Ok(LargeProofBenchmarkResult {
log_size,
num_polynomials,
num_fft_rounds,
total_ffts,
elements_processed,
setup_time,
upload_time,
compute_time,
download_time,
total_time,
throughput_gflops,
})
}
#[derive(Debug)]
pub struct LargeProofBenchmarkResult {
pub log_size: u32,
pub num_polynomials: usize,
pub num_fft_rounds: usize,
pub total_ffts: usize,
pub elements_processed: u64,
pub setup_time: std::time::Duration,
pub upload_time: std::time::Duration,
pub compute_time: std::time::Duration,
pub download_time: std::time::Duration,
pub total_time: std::time::Duration,
pub throughput_gflops: f64,
}
impl std::fmt::Display for LargeProofBenchmarkResult {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
writeln!(f, "Large Proof Benchmark Results")?;
writeln!(f, "=============================")?;
writeln!(f, " Polynomial size: 2^{} = {} elements", self.log_size, 1usize << self.log_size)?;
writeln!(f, " Polynomials: {}", self.num_polynomials)?;
writeln!(f, " FFT rounds: {}", self.num_fft_rounds)?;
writeln!(f, " Total FFTs: {}", self.total_ffts)?;
writeln!(f, " Elements processed: {:.2}M", self.elements_processed as f64 / 1e6)?;
writeln!(f)?;
writeln!(f, "Timing:")?;
writeln!(f, " Setup: {:?}", self.setup_time)?;
writeln!(f, " Upload: {:?}", self.upload_time)?;
writeln!(f, " Compute: {:?}", self.compute_time)?;
writeln!(f, " Download: {:?}", self.download_time)?;
writeln!(f, " Total: {:?}", self.total_time)?;
writeln!(f)?;
writeln!(f, "Performance:")?;
writeln!(f, " Throughput: {:.2} GFLOPS", self.throughput_gflops)?;
writeln!(f, " Time per FFT: {:?}", self.compute_time / self.total_ffts as u32)?;
let compute_pct = self.compute_time.as_secs_f64() / self.total_time.as_secs_f64() * 100.0;
writeln!(f, " Compute efficiency: {:.1}%", compute_pct)?;
Ok(())
}
}
#[cfg(feature = "cuda-runtime")]
pub fn benchmark_batch_proofs(
log_size: u32,
num_proofs: usize,
num_polynomials_per_proof: usize,
num_fft_rounds: usize,
) -> Result<BatchProofBenchmarkResult, CudaFftError> {
use std::time::Instant;
let n = 1usize << log_size;
let setup_start = Instant::now();
let mut batch = BatchProofProcessor::new(log_size, num_proofs)?;
let setup_time = setup_start.elapsed();
let upload_start = Instant::now();
for proof_idx in 0..num_proofs {
for poly_idx in 0..num_polynomials_per_proof {
let data: Vec<u32> = (0..n)
.map(|i| ((i * 7 + poly_idx * 13 + proof_idx * 17 + 23) as u32) % ((1 << 31) - 1))
.collect();
batch.upload_polynomial(proof_idx, &data)?;
}
}
batch.sync()?;
let upload_time = upload_start.elapsed();
let compute_start = Instant::now();
for _round in 0..num_fft_rounds {
for poly_idx in 0..num_polynomials_per_proof {
batch.batch_ifft(poly_idx)?;
}
for poly_idx in 0..num_polynomials_per_proof {
batch.batch_fft(poly_idx)?;
}
}
batch.sync()?;
let compute_time = compute_start.elapsed();
let total_time = setup_time + upload_time + compute_time;
let total_ffts = num_proofs * num_polynomials_per_proof * num_fft_rounds * 2;
Ok(BatchProofBenchmarkResult {
log_size,
num_proofs,
num_polynomials_per_proof,
num_fft_rounds,
total_ffts,
setup_time,
upload_time,
compute_time,
total_time,
})
}
#[derive(Debug)]
pub struct BatchProofBenchmarkResult {
pub log_size: u32,
pub num_proofs: usize,
pub num_polynomials_per_proof: usize,
pub num_fft_rounds: usize,
pub total_ffts: usize,
pub setup_time: std::time::Duration,
pub upload_time: std::time::Duration,
pub compute_time: std::time::Duration,
pub total_time: std::time::Duration,
}
impl BatchProofBenchmarkResult {
pub fn time_per_proof(&self) -> std::time::Duration {
self.compute_time / self.num_proofs as u32
}
pub fn time_per_fft(&self) -> std::time::Duration {
self.compute_time / self.total_ffts as u32
}
}
impl std::fmt::Display for BatchProofBenchmarkResult {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
writeln!(f, "Batch Proof Benchmark Results")?;
writeln!(f, "==============================")?;
writeln!(f, " Polynomial size: 2^{} = {} elements", self.log_size, 1usize << self.log_size)?;
writeln!(f, " Proofs in batch: {}", self.num_proofs)?;
writeln!(f, " Polys per proof: {}", self.num_polynomials_per_proof)?;
writeln!(f, " FFT rounds: {}", self.num_fft_rounds)?;
writeln!(f, " Total FFTs: {}", self.total_ffts)?;
writeln!(f)?;
writeln!(f, "Timing:")?;
writeln!(f, " Setup: {:?}", self.setup_time)?;
writeln!(f, " Upload: {:?}", self.upload_time)?;
writeln!(f, " Compute: {:?}", self.compute_time)?;
writeln!(f, " Total: {:?}", self.total_time)?;
writeln!(f)?;
writeln!(f, "Performance:")?;
writeln!(f, " Time per proof: {:?}", self.time_per_proof())?;
writeln!(f, " Time per FFT: {:?}", self.time_per_fft())?;
let compute_pct = self.compute_time.as_secs_f64() / self.total_time.as_secs_f64() * 100.0;
writeln!(f, " Compute efficiency: {:.1}%", compute_pct)?;
Ok(())
}
}
#[cfg(feature = "cuda-runtime")]
pub struct GpuStreamingPipeline {
buffers: [Vec<CudaSlice<u32>>; 2],
current_buffer: usize,
compute_stream: Option<CudaStream>,
transfer_stream: Option<CudaStream>,
itwiddles: CudaSlice<u32>,
twiddles: CudaSlice<u32>,
twiddle_offsets: CudaSlice<u32>,
log_size: u32,
itwiddles_cpu: Vec<Vec<u32>>,
twiddles_cpu: Vec<Vec<u32>>,
}
#[cfg(feature = "cuda-runtime")]
impl GpuStreamingPipeline {
pub fn new(log_size: u32) -> Result<Self, CudaFftError> {
let executor = get_cuda_executor().map_err(|e| e.clone())?;
let itwiddles_cpu = compute_itwiddle_dbls_cpu(log_size);
let twiddles_cpu = compute_twiddle_dbls_cpu(log_size);
let flat_itwiddles: Vec<u32> = itwiddles_cpu.iter().flatten().copied().collect();
let flat_twiddles: Vec<u32> = twiddles_cpu.iter().flatten().copied().collect();
let itwiddles = executor.device.htod_sync_copy(&flat_itwiddles)
.map_err(|e| CudaFftError::MemoryAllocation(format!("{:?}", e)))?;
let twiddles = executor.device.htod_sync_copy(&flat_twiddles)
.map_err(|e| CudaFftError::MemoryAllocation(format!("{:?}", e)))?;
let mut offsets: Vec<u32> = Vec::new();
let mut offset = 0u32;
for tw in &itwiddles_cpu {
offsets.push(offset);
offset += tw.len() as u32;
}
let twiddle_offsets = executor.device.htod_sync_copy(&offsets)
.map_err(|e| CudaFftError::MemoryAllocation(format!("{:?}", e)))?;
let compute_stream = executor.device.fork_default_stream()
.map_err(|e| CudaFftError::DriverInit(format!("Failed to create compute stream: {:?}", e)))
.ok();
let transfer_stream = executor.device.fork_default_stream()
.map_err(|e| CudaFftError::DriverInit(format!("Failed to create transfer stream: {:?}", e)))
.ok();
Ok(Self {
buffers: [Vec::new(), Vec::new()],
current_buffer: 0,
compute_stream,
transfer_stream,
itwiddles,
twiddles,
twiddle_offsets,
log_size,
itwiddles_cpu,
twiddles_cpu,
})
}
pub fn preallocate(&mut self, num_polynomials: usize) -> Result<(), CudaFftError> {
let executor = get_cuda_executor().map_err(|e| e.clone())?;
let n = 1usize << self.log_size;
for buffer in &mut self.buffers {
buffer.clear();
for _ in 0..num_polynomials {
let d_data = unsafe {
executor.device.alloc::<u32>(n)
}.map_err(|e| CudaFftError::MemoryAllocation(format!("{:?}", e)))?;
buffer.push(d_data);
}
}
Ok(())
}
pub fn upload_async(&mut self, poly_idx: usize, data: &[u32]) -> Result<(), CudaFftError> {
let next_buffer = 1 - self.current_buffer;
if poly_idx >= self.buffers[next_buffer].len() {
return Err(CudaFftError::InvalidSize(
format!("Invalid polynomial index: {}", poly_idx)
));
}
let n = 1usize << self.log_size;
if data.len() != n {
return Err(CudaFftError::InvalidSize(
format!("Expected {} elements, got {}", n, data.len())
));
}
let executor = get_cuda_executor().map_err(|e| e.clone())?;
executor.device.htod_sync_copy_into(data, &mut self.buffers[next_buffer][poly_idx])
.map_err(|e| CudaFftError::MemoryTransfer(format!("{:?}", e)))?;
Ok(())
}
pub fn compute_ifft(&mut self, poly_idx: usize) -> Result<(), CudaFftError> {
if poly_idx >= self.buffers[self.current_buffer].len() {
return Err(CudaFftError::InvalidSize(
format!("Invalid polynomial index: {}", poly_idx)
));
}
let executor = get_cuda_executor().map_err(|e| e.clone())?;
executor.execute_ifft_on_device(
&mut self.buffers[self.current_buffer][poly_idx],
&self.itwiddles,
&self.twiddle_offsets,
&self.itwiddles_cpu,
self.log_size,
)
}
pub fn download(&self, poly_idx: usize) -> Result<Vec<u32>, CudaFftError> {
if poly_idx >= self.buffers[self.current_buffer].len() {
return Err(CudaFftError::InvalidSize(
format!("Invalid polynomial index: {}", poly_idx)
));
}
let executor = get_cuda_executor().map_err(|e| e.clone())?;
let n = 1usize << self.log_size;
let mut result = vec![0u32; n];
executor.device.dtoh_sync_copy_into(&self.buffers[self.current_buffer][poly_idx], &mut result)
.map_err(|e| CudaFftError::MemoryTransfer(format!("{:?}", e)))?;
Ok(result)
}
pub fn swap_buffers(&mut self) {
self.current_buffer = 1 - self.current_buffer;
}
pub fn sync(&self) -> Result<(), CudaFftError> {
let executor = get_cuda_executor().map_err(|e| e.clone())?;
if let Some(ref stream) = self.compute_stream {
executor.device.wait_for(stream)
.map_err(|e| CudaFftError::KernelExecution(format!("Compute stream sync failed: {:?}", e)))?;
}
if let Some(ref stream) = self.transfer_stream {
executor.device.wait_for(stream)
.map_err(|e| CudaFftError::KernelExecution(format!("Transfer stream sync failed: {:?}", e)))?;
}
executor.device.synchronize()
.map_err(|e| CudaFftError::KernelExecution(format!("Device sync failed: {:?}", e)))
}
pub fn compute_fft(&mut self, poly_idx: usize) -> Result<(), CudaFftError> {
if poly_idx >= self.buffers[self.current_buffer].len() {
return Err(CudaFftError::InvalidSize(
format!("Invalid polynomial index: {}", poly_idx)
));
}
let executor = get_cuda_executor().map_err(|e| e.clone())?;
let block_size = 256u32;
let num_layers = self.twiddles_cpu.len();
let mut twiddle_offsets: Vec<usize> = Vec::new();
let mut offset = 0usize;
for tw in &self.twiddles_cpu {
twiddle_offsets.push(offset);
offset += tw.len();
}
for layer in (0..num_layers).rev() {
let n_twiddles = self.twiddles_cpu[layer].len() as u32;
let butterflies_per_twiddle = 1u32 << layer;
let total_butterflies = n_twiddles * butterflies_per_twiddle;
let grid_size = (total_butterflies + block_size - 1) / block_size;
let twiddle_offset = twiddle_offsets[layer];
let cfg = LaunchConfig {
grid_dim: (grid_size, 1, 1),
block_dim: (block_size, 1, 1),
shared_mem_bytes: 0,
};
let twiddle_view = self.twiddles.slice(twiddle_offset..);
unsafe {
executor.kernels.fft_layer.clone().launch(
cfg,
(&mut self.buffers[self.current_buffer][poly_idx], &twiddle_view, layer as u32, self.log_size, n_twiddles),
).map_err(|e| CudaFftError::KernelExecution(format!("{:?}", e)))?;
}
}
executor.device.synchronize()
.map_err(|e| CudaFftError::KernelExecution(format!("Sync failed: {:?}", e)))?;
Ok(())
}
pub fn has_streams(&self) -> bool {
self.compute_stream.is_some() && self.transfer_stream.is_some()
}
pub fn log_size(&self) -> u32 {
self.log_size
}
pub fn num_polynomials(&self) -> usize {
self.buffers[self.current_buffer].len()
}
pub fn process_batch_overlapped(
&mut self,
input_batches: &[Vec<Vec<u32>>],
) -> Result<Vec<Vec<Vec<u32>>>, CudaFftError> {
if input_batches.is_empty() {
return Ok(Vec::new());
}
let num_polys_per_batch = input_batches[0].len();
let num_batches = input_batches.len();
self.preallocate(num_polys_per_batch)?;
let mut results: Vec<Vec<Vec<u32>>> = Vec::with_capacity(num_batches);
for (poly_idx, data) in input_batches[0].iter().enumerate() {
self.upload_async(poly_idx, data)?;
}
self.swap_buffers();
for batch_idx in 0..num_batches {
for poly_idx in 0..num_polys_per_batch {
self.compute_ifft(poly_idx)?;
}
if batch_idx + 1 < num_batches {
for (poly_idx, data) in input_batches[batch_idx + 1].iter().enumerate() {
self.upload_async(poly_idx, data)?;
}
}
self.sync()?;
let mut batch_results = Vec::with_capacity(num_polys_per_batch);
for poly_idx in 0..num_polys_per_batch {
batch_results.push(self.download(poly_idx)?);
}
results.push(batch_results);
self.swap_buffers();
}
Ok(results)
}
}
#[cfg(not(feature = "cuda-runtime"))]
pub struct GpuStreamingPipeline;
#[cfg(not(feature = "cuda-runtime"))]
impl GpuStreamingPipeline {
pub fn new(_log_size: u32) -> Result<Self, String> {
Err("CUDA runtime not available".into())
}
}
#[cfg(not(feature = "cuda-runtime"))]
pub struct GpuProofPipeline;
#[cfg(not(feature = "cuda-runtime"))]
impl GpuProofPipeline {
pub fn new(_log_size: u32) -> Result<Self, String> {
Err("CUDA runtime not available".into())
}
}
#[cfg(not(feature = "cuda-runtime"))]
pub struct BatchProofProcessor;
#[cfg(not(feature = "cuda-runtime"))]
impl BatchProofProcessor {
pub fn new(_log_size: u32, _num_proofs: usize) -> Result<Self, String> {
Err("CUDA runtime not available".into())
}
}