#[cfg(feature = "cuda-runtime")]
use cudarc::driver::{CudaDevice, CudaSlice, CudaStream, LaunchConfig, LaunchAsync, DevicePtr};
#[cfg(feature = "cuda-runtime")]
use std::sync::Arc;
#[cfg(feature = "cuda-runtime")]
use super::cuda_executor::{CudaFftError, get_cuda_executor};
#[cfg(feature = "cuda-runtime")]
use super::fft::{compute_itwiddle_dbls_cpu, compute_twiddle_dbls_cpu};
#[cfg(feature = "cuda-runtime")]
pub struct CudaStreamManager {
device: Arc<CudaDevice>,
compute_stream: CudaStream,
h2d_stream: CudaStream,
d2h_stream: CudaStream,
extra_streams: Vec<CudaStream>,
}
#[cfg(feature = "cuda-runtime")]
impl CudaStreamManager {
pub fn new(device: Arc<CudaDevice>) -> Result<Self, CudaFftError> {
let compute_stream = device.fork_default_stream()
.map_err(|e| CudaFftError::DriverInit(format!("Failed to create compute stream: {:?}", e)))?;
let h2d_stream = device.fork_default_stream()
.map_err(|e| CudaFftError::DriverInit(format!("Failed to create H2D stream: {:?}", e)))?;
let d2h_stream = device.fork_default_stream()
.map_err(|e| CudaFftError::DriverInit(format!("Failed to create D2H stream: {:?}", e)))?;
tracing::info!("Created CUDA stream manager with 3 streams");
Ok(Self {
device,
compute_stream,
h2d_stream,
d2h_stream,
extra_streams: Vec::new(),
})
}
pub fn create_extra_streams(&mut self, count: usize) -> Result<(), CudaFftError> {
for i in 0..count {
let stream = self.device.fork_default_stream()
.map_err(|e| CudaFftError::DriverInit(format!("Failed to create extra stream {}: {:?}", i, e)))?;
self.extra_streams.push(stream);
}
tracing::info!("Created {} additional CUDA streams", count);
Ok(())
}
pub fn compute_stream(&self) -> &CudaStream {
&self.compute_stream
}
pub fn h2d_stream(&self) -> &CudaStream {
&self.h2d_stream
}
pub fn d2h_stream(&self) -> &CudaStream {
&self.d2h_stream
}
pub fn sync_compute(&self) -> Result<(), CudaFftError> {
self.device.wait_for(&self.compute_stream)
.map_err(|e| CudaFftError::KernelExecution(format!("Compute stream sync failed: {:?}", e)))
}
pub fn sync_h2d(&self) -> Result<(), CudaFftError> {
self.device.wait_for(&self.h2d_stream)
.map_err(|e| CudaFftError::KernelExecution(format!("H2D stream sync failed: {:?}", e)))
}
pub fn sync_d2h(&self) -> Result<(), CudaFftError> {
self.device.wait_for(&self.d2h_stream)
.map_err(|e| CudaFftError::KernelExecution(format!("D2H stream sync failed: {:?}", e)))
}
pub fn sync_all(&self) -> Result<(), CudaFftError> {
self.sync_compute()?;
self.sync_h2d()?;
self.sync_d2h()?;
for (i, stream) in self.extra_streams.iter().enumerate() {
self.device.wait_for(stream)
.map_err(|e| CudaFftError::KernelExecution(format!("Extra stream {} sync failed: {:?}", i, e)))?;
}
Ok(())
}
pub fn device_sync(&self) -> Result<(), CudaFftError> {
self.device.synchronize()
.map_err(|e| CudaFftError::KernelExecution(format!("Device sync failed: {:?}", e)))
}
}
#[cfg(feature = "cuda-runtime")]
pub struct PinnedBuffer<T: Copy + Default> {
ptr: *mut T,
len: usize,
capacity: usize,
}
#[cfg(feature = "cuda-runtime")]
impl<T: Copy + Default> PinnedBuffer<T> {
pub fn new(capacity: usize) -> Result<Self, CudaFftError> {
use super::compat;
if capacity == 0 {
return Ok(Self {
ptr: std::ptr::null_mut(),
len: 0,
capacity: 0,
});
}
let size_bytes = capacity * std::mem::size_of::<T>();
let ptr = compat::mem_alloc_host(size_bytes)
.map_err(|e| CudaFftError::MemoryAllocation(
format!("Failed to allocate pinned memory ({} bytes): {}", size_bytes, e)
))?;
tracing::debug!("Allocated {} bytes of pinned memory", size_bytes);
Ok(Self {
ptr: ptr as *mut T,
len: 0,
capacity,
})
}
#[inline]
pub fn as_ptr(&self) -> *const T {
self.ptr
}
#[inline]
pub fn as_mut_ptr(&mut self) -> *mut T {
self.ptr
}
#[inline]
pub fn len(&self) -> usize {
self.len
}
#[inline]
pub fn is_empty(&self) -> bool {
self.len == 0
}
#[inline]
pub fn capacity(&self) -> usize {
self.capacity
}
pub fn copy_from_slice(&mut self, data: &[T]) -> Result<(), CudaFftError> {
if data.len() > self.capacity {
return Err(CudaFftError::InvalidSize(
format!("Data ({}) exceeds pinned buffer capacity ({})", data.len(), self.capacity)
));
}
unsafe {
std::ptr::copy_nonoverlapping(data.as_ptr(), self.ptr, data.len());
}
self.len = data.len();
Ok(())
}
pub fn copy_to_slice(&self, dest: &mut [T]) -> Result<(), CudaFftError> {
if dest.len() < self.len {
return Err(CudaFftError::InvalidSize(
format!("Destination ({}) smaller than buffer ({})", dest.len(), self.len)
));
}
unsafe {
std::ptr::copy_nonoverlapping(self.ptr, dest.as_mut_ptr(), self.len);
}
Ok(())
}
pub fn as_slice(&self) -> &[T] {
if self.ptr.is_null() || self.len == 0 {
return &[];
}
unsafe { std::slice::from_raw_parts(self.ptr, self.len) }
}
pub fn as_mut_slice(&mut self) -> &mut [T] {
if self.ptr.is_null() || self.len == 0 {
return &mut [];
}
unsafe { std::slice::from_raw_parts_mut(self.ptr, self.len) }
}
}
#[cfg(feature = "cuda-runtime")]
impl<T: Copy + Default> Drop for PinnedBuffer<T> {
fn drop(&mut self) {
if !self.ptr.is_null() {
use super::compat;
if let Err(e) = compat::mem_free_host(self.ptr as *mut std::ffi::c_void) {
tracing::warn!("Failed to free pinned memory: {}", e);
}
}
}
}
#[cfg(feature = "cuda-runtime")]
unsafe impl<T: Copy + Default + Send> Send for PinnedBuffer<T> {}
#[cfg(feature = "cuda-runtime")]
unsafe impl<T: Copy + Default + Sync> Sync for PinnedBuffer<T> {}
#[cfg(feature = "cuda-runtime")]
pub struct AsyncTransferManager {
device: Arc<CudaDevice>,
upload_buffer: PinnedBuffer<u32>,
download_buffer: PinnedBuffer<u32>,
h2d_stream: CudaStream,
d2h_stream: CudaStream,
}
#[cfg(feature = "cuda-runtime")]
impl AsyncTransferManager {
pub fn new(device: Arc<CudaDevice>, buffer_size: usize) -> Result<Self, CudaFftError> {
let upload_buffer = PinnedBuffer::new(buffer_size)?;
let download_buffer = PinnedBuffer::new(buffer_size)?;
let h2d_stream = device.fork_default_stream()
.map_err(|e| CudaFftError::DriverInit(format!("H2D stream: {:?}", e)))?;
let d2h_stream = device.fork_default_stream()
.map_err(|e| CudaFftError::DriverInit(format!("D2H stream: {:?}", e)))?;
tracing::info!("Created async transfer manager with {} element buffers", buffer_size);
Ok(Self {
device,
upload_buffer,
download_buffer,
h2d_stream,
d2h_stream,
})
}
pub fn upload_async(&mut self, data: &[u32], gpu_dest: &mut CudaSlice<u32>) -> Result<(), CudaFftError> {
use super::compat;
self.upload_buffer.copy_from_slice(data)?;
let src_ptr = self.upload_buffer.as_ptr() as *const std::ffi::c_void;
let dst_ptr = *gpu_dest.device_ptr();
let size_bytes = data.len() * std::mem::size_of::<u32>();
let stream_handle = self.h2d_stream.stream;
compat::memcpy_htod_async(dst_ptr, src_ptr, size_bytes, stream_handle)
.map_err(|e| CudaFftError::MemoryTransfer(e))?;
Ok(())
}
pub fn download_async(&mut self, gpu_src: &CudaSlice<u32>, len: usize) -> Result<(), CudaFftError> {
use super::compat;
if len > self.download_buffer.capacity() {
return Err(CudaFftError::InvalidSize(
format!("Download size ({}) exceeds buffer capacity ({})", len, self.download_buffer.capacity())
));
}
self.download_buffer.len = len;
let src_ptr = *gpu_src.device_ptr();
let dst_ptr = self.download_buffer.as_mut_ptr() as *mut std::ffi::c_void;
let size_bytes = len * std::mem::size_of::<u32>();
let stream_handle = self.d2h_stream.stream;
compat::memcpy_dtoh_async(dst_ptr, src_ptr, size_bytes, stream_handle)
.map_err(|e| CudaFftError::MemoryTransfer(e))?;
Ok(())
}
pub fn sync_h2d(&self) -> Result<(), CudaFftError> {
self.device.wait_for(&self.h2d_stream)
.map_err(|e| CudaFftError::MemoryTransfer(format!("H2D sync: {:?}", e)))
}
pub fn sync_d2h(&self) -> Result<(), CudaFftError> {
self.device.wait_for(&self.d2h_stream)
.map_err(|e| CudaFftError::MemoryTransfer(format!("D2H sync: {:?}", e)))
}
pub fn get_download_buffer(&self) -> &[u32] {
self.download_buffer.as_slice()
}
}
#[cfg(feature = "cuda-runtime")]
pub struct TripleBufferedPipeline {
streams: CudaStreamManager,
buffers: [Vec<CudaSlice<u32>>; 3],
compute_idx: usize,
upload_idx: usize,
download_idx: usize,
itwiddles: CudaSlice<u32>,
twiddles: CudaSlice<u32>,
twiddle_offsets: CudaSlice<u32>,
itwiddles_cpu: Vec<Vec<u32>>,
twiddles_cpu: Vec<Vec<u32>>,
log_size: u32,
num_polynomials: usize,
}
#[cfg(feature = "cuda-runtime")]
impl TripleBufferedPipeline {
pub fn new(log_size: u32, num_polynomials: usize) -> Result<Self, CudaFftError> {
let executor = get_cuda_executor().map_err(|e| e.clone())?;
let device = executor.device.clone();
let n = 1usize << log_size;
let streams = CudaStreamManager::new(device.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 = device.htod_sync_copy(&flat_itwiddles)
.map_err(|e| CudaFftError::MemoryAllocation(format!("{:?}", e)))?;
let twiddles = 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 = device.htod_sync_copy(&offsets)
.map_err(|e| CudaFftError::MemoryAllocation(format!("{:?}", e)))?;
let mut buffers: [Vec<CudaSlice<u32>>; 3] = [Vec::new(), Vec::new(), Vec::new()];
for buffer in &mut buffers {
for _ in 0..num_polynomials {
let d_poly = unsafe {
device.alloc::<u32>(n)
}.map_err(|e| CudaFftError::MemoryAllocation(format!("{:?}", e)))?;
buffer.push(d_poly);
}
}
tracing::info!(
"Created triple-buffered pipeline: log_size={}, polys={}, buffers=3x{}",
log_size, num_polynomials, n
);
Ok(Self {
streams,
buffers,
compute_idx: 0,
upload_idx: 1,
download_idx: 2,
itwiddles,
twiddles,
twiddle_offsets,
itwiddles_cpu,
twiddles_cpu,
log_size,
num_polynomials,
})
}
fn rotate_buffers(&mut self) {
let old_compute = self.compute_idx;
self.compute_idx = self.upload_idx;
self.upload_idx = self.download_idx;
self.download_idx = old_compute;
}
pub fn upload_async(&mut self, poly_idx: usize, data: &[u32]) -> Result<(), CudaFftError> {
if poly_idx >= self.num_polynomials {
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[self.upload_idx][poly_idx])
.map_err(|e| CudaFftError::MemoryTransfer(format!("{:?}", e)))?;
Ok(())
}
pub fn compute_ifft_async(&mut self, poly_idx: usize) -> Result<(), CudaFftError> {
if poly_idx >= self.num_polynomials {
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.compute_idx][poly_idx],
&self.itwiddles,
&self.twiddle_offsets,
&self.itwiddles_cpu,
self.log_size,
)
}
pub fn compute_fft_async(&mut self, poly_idx: usize) -> Result<(), CudaFftError> {
if poly_idx >= self.num_polynomials {
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.compute_idx][poly_idx],
&twiddle_view,
layer as u32,
self.log_size,
n_twiddles,
),
).map_err(|e| CudaFftError::KernelExecution(format!("{:?}", e)))?;
}
}
Ok(())
}
pub fn download_async(&self, poly_idx: usize) -> Result<Vec<u32>, CudaFftError> {
if poly_idx >= self.num_polynomials {
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.download_idx][poly_idx], &mut result)
.map_err(|e| CudaFftError::MemoryTransfer(format!("{:?}", e)))?;
Ok(result)
}
pub fn process_batches(
&mut self,
batches: &[Vec<Vec<u32>>],
operation: PipelineOperation,
) -> Result<Vec<Vec<Vec<u32>>>, CudaFftError> {
if batches.is_empty() {
return Ok(Vec::new());
}
let num_batches = batches.len();
let mut results: Vec<Vec<Vec<u32>>> = Vec::with_capacity(num_batches);
for (poly_idx, data) in batches[0].iter().enumerate() {
self.upload_async(poly_idx, data)?;
}
self.rotate_buffers();
for batch_idx in 0..num_batches {
if batch_idx + 1 < num_batches {
for (poly_idx, data) in batches[batch_idx + 1].iter().enumerate() {
self.upload_async(poly_idx, data)?;
}
}
for poly_idx in 0..self.num_polynomials {
match operation {
PipelineOperation::Ifft => self.compute_ifft_async(poly_idx)?,
PipelineOperation::Fft => self.compute_fft_async(poly_idx)?,
PipelineOperation::IfftThenFft => {
self.compute_ifft_async(poly_idx)?;
self.compute_fft_async(poly_idx)?;
}
}
}
self.streams.sync_compute()?;
let mut batch_results = Vec::with_capacity(self.num_polynomials);
for poly_idx in 0..self.num_polynomials {
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.compute_idx][poly_idx], &mut result)
.map_err(|e| CudaFftError::MemoryTransfer(format!("{:?}", e)))?;
batch_results.push(result);
}
results.push(batch_results);
self.rotate_buffers();
}
Ok(results)
}
pub fn sync_all(&self) -> Result<(), CudaFftError> {
self.streams.sync_all()
}
pub fn stats(&self) -> PipelineStats {
PipelineStats {
log_size: self.log_size,
num_polynomials: self.num_polynomials,
num_buffers: 3,
buffer_size_bytes: (1usize << self.log_size) * 4 * self.num_polynomials * 3,
}
}
}
#[derive(Debug, Clone, Copy)]
pub enum PipelineOperation {
Ifft,
Fft,
IfftThenFft,
}
#[derive(Debug, Clone)]
pub struct PipelineStats {
pub log_size: u32,
pub num_polynomials: usize,
pub num_buffers: usize,
pub buffer_size_bytes: usize,
}
impl std::fmt::Display for PipelineStats {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
writeln!(f, "Pipeline Statistics:")?;
writeln!(f, " Polynomial size: 2^{} = {} elements", self.log_size, 1usize << self.log_size)?;
writeln!(f, " Polynomials per batch: {}", self.num_polynomials)?;
writeln!(f, " Number of buffers: {}", self.num_buffers)?;
writeln!(f, " Total GPU memory: {:.2} MB", self.buffer_size_bytes as f64 / (1024.0 * 1024.0))?;
Ok(())
}
}
#[cfg(feature = "cuda-runtime")]
pub struct AsyncProofPipeline {
core: TripleBufferedPipeline,
fri_twiddles: Vec<CudaSlice<u32>>,
proofs_generated: usize,
total_compute_time_ns: u128,
#[allow(dead_code)]
total_transfer_time_ns: u128,
}
#[cfg(feature = "cuda-runtime")]
impl AsyncProofPipeline {
pub fn new(log_size: u32, num_polynomials: usize) -> Result<Self, CudaFftError> {
let core = TripleBufferedPipeline::new(log_size, num_polynomials)?;
Ok(Self {
core,
fri_twiddles: Vec::new(),
proofs_generated: 0,
total_compute_time_ns: 0,
total_transfer_time_ns: 0,
})
}
pub fn cache_fri_twiddles(&mut self, num_layers: usize) -> Result<(), CudaFftError> {
let executor = get_cuda_executor().map_err(|e| e.clone())?;
let n = 1usize << self.core.log_size;
let mut current_size = n;
for _ in 0..num_layers {
let n_twiddles = current_size / 2;
let layer_twiddles: Vec<u32> = (0..n_twiddles)
.map(|i| ((i as u64 * 31337) % 0x7FFFFFFF) as u32)
.collect();
let d_twiddles = executor.device.htod_sync_copy(&layer_twiddles)
.map_err(|e| CudaFftError::MemoryAllocation(format!("{:?}", e)))?;
self.fri_twiddles.push(d_twiddles);
current_size /= 2;
}
tracing::info!("Cached {} FRI twiddle layers on GPU", num_layers);
Ok(())
}
pub fn generate_proofs_batched(
&mut self,
batches: &[Vec<Vec<u32>>],
) -> Result<Vec<Vec<Vec<u32>>>, CudaFftError> {
use std::time::Instant;
let start = Instant::now();
let results = self.core.process_batches(batches, PipelineOperation::IfftThenFft)?;
let elapsed = start.elapsed();
self.proofs_generated += batches.len();
self.total_compute_time_ns += elapsed.as_nanos();
Ok(results)
}
pub fn throughput_stats(&self) -> ThroughputStats {
let total_time_secs = self.total_compute_time_ns as f64 / 1e9;
let proofs_per_sec = if total_time_secs > 0.0 {
self.proofs_generated as f64 / total_time_secs
} else {
0.0
};
ThroughputStats {
proofs_generated: self.proofs_generated,
total_time_secs,
proofs_per_sec,
avg_latency_ms: if self.proofs_generated > 0 {
(total_time_secs * 1000.0) / self.proofs_generated as f64
} else {
0.0
},
}
}
}
#[derive(Debug, Clone)]
pub struct ThroughputStats {
pub proofs_generated: usize,
pub total_time_secs: f64,
pub proofs_per_sec: f64,
pub avg_latency_ms: f64,
}
impl std::fmt::Display for ThroughputStats {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
writeln!(f, "Throughput Statistics:")?;
writeln!(f, " Proofs generated: {}", self.proofs_generated)?;
writeln!(f, " Total time: {:.3}s", self.total_time_secs)?;
writeln!(f, " Throughput: {:.1} proofs/sec", self.proofs_per_sec)?;
writeln!(f, " Avg latency: {:.2}ms", self.avg_latency_ms)?;
Ok(())
}
}
#[cfg(feature = "cuda-runtime")]
pub fn benchmark_streaming_pipeline(
log_size: u32,
num_polynomials: usize,
num_batches: usize,
) -> Result<StreamingBenchmarkResult, CudaFftError> {
use std::time::Instant;
let n = 1usize << log_size;
let batches: Vec<Vec<Vec<u32>>> = (0..num_batches)
.map(|batch_idx| {
(0..num_polynomials)
.map(|poly_idx| {
(0..n)
.map(|i| ((i * 7 + poly_idx * 13 + batch_idx * 17 + 23) as u32) % 0x7FFFFFFF)
.collect()
})
.collect()
})
.collect();
let setup_start = Instant::now();
let mut pipeline = TripleBufferedPipeline::new(log_size, num_polynomials)?;
let setup_time = setup_start.elapsed();
let streaming_start = Instant::now();
let _results = pipeline.process_batches(&batches, PipelineOperation::IfftThenFft)?;
let streaming_time = streaming_start.elapsed();
let total_ffts = num_batches * num_polynomials * 2; let throughput = total_ffts as f64 / streaming_time.as_secs_f64();
Ok(StreamingBenchmarkResult {
log_size,
num_polynomials,
num_batches,
total_ffts,
setup_time,
streaming_time,
throughput_ffts_per_sec: throughput,
avg_latency_per_batch: streaming_time / num_batches as u32,
})
}
#[derive(Debug)]
pub struct StreamingBenchmarkResult {
pub log_size: u32,
pub num_polynomials: usize,
pub num_batches: usize,
pub total_ffts: usize,
pub setup_time: std::time::Duration,
pub streaming_time: std::time::Duration,
pub throughput_ffts_per_sec: f64,
pub avg_latency_per_batch: std::time::Duration,
}
impl std::fmt::Display for StreamingBenchmarkResult {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
writeln!(f, "Streaming Pipeline Benchmark Results")?;
writeln!(f, "=====================================")?;
writeln!(f, " Polynomial size: 2^{} = {} elements", self.log_size, 1usize << self.log_size)?;
writeln!(f, " Polynomials/batch: {}", self.num_polynomials)?;
writeln!(f, " Number of batches: {}", self.num_batches)?;
writeln!(f, " Total FFTs: {}", self.total_ffts)?;
writeln!(f)?;
writeln!(f, "Timing:")?;
writeln!(f, " Setup: {:?}", self.setup_time)?;
writeln!(f, " Streaming: {:?}", self.streaming_time)?;
writeln!(f)?;
writeln!(f, "Performance:")?;
writeln!(f, " Throughput: {:.1} FFTs/sec", self.throughput_ffts_per_sec)?;
writeln!(f, " Avg batch latency: {:?}", self.avg_latency_per_batch)?;
Ok(())
}
}
#[cfg(not(feature = "cuda-runtime"))]
pub struct CudaStreamManager;
#[cfg(not(feature = "cuda-runtime"))]
impl CudaStreamManager {
pub fn new() -> Result<Self, String> {
Err("CUDA runtime not available".into())
}
}
#[cfg(not(feature = "cuda-runtime"))]
pub struct TripleBufferedPipeline;
#[cfg(not(feature = "cuda-runtime"))]
impl TripleBufferedPipeline {
pub fn new(_log_size: u32, _num_polynomials: usize) -> Result<Self, String> {
Err("CUDA runtime not available".into())
}
}
#[cfg(not(feature = "cuda-runtime"))]
pub struct AsyncProofPipeline;
#[cfg(not(feature = "cuda-runtime"))]
impl AsyncProofPipeline {
pub fn new(_log_size: u32, _num_polynomials: usize) -> Result<Self, String> {
Err("CUDA runtime not available".into())
}
}