1use crate::{Device, Result, TensorError};
2use std::collections::HashMap;
3use std::sync::Arc;
4
5pub trait DeviceAllocator: Send + Sync {
7 fn allocate(&self, size: usize) -> Result<*mut u8>;
9
10 unsafe fn deallocate(&self, ptr: *mut u8, size: usize);
18
19 fn copy_from_host(&self, dst: *mut u8, src: &[u8]) -> Result<()>;
21
22 fn copy_to_host(&self, dst: &mut [u8], src: *const u8) -> Result<()>;
24
25 fn copy_device_to_device(&self, dst: *mut u8, src: *const u8, size: usize) -> Result<()>;
27}
28
29pub trait DeviceContext: Send + Sync {
31 fn device(&self) -> &Device;
33
34 fn allocator(&self) -> &dyn DeviceAllocator;
36
37 fn synchronize(&self) -> Result<()>;
39
40 fn create_stream(&self) -> Result<Box<dyn DeviceStream>>;
42
43 fn properties(&self) -> DeviceProperties;
45}
46
47pub trait DeviceStream: Send + Sync {
49 fn launch_kernel(&self, kernel: &dyn DeviceKernel, args: KernelArgs) -> Result<()>;
51
52 fn synchronize(&self) -> Result<()>;
54
55 fn is_complete(&self) -> bool;
57}
58
59pub trait DeviceKernel: Send + Sync {
61 fn name(&self) -> &str;
63
64 fn shared_memory_size(&self) -> usize;
66
67 fn optimal_block_size(&self) -> usize;
69}
70
71pub struct KernelArgs {
73 pub grid_size: [u32; 3],
74 pub block_size: [u32; 3],
75 pub shared_memory: usize,
76 pub inputs: Vec<*const u8>,
77 pub outputs: Vec<*mut u8>,
78 pub params: HashMap<String, KernelParam>,
79}
80
81#[derive(Clone)]
83pub enum KernelParam {
84 I32(i32),
85 U32(u32),
86 F32(f32),
87 F64(f64),
88 Ptr(*const u8),
89}
90
91#[derive(Debug, Clone)]
93pub struct DeviceProperties {
94 pub name: String,
95 pub total_memory: usize,
96 pub compute_capability: (u32, u32),
97 pub max_threads_per_block: u32,
98 pub max_blocks: [u32; 3],
99 pub shared_memory_per_block: usize,
100 pub warp_size: u32,
101}
102
103pub struct CpuContext {
105 allocator: CpuAllocator,
106}
107
108impl CpuContext {
109 pub fn new() -> Self {
110 Self {
111 allocator: CpuAllocator,
112 }
113 }
114}
115
116impl Default for CpuContext {
117 fn default() -> Self {
118 Self::new()
119 }
120}
121
122impl DeviceContext for CpuContext {
123 fn device(&self) -> &Device {
124 &Device::Cpu
125 }
126
127 fn allocator(&self) -> &dyn DeviceAllocator {
128 &self.allocator
129 }
130
131 fn synchronize(&self) -> Result<()> {
132 Ok(())
134 }
135
136 fn create_stream(&self) -> Result<Box<dyn DeviceStream>> {
137 Ok(Box::new(CpuStream))
138 }
139
140 fn properties(&self) -> DeviceProperties {
141 DeviceProperties {
142 name: "CPU".to_string(),
143 total_memory: sys_info::mem_info()
144 .map(|info| info.total as usize * 1024)
145 .unwrap_or(0),
146 compute_capability: (1, 0),
147 max_threads_per_block: 1,
148 max_blocks: [1, 1, 1],
149 shared_memory_per_block: 0,
150 warp_size: 1,
151 }
152 }
153}
154
155struct CpuAllocator;
157
158impl DeviceAllocator for CpuAllocator {
159 fn allocate(&self, size: usize) -> Result<*mut u8> {
160 if size == 0 {
161 return Ok(std::ptr::null_mut());
162 }
163
164 let layout = std::alloc::Layout::from_size_align(size, 64)
165 .map_err(|e| TensorError::allocation_error_simple(e.to_string()))?;
166
167 let ptr = unsafe { std::alloc::alloc(layout) };
168 if ptr.is_null() {
169 Err(TensorError::allocation_error_simple(format!(
170 "Failed to allocate {size} bytes"
171 )))
172 } else {
173 Ok(ptr)
174 }
175 }
176
177 unsafe fn deallocate(&self, ptr: *mut u8, size: usize) {
178 if !ptr.is_null() && size > 0 {
179 let layout = std::alloc::Layout::from_size_align_unchecked(size, 64);
180 std::alloc::dealloc(ptr, layout);
181 }
182 }
183
184 fn copy_from_host(&self, dst: *mut u8, src: &[u8]) -> Result<()> {
185 unsafe {
186 std::ptr::copy_nonoverlapping(src.as_ptr(), dst, src.len());
187 }
188 Ok(())
189 }
190
191 fn copy_to_host(&self, dst: &mut [u8], src: *const u8) -> Result<()> {
192 unsafe {
193 std::ptr::copy_nonoverlapping(src, dst.as_mut_ptr(), dst.len());
194 }
195 Ok(())
196 }
197
198 fn copy_device_to_device(&self, dst: *mut u8, src: *const u8, size: usize) -> Result<()> {
199 unsafe {
200 std::ptr::copy_nonoverlapping(src, dst, size);
201 }
202 Ok(())
203 }
204}
205
206struct CpuStream;
208
209impl DeviceStream for CpuStream {
210 fn launch_kernel(&self, _kernel: &dyn DeviceKernel, _args: KernelArgs) -> Result<()> {
211 Ok(())
213 }
214
215 fn synchronize(&self) -> Result<()> {
216 Ok(())
217 }
218
219 fn is_complete(&self) -> bool {
220 true
221 }
222}
223
224pub struct DeviceManager {
226 contexts: std::sync::RwLock<HashMap<Device, Arc<dyn DeviceContext>>>,
227}
228
229impl DeviceManager {
230 pub fn new() -> Self {
231 Self {
232 contexts: std::sync::RwLock::new(HashMap::new()),
233 }
234 }
235}
236
237impl Default for DeviceManager {
238 fn default() -> Self {
239 Self::new()
240 }
241}
242
243impl DeviceManager {
244 pub fn get_context(&self, device: &Device) -> Result<Arc<dyn DeviceContext>> {
246 {
248 let contexts = self.contexts.read().map_err(|_| {
249 TensorError::invalid_operation_simple("device contexts lock poisoned".to_string())
250 })?;
251 if let Some(ctx) = contexts.get(device) {
252 return Ok(Arc::clone(ctx));
253 }
254 }
255
256 let context: Arc<dyn DeviceContext> = match device {
258 Device::Cpu => Arc::new(CpuContext::new()),
259 #[cfg(feature = "gpu")]
260 Device::Gpu(id) => Arc::new(GpuContext::new(*id)?),
261 #[cfg(feature = "rocm")]
262 Device::Rocm(id) => Arc::new(GpuContext::new(*id)?), };
264
265 {
267 let mut contexts = self.contexts.write().map_err(|_| {
268 TensorError::invalid_operation_simple("device contexts lock poisoned".to_string())
269 })?;
270 contexts.insert(*device, Arc::clone(&context));
271 }
272
273 Ok(context)
274 }
275}
276
277lazy_static::lazy_static! {
279 pub static ref DEVICE_MANAGER: DeviceManager = DeviceManager::new();
280}
281
282#[cfg(feature = "gpu")]
284pub struct GpuContext {
285 device: Arc<wgpu::Device>,
286 queue: Arc<wgpu::Queue>,
287 adapter: wgpu::Adapter,
288 allocator: GpuAllocator,
289 device_enum: Device,
290}
291
292#[cfg(feature = "gpu")]
293impl GpuContext {
294 pub fn new(device_id: usize) -> Result<Self> {
295 pollster::block_on(Self::new_async(device_id))
296 }
297
298 async fn new_async(device_id: usize) -> Result<Self> {
299 let instance = wgpu::Instance::new(wgpu::InstanceDescriptor::new_without_display_handle());
300
301 let adapter = instance
302 .request_adapter(&wgpu::RequestAdapterOptions {
303 power_preference: wgpu::PowerPreference::HighPerformance,
304 compatible_surface: None,
305 force_fallback_adapter: false,
306 apply_limit_buckets: false,
307 })
308 .await
309 .map_err(|_e| {
310 TensorError::device_error_simple("Failed to find suitable GPU adapter".to_string())
311 })?;
312
313 let (device, queue) = adapter
314 .request_device(&wgpu::DeviceDescriptor {
315 label: Some(&format!("TenfloweRS GPU Device {device_id}")),
316 required_features: wgpu::Features::empty(),
317 required_limits: wgpu::Limits::default(),
318 memory_hints: wgpu::MemoryHints::Performance,
319 experimental_features: wgpu::ExperimentalFeatures::default(),
320 trace: wgpu::Trace::default(),
321 })
322 .await
323 .map_err(|e| {
324 TensorError::device_error_simple(format!("Failed to create GPU device: {e}"))
325 })?;
326
327 let device = Arc::new(device);
328 let queue = Arc::new(queue);
329 let allocator = GpuAllocator::new(Arc::clone(&device), Arc::clone(&queue));
330
331 Ok(Self {
332 device,
333 queue,
334 adapter,
335 allocator,
336 device_enum: Device::Gpu(device_id),
337 })
338 }
339
340 pub fn adapter_capabilities(&self) -> GpuAdapterCapabilities {
349 GpuAdapterCapabilities {
350 info: self.adapter.get_info(),
351 limits: self.adapter.limits(),
352 features: self.adapter.features(),
353 }
354 }
355}
356
357#[cfg(feature = "gpu")]
358impl DeviceContext for GpuContext {
359 fn device(&self) -> &Device {
360 &self.device_enum
361 }
362
363 fn allocator(&self) -> &dyn DeviceAllocator {
364 &self.allocator
365 }
366
367 fn synchronize(&self) -> Result<()> {
368 self.device.poll(wgpu::PollType::wait_indefinitely()).ok();
369 Ok(())
370 }
371
372 fn create_stream(&self) -> Result<Box<dyn DeviceStream>> {
373 Ok(Box::new(GpuStream::new(
374 Arc::clone(&self.device),
375 Arc::clone(&self.queue),
376 )))
377 }
378
379 fn properties(&self) -> DeviceProperties {
380 let info = self.adapter.get_info();
381 let limits = self.adapter.limits();
382
383 DeviceProperties {
384 name: info.name,
385 total_memory: 0, compute_capability: (1, 0), max_threads_per_block: limits.max_compute_workgroup_size_x,
388 max_blocks: [
389 limits.max_compute_workgroups_per_dimension,
390 limits.max_compute_workgroups_per_dimension,
391 limits.max_compute_workgroups_per_dimension,
392 ],
393 shared_memory_per_block: limits.max_compute_workgroup_storage_size as usize,
394 warp_size: 32, }
396 }
397}
398
399#[cfg(feature = "gpu")]
401struct GpuAllocator {
402 device: Arc<wgpu::Device>,
403 queue: Arc<wgpu::Queue>,
404}
405
406#[cfg(feature = "gpu")]
407impl GpuAllocator {
408 fn new(device: Arc<wgpu::Device>, queue: Arc<wgpu::Queue>) -> Self {
409 Self { device, queue }
410 }
411}
412
413#[cfg(feature = "gpu")]
414impl DeviceAllocator for GpuAllocator {
415 fn allocate(&self, _size: usize) -> Result<*mut u8> {
416 Err(TensorError::unsupported_operation_simple(
421 "raw-pointer GPU allocation is not supported; use wgpu::Buffer API for GPU memory management".to_string(),
422 ))
423 }
424
425 unsafe fn deallocate(&self, _ptr: *mut u8, _size: usize) {
426 }
429
430 fn copy_from_host(&self, _dst: *mut u8, _src: &[u8]) -> Result<()> {
431 Err(TensorError::unsupported_operation_simple(
432 "raw-pointer GPU copy_from_host is not supported; use wgpu::Queue::write_buffer for host-to-GPU transfers".to_string(),
433 ))
434 }
435
436 fn copy_to_host(&self, _dst: &mut [u8], _src: *const u8) -> Result<()> {
437 Err(TensorError::unsupported_operation_simple(
438 "raw-pointer GPU copy_to_host is not supported; use wgpu::Buffer::map_read for GPU-to-host transfers".to_string(),
439 ))
440 }
441
442 fn copy_device_to_device(&self, _dst: *mut u8, _src: *const u8, _size: usize) -> Result<()> {
443 Err(TensorError::unsupported_operation_simple(
444 "raw-pointer GPU copy_device_to_device is not supported; use wgpu::CommandEncoder::copy_buffer_to_buffer for GPU-to-GPU copies".to_string(),
445 ))
446 }
447}
448
449#[cfg(feature = "gpu")]
451struct GpuStream {
452 device: Arc<wgpu::Device>,
453 queue: Arc<wgpu::Queue>,
454}
455
456#[cfg(feature = "gpu")]
457impl GpuStream {
458 fn new(device: Arc<wgpu::Device>, queue: Arc<wgpu::Queue>) -> Self {
459 Self { device, queue }
460 }
461}
462
463#[cfg(feature = "gpu")]
464impl DeviceStream for GpuStream {
465 fn launch_kernel(&self, _kernel: &dyn DeviceKernel, _args: KernelArgs) -> Result<()> {
466 Ok(())
468 }
469
470 fn synchronize(&self) -> Result<()> {
471 self.device.poll(wgpu::PollType::wait_indefinitely()).ok();
472 Ok(())
473 }
474
475 fn is_complete(&self) -> bool {
476 true
478 }
479}
480
481#[cfg(feature = "gpu")]
483pub fn get_gpu_context(device_id: usize) -> Result<GpuContextInfo> {
484 let device = Device::Gpu(device_id);
485 let ctx = DEVICE_MANAGER.get_context(&device)?;
486
487 let gpu_ctx = GpuContext::new(device_id)?;
489 Ok(GpuContextInfo {
490 device: gpu_ctx.device.clone(),
491 queue: gpu_ctx.queue.clone(),
492 })
493}
494
495#[cfg(feature = "gpu")]
504pub fn get_gpu_adapter_capabilities(device_id: usize) -> Result<GpuAdapterCapabilities> {
505 let gpu_ctx = GpuContext::new(device_id)?;
506 Ok(gpu_ctx.adapter_capabilities())
507}
508
509#[cfg(any(feature = "gpu", feature = "cudnn"))]
511pub fn get_enhanced_gpu_context(device_id: usize) -> Result<EnhancedGpuContext> {
512 #[cfg(all(feature = "cudnn", any(target_os = "linux", target_os = "windows")))]
514 {
515 if crate::gpu::cudnn::CudnnContext::is_available() {
516 let mut cudnn_ctx = crate::gpu::cudnn::global_cudnn_context();
517 let cudnn_handle = cudnn_ctx.get_handle(device_id)?;
518
519 let wgpu_ctx = get_gpu_context(device_id)?;
521
522 return Ok(EnhancedGpuContext {
523 device_id,
524 backend: GpuBackend::CuDNN(cudnn_handle),
525 wgpu_fallback: Some(wgpu_ctx),
526 });
527 }
528 }
529
530 #[cfg(feature = "gpu")]
532 {
533 let wgpu_ctx = get_gpu_context(device_id)?;
534 Ok(EnhancedGpuContext {
535 device_id,
536 backend: GpuBackend::WGPU(wgpu_ctx),
537 wgpu_fallback: None,
538 })
539 }
540 #[cfg(not(feature = "gpu"))]
541 {
542 Err(TensorError::unsupported_operation_simple(
543 "No GPU backend available",
544 ))
545 }
546}
547
548#[cfg(feature = "gpu")]
549#[derive(Debug, Clone)]
550pub struct GpuContextInfo {
551 pub device: Arc<wgpu::Device>,
552 pub queue: Arc<wgpu::Queue>,
553}
554
555#[cfg(feature = "gpu")]
562#[derive(Debug, Clone)]
563pub struct GpuAdapterCapabilities {
564 pub info: wgpu::AdapterInfo,
566 pub limits: wgpu::Limits,
568 pub features: wgpu::Features,
570}
571
572#[cfg(any(feature = "gpu", feature = "cudnn"))]
574#[derive(Debug, Clone)]
575pub enum GpuBackend {
576 #[cfg(feature = "gpu")]
577 WGPU(GpuContextInfo),
578 #[cfg(all(feature = "cudnn", any(target_os = "linux", target_os = "windows")))]
579 CuDNN(Arc<crate::gpu::cudnn::CudnnHandle>),
580}
581
582#[cfg(any(feature = "gpu", feature = "cudnn"))]
584#[derive(Debug, Clone)]
585pub struct EnhancedGpuContext {
586 pub device_id: usize,
587 pub backend: GpuBackend,
588 pub wgpu_fallback: Option<GpuContextInfo>,
589}
590
591#[cfg(any(feature = "gpu", feature = "cudnn"))]
592impl EnhancedGpuContext {
593 #[cfg(all(feature = "cudnn", any(target_os = "linux", target_os = "windows")))]
595 pub fn is_cudnn(&self) -> bool {
596 matches!(self.backend, GpuBackend::CuDNN(_))
597 }
598
599 #[cfg(feature = "gpu")]
601 pub fn is_wgpu(&self) -> bool {
602 matches!(self.backend, GpuBackend::WGPU(_))
603 }
604
605 #[cfg(all(feature = "cudnn", any(target_os = "linux", target_os = "windows")))]
607 pub fn get_cudnn_handle(&self) -> Option<&Arc<crate::gpu::cudnn::CudnnHandle>> {
608 match &self.backend {
609 GpuBackend::CuDNN(handle) => Some(handle),
610 _ => None,
611 }
612 }
613
614 #[cfg(feature = "gpu")]
616 pub fn get_wgpu_context(&self) -> Option<&GpuContextInfo> {
617 match &self.backend {
618 GpuBackend::WGPU(ctx) => Some(ctx),
619 #[cfg(all(feature = "cudnn", any(target_os = "linux", target_os = "windows")))]
620 _ => self.wgpu_fallback.as_ref(),
621 }
622 }
623
624 pub fn device_id(&self) -> usize {
626 self.device_id
627 }
628}
629
630#[cfg(not(target_arch = "wasm32"))]
632extern crate sys_info;
633
634#[cfg(target_arch = "wasm32")]
635mod sys_info {
636 pub struct MemInfo {
637 pub total: u64,
638 }
639
640 pub fn mem_info() -> Result<MemInfo, &'static str> {
641 Ok(MemInfo {
642 total: 1024 * 1024 * 1024,
643 }) }
645}