Skip to content

Expression Graph

hologram-graph is the directed acyclic graph the compiler consumes. A graph is a flat list of Nodes; each node’s op slot is a GraphOp, and its inputs reference earlier nodes, constants, or graph-input ports.

  • Graph — the append-only node list, shape registry, and constant store
  • Node{ op: GraphOp, inputs: SmallVec<[InputSource; 4]>, output_dtype: DTypeId, output_shape: ShapeId }
  • GraphOpInput, Output, Op(OpKind), or Constant(ConstantId)
  • OpKind — the closed op catalog (re-exported from hologram_ops): Relu, Sigmoid, MatMul, Gemm, Conv2d, LayerNorm, Softmax, Attention, Gather (runtime-indexed embedding lookup), Cast (numeric dtype conversion), and many more
  • InputSourceNode(NodeId), Constant(ConstantId), or GraphInput(u32)
  • NodeId / ConstantId — opaque handles
  • ConstantStore — inline constant tensors

From hologram_graph::registry:

  • DTypeId(u8) — dtype tag (e.g. DTypeId(8) = f32)
  • ShapeDescriptor — rank + dims; constructors like ShapeDescriptor::rank2(m, n)
  • ShapeId — handle into the interned ShapeRegistry

There is no GraphBuilder; graphs are built directly from Node values.

use hologram_graph::{Graph, Node, GraphOp, OpKind, InputSource};
use hologram_graph::registry::{DTypeId, ShapeDescriptor};
use smallvec::SmallVec;
let mut g = Graph::new();
let f32 = DTypeId(8);
let s = g.shape_registry_mut().intern(ShapeDescriptor::rank2(2, 3));
let x = g.add_node(Node {
op: GraphOp::Input,
inputs: SmallVec::new(),
output_dtype: f32,
output_shape: s,
});
g.add_input(x);
let y = g.add_node(Node {
op: GraphOp::Op(OpKind::Relu),
inputs: SmallVec::from_iter([InputSource::Node(x)]),
output_dtype: f32,
output_shape: s,
});
let out = g.add_node(Node {
op: GraphOp::Output,
inputs: SmallVec::from_iter([InputSource::Node(y)]),
output_dtype: f32,
output_shape: s,
});
g.add_output(out);

g.add_input(node) / g.add_output(node) register a port with an empty name. To give a port a semantic identity (so a multi-input model can be addressed by name rather than position), use g.add_named_input(node, "input_ids") / g.add_named_output(node, "logits") — the name and the node’s shape travel into the archive’s PortDescriptor and surface at runtime through InferenceSession::input_port_by_name / output_port_by_name.

Producer-defined metadata (tokenizer, generation config, class labels, calibration tables, provenance) is attached with g.add_extension(key, bytes); it flows through compile() into an open, repeatable Extension archive section and is read back at runtime via InferenceSession::extension(key) / extension_keys().

Shapes are interned through g.shape_registry_mut().intern(...), which returns a ShapeId referenced by Node::output_shape. Inline constants live in the constant store (g.constants_mut()) and are referenced via InputSource::Constant(ConstantId).

A built Graph is handed directly to hologram_compiler::Compiler (or the free compile function), which desugars composites, elides invariants, schedules, and emits a .holo archive. See the Compiler Pipeline reference.