Skip to main content

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}