Expand description
§fuel-graph
Lazy computation graph for Phase 6 of the fuel ML framework.
§Role
Phase 6 transitions fuel from eager execution to a lazy computation graph with an autonomous router. This crate holds the minimal vocabulary that makes that transition possible:
Op— a closed enum describing every operation that can appear in a graph node. New ops are added here and consumed by every backend.Graph— an arena ofNodes. Nodes are immutable once added and referenced by index. The arena grows; it never shrinks.NodeHandle— a cheaply-cloneable handle to a(graph, node_id)pair. Users build computations by calling methods onNodeHandle; each method appends a node to the underlying graph and returns a new handle.
Execution is deliberately not implemented here. A separate executor —
fuel-dispatch’s PipelinedExecutor, dispatching to the per-backend
kernels (fuel-cpu-backend, fuel-cuda-backend, fuel-vulkan-backend) —
walks a Graph and produces concrete outputs. (The retired
fuel-reference-backend textbook walker was the original executor; the
CPU backend’s bit-stable kernels are the correctness oracle now.)
§Scope of the MVP
This is the first landing of the graph types. It is deliberately minimal:
- Four float dtypes:
f32,f64,bf16,f16. Integer dtypes will be added alongside the reference backend’s integer op coverage, not speculatively here. - A small starter
Opcatalog:Const,Add,Mul,MatMul,Transpose,Relu,Sqr,Exp. More ops are added as validation tests for them land. - Unfused backward autograd:
NodeHandle::backwardwalks the forward graph in reverse, applies per-op gradient rules, and emits new graph nodes for the gradient of every leaf. Fused backward and symbolic graph rewriting are deferred to Phase 6d. - No fusion, no planner. Those belong to later sub-phases of Phase 6.
- Thread-safe. The graph is wrapped in
Arc<RwLock<_>>so thatfuel_graph::NodeHandle— the lazy build handle — and any handle that embeds it areSend + Sync. Borrow access goes throughread().unwrap()/write().unwrap().
Modules§
- grad
- Symbolic autograd (Phase 6d Track 2).
- jit
- Kernel-seam JIT — the Fuel-side projection + structural matcher
(kernel-seam-interop §5; fkc-fusion-patterns §3/§3a). The frozen grammar
types (
OpTag,OpAttrs,PatternNode) live infuel_kernel_seam_typesand are re-exported here; this module holds the two parts that need the graph: theOp -> OpTagprojectionop_to_tagand the structural matchermatch_region(the declarative-fusion engine behindPatternKind::Declarative). - opt
- Graph-level, backend-agnostic optimization passes.
- registry
- FusedOpRegistry — metadata side. Phase 7.6 step 1 (skeleton).
- run
- Run extraction + the transient
lower_runview + the fewness gate. - runtime_
fused - Runtime fused-op registration — the Tier-2 sidecar
(
docs/specs/runtime-fused-op-registration.md). - scan
unroll_scan: materialize a boundedcrate::Op::Scaninto real primitive nodes on demand. Used as (a) the FKC/Spec-B numeric oracle and (b) the fallback lowering for a backend without a scan kernel. NOT registered as anyone’s.decompose—Op::Scanis a bare primitive that stays terminal in the base map.
Structs§
- Branch
Builder - Accumulates the arms of an in-construction
Op::Branch(phi/merge) node beforefinalize_branchesvalidates them and emits the node. Created byGraph::open_branch. - GradMap
- Map from forward-graph node IDs to the IDs of their accumulated
backward-graph gradient nodes, returned by
NodeHandle::backward. - Graph
- The graph arena. Stores every node added during a computation-building
session. Nodes are append-only and indexed by
NodeId. - GraphId
- Process-unique identifier for a
Graph, minted monotonically byGraph::new. Exists so affinity diagnostics can name which graphs disagree; it is not a stable address, an index, or anything to persist. - Node
- A single node in the graph: an operation and the IDs of its inputs. The node also caches its output shape and dtype so downstream builders can validate without walking back through the graph.
- Node
Handle - A handle to a node in a shared graph. Cheap to clone. Carries the graph reference so builder methods have everything they need to append a new node.
- NodeId
- A node ID in the arena. Stable for the lifetime of the
Graph. - Node
Remap - The old → new
NodeIdmapping produced bycompact. - Run
- A maximal straight-line, single-device op-sequence between two decision points — the dispatch / command-buffer-capture unit.
- Scan
Predicate - Phase-1 placeholder for
Op::Scan’searly_exitfield. The FIELD is defined now so the enum shape is final (one03-irMAJOR bump); the data-dependent early-exit MECHANISM is Phase 2. It is neverSomeon any live Phase-1 path; if a realize/lowering path seesSome, that path returns a clearErr(a surfaced Phase-2 gap), never a panic.
Enums§
- Op
- The closed enum of operations a graph node can represent.
- PadMode
- Fill mode for
Op::Pad. OnlyPadMode::Constantis implemented in the v1 cut; the other variants are accepted at the IR level so the enum is forward-stable, but the executor returns a clean error until they ship. - Quant
Type - Quantization block format for the
QMatMulfused op (crate::registry::FusedOpParams::QMatMul). Matches the GGML/GGUF block layouts; each variant implies a fixed bytes-per-block and elements-per-block. Only variants for which a backend has a fused dequant-in-kernel matmul are currently exposed. - Scan
Emit - What a
Op::Scanemits.Allstacks the per-stepbody_yover the bound (slot 0);Finalreturns only the final carry (slot 1). The op always produces the 2-slot bundle;emitselects the builder’s default projection. (Op::ReduceisOp::Scan { emit: Final }conceptually — no separate variant.) - Scan
Role - Which body hole a
Op::ScanPlaceholderstands in for.Carryis the per-step recurrent carry (always index 0 in v1’s single-carry model);Elemis the per-step slice ofxs[index]. - Storage
Class - How a node’s storage is shared and how long it lives — its storage
class ([03-ir §“Storage classes and sessions”]). A node is a
structural fact and does not need storage to exist; when storage is
attached, its class governs sharing and lifetime so one optimized graph
can serve many concurrent sessions (weights shared, session state
SessionId-keyed, activations per-realize scratch).
Constants§
- FEWNESS_
THRESHOLD - The fewness-gate threshold: branch points must be at most ~5% of reachable nodes. Deliberate-fork branching stays this sparse; per-op branching does not.
Functions§
- branch_
density - Branch density: the share of reachable nodes that are decision
points (
Op::Branchnodes), overroot. - branch_
density_ multi - Multi-root variant of
branch_density. - branches_
in_ topo_ order - The reachable
Op::Branchdecision points overroots, in topological order of theirreconverge_atmerge points — the order the runtime route picker (Picker 2) resolves them in, so that coupled upstream decisions are committed before a downstream branch is reached (06-runtime §“Resolution order matters when decisions are coupled”). - build_
rope_ tables - Build a constant tensor of the given shape and dtype filled with
value(expressed asf64and converted to the target dtype). Used by backward rules that need a scalar-valued broadcast, for exampleMeanAll’s1/nfactor expressed as “divide by an n-filled const.” - build_
rope_ tables_ with_ inv_ freq build_rope_tableswith the inverse frequencies supplied directly rather than derived from a singlebase.- compact
- Required compaction of the append-only arena (PR-B4).
- derive_
view_ output_ layout - Compute the output
Layoutof a metadata-only view op from its input layout + the op variant. ReturnsErrif the op variant isn’t a view op (caller’s contract — typically guarded byOp::is_view_op). - device_
alternating_ order - Device-overlap topological reorder of a run list (Step E Phase C,
PR C3 — the auto-overlap pass). Returns a permutation of
0..runs.len()(indices intoruns) that is a valid topological reordering — every run still appears after all the runs it depends on — chosen so independent cross-device sub-DAGs DISPATCH so they overlap: the longest compute chunk of each device is enqueued/recorded BEFORE the host-blocking cross-device drain that feeds the join. - effective_
roots - The effective root set for a run/density walk: the caller’s
rootsplus everyOp::Branchnode participating in the reachable computation. A finalizedOp::Branchis typically orphaned — nothing downstream reads it (thereconverge_atnode reads arm 0 directly, per PR-A1’s runnability invariant), so it would never be found by a plain forward walk. We therefore scan the whole arena and seed any Branch whosereconverge_at(or any arm exit) is already reachable; seeding it pulls its candidate arms into the walk. Run to a fixpoint so a Branch reached only through another Branch’s arm is also covered. - extract_
runs - Extract the runs of the graph reachable from
root, in topological order of their entries. - extract_
runs_ multi - Multi-root variant of
extract_runs— the reachable set is the union overroots(mirrorstopo_order_multi) plus the arms of every reachableOp::Branch. A Branch’s non-arm-0 arms are read only by the Branch node itself (arm 0 is the runnability fallback thereconverge_atnode reads directly, per PR-A1), so seeding the walk from the Branch nodes pulls every candidate route into the reachable set. - infer_
storage_ class - The op-inferred storage class for a node, before any explicit
override (see
StorageClassandGraph::storage_class): - lower_
picked_ route - Route-aware lowering (Phase C PR-C1) — the generalization of
lower_runs_arm0. The flat executable dispatch order that follows the chosen arm at eachOp::Branch(perpicked, defaulting to arm 0 for any branch the picker did not resolve) and skips every non-chosen arm’s run. - lower_
picked_ route_ streaming - Streaming route-aware lowering (Step E Phase C, PR C1) — the
incremental form of
lower_picked_route. Instead of taking a fully-resolvedPickedRouteup front, it walks the run partition in topological order and resolves each branch lazily, the first time the walk reaches one of its arm-entry runs, by callingresolve(branch). It emits only the chosen arm’s runs (skipping the non-chosen arms), exactly likelower_picked_route— but the arm decision happens at the frontier, not before the walk. - lower_
run - The transient executable view of a single run: its ordered member
NodeIds, ready to be recorded into a command buffer (Phase C). - lower_
runs_ arm0 - The arm-0 single-route lowering (Phase A PR-A4, architect-
approved temporary fix). The flat executable dispatch order that
follows arm 0 through every
Op::Branch— the pre-run, arm 0’s run, then the post-run — and skips every non-arm-0 arm’s run. - passes_
fewness_ gate - Whether the graph passes the fewness gate over
root(branch density strictly belowFEWNESS_THRESHOLD). - primitive_
shape - The single source of truth for primitive-Op shape+dtype inference. Given a
primitive
Op(its params live on the variant) plus its input shapes and dtypes, returns the(output_shape, output_dtype)the op produces. Reads params off theOp, not offOpAttrs. Never panics: a malformed op/shape, a leaf/bookkeeping op with no pure inference, or a higher-order/basis-gap op is an honestErr. - topo_
order - Compute the topological order of every node reachable from
root. - topo_
order_ multi - Compute the topological order of every node reachable from ANY of the given roots. Each node appears in the output exactly once; the order still satisfies the dependency-before-dependent property, so walking it and caching results as you go computes every input before any node that depends on it.
- walk_
picked_ route_ streaming - The callback core of the streaming lowering — the form the compiler
thread drives so resolution and emission genuinely interleave (the
thread compiles + dispatches each chosen run as the walk reaches it,
rather than first materializing a flat order).
resolve(branch)yields the chosen arm index at the frontier;emit(&[NodeId])receives each kept run’s members in topological order.lower_picked_route_streamingis theVec-collecting wrapper over this (used by the byte-identity test); the executor’s compiler thread passes anemitthatcompile_one’s + sends each node.
Type Aliases§
- Picked
Route - A resolved route through the multi-path graph: for each
Op::Branchdecision point (keyed by the Branch node’sNodeId) the arm index the runtime route picker (Picker 2) chose. Armiis the branch’sinputs[i](arm-0 =inputs[0]= the cost-vector winner). - Shared
Graph - A shared, mutable graph. Builders clone this cheaply and hand it to every derived tensor so that all tensors in one computation point to the same arena.