pub struct Graph { /* private fields */ }Expand description
The graph arena. Stores every node added during a computation-building
session. Nodes are append-only and indexed by NodeId.
Placement metadata (DeviceLocation per node) lives in a side-table
so existing Node { ... } construction sites don’t need to be
modified. This is the Phase-1 shape: placements are opt-in, inert
hints that the executor may validate but does not yet act on.
Default is implemented manually (not derived) so that every graph —
however it is created — mints a fresh GraphId from the monotonic
counter. A derived Default would hand out a zeroed id, so two
Graph::default() graphs would claim the same identity and affinity
diagnostics would report nonsense.
Implementations§
Source§impl Graph
impl Graph
Sourcepub fn id(&self) -> GraphId
pub fn id(&self) -> GraphId
This graph’s process-unique GraphId. Used by affinity diagnostics to
name which graphs disagree; see NodeHandle::graph_id.
Sourcepub fn target_backend(&self, id: NodeId) -> Option<BackendId>
pub fn target_backend(&self, id: NodeId) -> Option<BackendId>
Look up the resolved target backend for a node, if any has
been set. Returns None if dispatch resolution hasn’t
happened yet for this node — common during the migration
because old op-builder paths don’t call the resolver.
Phase 7.5 B2.
Sourcepub fn set_target_backend(&mut self, id: NodeId, backend: BackendId)
pub fn set_target_backend(&mut self, id: NodeId, backend: BackendId)
Record the resolved target backend for id. Set once at DAG
construction time (or first-realize time, depending on the
dispatch policy chosen in Phase B3). Idempotent: calling
twice with the same value is fine; calling twice with
different values overwrites.
Phase 7.5 B2.
Sourcepub fn target_backend_count(&self) -> usize
pub fn target_backend_count(&self) -> usize
Number of nodes with a resolved target backend. Mostly for tests and diagnostics.
Sourcepub fn storage_class(&self, id: NodeId) -> StorageClass
pub fn storage_class(&self, id: NodeId) -> StorageClass
The storage class of id: the explicit override if one was set via
Graph::set_storage_class, else the op-inferred default
(infer_storage_class). Every node has a well-defined class.
Phase D substrate (PR-D1).
Sourcepub fn set_storage_class(&mut self, id: NodeId, class: StorageClass)
pub fn set_storage_class(&mut self, id: NodeId, class: StorageClass)
Record an explicit storage-class override for id, replacing the
op-inferred default. Used for nodes whose op does not determine the
class — chiefly KV-cache placeholder Op::Consts, which are session
state rather than shared weights.
Phase D substrate (PR-D1).
Sourcepub fn has_storage_class_override(&self, id: NodeId) -> bool
pub fn has_storage_class_override(&self, id: NodeId) -> bool
Whether id carries an explicit storage-class override (as opposed
to relying on the op-inferred default).
Sourcepub fn storage_class_override_count(&self) -> usize
pub fn storage_class_override_count(&self) -> usize
Number of explicit storage-class overrides. Mostly for tests and diagnostics.
Sourcepub fn layout(&self, id: NodeId) -> Layout
pub fn layout(&self, id: NodeId) -> Layout
The Layout associated with id. If the side-table has
no explicit entry, returns Layout::contiguous(shape) —
every Node logically has a Layout; the side-table is just
the sparse storage for the strided / offset cases.
Phase 7.5 storage-unification: metadata-only view ops
(Op::Transpose, Op::Permute, Op::BroadcastTo,
0-copy Op::Reshape) populate this side-table during
graph construction. Kernel ops produce contiguous results
and rely on the fallback.
Sourcepub fn set_layout(&mut self, id: NodeId, layout: Layout)
pub fn set_layout(&mut self, id: NodeId, layout: Layout)
Record an explicit Layout for id. The Layout’s
shape() must equal nodes[id].shape — otherwise the
graph becomes incoherent (downstream ops were validated
against node.shape). Panics in debug_assertions mode
on mismatch; in release the mismatch is silently kept
because the alternative would require this method to
return Result, which would break call ergonomics in
graph-construction code.
Set this exactly when an op produces a strided / offset
view over upstream Storage. For the common case
(contiguous kernel output), don’t call this at all —
Graph::layout returns Layout::contiguous(shape)
as the implicit fallback.
Sourcepub fn has_explicit_layout(&self, id: NodeId) -> bool
pub fn has_explicit_layout(&self, id: NodeId) -> bool
Whether id has an explicit Layout entry in the side-table
(i.e. it’s a strided / offset view; not the default
contiguous case).
Sourcepub fn explicit_layout_count(&self) -> usize
pub fn explicit_layout_count(&self) -> usize
Number of nodes with an explicit (non-contiguous) Layout. Mostly for tests and diagnostics.
Sourcepub fn output_views(&self, id: NodeId) -> Option<&[OutputView]>
pub fn output_views(&self, id: NodeId) -> Option<&[OutputView]>
Borrow the per-slot output-view specs for id, if it’s been
declared as a multi-output producer. Returns None for
single-output nodes (the common case).
Op::View and Op::ViewOwned builders read this to derive
the slot’s shape/dtype/layout at graph-build time.
Sourcepub fn output_views_arc(&self, id: NodeId) -> Option<Arc<[OutputView]>>
pub fn output_views_arc(&self, id: NodeId) -> Option<Arc<[OutputView]>>
Clone the Arc handle to id’s per-slot output-view specs.
Used by the realization path to attach the same bundle metadata
to the realized Storage without copying the slice.
Sourcepub fn set_output_views(
&mut self,
id: NodeId,
views: Arc<[OutputView]>,
) -> Result<(), Error>
pub fn set_output_views( &mut self, id: NodeId, views: Arc<[OutputView]>, ) -> Result<(), Error>
Declare id as a multi-output producer with the given per-slot
specs. Validates at graph-build time:
idis in bounds.viewsis non-empty (a “multi-output” node with zero slots is a contract bug — use single-output if there’s nothing to project).- Slot 0’s dtype equals
nodes[id].dtype(the bundle’s primary dtype convention). - Slot 0’s shape equals
nodes[id].shape(slot 0 is the “primary” slot whose shape the single-output infrastructure already sees). - Each slot’s
layout.shape()equals the slot’sshape(Layout adds strides/offset; the visible shape is identical).
Idempotent on (id, views). Replacing an existing declaration asserts the new specs are byte-identical to the old in debug builds; in release the last write wins (matches the layouts side-table pattern).
Sourcepub fn is_multi_output(&self, id: NodeId) -> bool
pub fn is_multi_output(&self, id: NodeId) -> bool
Whether id has been declared as a multi-output producer.
Sourcepub fn multi_output_count(&self) -> usize
pub fn multi_output_count(&self) -> usize
Number of nodes declared as multi-output producers. Mostly for tests and diagnostics.
Sourcepub fn binds_data_determined_sym(&self, sym: SymId) -> bool
pub fn binds_data_determined_sym(&self, sym: SymId) -> bool
Whether some node in this graph produces (binds mid-realize) the
runtime value sym — i.e. sym is data-determined, computed by
an op during the pass rather than supplied up front. Used to tell a
data-determined DynScalar::Sym (which is legitimately unbound in
the compile-time SymEnv and must be resolved at execute time)
apart from a caller error (a truly-unbound input-determined sym,
which must surface as an error at realize). Today the only producer
of a data-determined symbol is Op::NonZeroIndices (its
count_sym); extend this match as more data-determined producers
land.
Sourcepub fn next_data_determined_sym(&self) -> SymId
pub fn next_data_determined_sym(&self) -> SymId
The next data-determined SymId not yet used by any producer in
this graph — one past the max Op::NonZeroIndices count_sym, or
SymId(0) if none exist. Lets a builder that emits several
data-determined producers (e.g. one NonZeroIndices per MoE expert,
across several stacked layers on the same graph) allocate
non-colliding count syms without threading an external
fuel_ir::SymGen: each call reflects the producers already added,
so successive layers claim strictly higher ranges.
This covers the data-determined space (the only producers of which
are NonZeroIndices nodes). A graph that also mixes
input-determined symbolic extents in the same low sym range should
thread a shared SymGen instead — those live in a separate
compile-time SymEnv this scan does not see.
Sourcepub fn set_matmul_row_count(&mut self, id: NodeId, row_count: DynScalar)
pub fn set_matmul_row_count(&mut self, id: NodeId, row_count: DynScalar)
Attach a data-determined row count to an Op::MatMul node — the
sparse-MoE capacity-buffer support. The node’s Node::shape stays
the row-capacity output shape; row_count (a DynScalar) is how
many rows to actually compute. See NodeHandle::matmul_dyn_m.
Sourcepub fn matmul_row_count(&self, id: NodeId) -> Option<DynScalar>
pub fn matmul_row_count(&self, id: NodeId) -> Option<DynScalar>
The data-determined row count for id, if it carries one (i.e. it
is a dynamic-M matmul). None ⇒ an ordinary static-M matmul.
Sourcepub fn storage_for(&self, id: NodeId) -> Option<Arc<RwLock<Storage>>>
pub fn storage_for(&self, id: NodeId) -> Option<Arc<RwLock<Storage>>>
Look up the realized storage for a node, if any has been registered. Returns an Arc clone — the slot stays in the map after this call.
Sourcepub fn set_storage(&mut self, id: NodeId, storage: Arc<RwLock<Storage>>)
pub fn set_storage(&mut self, id: NodeId, storage: Arc<RwLock<Storage>>)
Register a realized-storage slot for id. Replaces any
existing entry (callers responsible for ensuring this is the
intended semantics — e.g. re-realization after eviction).
Sourcepub fn set_storage_owned(&mut self, id: NodeId, storage: Storage)
pub fn set_storage_owned(&mut self, id: NodeId, storage: Storage)
Convenience: register an owned Storage directly. Wraps in
Arc<RwLock<...>>.
Sourcepub fn remove_storage(&mut self, id: NodeId) -> Option<Arc<RwLock<Storage>>>
pub fn remove_storage(&mut self, id: NodeId) -> Option<Arc<RwLock<Storage>>>
Remove a slot. Used by eviction / Op::Release paths.
Returns the Arc if present so callers that want to keep the
bytes alive can hold it; otherwise drops on the floor and any
outstanding Arc clones still see the bytes.
Sourcepub fn has_storage(&self, id: NodeId) -> bool
pub fn has_storage(&self, id: NodeId) -> bool
Whether a slot is currently registered for id.
Sourcepub fn storage_len(&self) -> usize
pub fn storage_len(&self) -> usize
Number of registered storage slots.
Sourcepub fn add_side_effect_root(&mut self, id: NodeId)
pub fn add_side_effect_root(&mut self, id: NodeId)
Register a NodeId as a side-effect root — a node whose output
isn’t reachable from any user-requested root but whose
execution the executor must still schedule. Used for
destructive ops (e.g. Op::Release) emitted by graph-mutating
rules: the rule needs Release to run (freeing device memory),
but Release’s zero-element marker has no consumer.
The executor’s realize paths walk the user’s roots AND these
side-effect roots via a single combined execution_plan call.
Sourcepub fn side_effect_roots(&self) -> &[NodeId]
pub fn side_effect_roots(&self) -> &[NodeId]
Snapshot the registered side-effect roots. Executor concatenates these with the user’s requested roots before computing the plan.
Sourcepub fn node(&self, id: NodeId) -> &Node
pub fn node(&self, id: NodeId) -> &Node
Borrow a node by ID. Panics if the ID is out of bounds — it should
not be possible for a valid NodeHandle handle to produce an invalid
ID, so any such panic indicates a bug in graph construction.
Sourcepub fn describe_node(&self, id: NodeId) -> String
pub fn describe_node(&self, id: NodeId) -> String
Produce a one-line human-readable description of a node and its immediate inputs — the identifier string that executor panics prepend so realize-time failures can be localized to the exact graph position that blew up. Format is stable enough for grep but explicitly intended for humans reading panic output, not for parsing.
Sourcepub fn push(&mut self, node: Node) -> NodeId
pub fn push(&mut self, node: Node) -> NodeId
Append a node and return its fresh ID. Used by the NodeHandle
builders, by opt passes that canonicalize or rewrite the
graph by appending fresh nodes, and by external consumers
(fuel-storage’s pipelined executor tests, custom-op authors)
that need direct graph construction.
For metadata-only view ops (Op::Transpose, Op::Permute,
Op::BroadcastTo, Op::Slice) this also populates the
layouts side-table by deriving the output
layout from the input’s layout via
derive_view_output_layout. After this call,
graph.layout(new_id) returns the strided/offset view layout
without any further work — the side-table is the single source
of truth for layout, with Layout::contiguous as the
fallback for kernel-output nodes.
Misuse (malformed Node, dangling input ids, etc.) is caught at execute time as a typed error, not a panic — per project’s no-panic-in-production rule.
Sourcepub fn collapse_variant_branch(
&mut self,
branch: NodeId,
winner_arm: usize,
) -> Result<(), Error>
pub fn collapse_variant_branch( &mut self, branch: NodeId, winner_arm: usize, ) -> Result<(), Error>
Collapse a decision point to a single baked arm — the graph-side
half of optimize-time kernel-variant baking (04-optimization:
“kernel-variant choice is largely baked at optimize time”). Given a
finalized Op::Branch and the arm the optimizer chose (winner_arm,
an index into the branch’s inputs), this rewrites the graph so the
chosen arm is the realized route and the branch is no longer a
decision point:
- The
reconverge_atmerge is rewired to read the winner’s exit (inputs[winner_arm]) in place of arm 0’s exit (inputs[0]). Forwinner_arm == 0this is a no-op (the merge already reads arm 0, the runnability fallback). - The branch’s
inputsare reduced to[winner_exit]— a single-arm branch, whichrun::extract_runs_multi/non_chosen_arm_nodes/ the route picker all treat as no decision (arms.len() < 2), so the winner’s region lowers inline and the non-chosen arms become orphaned (pruned from the reachable set — nothing reads them once the merge is rewired). The arm-0 runnability invariant is preserved: after the reduce,inputs[0]is the winner and the merge reads it.
This is the “the Branch collapses” resolution the constitution names for
same-device kernel-variant choices (decomposed-region vs. a fused
kernel on the same device), as distinct from placement choices
(arms on different devices) which stay live Op::Branch decision
points for the runtime route picker (06-runtime). The optimizer decides
which branches are variant branches and computes winner_arm; this
method only performs the structural collapse.
Never-panic: returns fuel_ir::Error::InvalidBranch on a
non-Op::Branch node or an out-of-range winner_arm, mutating nothing.
Idempotent on an already-collapsed (single-arm) branch when
winner_arm == 0.
Sourcepub fn set_placement(&mut self, id: NodeId, loc: DeviceLocation)
pub fn set_placement(&mut self, id: NodeId, loc: DeviceLocation)
Tag a node with a target device. The executor will validate (post-Phase-1) that the node’s op can be evaluated on that device. In Phase 1 the tag is informational only.
Sourcepub fn placement(&self, id: NodeId) -> Option<DeviceLocation>
pub fn placement(&self, id: NodeId) -> Option<DeviceLocation>
Read a node’s placement hint, or None if the node inherits from
the executor default.
Sourcepub fn open_branch(&self, diverge: NodeId) -> BranchBuilder
pub fn open_branch(&self, diverge: NodeId) -> BranchBuilder
Open a BranchBuilder rooted at a shared diverge point — the
first half of the open_branch / add_arm / finalize_branches
trio that constructs an Op::Branch (phi/merge) node with
build-time validation (Phase A PR-A1 of the “plan IS the graph”
rebuild).
diverge names the single node from which every candidate route
(arm) departs; each arm’s exit (added via
BranchBuilder::add_arm) becomes one inputs[i] of the emitted
Op::Branch. The builder borrows nothing — it is a pure
accumulator of NodeIds — so the graph stays free to keep building
arms between open_branch and finalize_branches. All validation
(descendant reconverge_at, internally-disjoint arms,
cast-to-uniform shape/dtype, arm-0 runnability) happens in
BranchBuilder::finalize_branches, which returns a typed
fuel_ir::error::Error::InvalidBranch rather than panicking.
Sourcepub fn verify_no_dangling(&self) -> Result<(), Error>
pub fn verify_no_dangling(&self) -> Result<(), Error>
Verify that no NodeId reference in the graph points outside
the arena — the post-compaction safety net for a missed remap.
Checks every node’s inputs, the op-carried Op::Branch
reconverge_at, every NodeId-keyed side-table, and the
side_effect_roots vector. Returns the first offending reference as
a typed error (never panics); used by crate::compact’s
debug_assert and by tests.