(&self, data: &[f32])
| 215 | |
| 216 | #[inline] |
| 217 | fn gpu_upload(&self, data: &[f32]) -> Result<GpuBuf> { |
| 218 | let buf = self.gpu_alloc(data.len())?; |
| 219 | let bytes = data.len() * 4; |
| 220 | // Try pinned staging buffer for faster DMA (important on UMA like Steam Deck) |
| 221 | let mut pinned = std::ptr::null_mut(); |
| 222 | if unsafe { (self.ffi.hip_host_malloc)(&mut pinned, bytes, 0) } == 0 { |
| 223 | unsafe { |
| 224 | std::ptr::copy_nonoverlapping(data.as_ptr() as *const u8, pinned as *mut u8, bytes); |
| 225 | } |
| 226 | let err = unsafe { |
| 227 | (self.ffi.hip_memcpy_async)(buf.ptr, pinned, bytes, HIP_MEMCPY_HOST_TO_DEVICE, self.stream) |
| 228 | }; |
| 229 | // Sync before freeing pinned buffer (memcpy must complete first) |
| 230 | let _ = unsafe { (self.ffi.hip_stream_synchronize)(self.stream) }; |
| 231 | unsafe { (self.ffi.hip_host_free)(pinned) }; |
| 232 | if err != 0 { return Err(candle_core::Error::Msg(format!("hipMemcpyAsync H2D: {err}"))); } |
| 233 | } else { |
| 234 | // Fallback: regular async memcpy from unpinned memory |
| 235 | let err = unsafe { |
| 236 | (self.ffi.hip_memcpy_async)(buf.ptr, data.as_ptr() as *const _, bytes, HIP_MEMCPY_HOST_TO_DEVICE, self.stream) |
| 237 | }; |
| 238 | if err != 0 { return Err(candle_core::Error::Msg(format!("hipMemcpyAsync H2D: {err}"))); } |
| 239 | } |
| 240 | Ok(buf) |
| 241 | } |
| 242 | |
| 243 | #[inline] |
| 244 | fn to_f32_vec(t: &Tensor) -> Result<Vec<f32>> { |
no test coverage detected