fuel_backend_contract/dyn_backend.rs
1// SPDX-License-Identifier: MIT OR Apache-2.0
2//! Object-safe backend traits for dynamic dispatch.
3//!
4//! [`DynBackendStorage`] and [`DynBackendDevice`] are the object-safe
5//! contract surface. `DynBackendStorage` mirrors
6//! [`BackendStorage`](crate::backend::BackendStorage); the generic
7//! `BackendDevice` trait `DynBackendDevice` once mirrored was DELETED in
8//! the 2026-04-30 backend-agnostic refactor (see the note on
9//! [`crate::backend`]), and backends implement it directly. They replace `Self`
10//! return types with `Box<dyn DynBackendStorage>`, eliminate generic type
11//! parameters, and drop the `Sized` bound so that `dyn DynBackendStorage`
12//! and `dyn DynBackendDevice` are legal trait objects.
13//!
14//! These traits are the primary interface for all backends in fuel.
15//! Every backend — built-in or third-party — implements these traits, and
16//! `Device` and `Storage` are newtype wrappers around `Arc<dyn DynBackendDevice>`
17//! and `Box<dyn DynBackendStorage>` respectively.
18//!
19//! # Implementing a custom backend
20//!
21//! ```rust,ignore
22//! use fuel_backend_contract::dyn_backend::{DynBackendDevice, DynBackendStorage};
23//! use std::sync::Arc;
24//!
25//! struct MyDevice { /* ... */ }
26//! impl DynBackendDevice for MyDevice { /* ... */ }
27//!
28//! struct MyStorage { /* ... */ }
29//! impl DynBackendStorage for MyStorage { /* ... */ }
30//! ```
31use fuel_ir::conv::{ParamsConv1D, ParamsConv2D, ParamsConvTranspose1D, ParamsConvTranspose2D};
32use fuel_ir::op::{BinaryOp, CmpOp, ReduceOp, UnaryOp};
33use fuel_ir::{DType, DeviceLocation, HostBuffer, Layout, Result, Scalar, Shape};
34use std::any::Any;
35use std::sync::Arc;
36
37/// Object-safe counterpart of [`BackendStorage`](crate::backend::BackendStorage).
38///
39/// Every method mirrors the corresponding `BackendStorage` method but returns
40/// `Box<dyn DynBackendStorage>` instead of `Self` and accepts `&dyn
41/// DynBackendStorage` instead of `&Self` for multi-operand operations.
42///
43/// For binary and multi-operand methods (`cmp`, `binary_op`, `where_cond`,
44/// etc.), the implementation must downcast the `&dyn DynBackendStorage`
45/// argument to its concrete type. Operations between different
46/// backends are treated as a device mismatch.
47pub trait DynBackendStorage: Send + Sync + std::fmt::Debug {
48 /// Clone the elements described by `layout` into a new storage.
49 fn try_clone_dyn(&self, layout: &Layout) -> Result<Box<dyn DynBackendStorage>>;
50
51 /// The element dtype of this storage.
52 fn dtype_dyn(&self) -> DType;
53
54 /// The device that owns this storage.
55 fn device_dyn(&self) -> &dyn DynBackendDevice;
56
57 /// Return a cloned `Arc` handle to the owning device.
58 fn device_arc_dyn(&self) -> Arc<dyn DynBackendDevice>;
59
60 /// Copy the entire storage to a [`HostBuffer`](fuel_ir::HostBuffer).
61 fn to_host_buffer_dyn(&self) -> Result<HostBuffer>;
62
63 /// Deprecated alias for `to_host_buffer_dyn`.
64 fn to_cpu_storage_dyn(&self) -> Result<HostBuffer> {
65 self.to_host_buffer_dyn()
66 }
67
68 /// Elementwise affine: `x * mul + add`.
69 fn affine_dyn(&self, layout: &Layout, mul: f64, add: f64)
70 -> Result<Box<dyn DynBackendStorage>>;
71
72 /// Raise every element to the power `e`.
73 fn powf_dyn(&self, layout: &Layout, e: f64) -> Result<Box<dyn DynBackendStorage>>;
74
75 /// ELU activation.
76 fn elu_dyn(&self, layout: &Layout, alpha: f64) -> Result<Box<dyn DynBackendStorage>>;
77
78 /// Reduction (sum, min, max, …) over `axes`.
79 fn reduce_op_dyn(
80 &self,
81 op: ReduceOp,
82 layout: &Layout,
83 axes: &[usize],
84 ) -> Result<Box<dyn DynBackendStorage>>;
85
86 /// Elementwise comparison, returning a `U8` buffer.
87 fn cmp_dyn(
88 &self,
89 op: CmpOp,
90 rhs: &dyn DynBackendStorage,
91 lhs_layout: &Layout,
92 rhs_layout: &Layout,
93 ) -> Result<Box<dyn DynBackendStorage>>;
94
95 /// Cast to a different dtype.
96 fn to_dtype_dyn(&self, layout: &Layout, dtype: DType) -> Result<Box<dyn DynBackendStorage>>;
97
98 /// Elementwise unary operation.
99 fn unary_op_dyn(&self, layout: &Layout, op: UnaryOp) -> Result<Box<dyn DynBackendStorage>>;
100
101 /// Elementwise binary operation.
102 fn binary_op_dyn(
103 &self,
104 rhs: &dyn DynBackendStorage,
105 lhs_layout: &Layout,
106 rhs_layout: &Layout,
107 op: BinaryOp,
108 ) -> Result<Box<dyn DynBackendStorage>>;
109
110 /// Select `on_true` where condition is nonzero, `on_false` elsewhere.
111 fn where_cond_dyn(
112 &self,
113 cond_layout: &Layout,
114 on_true: &dyn DynBackendStorage,
115 on_true_layout: &Layout,
116 on_false: &dyn DynBackendStorage,
117 on_false_layout: &Layout,
118 ) -> Result<Box<dyn DynBackendStorage>>;
119
120 /// 1-D convolution.
121 fn conv1d_dyn(
122 &self,
123 l: &Layout,
124 kernel: &dyn DynBackendStorage,
125 kernel_l: &Layout,
126 params: &ParamsConv1D,
127 ) -> Result<Box<dyn DynBackendStorage>>;
128
129 /// 1-D transposed convolution.
130 fn conv_transpose1d_dyn(
131 &self,
132 l: &Layout,
133 kernel: &dyn DynBackendStorage,
134 kernel_l: &Layout,
135 params: &ParamsConvTranspose1D,
136 ) -> Result<Box<dyn DynBackendStorage>>;
137
138 /// 2-D convolution.
139 fn conv2d_dyn(
140 &self,
141 l: &Layout,
142 kernel: &dyn DynBackendStorage,
143 kernel_l: &Layout,
144 params: &ParamsConv2D,
145 ) -> Result<Box<dyn DynBackendStorage>>;
146
147 /// 2-D transposed convolution.
148 fn conv_transpose2d_dyn(
149 &self,
150 l: &Layout,
151 kernel: &dyn DynBackendStorage,
152 kernel_l: &Layout,
153 params: &ParamsConvTranspose2D,
154 ) -> Result<Box<dyn DynBackendStorage>>;
155
156 /// 2-D average pooling.
157 fn avg_pool2d_dyn(
158 &self,
159 layout: &Layout,
160 kernel: (usize, usize),
161 stride: (usize, usize),
162 ) -> Result<Box<dyn DynBackendStorage>>;
163
164 /// 2-D max pooling.
165 fn max_pool2d_dyn(
166 &self,
167 layout: &Layout,
168 kernel: (usize, usize),
169 stride: (usize, usize),
170 ) -> Result<Box<dyn DynBackendStorage>>;
171
172 /// 1-D nearest-neighbor upsampling.
173 fn upsample_nearest1d_dyn(
174 &self,
175 layout: &Layout,
176 target_size: usize,
177 ) -> Result<Box<dyn DynBackendStorage>>;
178
179 /// 2-D nearest-neighbor upsampling.
180 fn upsample_nearest2d_dyn(
181 &self,
182 layout: &Layout,
183 target_h: usize,
184 target_w: usize,
185 ) -> Result<Box<dyn DynBackendStorage>>;
186
187 /// 2-D bilinear upsampling.
188 fn upsample_bilinear2d_dyn(
189 &self,
190 layout: &Layout,
191 target_h: usize,
192 target_w: usize,
193 align_corners: bool,
194 scale_h: Option<f64>,
195 scale_w: Option<f64>,
196 ) -> Result<Box<dyn DynBackendStorage>>;
197
198 /// Gather elements along `dim` using `ids`.
199 fn gather_dyn(
200 &self,
201 src_layout: &Layout,
202 ids: &dyn DynBackendStorage,
203 ids_layout: &Layout,
204 dim: usize,
205 ) -> Result<Box<dyn DynBackendStorage>>;
206
207 /// Scatter-set `src` into `self` along `dim` at positions in `ids`.
208 fn scatter_set_dyn(
209 &mut self,
210 self_layout: &Layout,
211 src: &dyn DynBackendStorage,
212 src_layout: &Layout,
213 ids: &dyn DynBackendStorage,
214 ids_layout: &Layout,
215 dim: usize,
216 ) -> Result<()>;
217
218 /// Scatter-add `src` into `self` along `dim` at positions in `ids`.
219 fn scatter_add_set_dyn(
220 &mut self,
221 self_layout: &Layout,
222 src: &dyn DynBackendStorage,
223 src_layout: &Layout,
224 ids: &dyn DynBackendStorage,
225 ids_layout: &Layout,
226 dim: usize,
227 ) -> Result<()>;
228
229 /// Select slices along `dim` at positions in `ids`.
230 fn index_select_dyn(
231 &self,
232 ids: &dyn DynBackendStorage,
233 src_layout: &Layout,
234 ids_layout: &Layout,
235 dim: usize,
236 ) -> Result<Box<dyn DynBackendStorage>>;
237
238 /// Index-add: accumulate `src` slices into `self` along `dim` at `ids`.
239 fn index_add_dyn(
240 &self,
241 self_layout: &Layout,
242 ids: &dyn DynBackendStorage,
243 ids_layout: &Layout,
244 src: &dyn DynBackendStorage,
245 src_layout: &Layout,
246 dim: usize,
247 ) -> Result<Box<dyn DynBackendStorage>>;
248
249 /// Matrix multiply.
250 fn matmul_dyn(
251 &self,
252 rhs: &dyn DynBackendStorage,
253 bmnk: (usize, usize, usize, usize),
254 lhs_layout: &Layout,
255 rhs_layout: &Layout,
256 ) -> Result<Box<dyn DynBackendStorage>>;
257
258 /// Copy strided source into `dst` starting at `dst_offset`.
259 fn copy_strided_src_dyn(
260 &self,
261 dst: &mut dyn DynBackendStorage,
262 dst_offset: usize,
263 src_layout: &Layout,
264 ) -> Result<()>;
265
266 /// Copy a 2-D tile from `self` into `dst`.
267 fn copy2d_dyn(
268 &self,
269 dst: &mut dyn DynBackendStorage,
270 d1: usize,
271 d2: usize,
272 src_stride1: usize,
273 dst_stride1: usize,
274 src_offset: usize,
275 dst_offset: usize,
276 ) -> Result<()>;
277
278 /// Set every element described by `layout` to `value`.
279 fn const_set_dyn(&mut self, value: Scalar, layout: &Layout) -> Result<()>;
280
281 /// Downcast to the concrete storage type.
282 fn as_any(&self) -> &dyn Any;
283
284 /// Downcast to the concrete storage type (mutable).
285 fn as_any_mut(&mut self) -> &mut dyn Any;
286}
287
288/// Object-safe device abstraction. The generic `BackendDevice` trait this
289/// once mirrored was DELETED in the 2026-04-30 backend-agnostic refactor;
290/// backends implement this trait directly.
291///
292/// All factory methods return `Box<dyn DynBackendStorage>` instead of
293/// `Self::Storage`, and there is no `storage_from_slice` (use
294/// [`storage_from_cpu_storage`](DynBackendDevice::storage_from_cpu_storage_dyn)
295/// after constructing a [`HostBuffer`] on the host side).
296pub trait DynBackendDevice: Send + Sync + std::fmt::Debug {
297 /// Canonical device location.
298 fn location_dyn(&self) -> DeviceLocation;
299
300 /// Whether `self` and `other` refer to the same physical device.
301 fn same_device_dyn(&self, other: &dyn DynBackendDevice) -> bool;
302
303 /// Whether this device has native BF16 support.
304 ///
305 /// Defaults to `false`. Override in GPU backends that support BF16 natively.
306 fn supports_bf16(&self) -> bool {
307 false
308 }
309
310 /// Allocate zero-initialized storage.
311 fn zeros_impl_dyn(&self, shape: &Shape, dtype: DType) -> Result<Box<dyn DynBackendStorage>>;
312
313 /// Allocate uninitialized storage.
314 ///
315 /// # Safety
316 ///
317 /// Caller must initialize all elements before reading.
318 unsafe fn alloc_uninit_dyn(
319 &self,
320 shape: &Shape,
321 dtype: DType,
322 ) -> Result<Box<dyn DynBackendStorage>>;
323
324 /// Create storage from a host buffer (borrowed).
325 fn storage_from_host_buffer_dyn(&self, buf: &HostBuffer) -> Result<Box<dyn DynBackendStorage>>;
326
327 /// Create storage from a host buffer (owned).
328 fn storage_from_host_buffer_owned_dyn(
329 &self,
330 buf: HostBuffer,
331 ) -> Result<Box<dyn DynBackendStorage>>;
332
333 /// Deprecated alias for `storage_from_host_buffer_dyn`.
334 fn storage_from_cpu_storage_dyn(&self, cpu: &HostBuffer) -> Result<Box<dyn DynBackendStorage>> {
335 self.storage_from_host_buffer_dyn(cpu)
336 }
337
338 /// Deprecated alias for `storage_from_host_buffer_owned_dyn`.
339 fn storage_from_cpu_storage_owned_dyn(
340 &self,
341 cpu: HostBuffer,
342 ) -> Result<Box<dyn DynBackendStorage>> {
343 self.storage_from_host_buffer_owned_dyn(cpu)
344 }
345
346 /// Fill storage with uniform random values in `[lo, hi)`.
347 fn rand_uniform_dyn(
348 &self,
349 shape: &Shape,
350 dtype: DType,
351 lo: f64,
352 hi: f64,
353 ) -> Result<Box<dyn DynBackendStorage>>;
354
355 /// Fill storage with normally distributed random values.
356 fn rand_normal_dyn(
357 &self,
358 shape: &Shape,
359 dtype: DType,
360 mean: f64,
361 std: f64,
362 ) -> Result<Box<dyn DynBackendStorage>>;
363
364 /// Set the RNG seed.
365 fn set_seed_dyn(&self, seed: u64) -> Result<()>;
366
367 /// Get the current RNG seed.
368 fn get_current_seed_dyn(&self) -> Result<u64>;
369
370 /// Block until all pending operations complete.
371 fn synchronize_dyn(&self) -> Result<()>;
372
373 /// Downcast to the concrete device type.
374 fn as_any(&self) -> &dyn Any;
375
376 /// Optional quantized-kernel adapter for backends that support GGUF/ggml
377 /// block formats. fuel-core walks this to allocate `qzeros` storage and
378 /// load pre-quantized blocks without naming concrete backend types.
379 /// Backends that don't support quantization return `None` (the default).
380 fn as_quantized_kernels(&self) -> Option<&dyn crate::quantized::QuantizedDeviceKernels> {
381 None
382 }
383
384 /// Optional runtime-state adapter (backend contract v0.4, Tier 1).
385 /// Backends whose device type implements
386 /// [`crate::backend::BackendRuntime`] return `Some(self)` so
387 /// holders of an `Arc<dyn DynBackendDevice>` (the picker arc's
388 /// pressure-aware runtime selectors) can query live memory
389 /// signals without naming concrete backend types. Backends
390 /// without a runtime impl keep the default `None` — selectors
391 /// treat that as [`fuel_ir::backend::FitStatus::Unknown`] (no
392 /// signal), never as pressure.
393 fn as_backend_runtime(&self) -> Option<&dyn crate::backend::BackendRuntime> {
394 None
395 }
396}