#[cfg(feature = "cuda-runtime")]
use cudarc::driver::{CudaDevice, CudaSlice, DeviceRepr};
#[cfg(feature = "cuda-runtime")]
use std::sync::Arc;
use super::cuda_executor::CudaFftError;
#[cfg(feature = "cuda-runtime")]
use super::cuda_executor::get_cuda_executor;
#[cfg(feature = "cuda-runtime")]
use std::cell::RefCell;
#[cfg(feature = "cuda-runtime")]
pub struct FriGpuState {
pub d_data: CudaSlice<u32>,
pub len: usize,
}
#[cfg(feature = "cuda-runtime")]
thread_local! {
static FRI_GPU_CACHE: RefCell<Option<FriGpuState>> = RefCell::new(None);
}
#[cfg(feature = "cuda-runtime")]
pub fn cache_fri_gpu_data<B: crate::prover::backend::ColumnOps<crate::core::fields::m31::BaseField>>(
col: &crate::prover::secure_column::SecureColumnByCoords<B>,
d_data: CudaSlice<u32>,
) {
let len = col.len();
FRI_GPU_CACHE.with(|cache| {
*cache.borrow_mut() = Some(FriGpuState { d_data, len });
});
}
#[cfg(feature = "cuda-runtime")]
pub fn take_cached_fri_gpu_data<B: crate::prover::backend::ColumnOps<crate::core::fields::m31::BaseField>>(
col: &crate::prover::secure_column::SecureColumnByCoords<B>,
) -> Option<CudaSlice<u32>> {
let len = col.len();
FRI_GPU_CACHE.with(|cache| {
let mut guard = cache.borrow_mut();
if let Some(ref state) = *guard {
if state.len == len {
return guard.take().map(|s| s.d_data);
}
}
None
})
}
#[cfg(feature = "cuda-runtime")]
pub fn prepopulate_fri_gpu_cache<B: crate::prover::backend::ColumnOps<crate::core::fields::m31::BaseField>>(
col: &crate::prover::secure_column::SecureColumnByCoords<B>,
d_data: CudaSlice<u32>,
) {
cache_fri_gpu_data(col, d_data);
}
#[cfg(feature = "cuda-runtime")]
pub fn clear_fri_gpu_cache() {
FRI_GPU_CACHE.with(|cache| {
*cache.borrow_mut() = None;
});
}
#[cfg(feature = "cuda-runtime")]
thread_local! {
static COLUMN_GPU_CACHE: RefCell<std::collections::HashMap<(usize, usize), CudaSlice<u32>>> =
RefCell::new(std::collections::HashMap::new());
}
#[cfg(feature = "cuda-runtime")]
pub fn cache_column_gpu(col_ptr: usize, col_len: usize, d_data: CudaSlice<u32>) {
COLUMN_GPU_CACHE.with(|cache| {
cache.borrow_mut().insert((col_ptr, col_len), d_data);
});
}
#[cfg(feature = "cuda-runtime")]
pub fn take_cached_column_gpu(col_ptr: usize, col_len: usize) -> Option<CudaSlice<u32>> {
COLUMN_GPU_CACHE.with(|cache| {
cache.borrow_mut().remove(&(col_ptr, col_len))
})
}
#[cfg(feature = "cuda-runtime")]
pub fn peek_cached_column_gpu(col_ptr: usize, col_len: usize) -> bool {
COLUMN_GPU_CACHE.with(|cache| {
cache.borrow().contains_key(&(col_ptr, col_len))
})
}
#[cfg(not(feature = "cuda-runtime"))]
pub fn peek_cached_column_gpu(_col_ptr: usize, _col_len: usize) -> bool {
false
}
#[cfg(feature = "cuda-runtime")]
pub fn clear_column_gpu_cache() {
COLUMN_GPU_CACHE.with(|cache| {
cache.borrow_mut().clear();
});
}
#[cfg(not(feature = "cuda-runtime"))]
pub fn cache_column_gpu(_col_ptr: usize, _col_len: usize, _d_data: ()) {}
#[cfg(not(feature = "cuda-runtime"))]
pub fn take_cached_column_gpu(_col_ptr: usize, _col_len: usize) -> Option<()> {
None
}
#[cfg(not(feature = "cuda-runtime"))]
pub fn clear_column_gpu_cache() {}
#[cfg(not(feature = "cuda-runtime"))]
pub fn clear_fri_gpu_cache() {}
#[cfg(feature = "cuda-runtime")]
thread_local! {
static MERKLE_PREV_LAYER_GPU_CACHE: RefCell<Option<CudaSlice<u64>>> =
RefCell::new(None);
}
#[cfg(feature = "cuda-runtime")]
pub fn cache_merkle_prev_layer_gpu(d_layer: CudaSlice<u64>) {
MERKLE_PREV_LAYER_GPU_CACHE.with(|cache| {
*cache.borrow_mut() = Some(d_layer);
});
}
#[cfg(feature = "cuda-runtime")]
pub fn take_merkle_prev_layer_gpu() -> Option<CudaSlice<u64>> {
MERKLE_PREV_LAYER_GPU_CACHE.with(|cache| {
cache.borrow_mut().take()
})
}
#[cfg(feature = "cuda-runtime")]
pub fn clear_merkle_prev_layer_gpu_cache() {
MERKLE_PREV_LAYER_GPU_CACHE.with(|cache| {
*cache.borrow_mut() = None;
});
}
#[cfg(not(feature = "cuda-runtime"))]
pub fn clear_merkle_prev_layer_gpu_cache() {}
#[cfg(feature = "cuda-runtime")]
thread_local! {
static MERKLE_PRECOMPUTED_LAYERS: RefCell<std::collections::HashMap<usize, Vec<u64>>> =
RefCell::new(std::collections::HashMap::new());
}
#[cfg(feature = "cuda-runtime")]
pub fn cache_precomputed_merkle_layer(n_hashes: usize, data: Vec<u64>) {
MERKLE_PRECOMPUTED_LAYERS.with(|cache| {
cache.borrow_mut().insert(n_hashes, data);
});
}
#[cfg(feature = "cuda-runtime")]
pub fn take_precomputed_merkle_layer(n_hashes: usize) -> Option<Vec<u64>> {
MERKLE_PRECOMPUTED_LAYERS.with(|cache| {
cache.borrow_mut().remove(&n_hashes)
})
}
#[cfg(feature = "cuda-runtime")]
pub fn clear_precomputed_merkle_layers() {
MERKLE_PRECOMPUTED_LAYERS.with(|cache| {
cache.borrow_mut().clear();
});
}
#[cfg(not(feature = "cuda-runtime"))]
pub fn clear_precomputed_merkle_layers() {}
#[cfg(feature = "cuda-runtime")]
thread_local! {
static BLAKE2S_PRECOMPUTED_LAYERS: RefCell<std::collections::HashMap<usize, Vec<crate::core::vcs::blake2_hash::Blake2sHash>>> =
RefCell::new(std::collections::HashMap::new());
}
#[cfg(feature = "cuda-runtime")]
pub fn cache_precomputed_blake2s_layer(n_hashes: usize, data: Vec<crate::core::vcs::blake2_hash::Blake2sHash>) {
BLAKE2S_PRECOMPUTED_LAYERS.with(|cache| {
cache.borrow_mut().insert(n_hashes, data);
});
}
#[cfg(feature = "cuda-runtime")]
pub fn take_precomputed_blake2s_layer(n_hashes: usize) -> Option<Vec<crate::core::vcs::blake2_hash::Blake2sHash>> {
BLAKE2S_PRECOMPUTED_LAYERS.with(|cache| {
cache.borrow_mut().remove(&n_hashes)
})
}
#[cfg(feature = "cuda-runtime")]
pub fn clear_precomputed_blake2s_layers() {
BLAKE2S_PRECOMPUTED_LAYERS.with(|cache| {
cache.borrow_mut().clear();
});
}
#[cfg(not(feature = "cuda-runtime"))]
pub fn clear_precomputed_blake2s_layers() {}
#[cfg(feature = "cuda-runtime")]
pub struct DeferredDownload {
pub d_aos: CudaSlice<u32>,
pub n_elements: usize,
}
#[cfg(feature = "cuda-runtime")]
thread_local! {
static DEFERRED_D2H_CACHE: RefCell<std::collections::HashMap<usize, DeferredDownload>> =
RefCell::new(std::collections::HashMap::new());
}
#[cfg(feature = "cuda-runtime")]
pub fn register_deferred_download(col_ptr: usize, d_aos: CudaSlice<u32>, n_elements: usize) {
DEFERRED_D2H_CACHE.with(|cache| {
cache.borrow_mut().insert(col_ptr, DeferredDownload { d_aos, n_elements });
});
}
#[cfg(feature = "cuda-runtime")]
pub fn execute_deferred_download(col_ptr: usize) -> Option<Vec<u32>> {
DEFERRED_D2H_CACHE.with(|cache| {
let entry = cache.borrow_mut().remove(&col_ptr)?;
let mut cpu_data = vec![0u32; entry.n_elements * 4];
if let Ok(executor) = super::cuda_executor::get_cuda_executor() {
if executor.device.dtoh_sync_copy_into(&entry.d_aos, &mut cpu_data).is_ok() {
tracing::debug!("Deferred D2H: downloaded {} QM31 elements", entry.n_elements);
return Some(cpu_data);
}
}
None
})
}
#[cfg(feature = "cuda-runtime")]
pub fn clear_deferred_d2h_cache() {
DEFERRED_D2H_CACHE.with(|cache| {
cache.borrow_mut().clear();
});
}
#[cfg(not(feature = "cuda-runtime"))]
pub fn clear_deferred_d2h_cache() {}
#[cfg(feature = "cuda-runtime")]
pub struct DeferredFriFoldEntry {
pub d_aos: CudaSlice<u32>,
pub n_output: usize,
}
#[cfg(feature = "cuda-runtime")]
thread_local! {
static DEFERRED_FRI_PIPELINE: RefCell<std::collections::VecDeque<DeferredFriFoldEntry>> =
RefCell::new(std::collections::VecDeque::new());
}
#[cfg(feature = "cuda-runtime")]
pub fn push_deferred_fri_fold(d_aos: CudaSlice<u32>, n_output: usize) {
DEFERRED_FRI_PIPELINE.with(|pipeline| {
pipeline.borrow_mut().push_back(DeferredFriFoldEntry { d_aos, n_output });
});
}
#[cfg(feature = "cuda-runtime")]
pub fn replace_last_deferred_fri_fold(d_aos: CudaSlice<u32>, n_output: usize) {
DEFERRED_FRI_PIPELINE.with(|pipeline| {
let mut p = pipeline.borrow_mut();
p.pop_back(); p.push_back(DeferredFriFoldEntry { d_aos, n_output });
});
}
#[cfg(feature = "cuda-runtime")]
pub fn pop_next_deferred_fri_fold() -> Option<DeferredFriFoldEntry> {
DEFERRED_FRI_PIPELINE.with(|pipeline| {
pipeline.borrow_mut().pop_front()
})
}
#[cfg(feature = "cuda-runtime")]
pub fn deferred_fri_pipeline_len() -> usize {
DEFERRED_FRI_PIPELINE.with(|pipeline| {
pipeline.borrow().len()
})
}
#[cfg(feature = "cuda-runtime")]
pub fn clear_deferred_fri_pipeline() {
DEFERRED_FRI_PIPELINE.with(|pipeline| {
pipeline.borrow_mut().clear();
});
}
#[cfg(not(feature = "cuda-runtime"))]
pub fn clear_deferred_fri_pipeline() {}
#[cfg(feature = "cuda-runtime")]
thread_local! {
static FRI_COLUMN_GPU_CACHE: RefCell<std::collections::HashMap<usize, CudaSlice<u32>>> =
RefCell::new(std::collections::HashMap::new());
}
#[cfg(feature = "cuda-runtime")]
pub fn cache_fri_column_gpu(col_ptr: usize, d_data: CudaSlice<u32>) {
FRI_COLUMN_GPU_CACHE.with(|cache| {
cache.borrow_mut().insert(col_ptr, d_data);
});
}
#[cfg(feature = "cuda-runtime")]
pub fn take_fri_column_gpu(col_ptr: usize) -> Option<CudaSlice<u32>> {
FRI_COLUMN_GPU_CACHE.with(|cache| {
cache.borrow_mut().remove(&col_ptr)
})
}
#[cfg(feature = "cuda-runtime")]
pub fn clear_fri_column_gpu_cache() {
FRI_COLUMN_GPU_CACHE.with(|cache| {
cache.borrow_mut().clear();
});
}
#[cfg(not(feature = "cuda-runtime"))]
pub fn clear_fri_column_gpu_cache() {}
#[cfg(feature = "cuda-runtime")]
pub struct GpuBuffer<T: DeviceRepr> {
slice: CudaSlice<T>,
device: Arc<CudaDevice>,
len: usize,
}
#[cfg(feature = "cuda-runtime")]
impl<T: DeviceRepr + Clone + Default> GpuBuffer<T> {
pub fn alloc_uninit(len: usize) -> Result<Self, CudaFftError> {
let executor = get_cuda_executor().map_err(|e| e.clone())?;
let device = executor.device.clone();
let slice = unsafe {
device.alloc::<T>(len)
}.map_err(|e| CudaFftError::MemoryAllocation(format!("{:?}", e)))?;
Ok(Self { slice, device, len })
}
pub fn zeros(len: usize) -> Result<Self, CudaFftError> {
let executor = get_cuda_executor().map_err(|e| e.clone())?;
let device = executor.device.clone();
let zeros: Vec<T> = vec![T::default(); len];
let slice = device.htod_sync_copy(&zeros)
.map_err(|e| CudaFftError::MemoryAllocation(format!("{:?}", e)))?;
Ok(Self { slice, device, len })
}
pub fn from_cpu(data: &[T]) -> Result<Self, CudaFftError> {
let executor = get_cuda_executor().map_err(|e| e.clone())?;
let device = executor.device.clone();
let slice = device.htod_sync_copy(data)
.map_err(|e| CudaFftError::MemoryTransfer(format!("H2D failed: {:?}", e)))?;
Ok(Self {
slice,
device,
len: data.len(),
})
}
pub fn to_cpu(&self) -> Result<Vec<T>, CudaFftError> {
let mut result = vec![T::default(); self.len];
self.device.dtoh_sync_copy_into(&self.slice, &mut result)
.map_err(|e| CudaFftError::MemoryTransfer(format!("D2H failed: {:?}", e)))?;
Ok(result)
}
pub fn to_cpu_into(&self, dst: &mut [T]) -> Result<(), CudaFftError> {
assert_eq!(dst.len(), self.len, "Destination buffer size mismatch");
self.device.dtoh_sync_copy_into(&self.slice, dst)
.map_err(|e| CudaFftError::MemoryTransfer(format!("D2H failed: {:?}", e)))?;
Ok(())
}
pub fn len(&self) -> usize {
self.len
}
pub fn is_empty(&self) -> bool {
self.len == 0
}
pub fn as_slice(&self) -> &CudaSlice<T> {
&self.slice
}
pub fn as_slice_mut(&mut self) -> &mut CudaSlice<T> {
&mut self.slice
}
pub fn device(&self) -> &Arc<CudaDevice> {
&self.device
}
}
#[cfg(feature = "cuda-runtime")]
pub type GpuM31Buffer = GpuBuffer<u32>;
#[cfg(feature = "cuda-runtime")]
impl GpuM31Buffer {
pub fn from_m31(data: &[crate::core::fields::m31::M31]) -> Result<Self, CudaFftError> {
let raw: &[u32] = unsafe {
std::slice::from_raw_parts(
data.as_ptr() as *const u32,
data.len()
)
};
Self::from_cpu(raw)
}
pub fn to_m31(&self) -> Result<Vec<crate::core::fields::m31::M31>, CudaFftError> {
let raw = self.to_cpu()?;
Ok(raw.into_iter().map(crate::core::fields::m31::M31).collect())
}
}
#[cfg(feature = "cuda-runtime")]
pub struct GpuBufferPool {
available: std::collections::HashMap<usize, Vec<CudaSlice<u32>>>,
device: Arc<CudaDevice>,
total_allocated: usize,
}
#[cfg(feature = "cuda-runtime")]
impl GpuBufferPool {
pub fn new() -> Result<Self, CudaFftError> {
let executor = get_cuda_executor().map_err(|e| e.clone())?;
Ok(Self {
available: std::collections::HashMap::new(),
device: executor.device.clone(),
total_allocated: 0,
})
}
pub fn get(&mut self, min_len: usize) -> Result<CudaSlice<u32>, CudaFftError> {
let size = min_len.next_power_of_two();
if let Some(buffers) = self.available.get_mut(&size) {
if let Some(buffer) = buffers.pop() {
return Ok(buffer);
}
}
let buffer = unsafe {
self.device.alloc::<u32>(size)
}.map_err(|e| CudaFftError::MemoryAllocation(format!("{:?}", e)))?;
self.total_allocated += size * std::mem::size_of::<u32>();
Ok(buffer)
}
pub fn put(&mut self, buffer: CudaSlice<u32>, size: usize) {
let size = size.next_power_of_two();
self.available.entry(size).or_default().push(buffer);
}
pub fn total_allocated_bytes(&self) -> usize {
self.total_allocated
}
pub fn clear(&mut self) {
self.available.clear();
}
}
#[cfg(feature = "cuda-runtime")]
impl Default for GpuBufferPool {
fn default() -> Self {
Self::new().expect("Failed to create GPU buffer pool")
}
}
#[cfg(not(feature = "cuda-runtime"))]
pub struct GpuBuffer<T> {
_phantom: std::marker::PhantomData<T>,
}
#[cfg(not(feature = "cuda-runtime"))]
impl<T> GpuBuffer<T> {
pub fn from_cpu(_data: &[T]) -> Result<Self, CudaFftError> {
Err(CudaFftError::NoDevice)
}
pub fn to_cpu(&self) -> Result<Vec<T>, CudaFftError> {
Err(CudaFftError::NoDevice)
}
pub fn len(&self) -> usize { 0 }
pub fn is_empty(&self) -> bool { true }
}
#[cfg(not(feature = "cuda-runtime"))]
pub type GpuM31Buffer = GpuBuffer<u32>;
#[cfg(test)]
mod tests {
use super::GpuBuffer;
#[test]
#[cfg(not(feature = "cuda-runtime"))]
fn test_no_cuda_returns_error() {
let result = GpuBuffer::<u32>::from_cpu(&[1, 2, 3]);
assert!(result.is_err());
}
}