#[cfg(feature = "cuda-runtime")]
use super::cuda_executor::{CudaFftError, get_cuda_executor};
#[cfg(feature = "cuda-runtime")]
use super::pipeline::GpuProofPipeline;
#[derive(Debug, Clone)]
pub struct MemoryRequirements {
pub log_size: u32,
pub elements: usize,
pub bytes_per_poly: usize,
pub total_poly_bytes: usize,
pub twiddle_bytes: usize,
pub buffer_bytes: usize,
pub total_bytes: usize,
}
impl MemoryRequirements {
pub fn calculate(log_size: u32, num_polynomials: usize, num_buffers: usize) -> Self {
let elements = 1usize << log_size;
let bytes_per_poly = elements * 4; let total_poly_bytes = bytes_per_poly * num_polynomials * num_buffers;
let twiddle_bytes = elements * 4 * 2;
let buffer_bytes = bytes_per_poly * 2;
let total_bytes = total_poly_bytes + twiddle_bytes + buffer_bytes;
Self {
log_size,
elements,
bytes_per_poly,
total_poly_bytes,
twiddle_bytes,
buffer_bytes,
total_bytes,
}
}
pub fn fits_in_memory(&self, available_bytes: usize) -> bool {
self.total_bytes < available_bytes
}
pub fn recommended_chunk_log_size(&self, available_bytes: usize) -> u32 {
let usable_bytes = (available_bytes as f64 * 0.8) as usize;
for try_log in (10..=self.log_size).rev() {
let try_reqs = Self::calculate(try_log, 1, 2);
if try_reqs.total_bytes < usable_bytes {
return try_log;
}
}
10 }
}
impl std::fmt::Display for MemoryRequirements {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
writeln!(f, "Memory Requirements for 2^{} ({} elements):", self.log_size, self.elements)?;
writeln!(f, " Per polynomial: {} MB", self.bytes_per_poly / (1024 * 1024))?;
writeln!(f, " All polynomials: {} MB", self.total_poly_bytes / (1024 * 1024))?;
writeln!(f, " Twiddles: {} MB", self.twiddle_bytes / (1024 * 1024))?;
writeln!(f, " Buffers: {} MB", self.buffer_bytes / (1024 * 1024))?;
writeln!(f, " Total: {} MB ({:.2} GB)",
self.total_bytes / (1024 * 1024),
self.total_bytes as f64 / (1024.0 * 1024.0 * 1024.0))?;
Ok(())
}
}
#[cfg(feature = "cuda-runtime")]
pub struct LargeProofPipeline {
inner: LargeProofInner,
requirements: MemoryRequirements,
available_memory: usize,
}
#[cfg(feature = "cuda-runtime")]
enum LargeProofInner {
Direct(GpuProofPipeline),
Chunked {
chunk_log_size: u32,
num_chunks: usize,
chunk_pipeline: GpuProofPipeline,
},
}
#[cfg(feature = "cuda-runtime")]
impl LargeProofPipeline {
pub fn new(log_size: u32, num_polynomials: usize) -> Result<Self, CudaFftError> {
let executor = get_cuda_executor().map_err(|e| e.clone())?;
let available_memory = executor.device_info.total_memory_bytes;
let requirements = MemoryRequirements::calculate(log_size, num_polynomials, 2);
let inner = if requirements.fits_in_memory(available_memory) {
tracing::info!(
"Large proof pipeline: direct mode (needs {} MB, have {} MB)",
requirements.total_bytes / (1024 * 1024),
available_memory / (1024 * 1024)
);
LargeProofInner::Direct(GpuProofPipeline::new(log_size)?)
} else {
let chunk_log_size = requirements.recommended_chunk_log_size(available_memory);
let num_chunks = 1 << (log_size - chunk_log_size);
tracing::info!(
"Large proof pipeline: chunked mode ({} chunks of 2^{})",
num_chunks, chunk_log_size
);
LargeProofInner::Chunked {
chunk_log_size,
num_chunks,
chunk_pipeline: GpuProofPipeline::new(chunk_log_size)?,
}
};
Ok(Self {
inner,
requirements,
available_memory,
})
}
pub fn is_chunked(&self) -> bool {
matches!(self.inner, LargeProofInner::Chunked { .. })
}
pub fn requirements(&self) -> &MemoryRequirements {
&self.requirements
}
pub fn available_memory(&self) -> usize {
self.available_memory
}
pub fn process_ifft(&mut self, data: &[u32]) -> Result<Vec<u32>, CudaFftError> {
match &mut self.inner {
LargeProofInner::Direct(pipeline) => {
pipeline.upload_polynomial(data)?;
pipeline.ifft(0)?;
let result = pipeline.download_polynomial(0)?;
pipeline.clear_polynomials();
Ok(result)
}
LargeProofInner::Chunked { chunk_log_size, num_chunks, chunk_pipeline } => {
let chunk_size = 1usize << *chunk_log_size;
let mut result = Vec::with_capacity(data.len());
for chunk_idx in 0..*num_chunks {
let start = chunk_idx * chunk_size;
let end = start + chunk_size;
let chunk_data = &data[start..end];
chunk_pipeline.upload_polynomial(chunk_data)?;
chunk_pipeline.ifft(0)?;
let chunk_result = chunk_pipeline.download_polynomial(0)?;
chunk_pipeline.clear_polynomials();
result.extend_from_slice(&chunk_result);
if (chunk_idx + 1) % 10 == 0 {
tracing::info!("Processed chunk {}/{}", chunk_idx + 1, num_chunks);
}
}
Ok(result)
}
}
}
pub fn process_fft(&mut self, data: &[u32]) -> Result<Vec<u32>, CudaFftError> {
match &mut self.inner {
LargeProofInner::Direct(pipeline) => {
pipeline.upload_polynomial(data)?;
pipeline.fft(0)?;
let result = pipeline.download_polynomial(0)?;
pipeline.clear_polynomials();
Ok(result)
}
LargeProofInner::Chunked { chunk_log_size, num_chunks, chunk_pipeline } => {
let chunk_size = 1usize << *chunk_log_size;
let mut result = Vec::with_capacity(data.len());
for chunk_idx in 0..*num_chunks {
let start = chunk_idx * chunk_size;
let end = start + chunk_size;
let chunk_data = &data[start..end];
chunk_pipeline.upload_polynomial(chunk_data)?;
chunk_pipeline.fft(0)?;
let chunk_result = chunk_pipeline.download_polynomial(0)?;
chunk_pipeline.clear_polynomials();
result.extend_from_slice(&chunk_result);
if (chunk_idx + 1) % 10 == 0 {
tracing::info!("Processed chunk {}/{}", chunk_idx + 1, num_chunks);
}
}
Ok(result)
}
}
}
}
#[cfg(feature = "cuda-runtime")]
pub fn benchmark_large_proof(
log_size: u32,
num_polynomials: usize,
) -> Result<LargeBenchmarkResult, CudaFftError> {
use std::time::Instant;
let n = 1usize << log_size;
let requirements = MemoryRequirements::calculate(log_size, num_polynomials, 2);
let executor = get_cuda_executor().map_err(|e| e.clone())?;
let available = executor.device_info.total_memory_bytes;
if !requirements.fits_in_memory(available) {
return Err(CudaFftError::MemoryAllocation(format!(
"Proof requires {} GB but only {} GB available",
requirements.total_bytes as f64 / (1024.0 * 1024.0 * 1024.0),
available as f64 / (1024.0 * 1024.0 * 1024.0)
)));
}
let test_data: Vec<Vec<u32>> = (0..num_polynomials)
.map(|p| {
(0..n)
.map(|i| ((i * 7 + p * 13 + 17) as u32) % 0x7FFFFFFF)
.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 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();
for poly_idx in 0..num_polynomials {
let _ = 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 * 2;
let throughput = total_ffts as f64 / compute_time.as_secs_f64();
let data_processed_gb = (n * 4 * num_polynomials * 2) as f64 / (1024.0 * 1024.0 * 1024.0);
let bandwidth_gbps = data_processed_gb / compute_time.as_secs_f64();
Ok(LargeBenchmarkResult {
log_size,
num_polynomials,
elements: n,
requirements,
setup_time,
upload_time,
compute_time,
download_time,
total_time,
total_ffts,
throughput_ffts_per_sec: throughput,
bandwidth_gbps,
})
}
#[derive(Debug)]
pub struct LargeBenchmarkResult {
pub log_size: u32,
pub num_polynomials: usize,
pub elements: usize,
pub requirements: MemoryRequirements,
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 total_ffts: usize,
pub throughput_ffts_per_sec: f64,
pub bandwidth_gbps: f64,
}
impl std::fmt::Display for LargeBenchmarkResult {
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, self.elements)?;
writeln!(f, " Polynomials: {}", self.num_polynomials)?;
writeln!(f, " Data size: {:.2} GB",
(self.elements * 4 * self.num_polynomials) as f64 / (1024.0 * 1024.0 * 1024.0))?;
writeln!(f)?;
writeln!(f, "Memory Usage:")?;
writeln!(f, " GPU memory used: {:.2} GB",
self.requirements.total_bytes as f64 / (1024.0 * 1024.0 * 1024.0))?;
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: {:.1} FFTs/sec", self.throughput_ffts_per_sec)?;
writeln!(f, " Bandwidth: {:.2} GB/s", self.bandwidth_gbps)?;
Ok(())
}
}
#[cfg(feature = "cuda-runtime")]
pub fn detect_max_proof_size(num_polynomials: usize) -> Result<MaxProofSize, CudaFftError> {
let executor = get_cuda_executor().map_err(|e| e.clone())?;
let available = executor.device_info.total_memory_bytes;
let mut max_log_size = 10u32;
for try_log in 10..=32 {
let reqs = MemoryRequirements::calculate(try_log, num_polynomials, 2);
if reqs.fits_in_memory(available) {
max_log_size = try_log;
} else {
break;
}
}
let requirements = MemoryRequirements::calculate(max_log_size, num_polynomials, 2);
Ok(MaxProofSize {
max_log_size,
max_elements: 1 << max_log_size,
available_memory: available,
requirements,
})
}
#[derive(Debug)]
pub struct MaxProofSize {
pub max_log_size: u32,
pub max_elements: usize,
pub available_memory: usize,
pub requirements: MemoryRequirements,
}
impl std::fmt::Display for MaxProofSize {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
writeln!(f, "Maximum Proof Size Detection")?;
writeln!(f, "============================")?;
writeln!(f, " Available GPU memory: {:.2} GB",
self.available_memory as f64 / (1024.0 * 1024.0 * 1024.0))?;
writeln!(f, " Maximum log_size: {}", self.max_log_size)?;
writeln!(f, " Maximum elements: {} ({:.1}M)",
self.max_elements,
self.max_elements as f64 / 1_000_000.0)?;
writeln!(f)?;
writeln!(f, "Memory breakdown for max size:")?;
write!(f, "{}", self.requirements)?;
Ok(())
}
}
#[cfg(not(feature = "cuda-runtime"))]
pub struct LargeProofPipeline;
#[cfg(not(feature = "cuda-runtime"))]
impl LargeProofPipeline {
pub fn new(_log_size: u32, _num_polynomials: usize) -> Result<Self, String> {
Err("CUDA runtime not available".into())
}
}