---
title: "ModelGraph"
sidebar_label: "ModelGraph"
description: "The clika_runtime.io ModelGraph class."
---

<!-- Generated by tools/api_reference/generate_api_docs.py. Do not edit. -->

A graph: the compiled form of an ONNX model or the recording trace() produces. compile() and trace() return it as built; it is optimized when wanted (optimize()), then finalized (finalize()), then run. run() binds inputs by position or by name and returns the outputs in output_names() order.

## `device` (property)

The device the graph serves on: the one its compile's weights or to() named, else where its operators sit (the first one's device when they sit on several). A graph with no operators reports the CPU.

## `__init__`

```python
__init__(self, /, *args, **kwargs)
```

Initialize self.  See help(type(self)) for accurate signature.

## `add_constant`

```python
add_constantadd_constant(self, tensor: clika_runtime._core.Tensor) -> object
```

add_constant(self, tensor: clika_runtime._core.Tensor) -> object

add_constant(tensor) -> Value

A new constant holding `tensor`'s bytes (a handle, no copy) that nothing reads yet; set_input() and replace_all_uses_with() take the Value it returns, and constants() lists it. Once an operator reads it, it lives while something reads it, like every constant; finalize() drops it if nothing reads it by then. Raises InvalidArgumentError for a tensor with no data (a traced or a meta tensor).

## `add_input`

```python
add_inputadd_input(self, spec: clika_runtime._core.TensorSpec) -> object
```

add_input(self, spec: clika_runtime._core.TensorSpec) -> object

add_input(spec) -> Node

A new graph input named `spec.name`, last in input_names(), with the dtype and dims of `spec`: a dynamic dim (-1) takes its size at run(), and dims that share a dim_names entry share that size. Returns its Input node, whose value (output(0)) the other edits take. Raises InvalidArgumentError for an empty name, a name a node carries already, and a dim that is neither positive nor -1.

## `add_node`

```python
add_nodeadd_node(self, code: clika_runtime._core.graph.OpCode, inputs: object | None, attributes: object | None = None) -> object
```

add_node(self, code: clika_runtime._core.graph.OpCode, inputs: object | None, attributes: object | None = None) -> object

add_node(code, inputs, attributes=None) -> Node

A new operator of `code` (an OpCode), built the way its public op builds it, so the op's own checks apply. `inputs` holds its values, one entry per input port in the op's order: a Value of this graph, or None for an omitted optional operand; trailing entries may be left off, and a constant enters as the Value add_constant() returns. `attributes` maps the setting names Node.attributes reports to their values: an int also serves a number, an enumerated setting takes its enumerator's name ('Tanh'), a list setting a list of ints, a dtype setting a dtype, and a setting left out takes the op's default. An int past 2**63 - 1 goes in as its 64-bit pattern, which an unsigned setting (a seed) reads back as given. Nothing reads its outputs yet: wire them with set_input(), replace_all_uses_with() or add_output(); finalize() drops a node nothing reads by then, with what only it read. The node is named `<OpCode>_<N>`, as a trace names it (rename_node() renames it). Raises InvalidArgumentError for a value of another graph or one no longer held, a state buffer, a missing required input or setting, an input or a setting the op does not take, a setting of another kind, the op's own refusal of the arguments, and a code no single public op call builds (an operator with several outputs, one whose public op call takes a setting the operator does not record, or a quantized operator; build those from the public ops that compute them); TypeError for an input that is neither a Value nor None and a setting no op takes (a Tensor, an enumerator object); and OverflowError for an int outside the 64-bit range.

## `add_output`

```python
add_outputadd_output(self, value: clika_runtime._core.graph.Value, name: str) -> None
```

add_output(self, value: clika_runtime._core.graph.Value, name: str) -> None

add_output(value, name) -> None

Return `value` as a new graph output named `name`, last in output_names(); a value the graph returns already may be returned under a second name. Raises InvalidArgumentError for an empty name, a name another output carries, and a constant or a state buffer (a graph output never returns one directly).

## `all_simple_edge_paths`

```python
all_simple_edge_pathsall_simple_edge_paths(self, src: clika_runtime._core.graph.Node, dst: clika_runtime._core.graph.Node, *, cutoff: int | None = None, limit: int | None = None, avoid: collections.abc.Sequence[clika_runtime._core.graph.Node] = [], edge_filter: collections.abc.Callable | None = None) -> object
```

all_simple_edge_paths(self, src: clika_runtime._core.graph.Node, dst: clika_runtime._core.graph.Node, *, cutoff: int | None = None, limit: int | None = None, avoid: collections.abc.Sequence[clika_runtime._core.graph.Node] = [], edge_filter: collections.abc.Callable | None = None) -> object

all_simple_edge_paths(src, dst, *, cutoff=None, limit=None, avoid=(), edge_filter=None) -> Iterator[list[Edge]]

Every simple path from `src` to `dst`, as edges; each parallel edge gives its own path, as networkx's all_simple_edge_paths. src == dst gives one empty path. Depth-first in out_edges() order, with the bounds, the search at the call and the MAX_PATHS_WITHOUT_LIMIT refusal of find_paths_to().

## `all_simple_paths`

```python
all_simple_pathsall_simple_paths(self, src: clika_runtime._core.graph.Node, dst: clika_runtime._core.graph.Node, *, cutoff: int | None = None, limit: int | None = None, avoid: collections.abc.Sequence[clika_runtime._core.graph.Node] = [], edge_filter: collections.abc.Callable | None = None) -> object
```

all_simple_paths(self, src: clika_runtime._core.graph.Node, dst: clika_runtime._core.graph.Node, *, cutoff: int | None = None, limit: int | None = None, avoid: collections.abc.Sequence[clika_runtime._core.graph.Node] = [], edge_filter: collections.abc.Callable | None = None) -> object

all_simple_paths(src, dst, *, cutoff=None, limit=None, avoid=(), edge_filter=None) -> Iterator[list[Node]]

Every simple path from `src` to `dst`, as nodes; parallel edges give one path, as networkx's all_simple_paths over the graph with its parallel edges merged. src == dst gives the one-node path. The order, the bounds, the search at the call and the MAX_PATHS_WITHOUT_LIMIT refusal of find_paths_to().

## `ancestors`

