Skip to main content

forge/
device.rs

1use std::sync::Arc;
2
3use crate::backend::wgpu::{DispatchScope, WgpuContext};
4use crate::error::Result;
5
6/// Where a tensor lives and where its ops execute.
7#[derive(Clone, Debug)]
8pub enum Device {
9    /// Reference implementation used for testing and verification.
10    Cpu,
11    /// Production backend: WebGPU via wgpu (Vulkan/Metal/D3D12/Browser).
12    Wgpu(Arc<WgpuContext>),
13}
14
15impl Device {
16    /// Create the default WebGPU device (highest-performance adapter).
17    /// Sync facade — native only; on wasm use [`Device::wgpu_async`].
18    #[cfg(not(target_arch = "wasm32"))]
19    pub fn wgpu() -> Result<Device> {
20        Ok(Device::Wgpu(WgpuContext::new()?))
21    }
22
23    /// Async device creation — the primary form (roadmap v4, pitfall 14);
24    /// works on native and wasm32/browser.
25    pub async fn wgpu_async() -> Result<Device> {
26        Ok(Device::Wgpu(WgpuContext::new_async().await?))
27    }
28
29    /// Batch every dispatch made while the returned guard is alive into one
30    /// command buffer and one submit. `None` on the CPU backend, which has no
31    /// queue to batch.
32    ///
33    /// Hold it for the span of a logical step — a decode step issues ~100
34    /// kernels, and a submit each leaves the GPU waiting on the CPU between
35    /// every one of them. Readbacks flush automatically, so a scope can wrap
36    /// code that reads results without special care.
37    #[must_use = "the batching ends when the guard drops"]
38    pub fn dispatch_scope(&self) -> Option<DispatchScope> {
39        match self {
40            Device::Cpu => None,
41            Device::Wgpu(ctx) => Some(ctx.scope()),
42        }
43    }
44
45    /// Human-readable adapter description.
46    pub fn describe(&self) -> String {
47        match self {
48            Device::Cpu => "CPU (reference)".to_string(),
49            Device::Wgpu(ctx) => format!(
50                "WebGPU: {} ({:?})",
51                ctx.adapter_info.name, ctx.adapter_info.backend
52            ),
53        }
54    }
55
56    /// Largest single buffer binding this device supports, in bytes. Large
57    /// weights (GPT-2's wte) are row-chunked to stay under this.
58    pub fn max_binding_bytes(&self) -> usize {
59        match self {
60            Device::Cpu => usize::MAX,
61            Device::Wgpu(ctx) => ctx.device.limits().max_storage_buffer_binding_size as usize,
62        }
63    }
64
65    /// Two devices are compatible when tensors on them can be combined.
66    pub fn same_as(&self, other: &Device) -> bool {
67        match (self, other) {
68            (Device::Cpu, Device::Cpu) => true,
69            (Device::Wgpu(a), Device::Wgpu(b)) => Arc::ptr_eq(a, b),
70            _ => false,
71        }
72    }
73}