pub fn compile_plan(
graph: &Graph,
order: &[NodeId],
bindings_table: &KernelBindingTable,
options: &PlanOptions<'_>,
) -> Result<ExecutionPlan>Expand description
Build an ExecutionPlan from a topologically-ordered node
sequence + binding table + options.
For each node in order:
- If
op_to_op_kind(&node.op)returnsNone(view ops,Op::Const, ops not yet wired into dispatch), the node gets no entry — view-only adoption + const adoption flow through the legacy paths unchanged. Op::Copy/Op::Movenodes get no entry either (picker-arc step 4a): their kernel backend is residency-determined (the SOURCE backend), so transfer-kernel resolution stays with the executor’scompile_nodepath keyed by the bridge-maintained source-backend stamp.- Otherwise:
- Resolve
(op_kind, dtypes, device)from the graph + the options (deviceviaGraph::placement, thenPlanOptions::pinned_device, thendefault_device_for(target_backend)for legacy stamped graphs). - Build the placement list — either via
options.placements_for_device(cross-backend mode) or[(target_backend, device)](legacy single-backend mode). - Enumerate candidates via
enumerate_candidates. - Apply the default filter chain (precision floor + strided-input pref + bit-stable pref).
- If
populate_costs, compute static costs via the capabilities lookup — plus, when aPlanOptions::transfer_estimatoris configured, the Stage-2 inbound-transfer term per candidate — and rank ascending by composite cost. - Retain the per-ending-device Pareto frontier + crowding cap
(
AlternativeSet::retain_per_device_frontier,keep = KEEP_PER_DEVICE). - If the set is empty after filtering, return
Error::NoBackendForOp(fail-fast at plan time, not deep in executor dispatch). - Insert into
plan.alternatives.
- Resolve
§Stage-2 residency threading
order is topological, so each node’s producers are planned —
and their placements committed — before the node itself ranks.
The walk maintains a per-node residency view mirroring the
bridge’s effective_placements priority: residency-declaring
ops (Op::Copy/Move/Alloc targets) → explicit
Graph::placement → caller-supplied input residency
(PlanOptions::input_residency, consts + persistent cache
slots) → the plan’s own committed winner → backend stamp →
view-op pass-through inheritance. Inputs whose residency
resolves feed the inbound-transfer pricing; unknown residency
prices no term (conservative — an unpriceable edge neither
justifies nor penalizes a move).
§Stage-3 carry-forward placement DP
Relaxed nodes whose admissible candidates span ≥ 2 devices defer
their winner choice to the carry-forward DP
(crate::ranker::placement_dp): the walk opens a state row
per such node (best[d] = accumulated cost arriving with output
on d), extends rows along the dominant chain (view-shaped
pass-throughs alias through), merges joins per-device by the
cheaper producer state, prices the exit crossing back to the
realize target on terminal rows, and backtracks the cheapest
terminal state into per-node device commitments. Each committed
row’s AlternativeSet is then priced, ranked, and pruned to the
committed device — preserving the Stage-2 single-device-set
invariant the residency stitch depends on.
Gating: a row only opens when the node is relaxed (estimator + fallback enumerator configured, no explicit placement, not destructive, not already-resident) AND its candidates span more than one device. Single-device plans never open a row and take the Stage-2 greedy path bit-identically — zero overhead beyond an empty-map check per node.
Documented debt (see the placement_dp module docs): fan-out
rows chain into their FIRST consumer only (later consumers price
that edge as unknown); diamonds re-anchor the second branch as a
fresh chain; hard-pinned consumers of an open row close it
toward their device at walk time, which prices the crossing but
cannot revisit it later.