```python
ancestorsancestors(self, node: clika_runtime._core.graph.Node, *, max_depth: int | None = None, max_nodes: int | None = None, stop_at: collections.abc.Callable | None = None, edge_filter: collections.abc.Callable | None = None, skip_shape_lane: bool = False) -> list
```

ancestors(self, node: clika_runtime._core.graph.Node, *, max_depth: int | None = None, max_nodes: int | None = None, stop_at: collections.abc.Callable | None = None, edge_filter: collections.abc.Callable | None = None, skip_shape_lane: bool = False) -> list

ancestors(node, *, max_depth=None, max_nodes=None, stop_at=None, edge_filter=None, skip_shape_lane=False) -> list[Node]

Every node with a path to `node`, `node` left out, nearest first: a backward walk from it, bounded and filtered as walk() takes them.

## `bench`

```python
benchbench(self, shapes: collections.abc.Sequence[clika_runtime._core.io.InputShape], options: clika_runtime._core.io.BenchOptions = BenchOptions(warmup=3, iterations=20, fill=FeedFill.Zeros, device=None, reuse_output_buffers=True, batch_axis=None, profile_dir=None)) -> clika_runtime._core.io.BenchReport
```

bench(self, shapes: collections.abc.Sequence[clika_runtime._core.io.InputShape], options: clika_runtime._core.io.BenchOptions = BenchOptions(warmup=3, iterations=20, fill=FeedFill.Zeros, device=None, reuse_output_buffers=True, batch_axis=None, profile_dir=None)) -> clika_runtime._core.io.BenchReport
bench(self, shapes: collections.abc.Sequence[collections.abc.Sequence[int]], options: clika_runtime._core.io.BenchOptions = BenchOptions(warmup=3, iterations=20, fill=FeedFill.Zeros, device=None, reuse_output_buffers=True, batch_axis=None, profile_dir=None)) -> clika_runtime._core.io.BenchReport
bench(self, shapes: dict, options: clika_runtime._core.io.BenchOptions = BenchOptions(warmup=3, iterations=20, fill=FeedFill.Zeros, device=None, reuse_output_buffers=True, batch_axis=None, profile_dir=None)) -> clika_runtime._core.io.BenchReport

Overloaded function.

1. ``bench(self, shapes: collections.abc.Sequence[clika_runtime._core.io.InputShape], options: clika_runtime._core.io.BenchOptions = BenchOptions(warmup=3, iterations=20, fill=FeedFill.Zeros, device=None, reuse_output_buffers=True, batch_axis=None, profile_dir=None)) -> clika_runtime._core.io.BenchReport``

bench(shapes, options=BenchOptions()) -> BenchReport

Time the graph on synthesized inputs of the given shapes: the shapes are validated as validate_shapes() does, one tensor per input is filled per options.fill on the benchmark device, options.warmup untimed runs precede options.iterations timed ones, and the report carries every timed run's milliseconds (each output synchronized before the clock stops), the summary statistics, the process's peak resident set and the device's memory counters. `shapes` is a list of InputShape, a list of dims in input_names() order, a dict of name to dims, or a dict of name to Tensor (the tensors are fed as they are). InvalidArgumentError on a shape the domains reject; the run's own error when a run fails.

2. ``bench(self, shapes: collections.abc.Sequence[collections.abc.Sequence[int]], options: clika_runtime._core.io.BenchOptions = BenchOptions(warmup=3, iterations=20, fill=FeedFill.Zeros, device=None, reuse_output_buffers=True, batch_axis=None, profile_dir=None)) -> clika_runtime._core.io.BenchReport``

bench(shapes, options=BenchOptions()) -> BenchReport

The positional form: one dims sequence per input_names() entry, that order.

3. ``bench(self, shapes: dict, options: clika_runtime._core.io.BenchOptions = BenchOptions(warmup=3, iterations=20, fill=FeedFill.Zeros, device=None, reuse_output_buffers=True, batch_axis=None, profile_dir=None)) -> clika_runtime._core.io.BenchReport``

bench(shapes, options=BenchOptions()) -> BenchReport

