1use super::{MemoryDescriptor, Result, StorageError, StorageKind, actions, nixl::NixlDescriptor};
7use cudarc::driver::CudaContext;
8use std::any::Any;
9use std::sync::Arc;
10
11static USE_WRITE_COMBINED: std::sync::LazyLock<bool> = std::sync::LazyLock::new(|| {
18 if crate::env_is_truthy("DYN_KVBM_DISABLE_WRITE_COMBINED") {
19 tracing::debug!("DYN_KVBM_DISABLE_WRITE_COMBINED set; write-combined disabled");
20 return false;
21 }
22 unsafe {
25 match cudarc::driver::result::malloc_host(
26 1,
27 cudarc::driver::sys::CU_MEMHOSTALLOC_WRITECOMBINED,
28 ) {
29 Ok(ptr) => {
30 let _ = cudarc::driver::result::free_host(ptr);
31 true
32 }
33 Err(_) => {
34 tracing::debug!(
35 "Write-combined memory not supported on this system; \
36 will use regular pinned memory"
37 );
38 false
39 }
40 }
41 }
42});
43
44unsafe fn malloc_host_prefer_writecombined(size: usize) -> Result<*mut u8> {
50 if *USE_WRITE_COMBINED {
51 unsafe {
53 cudarc::driver::result::malloc_host(
54 size,
55 cudarc::driver::sys::CU_MEMHOSTALLOC_WRITECOMBINED,
56 )
57 }
58 .map(|ptr| ptr as *mut u8)
59 .map_err(StorageError::Cuda)
60 } else {
61 unsafe {
63 cudarc::driver::result::malloc_host(
64 size,
65 cudarc::driver::sys::CU_MEMHOSTALLOC_DEVICEMAP,
66 )
67 }
68 .map(|ptr| ptr as *mut u8)
69 .map_err(StorageError::Cuda)
70 }
71}
72
73#[derive(Debug)]
75pub struct PinnedStorage {
76 ptr: usize,
78 len: usize,
80 ctx: Arc<CudaContext>,
82}
83
84unsafe impl Send for PinnedStorage {}
85unsafe impl Sync for PinnedStorage {}
86
87impl PinnedStorage {
88 pub fn new(len: usize) -> Result<Self> {
95 Self::new_for_device(len, None)
96 }
97
98 pub fn new_for_device(len: usize, device_id: Option<u32>) -> Result<Self> {
118 if len == 0 {
119 return Err(StorageError::AllocationFailed(
120 "zero-sized allocations are not supported".into(),
121 ));
122 }
123
124 let gpu_id = device_id.unwrap_or(0);
125 let ctx = crate::device::cuda_context(gpu_id)?;
126
127 #[cfg(target_os = "linux")]
129 let numa_ptr = if let Some(gpu_id) = device_id {
130 if super::numa::is_numa_enabled() {
131 match super::numa::worker_pool::NumaWorkerPool::global()
132 .allocate_pinned_for_gpu(len, gpu_id)
133 {
134 Ok(Some(ptr)) => {
135 tracing::debug!(
136 "Using NUMA-aware allocation for {} bytes on GPU {}",
137 len,
138 gpu_id
139 );
140 Some(ptr as usize)
141 }
142 Ok(None) => None, Err(e) => return Err(StorageError::AllocationFailed(e)),
144 }
145 } else {
146 None
147 }
148 } else {
149 None
150 };
151
152 #[cfg(not(target_os = "linux"))]
153 let numa_ptr: Option<usize> = None;
154
155 let ptr = if let Some(ptr) = numa_ptr {
156 ptr
157 } else {
158 unsafe {
159 ctx.bind_to_thread().map_err(StorageError::Cuda)?;
160
161 let ptr = malloc_host_prefer_writecombined(len)?;
162
163 assert!(!ptr.is_null(), "Failed to allocate pinned memory");
164 assert!(ptr.is_aligned(), "Pinned memory is not aligned");
165 assert!(len < isize::MAX as usize);
166
167 ptr as usize
168 }
169 };
170
171 Ok(Self { ptr, len, ctx })
172 }
173
174 pub unsafe fn as_ptr(&self) -> *const u8 {
179 self.ptr as *const u8
180 }
181
182 pub unsafe fn as_mut_ptr(&mut self) -> *mut u8 {
188 self.ptr as *mut u8
189 }
190
191 pub fn ctx(&self) -> &Arc<CudaContext> {
193 &self.ctx
194 }
195}
196
197impl Drop for PinnedStorage {
198 fn drop(&mut self) {
199 if let Err(e) = self.ctx.bind_to_thread() {
200 tracing::debug!("failed to bind CUDA context for free: {e}");
201 }
202 unsafe {
203 if let Err(e) = cudarc::driver::result::free_host(self.ptr as _) {
204 tracing::debug!("failed to free pinned memory: {e}");
205 }
206 };
207 }
208}
209
210impl MemoryDescriptor for PinnedStorage {
211 fn addr(&self) -> usize {
212 unsafe { self.as_ptr() as usize }
213 }
214
215 fn size(&self) -> usize {
216 self.len
217 }
218
219 fn storage_kind(&self) -> StorageKind {
220 StorageKind::Pinned
221 }
222
223 fn as_any(&self) -> &dyn Any {
224 self
225 }
226
227 fn nixl_descriptor(&self) -> Option<NixlDescriptor> {
228 None
229 }
230}
231
232impl super::nixl::NixlCompatible for PinnedStorage {
234 fn nixl_params(&self) -> (*const u8, usize, nixl_sys::MemType, u64) {
235 let ptr = unsafe { self.as_ptr() };
236 (ptr, self.len, nixl_sys::MemType::Dram, 0)
237 }
238}
239
240impl actions::Memset for PinnedStorage {
241 fn memset(&mut self, value: u8, offset: usize, size: usize) -> Result<()> {
242 let end = offset
243 .checked_add(size)
244 .ok_or_else(|| StorageError::OperationFailed("memset: offset overflow".into()))?;
245 if end > self.len {
246 return Err(StorageError::OperationFailed(
247 "memset: offset + size > storage size".into(),
248 ));
249 }
250 unsafe {
251 let ptr = (self.ptr as *mut u8).add(offset);
252 std::ptr::write_bytes(ptr, value, size);
253 }
254 Ok(())
255 }
256}