tenferro_gpu/webgpu/
runtime.rs1use std::fmt;
2
3use cubecl::client::ComputeClient;
4use cubecl::Runtime;
5use cubecl_common::future;
6use cubecl_wgpu::{WgpuDevice, WgpuRuntime};
7use tenferro_tensor::AllocationDomainId;
8
9use super::apple::AppleDomainState;
10
11pub fn webgpu_available() -> bool {
16 std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
17 let device = WgpuDevice::DefaultDevice;
18 let _ = WgpuRuntime::client(&device);
19 }))
20 .is_ok()
21}
22
23#[derive(Clone, Debug)]
30pub struct WebGpuRuntimeIdentity {
31 marker: std::sync::Arc<()>,
32}
33
34impl WebGpuRuntimeIdentity {
35 fn fresh() -> Self {
36 Self {
37 marker: std::sync::Arc::new(()),
38 }
39 }
40}
41
42impl PartialEq for WebGpuRuntimeIdentity {
43 fn eq(&self, other: &Self) -> bool {
44 std::sync::Arc::ptr_eq(&self.marker, &other.marker)
45 }
46}
47
48impl Eq for WebGpuRuntimeIdentity {}
49
50#[derive(Clone)]
62pub struct WebGpuRuntime {
63 client: ComputeClient<WgpuRuntime>,
64 device_ordinal: usize,
65 pub(super) apple_domain: Option<std::sync::Arc<AppleDomainState>>,
66 identity: WebGpuRuntimeIdentity,
67 allocation_domain: AllocationDomainId,
68}
69
70impl fmt::Debug for WebGpuRuntime {
71 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
72 f.debug_struct("WebGpuRuntime")
73 .field("device_ordinal", &self.device_ordinal)
74 .finish_non_exhaustive()
75 }
76}
77
78impl WebGpuRuntime {
79 pub fn new(device_ordinal: usize) -> crate::Result<Self> {
94 Self::from_device(WgpuDevice::DiscreteGpu(device_ordinal), device_ordinal)
95 }
96
97 pub fn new_default() -> crate::Result<Self> {
112 Self::from_device(WgpuDevice::DefaultDevice, 0)
113 }
114
115 fn from_device(device: WgpuDevice, device_ordinal: usize) -> crate::Result<Self> {
116 let client = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
117 WgpuRuntime::client(&device)
118 }))
119 .map_err(|payload| {
120 crate::Error::runtime_state(
121 "webgpu_runtime_init",
122 format!("failed to initialize CubeCL WebGPU runtime: {payload:?}"),
123 )
124 })?;
125 Ok(Self {
126 client,
127 device_ordinal,
128 apple_domain: None,
129 identity: WebGpuRuntimeIdentity::fresh(),
130 allocation_domain: AllocationDomainId::fresh(),
131 })
132 }
133
134 pub(super) fn from_apple_client(
135 client: ComputeClient<WgpuRuntime>,
136 device_ordinal: usize,
137 domain: std::sync::Arc<AppleDomainState>,
138 ) -> Self {
139 let allocation_domain = domain.id;
140 Self {
141 client,
142 device_ordinal,
143 apple_domain: Some(domain),
144 identity: WebGpuRuntimeIdentity::fresh(),
145 allocation_domain,
146 }
147 }
148
149 pub(super) fn allocation_domain(&self) -> Option<&std::sync::Arc<AppleDomainState>> {
150 self.apple_domain.as_ref()
151 }
152
153 pub(super) fn allocation_domain_id(&self) -> AllocationDomainId {
154 self.allocation_domain
155 }
156
157 pub(super) fn record_upload(&self, bytes: usize) {
158 if let Some(domain) = &self.apple_domain {
159 domain.record_upload(bytes);
160 }
161 }
162
163 pub(super) fn record_download(&self, bytes: usize) {
164 if let Some(domain) = &self.apple_domain {
165 domain.record_download(bytes);
166 }
167 }
168
169 pub(crate) fn client(&self) -> &ComputeClient<WgpuRuntime> {
170 &self.client
171 }
172
173 pub fn device_ordinal(&self) -> usize {
183 self.device_ordinal
184 }
185
186 pub fn runtime_identity(&self) -> WebGpuRuntimeIdentity {
197 self.identity.clone()
198 }
199
200 pub fn synchronize(&self) -> crate::Result<()> {
215 const OP: &str = "webgpu_runtime_synchronize";
216 future::block_on(self.client.sync()).map_err(|err| crate::Error::backend_source(OP, err))
217 }
218}
219
220#[cfg(test)]
221mod identity_tests {
222 use super::{webgpu_available, WebGpuRuntimeIdentity};
223 use crate::webgpu::WebGpuBackend;
224
225 #[test]
226 fn webgpu_runtime_identity_is_clone_stable_and_instance_scoped() {
227 let first = WebGpuRuntimeIdentity::fresh();
228 let clone = first.clone();
229 let independent = WebGpuRuntimeIdentity::fresh();
230
231 assert_eq!(first, clone);
232 assert_ne!(first, independent);
233 }
234
235 #[test]
236 fn webgpu_backend_identity_tracks_the_exact_runtime_when_hardware_is_available() {
237 if !webgpu_available() {
238 return;
239 }
240
241 let first = WebGpuBackend::new_default().expect("WebGPU backend should initialize");
242 let clone = first.clone();
243 let independent =
244 WebGpuBackend::new_default().expect("second WebGPU backend should initialize");
245
246 assert_eq!(first.runtime_identity(), clone.runtime_identity());
247 assert_ne!(first.runtime_identity(), independent.runtime_identity());
248 assert_eq!(
249 first.runtime_identity(),
250 WebGpuBackend::from_runtime(first.runtime().clone()).runtime_identity()
251 );
252 }
253}