The named form: a dict of input name to dims (synthesized inputs), or of input name to Tensor (the caller's tensors, fed as they are; their shapes are still validated).

## `between`

```python
betweenbetween(self, src: clika_runtime._core.graph.Node, dst: clika_runtime._core.graph.Node) -> list
```

between(self, src: clika_runtime._core.graph.Node, dst: clika_runtime._core.graph.Node) -> list

between(src, dst) -> list[Node]

Every node on a path from `src` to `dst`, both ends included, in nodes() order; [] when no path runs.

## `bypass_node`

```python
bypass_nodebypass_node(self, node: clika_runtime._core.graph.Node, input_port: int = 0, output_port: int = 0) -> None
```

bypass_node(self, node: clika_runtime._core.graph.Node, input_port: int = 0, output_port: int = 0) -> None

bypass_node(node, input_port=0, output_port=0) -> None

Every read of `node`'s output `output_port` reads the value on its input `input_port` instead, and `node` is removed once nothing reads any of its outputs. Raises InvalidArgumentError for an Input node, an input port nothing feeds, an output port nothing reads, a type mismatch between the two values, a node that reads a state buffer, and a constant taking a graph output.

## `constants`

```python
constantsconstants(self) -> dict
```

constants(self) -> dict

constants() -> dict[str, Tensor]

The bound constants (weights, baked values) by value name.

## `consumer_cone`

```python
consumer_coneconsumer_cone(self, nodes: collections.abc.Sequence[clika_runtime._core.graph.Node]) -> list
```

consumer_cone(self, nodes: collections.abc.Sequence[clika_runtime._core.graph.Node]) -> list

consumer_cone(nodes) -> list[Node]

Every node that reads the outputs of `nodes`, directly or through others, in nodes() order; the cone of a node no other node reads is [].

## `count_paths`

```python
count_pathscount_paths(self, src: clika_runtime._core.graph.Node, dst: clika_runtime._core.graph.Node, *, cutoff: int | None = None, avoid: collections.abc.Sequence[clika_runtime._core.graph.Node] = [], edge_filter: collections.abc.Callable | None = None) -> int
```

count_paths(self, src: clika_runtime._core.graph.Node, dst: clika_runtime._core.graph.Node, *, cutoff: int | None = None, avoid: collections.abc.Sequence[clika_runtime._core.graph.Node] = [], edge_filter: collections.abc.Callable | None = None) -> int

count_paths(src, dst, *, cutoff=None, avoid=(), edge_filter=None) -> int

How many paths all_simple_paths() lists, counted without listing them, so MAX_PATHS_WITHOUT_LIMIT does not apply. Raises InvalidArgumentError when the count passes 2**64 - 1.

## `critical_path`

```python
critical_pathcritical_path(self, node_cost: collections.abc.Callable | None = None, edge_cost: collections.abc.Callable | None = None) -> object
```

critical_path(self, node_cost: collections.abc.Callable | None = None, edge_cost: collections.abc.Callable | None = None) -> object

critical_path(node_cost=None, edge_cost=None) -> WeightedPath

The path of greatest cost: `node_cost` (a callable taking a Node) summed over its nodes and `edge_cost` (taking an Edge) over its edges, each finite and of any sign; a cost not given counts 0, so no costs give the path with the most edges. A path may start and end at any node: a prefix whose sum falls below 0 is dropped and the path starts again at the next node, as networkx's dag_longest_path does. Ties go to more edges, then to the path found first. Each cost runs once per node or edge; a NaN or infinite cost raises InvalidArgumentError naming the node or edge.

## `default_transforms`

```python
default_transformsdefault_transforms(self, defaults: clika_runtime._core.io.DefaultsOptions | None = None) -> list[clika_runtime._core.graph.Transform]
```

default_transforms(self, defaults: clika_runtime._core.io.DefaultsOptions | None = None) -> list[clika_runtime._core.graph.Transform]

default_transforms(defaults=None) -> list[Transform]

The transform list optimize() runs on this graph under `defaults` (a DefaultsOptions; None takes every default) when it is handed no list, in the order it runs them: a starting point to edit and hand back as optimize(transforms=...). It follows how the graph was built, so it can differ from the graph-free transforms.defaults(defaults).

## `depths`

```python
depthsdepths(self) -> dict
```

depths(self) -> dict

depths() -> dict[Node, int]

Each node's generation: the most edges on a path that ends at it, so depths()[n] is the index of n's layer in layers(). One entry per node, in nodes() order.

## `descendants`

```python
descendantsdescendants(self, node: clika_runtime._core.graph.Node, *, max_depth: int | None = None, max_nodes: int | None = None, stop_at: collections.abc.Callable | None = None, edge_filter: collections.abc.Callable | None = None, skip_shape_lane: bool = False) -> list
```

descendants(self, node: clika_runtime._core.graph.Node, *, max_depth: int | None = None, max_nodes: int | None = None, stop_at: collections.abc.Callable | None = None, edge_filter: collections.abc.Callable | None = None, skip_shape_lane: bool = False) -> list

descendants(node, *, max_depth=None, max_nodes=None, stop_at=None, edge_filter=None, skip_shape_lane=False) -> list[Node]

Every node with a path from `node`, `node` left out, nearest first: a forward walk from it, bounded and filtered as walk() takes them.

## `dominators`

```python
dominatorsdominators(self) -> dict
```

dominators(self) -> dict

dominators() -> dict[Node, Node]

Each node's immediate dominator: the last node before it that every path from the graph inputs to it crosses. Paths start at the graph inputs, so a constant or weight operand opens none. A node no other node dominates (an input, or a node where two inputs' paths meet) maps to itself, and a node on no path from an input has no entry. Entries in nodes() order.

## `edges`

```python
edgesedges(self) -> list
```

edges(self) -> list

edges() -> list[Edge]

Every edge, in (source name, source port, destination name, destination port) order.

## `edges_between`

```python
edges_betweenedges_between(self, src: clika_runtime._core.graph.Node, dst: clika_runtime._core.graph.Node) -> list
```

edges_between(self, src: clika_runtime._core.graph.Node, dst: clika_runtime._core.graph.Node) -> list

edges_between(src, dst) -> list[Edge]

The edges from `src` to `dst`, in (source port, destination port) order; [] when no edge runs from one to the other, a node of another graph included. A view whose node an edit removed or renamed raises InvalidArgumentError, as its check() does.

## `export_onnx`

```python
export_onnxexport_onnx(self, path: object | None, *, opset_version: int = 0, contrib_ops: bool = True, optimize: bool = True, save_as_external_data: bool | None = None, producer_name: str = '') -> None
```

export_onnx(self, path: object | None, *, opset_version: int = 0, contrib_ops: bool = True, optimize: bool = True, save_as_external_data: bool | None = None, producer_name: str = '') -> None

export_onnx(path, *, opset_version=0, contrib_ops=True, optimize=True, save_as_external_data=None, producer_name='') -> None

