bb_runtime/roles/backend.rs
1//! `BackendRuntime` - **framework-internal** role trait for backend
2//! implementations.
3//!
4//! Backend's atomic opset IS `ai.onnx v1`, and the role op_types ARE
5//! the atomic ops. Every `ai.onnx::*` node goes straight to the
6//! atomic dispatch table.
7//!
8//! **Authoring API is the Contract trait, not this one.** Concrete
9//! backends implement [`crate::contracts::Backend`] (the 30 mandatory
10//! primitives + `execute(&GraphProto, …)`); the
11//! `#[derive(bb::Backend)]` proc-macro generates the matching
12//! `impl BackendRuntime` that bridges into the engine's
13//! atomic-dispatch table. Library makers don't write
14//! `impl BackendRuntime` by hand.
15//!
16//! Distinct from [`bb_dsl::placeholders::Backend`] - the placeholder
17//! unit struct Module authors embed as a generic placeholder slot.
18
19use crate::atomic::{AtomicOpsetDecl, DispatchResult};
20use crate::runtime::RuntimeResourceRef;
21use crate::slot_value::BackendMaterializeError;
22use crate::slot_value::SlotValue;
23
24/// Role trait for backend implementations. Universal contract per
25/// `docs/ROLES.md` §2 with no per-role methods (Backend's role
26/// opset is `ai.onnx v1` which IS its atomic opset).
27///
28/// Backends MUST minimally cover `ai.onnx v1` via `atomic_opset`.
29/// They MAY declare additional opsets (e.g. `ai.onnx v17` extensions
30/// or custom-domain ops like `mybackend.fused.MatMulAdd`) via
31/// `extension_opsets`. `Node::ready()` consults both at
32/// build time to verify every NodeProto in the loaded graphs has a
33/// covering dispatch entry.
34pub trait BackendRuntime: Send + Sync {
35 /// Backend-impl-specific error type.
36 type Error: std::error::Error + Send + Sync + 'static;
37
38 /// Atomic-op opset this impl owns at minimum - `ai.onnx v1`.
39 fn atomic_opset(&self) -> AtomicOpsetDecl;
40
41 /// Additional opsets this backend supports beyond
42 /// `atomic_opset`. Default empty - backends that ship pure
43 /// `ai.onnx v1` need not override.
44 ///
45 /// Examples of valid extensions:
46 /// - A newer `ai.onnx` version (`(ai.onnx, 17)`) declaring ops
47 /// absent from v1.
48 /// - A custom-domain opset (`(mybackend.fused, 1)`) the backend
49 /// recognizes via its `dispatch_atomic` body.
50 fn extension_opsets(&self) -> Vec<AtomicOpsetDecl> {
51 Vec::new()
52 }
53
54 /// Dispatch a single op or `BackendSubgraph` carrier. For
55 /// primitive ops (`Add`, `Mul`, …) each arm builds a one-node
56 /// `GraphProto` and calls `Backend::execute`. For the
57 /// `BackendSubgraph` op_type, the embedded `GraphProto` body
58 /// rides on the carrier NodeProto's `"body"` attribute and the
59 /// derive arm calls `Backend::dispatch` so user overrides
60 /// (caching, async) reach the engine.
61 fn dispatch_atomic(
62 &mut self,
63 op_type: &str,
64 inputs: &[(&str, &dyn SlotValue)],
65 ctx: &mut RuntimeResourceRef<'_>,
66 ) -> Result<DispatchResult, Self::Error>;
67
68 /// Engine-side bridge for `Backend::materialize_from_wire`. The
69 /// derive forwards `(type_hash, bytes)` through the user's
70 /// Contract method and re-boxes the typed `Self::Tensor` into a
71 /// [`BackendTensorCarrier`] wrapped in `Box<dyn SlotValue>` so
72 /// the engine can install it in the slot table without knowing
73 /// the backend's concrete tensor type. Returns
74 /// [`BackendMaterializeError`] on backend error; the engine
75 /// surfaces this as
76 /// [`crate::bus::WireReceiveErrorKind::BackendMaterializeFailed`].
77 ///
78 /// Library makers do not implement this method — `#[derive(bb::Backend)]`
79 /// emits the bridge.
80 fn materialize_from_wire(
81 &self,
82 type_hash: u64,
83 bytes: Vec<u8>,
84 ) -> Result<Box<dyn SlotValue>, BackendMaterializeError>;
85}