Model¤
Connex separates a model into two pieces:
GraphSpecis the immutable graph description: the directed acyclic graph, ordered input nodes, ordered output nodes, topological order, and dropout configuration.NeuralDAGis the trainable Equinox module compiled from that specification and an operation pipeline.
This split is intentional. Graph structure is static metadata from JAX's point
of view, while trainable arrays live inside operation objects. The result works
with eqx.filter_jit, eqx.filter_value_and_grad, eqx.apply_updates, and
ordinary PyTree filtering.
Graph Specifications¤
GraphSpec accepts a networkx.DiGraph or anything accepted by
networkx.DiGraph(...), including adjacency dictionaries and edge lists.
import connex as cnx
import networkx as nx
graph = nx.DiGraph()
graph.add_edges_from(
[
("x0", "h0"),
("x1", "h0"),
("h0", "y"),
]
)
spec = cnx.GraphSpec(
graph,
inputs=["x0", "x1"],
outputs=["y"],
topo_sort=["x0", "x1", "h0", "y"],
dropout={"h0": 0.1},
)
The input and output orders are semantic. A forward call places x[i] at
spec.inputs[i], and returns outputs in spec.outputs order.
Connex validates the structure before parameters are initialized:
- the graph must be acyclic;
- input nodes must exist and have no incoming edges;
- output nodes must exist and have no outgoing edges;
- no node may be both an input and an output;
- an explicit
topo_sort, if supplied, must contain exactly the graph nodes and respect every edge; - dropout probabilities must be in
[0, 1].
Supplying topo_sort is optional. It is useful when you want deterministic
placement of isolated hidden nodes or when you already have a topological order
from another graph construction step.
Runtime Model¤
NeuralDAG compiles the graph into integer node ids, edge-position tables, and
topological batches. A topological batch is a group of target nodes whose
predecessor values are already available. During a forward pass, Connex gathers
only the input layouts requested by the active operations, runs the operation
pipeline for each batch, and writes the new values back into a dense value
buffer.
import jax
import jax.numpy as jnp
import jax.random as jr
model = cnx.NeuralDAG(
spec,
ops=cnx.ops.default_ops(activation=jax.nn.relu),
key=jr.key(0),
)
y = model(jnp.array([1.0, -1.0]))
If ops is omitted, Connex uses connex.ops.default_ops(): a hybrid affine
operation, hidden-node activation, dropout, and output transform. Custom
operation sequences use the same NeuralDAG runtime.
Batching¤
Use model.batched(x) when every item in a batch can share the same runtime
state. This is the native batched path and avoids mapping over the whole model.
x = jnp.ones((32, 2))
y = model.batched(x)
For independent stochastic state per example, split keys and map over both inputs and keys:
keys = jr.split(jr.key(1), x.shape[0])
y = jax.vmap(lambda x_i, key_i: model(x_i, key=key_i))(x, keys)
This distinction matters for dropout. model.batched(x, key=key) uses one
runtime dropout state for the batch; jax.vmap(...) with split keys gives each
example its own mask.
Execution Planning¤
The runtime squeezes structure out of the compiled topology before execution:
- ordinary graph regions run one compiled topological batch at a time;
- compatible one-input chain regions run as
jax.lax.scansegments; - scan segments scatter only live-out values, meaning graph outputs and nodes consumed outside the segment;
- operation input layouts are prepared only when some operation declares that it needs them;
- the hybrid affine backend chooses padded rows, sparse edge accumulation, or dense matmul per topological batch.
Custom operations, attention features, normalization, and adaptive activation use the generic batch path. That path is more general because Connex cannot assume the local semantics required to fuse them into scan segments.
Exporting Weights¤
to_networkx_weighted_digraph() returns a copy of the original graph with
learned edge weights attached when a built-in affine operation is present.
weighted = model.to_networkx_weighted_digraph()
weight = weighted["x0"]["h0"]["weight"]
This is useful for downstream NetworkX analysis, visualization, pruning experiments, or debugging topology edits.
Reference¤
connex.GraphSpec
dataclass
¤
Validated, immutable graph definition for a Connex model.
A GraphSpec is the non-trainable description of a model: the graph, the
ordered input and output nodes, the canonical topological order, and the
dropout configuration. NeuralDAG compiles this object into static topology
metadata and initializes trainable operation parameters from it.
graph may be a networkx.DiGraph or any object accepted by
networkx.DiGraph(graph), including adjacency dictionaries and edge lists.
Connex validates that the graph is a DAG, that input nodes have no incoming
edges, that output nodes have no outgoing edges, and that no node appears in
both inputs and outputs.
The order of inputs and outputs is semantically meaningful. Forward
calls read x[i] into inputs[i] and return output values in outputs
order.
__init__(graph: nx.DiGraph | Any, inputs: Sequence[Any], outputs: Sequence[Any], *, topo_sort: Sequence[Any] | None = None, dropout: DropoutLike = 0.0)
¤
Create a validated graph specification.
Arguments:
graph: A directed graph or graph-like object accepted bynetworkx.DiGraph. The resulting graph must be acyclic.inputs: Ordered input node labels. These nodes must exist in the graph and must not receive incoming edges.outputs: Ordered output node labels. These nodes must exist in the graph and must not have outgoing edges.topo_sort: Optional topological order. Supplying this avoids a NetworkX topological sort during construction and gives stable ordering for isolated hidden nodes. The sequence must contain exactly the graph nodes and must respect every edge.dropout: Either a scalar probability or a mapping from node label to probability. Scalar dropout applies to hidden nodes only. Mapping dropout defaults unspecified nodes to zero and can target inputs, hidden nodes, and outputs.
Raises:
ValueError: If the graph is cyclic, missing required nodes, has invalid input/output edge structure, has overlapping inputs/outputs, has an invalid topological order, or contains dropout probabilities outside[0, 1].TypeError: Ifdropoutis neither a scalar nor a mapping.
dropout_by_node() -> dict[Any, float]
¤
Return dropout probabilities keyed by graph node label.
Scalar dropout expands to every hidden node and leaves inputs and
outputs at probability zero. Mapping dropout is returned with missing
nodes filled in as zero. The output dictionary follows topo_sort
ordering.
with_graph(graph: nx.DiGraph, *, inputs: Sequence[Any] | None = None, outputs: Sequence[Any] | None = None, topo_sort: Sequence[Any] | None = None, dropout: DropoutLike | None = None) -> GraphSpec
¤
Return a new GraphSpec with replacement graph metadata.
Any argument left as None is copied from the current specification.
The returned object is fully revalidated, so this method is useful for
small immutable graph transformations outside the editor API.
with_dropout(dropout: DropoutLike) -> GraphSpec
¤
Return a new GraphSpec with the same graph and new dropout.
connex.NeuralDAG
¤
Bases: Module
A trainable neural network runtime compiled from a DAG specification.
NeuralDAG is the core Connex model type. It is an equinox.Module whose
trainable leaves live inside operation objects, while graph structure and
compiled topology metadata are static. This makes models compatible with
eqx.filter_jit, eqx.filter_value_and_grad, eqx.apply_updates, and the
usual JAX transformations.
Forward evaluation proceeds in compiled topological order. Connex groups
nodes into batches whose predecessors are already available, gathers the
data requested by each operation, and writes newly computed node values into
a value buffer. Compatible one-input chain regions are represented as
jax.lax.scan execution-plan segments; unsupported operation stacks and
custom operations use the generic topological batch path.
__init__(spec: GraphSpec, *, ops: Sequence[Any] | None = None, key: Array | None = None)
¤
Initialize a model from a graph specification.
Arguments:
spec: Validated graph, input/output ordering, topological order, and dropout configuration.ops: Operation pipeline. IfNone, Connex usesconnex.ops.default_ops(), which is a hybrid affine op, activation, dropout, and output transform.key: JAX random key used to initialize operation parameters. If omitted,jax.random.key(0)is used.
The operation sequence is initialized once against the compiled topology. Each operation may create trainable arrays, static metadata, or no state at all.
__call__(x: Array, *, key: Array | None = None) -> Array
¤
Evaluate one input example.
Arguments:
x: Array whose trailing dimension matcheslen(spec.inputs). Values are assigned to input nodes in specification order.key: Optional JAX random key used by runtime stochastic operations such as dropout. If any active dropout operation is present, a key is required.
Returns:
Output values in spec.outputs order after the output-stage operations
have been applied.
batched(x: Array, *, key: Array | None = None) -> Array
¤
Evaluate a batch of examples with one shared runtime state.
This is the native batched path and avoids compiling jax.vmap(model)
over the whole DAG. It is appropriate for deterministic evaluation or
when a shared dropout mask is desired. For independent stochastic state
per example, split keys and use jax.vmap(lambda x_i, key_i:
model(x_i, key=key_i))(x, keys).
Arguments:
x: Array of shape(batch, len(spec.inputs)).key: Optional runtime key shared by the whole batch.
Returns:
Array of shape (batch, len(spec.outputs)).
rebuild(spec: GraphSpec, *, key: Array | None = None, ops: Sequence[Any] | None = None) -> NeuralDAG
¤
Recompile a model against a new graph specification.
The new operation pipeline is initialized for spec, then each old
operation is asked to transfer compatible parameters into the initialized
replacement. Built-in affine operations preserve parameters for nodes and
edges that still exist by label.
Arguments:
spec: New graph specification.key: Random key for initializing newly created parameters.ops: Optional replacement operation sequence. If omitted, the current operations are reused.
Returns:
A new NeuralDAG; the original model is unchanged.
to_networkx_weighted_digraph() -> nx.DiGraph
¤
Export the model graph with learned edge weights.
The returned networkx.DiGraph copies self.spec.graph and annotates
each edge with a "weight" attribute when a built-in affine operation is
present. Sparse, matmul, padded, and hybrid affine layouts are all
mapped back to the original graph edge labels.