Write the graph to `path`, a str or an os.PathLike, as a runnable ONNX model. The inputs and outputs keep their names, dtypes and dims, and a dynamic dimension is written under its name, so two inputs that share one still share it when the model is read back. The weights become the model's initializers, read one at a time as the file is written, so the export holds no second copy of them. `opset_version` is the ai.onnx opset the model imports (0 takes the library's default); `contrib_ops` lets the model use the com.microsoft operators; `optimize` simplifies the written model (transposes that undo each other compose, and nodes no output reads go); `save_as_external_data` puts the weights in a '<stem>_data' file beside the model: None once they pass 2 GiB, True always and False never; and `producer_name` is the producer the model records (empty takes the library's). OnnxModel.from_graph() builds the same model in memory, and OnnxModel.open(path).compile() reads the file back. A refusal writes nothing. Raises UnsupportedError for a graph that rewrites a state buffer in place (one given a KV cache by attach_kv_cache(), or traced over a function that writes its cache in place) and for an operator with no ONNX form at the chosen opset, naming the node; InvalidArgumentError for an opset_version the library does not know, and with the code FAILED_PRECONDITION while optimize(), finalize(), attach_kv_cache(), to() or an edit runs on the graph; and TypeError for a path of another type.

## `finalize`

```python
finalizefinalize(self) -> None
```

finalize(self) -> None

finalize() -> None

Finalize the graph for serving: place it on the device its compile's weights or to() named (a graph with neither keeps its operators where they are), pack the weights into their kernel layouts, absorb the constants the operators hold, and plan the execution order. A finalized graph runs and refuses optimize(); calling finalize() again changes nothing.

## `find_cycle`

```python
find_cyclefind_cycle(self) -> list
```

find_cycle(self) -> list

find_cycle() -> list[Edge]

The edges of one cycle, in order: each edge enters the node the next one leaves, and the last enters the node the first one leaves. [] when the graph has none, as every traced or compiled graph, where networkx's find_cycle() raises.

## `find_nodes`

```python
find_nodesfind_nodes(self, op_code: clika_runtime._core.graph.OpCode) -> list
```

find_nodes(self, op_code: clika_runtime._core.graph.OpCode) -> list
find_nodes(self, predicate: collections.abc.Callable) -> list

Overloaded function.

1. ``find_nodes(self, op_code: clika_runtime._core.graph.OpCode) -> list``

find_nodes(op_code) -> list[Node]

Every node running the operator, in name order.

2. ``find_nodes(self, predicate: collections.abc.Callable) -> list``

find_nodes(predicate) -> list[Node]

Every node `predicate` accepts, in name order: a callable taking a Node and returning True to accept it. It runs inside the search; an exception it raises ends the search, and find_nodes raises with its message.

## `find_nodes_by_name`

```python
find_nodes_by_namefind_nodes_by_name(self, pattern: str, match: clika_runtime._core.graph.NameMatch = NameMatch.Whole) -> list
```

find_nodes_by_name(self, pattern: str, match: clika_runtime._core.graph.NameMatch = NameMatch.Whole) -> list

find_nodes_by_name(pattern, match=NameMatch.Whole) -> list[Node]

Every node whose name the regular expression `pattern` matches, in name order: the whole name (NameMatch.Whole) or some part of it (NameMatch.Anywhere). The expression syntax is a large PCRE-like subset with no backreferences and no lookaround; a malformed pattern raises InvalidArgumentError with the regex engine's message.

## `find_paths_to`

```python
find_paths_tofind_paths_to(self, start: clika_runtime._core.graph.Node, target: collections.abc.Callable, *, cutoff: int | None = None, limit: int | None = None, avoid: collections.abc.Sequence[clika_runtime._core.graph.Node] = [], edge_filter: collections.abc.Callable | None = None) -> object
```

find_paths_to(self, start: clika_runtime._core.graph.Node, target: collections.abc.Callable, *, cutoff: int | None = None, limit: int | None = None, avoid: collections.abc.Sequence[clika_runtime._core.graph.Node] = [], edge_filter: collections.abc.Callable | None = None) -> object

find_paths_to(start, target, *, cutoff=None, limit=None, avoid=(), edge_filter=None) -> Iterator[list[Node]]

Every simple path from `start` that ends at the first node `target` accepts: a path never passes an accepted node, and an accepted `start` is the one-node path. Depth-first, each node's next nodes in out_edges() order; parallel edges give one path. `cutoff` is the most edges a path may have, `limit` the most paths returned (the first ones), `avoid` the nodes no path may hold (its two ends included), and only the edges `edge_filter` accepts are taken. The search runs at the call, so its refusals and a callable's exception surface there: without a `limit`, a search that finds more than MAX_PATHS_WITHOUT_LIMIT paths raises InvalidArgumentError rather than returning a cut list; pass a `limit`, or count them with count_paths(). The target and the edge filter each run once per node or edge.

## `find_pattern`

```python
find_patternfind_pattern(self, pattern: clika_runtime._core.graph.Pattern) -> list
```

find_pattern(self, pattern: clika_runtime._core.graph.Pattern) -> list

find_pattern(pattern) -> list[Match]

Every occurrence of the pattern in this graph, in match order (non-overlapping: a node belongs to at most one occurrence); each Match lists the matched nodes in the pattern's add_node order, None for an absent optional node. A pattern predicate that raises ends the search and the error carries its message.

## `has_path`

```python
has_pathhas_path(self, src: clika_runtime._core.graph.Node, dst: clika_runtime._core.graph.Node, *, cutoff: int | None = None, avoid: collections.abc.Sequence[clika_runtime._core.graph.Node] = [], edge_filter: collections.abc.Callable | None = None) -> bool
```

has_path(self, src: clika_runtime._core.graph.Node, dst: clika_runtime._core.graph.Node, *, cutoff: int | None = None, avoid: collections.abc.Sequence[clika_runtime._core.graph.Node] = [], edge_filter: collections.abc.Callable | None = None) -> bool

has_path(src, dst, *, cutoff=None, avoid=(), edge_filter=None) -> bool

True when a path runs from `src` to `dst` within the bounds find_paths_to() takes; a node reaches itself.

## `input_names`

```python
input_namesinput_names(self) -> list[str]
```

input_names(self) -> list[str]

input_names() -> list[str]

Runtime input names, declaration order; the order the positional run() binds.

## `input_shape_domain`

```python
input_shape_domaininput_shape_domain(self, name: str) -> clika_runtime._core.io.ShapeDomain
```

input_shape_domain(self, name: str) -> clika_runtime._core.io.ShapeDomain

input_shape_domain(name) -> ShapeDomain

The domain of the input named `name`; NotFoundError when no input carries it.

## `inputs`

```python
inputsinputs(self) -> list
```

inputs(self) -> list

inputs() -> list[tuple[str, DataType, tuple[int, ...]]]

Input slots as (name, dtype, dims) tuples; a dynamic dim reads as -1.

## `insert`

```python
insertinsert(self, fn: object | None, inputs: object | None) -> list
```

insert(self, fn: object | None, inputs: object | None) -> list

insert(fn, inputs) -> list[Value]

New operators computed from `inputs`, a sequence of Values of this graph: the ones `fn` records when it is traced over one Tensor per input, each with that value's dtype, device and shape (a dynamic dim keeps the graph's own symbol). A constant scalar input goes into `fn` as the literal it holds, so a value `fn` reads from it (a range's end, a count) keeps the extent it gives. `fn` returns a Tensor or a sequence of Tensors, and insert() returns their Values in order, which nothing reads yet: wire them as add_node()'s outputs are wired. A tensor holding data that `fn` makes or reads becomes a constant of the graph, a value `fn` returns unchanged comes back as the input it is, and the new nodes take fresh names. `fn` computes values: it does not write one of its inputs, or a tensor that holds data, in place, and it does not edit this graph or call optimize(). An exception `fn` raises leaves insert() as that same exception, with the graph as it was. Raises InvalidArgumentError for a value of another graph or one no longer held, a state buffer, a value with no recorded type, such an in-place write, a returned value that holds data, and a read of a tensor without data that is none of its inputs; and TypeError for an `fn` that cannot be called, an entry that is not a Value, and a return that is not a Tensor or a sequence of Tensors.

## `is_convex`

```python
is_convexis_convex(self, nodes: collections.abc.Sequence[clika_runtime._core.graph.Node]) -> bool
```

is_convex(self, nodes: collections.abc.Sequence[clika_runtime._core.graph.Node]) -> bool

is_convex(nodes) -> bool

True when `nodes` is convex: no path leaves the set and comes back into it, so the set can be cut out and replaced as one piece.

## `is_finalized`

```python
is_finalizedis_finalized(self) -> bool
```

is_finalized(self) -> bool

is_finalized() -> bool

True once finalize() ran. compile() and trace() return a graph that is not finalized yet.

## `is_topological_order`

```python
is_topological_orderis_topological_order(self, order: collections.abc.Sequence[clika_runtime._core.graph.Node]) -> bool
```

is_topological_order(self, order: collections.abc.Sequence[clika_runtime._core.graph.Node]) -> bool

is_topological_order(order) -> bool

True when `order`, a sequence of Nodes, is a topological order of this graph: every node exactly once, and every edge running forward. A node of another graph, a repeated or a missing node, or an edge running backward makes it False. A view whose node an edit removed or renamed raises InvalidArgumentError, as its check() does.

## `kv_cache_info`

```python
kv_cache_infokv_cache_info(self) -> list[clika_runtime._core.io.KVCacheLayerInfo]
```

kv_cache_info(self) -> list[clika_runtime._core.io.KVCacheLayerInfo]

kv_cache_info() -> list[KVCacheLayerInfo]

The attention layers' KV-cache wiring (past inputs, present outputs, heads, mask family), one entry per layer; empty for a graph without attention.

## `layers`

```python
layerslayers(self) -> list
```

layers(self) -> list

layers() -> list[list[Node]]

The topological generations: the nodes with no in-edge first (in_degree() 0), then each node one layer after its latest producer; every layer in nodes() order.

## `lowest_common_ancestors`

```python
lowest_common_ancestorslowest_common_ancestors(self, nodes: collections.abc.Sequence[clika_runtime._core.graph.Node]) -> list
```

lowest_common_ancestors(self, nodes: collections.abc.Sequence[clika_runtime._core.graph.Node]) -> list

lowest_common_ancestors(nodes) -> list[Node]

The lowest common ancestors of `nodes`, a sequence of at least one Node: the nodes every one of them descends from (each counting as its own ancestor) that no other such node descends from, in nodes() order. networkx's lowest_common_ancestor returns one of them.

## `merge`

```python
mergemerge(self, second: object | None, io_map: collections.abc.Sequence[tuple[str, str]] = []) -> list[clika_runtime._core.graph.Rename]
```

merge(self, second: object | None, io_map: collections.abc.Sequence[tuple[str, str]] = []) -> list[clika_runtime._core.graph.Rename]

merge(second, io_map=()) -> list[Rename]

Merge `second`, a ModelGraph, into this graph. Each io_map entry (this_output, second_input) feeds one of this graph's outputs into one of second's inputs; the connected outputs and inputs leave the lists, and everything else stays. The inputs are this graph's, then second's unconnected ones, and the outputs second's, then this graph's unconnected ones. This graph keeps every name, and a name of second's that this graph already uses takes the first free '<name>_<n>'. Returns every name that changed, each under the part 'second' (Rename). The rest is graph.merge()'s: the shapes, the devices, the KV caches and the refusals, the two graphs being the parts 'this' and 'second'. On success second is consumed: it is left empty, and a view of it raises afterwards. A refusal leaves both graphs as they were. Raises InvalidArgumentError for a merge of a graph with itself and graph.merge()'s refusals, with the code FAILED_PRECONDITION inside one of this graph's transforms, and TypeError for a second that is not a ModelGraph.

## `merge_point`

```python
merge_pointmerge_point(self, nodes: collections.abc.Sequence[clika_runtime._core.graph.Node]) -> object
```

merge_point(self, nodes: collections.abc.Sequence[clika_runtime._core.graph.Node]) -> object

merge_point(nodes) -> Node | None

Where the paths from `nodes`, a sequence of at least one Node, merge: the nearest node every path from them to the graph outputs crosses, each counting for itself. None when their paths meet only at the outputs. A node on no path to an output is not counted.

## `min_value_cut`

```python
min_value_cutmin_value_cut(self, before: collections.abc.Sequence[clika_runtime._core.graph.Node] = [], after: collections.abc.Sequence[clika_runtime._core.graph.Node] = []) -> object
```

min_value_cut(self, before: collections.abc.Sequence[clika_runtime._core.graph.Node] = [], after: collections.abc.Sequence[clika_runtime._core.graph.Node] = []) -> object

min_value_cut(before=(), after=()) -> ValueCut

The cheapest cut through the graph's values: a split of the nodes in two, every node before the cut with its producers before it, that hands the fewest bytes from the part before to the part after. The graph inputs and the nodes of `before` run before the cut and the nodes of `after` after it; a graph output made before the cut is one of the values. A value weighs its tensor's bytes: a view adds nothing to the tensor it re-describes, and a constant or a state buffer is never one of the values. Of the cheapest cuts this is the one nearest the inputs: the least set of nodes before the cut. networkx's minimum_cut returns the one nearest the sink instead; called on the reversed network, from the sink to the source, it returns this one. Raises InvalidArgumentError when no cut exists, naming the node of `after` that is a graph input, is in `before` or feeds a node of `before`, and UnsupportedError when a value has no fixed size in bytes (a dynamic dim), since the cut weighs every value.

## `node`

```python
nodenode(self, name: str) -> object
```

node(self, name: str) -> object

node(name) -> Node

The node named `name`; KeyError when absent.

## `nodes`

```python
nodesnodes(self) -> list
```

nodes(self) -> list

nodes() -> list[Node]

Every node in execution order (graph inputs first, then the operators). compile() and trace() return the graph as the model states it; read it there, before optimize() and finalize() rewrite it.

## `optimize`

```python
optimizeoptimize(self, transforms: collections.abc.Sequence[clika_runtime._core.graph.Transform] | None = None, max_num_iterations: int = 100, *, defaults: clika_runtime._core.io.DefaultsOptions | None = None) -> clika_runtime._core.io.OptimizeReport
```

optimize(self, transforms: collections.abc.Sequence[clika_runtime._core.graph.Transform] | None = None, max_num_iterations: int = 100, *, defaults: clika_runtime._core.io.DefaultsOptions | None = None) -> clika_runtime._core.io.OptimizeReport

optimize(transforms=None, max_num_iterations=100, *, defaults=None) -> OptimizeReport

Run the graph optimizer: `transforms`, a list of handles from clika_runtime.graph.transforms run in order each iteration (any of them as often as wanted), iterated until an iteration changes nothing or max_num_iterations (1 to 2147483647) have run. None runs this graph's default list (default_transforms(defaults)); an empty list runs none of them, though the run still drops the nodes nothing reads. `defaults` is a DefaultsOptions; None takes every default, and the transforms read their settings from it either way. Returns what the run did, one TransformReport per list entry. It runs before finalize(), and raises InvalidArgumentError on a finalized graph, while a run, optimize() or finalize() is in flight on this graph, and before anything runs when the list places a transform ahead of one it must follow or holds a transform that `defaults` switch off for this graph (each refusal names the list entry and what to change). A transform made from a Python function (Transform) that raises ends the run, and optimize() raises that same exception, with the graph as optimize() found it.

## `output_names`

```python
output_namesoutput_names(self) -> list[str]
```

output_names(self) -> list[str]

output_names() -> list[str]

Output names, declaration order; the order run() returns.

## `outputs`

```python
outputsoutputs(self) -> list
```

outputs(self) -> list

outputs() -> list[tuple[str, DataType, tuple[int, ...]]]

Output slots as (name, dtype, dims) tuples.

## `post_dominators`

```python
post_dominatorspost_dominators(self) -> dict
```

post_dominators(self) -> dict

post_dominators() -> dict[Node, Node]

Each node's immediate post-dominator: the first node after it that every path from it to the graph outputs crosses. A node no other node post-dominates maps to itself, and a node on no path to an output has no entry. Entries in nodes() order.

## `producer_cone`

```python
producer_coneproducer_cone(self, nodes: collections.abc.Sequence[clika_runtime._core.graph.Node]) -> list
```

producer_cone(self, nodes: collections.abc.Sequence[clika_runtime._core.graph.Node]) -> list

producer_cone(nodes) -> list[Node]

Every node whose output `nodes` read, directly or through others, in nodes() order. A node of `nodes` counts only when another one reads it, so the cone of an input is [].

## `random_topological_order`

```python
random_topological_orderrandom_topological_order(self, seed: int) -> list
```

random_topological_order(self, seed: int) -> list

random_topological_order(seed) -> list[Node]

A topological order drawn at random from the valid ones: each node once, after every node whose output it reads. One seed (an integer from 0 to 2**64 - 1) draws one order of a given graph, on every platform.

## `region`

```python
regionregion(self, entry: clika_runtime._core.graph.Node, exit: clika_runtime._core.graph.Node) -> list
```

region(self, entry: clika_runtime._core.graph.Node, exit: clika_runtime._core.graph.Node) -> list

region(entry, exit) -> list[Node]

The single-entry, single-exit region from `entry` to `exit`: the nodes `entry` dominates and `exit` post-dominates, both ends included, in nodes() order; a node alone is a region. Raises InvalidArgumentError, naming the condition that fails, unless `entry` dominates `exit` and `exit` post-dominates `entry`.

## `remove_node`

```python
remove_noderemove_node(self, node: clika_runtime._core.graph.Node) -> None
```

remove_node(self, node: clika_runtime._core.graph.Node) -> None

remove_node(node) -> None

Remove `node`, an operator nothing reads, with its reads of other values (a constant only it read goes with it). Raises InvalidArgumentError for an Input node and a node that reads a state buffer, and with code_name FAILED_PRECONDITION for a node whose outputs are still read, naming the first reader: rewire or remove that reader first.

## `remove_output`

```python
remove_outputremove_output(self, name: str) -> None
```

remove_output(self, name: str) -> None

remove_output(name) -> None

Stop returning the graph output `name`; the node that computes it stays. Raises InvalidArgumentError for a name no output carries, and with code_name FAILED_PRECONDITION for an output a KV cache layer returns its cache as, naming the layer.

## `rename_input`

```python
rename_inputrename_input(self, name: str, new_name: str) -> None
```

rename_input(self, name: str, new_name: str) -> None

rename_input(name, new_name) -> None

Rename the graph input `name`, and its Input node, `new_name`. Views of the old name are gone afterwards. Raises InvalidArgumentError for a name no input carries, an empty name and a name a node carries already, and with code_name FAILED_PRECONDITION for an input a KV cache layer reads its cache from, naming the layer.

## `rename_node`

```python
rename_noderename_node(self, node: clika_runtime._core.graph.Node, name: str) -> None
```

rename_node(self, node: clika_runtime._core.graph.Node, name: str) -> None

rename_node(node, name) -> None

Name `node` `name`; its outputs keep their names. Views of the old name are gone afterwards: find the node again with node(name). Raises InvalidArgumentError for an Input node (rename it with rename_input()), an empty name, and a name another node carries.

## `rename_output`

```python
rename_outputrename_output(self, name: str, new_name: str) -> None
```

rename_output(self, name: str, new_name: str) -> None

rename_output(name, new_name) -> None

Rename the graph output `name` `new_name`. Raises InvalidArgumentError for a name no output carries, an empty name and a name another output carries, and with code_name FAILED_PRECONDITION for an output a KV cache layer returns its cache as, naming the layer.

## `replace`

```python
replacereplace(self, old_outputs: object | None, fn: object | None, inputs: object | None) -> list
```

replace(self, old_outputs: object | None, fn: object | None, inputs: object | None) -> list

replace(old_outputs, fn, inputs) -> list[Value]

insert(fn, inputs), then every read of `old_outputs[i]` reads new value `i`, the graph's returns included (they keep their names), and the operators that computed `old_outputs` from `inputs` go, with every producer only they read. An operator an input is computed from stays, since the new values read that input; when it makes an old output (an input read from another of its outputs), that output's reads move all the same. Returns the new Values. An exception `fn` raises leaves replace() as that same exception, with the graph as it was. Raises what insert() raises, and InvalidArgumentError for no old outputs, one listed twice, or one that is a constant, a state buffer or a graph input's value; an old output that is also one of `inputs`; an old output not computed from `inputs`; a value those operators make that another node reads or the graph returns (naming it and its reader); a KV cache layer's attention among them; an input computed from a reader of an old output; a count of new values other than the old outputs'; a new value whose type differs from its old output's; and a constant a graph output would return.

## `replace_all_uses_with`

```python
replace_all_uses_withreplace_all_uses_with(self, old_value: clika_runtime._core.graph.Value, replacement: clika_runtime._core.graph.Value) -> None
```

replace_all_uses_with(self, old_value: clika_runtime._core.graph.Value, replacement: clika_runtime._core.graph.Value) -> None

replace_all_uses_with(old_value, replacement) -> None

Every read of `old_value` reads `replacement` instead: each operator input it feeds, and each graph output it returns (the output keeps its name). Nothing reads `old_value` afterwards, so its view is gone, unless it is a graph input's value. Raises InvalidArgumentError for `replacement` being `old_value`, a type mismatch, a replacement computed from one of `old_value`'s readers (a cycle), a state buffer's reads, a read an operator serves from a weight it holds itself, and a constant taking a graph output.

## `run`

```python
runrun(self, inputs: collections.abc.Sequence[clika_runtime._core.Tensor]) -> list[clika_runtime._core.Tensor]
```

run(self, inputs: collections.abc.Sequence[clika_runtime._core.Tensor]) -> list[clika_runtime._core.Tensor]
run(self, inputs: dict) -> list[clika_runtime._core.Tensor]

Overloaded function.

1. ``run(self, inputs: collections.abc.Sequence[clika_runtime._core.Tensor]) -> list[clika_runtime._core.Tensor]``

run(inputs) -> list[Tensor]

Run with positional inputs (input_names() order); returns the outputs in output_names() order.

2. ``run(self, inputs: dict) -> list[clika_runtime._core.Tensor]``

run(inputs) -> list[Tensor]

Run with named inputs (`dict[str, Tensor]`, strict); returns the outputs in output_names() order.

## `set_constant_input`

```python
set_constant_inputset_constant_input(self, node: clika_runtime._core.graph.Node, port: int, tensor: clika_runtime._core.Tensor) -> None
```

set_constant_input(self, node: clika_runtime._core.graph.Node, port: int, tensor: clika_runtime._core.Tensor) -> None

set_constant_input(node, port, tensor) -> None

`node`'s input `port` reads a new constant holding `tensor`'s bytes: the graph keeps a handle to them, no copy, and finalize() places them with the graph. The constant replaces the value the port reads, so the port already reads one, of the same type. Raises InvalidArgumentError for an Input node, a port nothing feeds, a state buffer's read, a port the operator serves from a weight it holds itself, a tensor with no data (a traced or a meta tensor), and a type mismatch with the value the port reads.

## `set_input`

```python
set_inputset_input(self, node: clika_runtime._core.graph.Node, port: int, value: clika_runtime._core.graph.Value) -> None
```

set_input(self, node: clika_runtime._core.graph.Node, port: int, value: clika_runtime._core.graph.Value) -> None

set_input(node, port, value) -> None

`node`'s input `port` reads `value`. The edit replaces the value the port reads, so the port already reads one: an input the operator was built without stays empty. Raises InvalidArgumentError for an Input node, a port nothing feeds, a state buffer's read, a port the operator serves from a weight it holds itself, a type mismatch with the value the port reads, and a value computed from `node`'s own outputs (a cycle).

## `shape_domains`

```python
shape_domainsshape_domains(self) -> list[clika_runtime._core.io.ShapeDomain]
```

shape_domains(self) -> list[clika_runtime._core.io.ShapeDomain]

shape_domains() -> list[ShapeDomain]

The shapes each input admits, input_names() order: a fixed dimension is Fixed, a dynamic one compiled with a shape spec is Range, any other dynamic one is Free.

## `shortest_path`

```python
shortest_pathshortest_path(self, src: clika_runtime._core.graph.Node, dst: clika_runtime._core.graph.Node, *, edge_cost: collections.abc.Callable | None = None, cutoff: int | None = None, avoid: collections.abc.Sequence[clika_runtime._core.graph.Node] = [], edge_filter: collections.abc.Callable | None = None) -> object
```

shortest_path(self, src: clika_runtime._core.graph.Node, dst: clika_runtime._core.graph.Node, *, edge_cost: collections.abc.Callable | None = None, cutoff: int | None = None, avoid: collections.abc.Sequence[clika_runtime._core.graph.Node] = [], edge_filter: collections.abc.Callable | None = None) -> object

shortest_path(src, dst, *, edge_cost=None, cutoff=None, avoid=(), edge_filter=None) -> list[Node] | WeightedPath

Without `edge_cost`, a path from `src` to `dst` with the fewest edges, as its nodes: [src] when the two are one node, [] when no path runs, one answer among equally short paths, the same on every call. With `edge_cost`, a callable taking an Edge and returning its cost, the cheapest path as a WeightedPath: the costs summed over the edges it takes (among parallel edges, the cheapest), ties to fewer edges, and under `cutoff` the cheapest path of at most that many edges. Each cost must be finite and at least 0, or InvalidArgumentError names the edge; the cost runs at most once per edge. Unlike networkx, which answers the length through a second function (shortest_path_length), the weighted answer carries its cost. `cutoff`, `avoid` and `edge_filter` bound the search as find_paths_to() takes them.

## `split_points`

```python
split_pointssplit_points(self) -> list
```

split_points(self) -> list

split_points() -> list[Node]

The nodes every path from the graph inputs to the graph outputs crosses, from the inputs to the outputs: where the graph splits into two stages at one node. [] when no node is on every such path.

## `to`

```python
toto(self, where: object) -> None
```

to(self, where: object) -> None

to(where) -> None

Move the graph to `where`: a Device, a device string, or a Stream so the graph computes on that stream. Before finalize() it records where finalize() places the graph and moves nothing, and `device` reports the new device at once. After finalize() it moves the finalized graph: an operator that changes device re-packs its weights there once, and no copy of a moved weight stays behind. A device that cannot serve is refused before anything moves; while a run, optimize(), finalize() or an edit is in flight on this graph it raises InvalidArgumentError, whose code_name is FAILED_PRECONDITION.

## `to_networkx`

```python
to_networkxto_networkx(self) -> object
```

to_networkx(self) -> object

to_networkx() -> networkx.MultiDiGraph

The graph as a networkx.MultiDiGraph: one node per Node, keyed by the Node view with its op_code and kind as node data, and one edge per Edge, keyed (src_port, dst_port), so the two reads of Mul(x, x) stay two edges. The networkx graph holds this graph alive through its Node keys. networkx is imported at the call; ImportError when it is not installed.

## `topological_generations`

```python
topological_generationstopological_generations(self) -> list
```

topological_generations(self) -> list

topological_generations() -> list[list[Node]]

layers() under networkx's name: the same generations.

## `topological_order`

```python
topological_ordertopological_order(self, order: clika_runtime._core.graph.TopologicalOrder = TopologicalOrder.Deterministic) -> list
```

topological_order(self, order: clika_runtime._core.graph.TopologicalOrder = TopologicalOrder.Deterministic) -> list

topological_order(order=TopologicalOrder.Deterministic) -> list[Node]

Every node in the topological order `order` names: each node once, after every node whose output it reads (see TopologicalOrder for how each kind breaks the ties).

## `validate_shapes`

```python
validate_shapesvalidate_shapes(self, shapes: collections.abc.Sequence[clika_runtime._core.io.InputShape]) -> None
```

validate_shapes(self, shapes: collections.abc.Sequence[clika_runtime._core.io.InputShape]) -> None
validate_shapes(self, shapes: collections.abc.Sequence[collections.abc.Sequence[int]]) -> None
validate_shapes(self, shapes: dict) -> None

Overloaded function.

1. ``validate_shapes(self, shapes: collections.abc.Sequence[clika_runtime._core.io.InputShape]) -> None``

validate_shapes(shapes) -> None

Check shapes against the domains: every input present once, every name an input, every rank the input's, every extent admitted. `shapes` is a list of InputShape, a list of dims in input_names() order, or a dict of name to dims. InvalidArgumentError names the input and the dimension that failed.

2. ``validate_shapes(self, shapes: collections.abc.Sequence[collections.abc.Sequence[int]]) -> None``

validate_shapes(shapes) -> None

The positional form: one dims sequence per input_names() entry, that order.

3. ``validate_shapes(self, shapes: dict) -> None``

validate_shapes(shapes) -> None

The named form: a dict of input name to dims.

## `visit`

```python
visitvisit(self, start: clika_runtime._core.graph.Node, visitor: collections.abc.Callable, *, direction: clika_runtime._core.graph.WalkDirection = WalkDirection.Forward, max_depth: int | None = None, max_nodes: int | None = None, stop_at: collections.abc.Callable | None = None, edge_filter: collections.abc.Callable | None = None, skip_shape_lane: bool = False) -> None
```

visit(self, start: clika_runtime._core.graph.Node, visitor: collections.abc.Callable, *, direction: clika_runtime._core.graph.WalkDirection = WalkDirection.Forward, max_depth: int | None = None, max_nodes: int | None = None, stop_at: collections.abc.Callable | None = None, edge_filter: collections.abc.Callable | None = None, skip_shape_lane: bool = False) -> None

visit(start, visitor, *, direction=WalkDirection.Forward, max_depth=None, max_nodes=None, stop_at=None, edge_filter=None, skip_shape_lane=False) -> None

The walk walk() takes, showing `visitor` each node it reaches, the start included. The visitor answers a VisitAction: Prune does not go past that node, and Stop ends the walk, which then returns. None reads as Continue; any other answer ends the walk and raises TypeError. A visitor or callable that raises ends the walk, and visit raises with its message.

## `visualize`

```python
visualizevisualize(self, path: str) -> None
```

visualize(self, path: str) -> None

visualize(path) -> None

Write a rendering of the graph's nodes and edges to `path` (the file suffix picks the format).

## `walk`

```python
walkwalk(self, start: clika_runtime._core.graph.Node, *, direction: clika_runtime._core.graph.WalkDirection = WalkDirection.Forward, max_depth: int | None = None, max_nodes: int | None = None, stop_at: collections.abc.Callable | None = None, edge_filter: collections.abc.Callable | None = None, skip_shape_lane: bool = False) -> list
```

walk(self, start: clika_runtime._core.graph.Node, *, direction: clika_runtime._core.graph.WalkDirection = WalkDirection.Forward, max_depth: int | None = None, max_nodes: int | None = None, stop_at: collections.abc.Callable | None = None, edge_filter: collections.abc.Callable | None = None, skip_shape_lane: bool = False) -> list

walk(start, *, direction=WalkDirection.Forward, max_depth=None, max_nodes=None, stop_at=None, edge_filter=None, skip_shape_lane=False) -> list[Node]

The nodes a walk from `start` reaches, in walk order: breadth-first, the start first, each node once at its fewest edges from the start, and a node's neighbors in out_edges() order (in_edges() when `direction` is Backward). `max_depth` is the most edges between the start and a node the walk reaches (0 reaches the start alone). `max_nodes` is the most nodes the walk may reach, the start included: a walk that would reach one more raises InvalidArgumentError naming the budget, never a cut answer. A node `stop_at` accepts is reached but not gone past (the start included), and the walk crosses only the edges `edge_filter` accepts. `skip_shape_lane` crosses no edge that carries shape or index arithmetic (the shape computations the graph optimizer marks; a graph that was not optimized carries no marks). A start of another graph raises InvalidArgumentError; a callable that raises ends the walk, and walk raises with its message.

## `weakly_connected_components`

```python
weakly_connected_componentsweakly_connected_components(self) -> list
```

weakly_connected_components(self) -> list

weakly_connected_components() -> list[list[Node]]

The groups of nodes joined by edges taken either way, each in name order, the groups ordered by their first node's name.

## `would_create_cycle`

```python
would_create_cyclewould_create_cycle(self, src: clika_runtime._core.graph.Node, dst: clika_runtime._core.graph.Node) -> bool
```

would_create_cycle(self, src: clika_runtime._core.graph.Node, dst: clika_runtime._core.graph.Node) -> bool

would_create_cycle(src, dst) -> bool

True when an edge from `src` to `dst` would close a cycle: `dst` reaches `src`, or the two are one node.
