1use crate::{Op, Shape};
12
13use crate::provenance::NodeOrigin;
14
15#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
17#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
18pub struct NodeId(pub u32);
19
20impl std::fmt::Display for NodeId {
21 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
22 write!(f, "%{}", self.0)
23 }
24}
25
26#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
28#[derive(Debug, Clone)]
29pub struct Node {
30 pub id: NodeId,
31 pub op: Op,
33 pub inputs: Vec<NodeId>,
35 pub shape: Shape,
37 pub name: Option<String>,
39 pub origin: Option<NodeOrigin>,
41}
42
43#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
64#[derive(Clone, Debug)]
65pub struct Graph {
66 pub name: String,
67 nodes: Vec<Node>,
68 pub outputs: Vec<NodeId>,
70}
71
72impl PartialEq for Graph {
76 fn eq(&self, other: &Self) -> bool {
77 self.name == other.name
78 && self.nodes.len() == other.nodes.len()
79 && self.outputs == other.outputs
80 }
81}
82
83impl Graph {
84 pub fn new(name: impl Into<String>) -> Self {
85 Self {
86 name: name.into(),
87 nodes: Vec::new(),
88 outputs: Vec::new(),
89 }
90 }
91
92 pub fn len(&self) -> usize {
94 self.nodes.len()
95 }
96 pub fn is_empty(&self) -> bool {
97 self.nodes.is_empty()
98 }
99
100 pub fn node(&self, id: NodeId) -> &Node {
102 &self.nodes[id.0 as usize]
103 }
104
105 pub fn nodes(&self) -> &[Node] {
107 &self.nodes
108 }
109
110 pub fn shape(&self, id: NodeId) -> &Shape {
112 &self.nodes[id.0 as usize].shape
113 }
114
115 pub fn set_outputs(&mut self, outputs: Vec<NodeId>) {
117 self.outputs = outputs;
118 }
119
120 pub fn set_inputs(&mut self, id: NodeId, inputs: Vec<NodeId>) {
126 self.nodes[id.0 as usize].inputs = inputs;
127 }
128
129 pub fn node_mut(&mut self, id: NodeId) -> &mut Node {
130 &mut self.nodes[id.0 as usize]
131 }
132
133 pub fn nodes_mut(&mut self) -> &mut [Node] {
134 &mut self.nodes
135 }
136
137 pub fn append_node(
143 &mut self,
144 op: Op,
145 inputs: Vec<NodeId>,
146 shape: Shape,
147 name: Option<String>,
148 ) -> NodeId {
149 self.push(op, inputs, shape, name)
150 }
151
152 pub(crate) fn push(
153 &mut self,
154 op: Op,
155 inputs: Vec<NodeId>,
156 shape: Shape,
157 name: Option<String>,
158 ) -> NodeId {
159 self.push_ext(op, inputs, shape, name, None)
160 }
161
162 pub(crate) fn push_ext(
163 &mut self,
164 op: Op,
165 inputs: Vec<NodeId>,
166 shape: Shape,
167 name: Option<String>,
168 origin: Option<NodeOrigin>,
169 ) -> NodeId {
170 let id = NodeId(self.nodes.len() as u32);
171 self.nodes.push(Node {
172 id,
173 op,
174 inputs,
175 shape,
176 name,
177 origin,
178 });
179 id
180 }
181
182 pub fn users(&self, id: NodeId) -> Vec<NodeId> {
189 self.nodes
190 .iter()
191 .filter(|n| n.inputs.contains(&id))
192 .map(|n| n.id)
193 .collect()
194 }
195
196 pub fn use_count(&self, id: NodeId) -> usize {
198 self.nodes.iter().filter(|n| n.inputs.contains(&id)).count()
199 }
200
201 pub fn node_id_by_name(&self, name: &str) -> Option<NodeId> {
210 self.nodes.iter().find_map(|n| match &n.op {
211 Op::Input { name: nm } | Op::Param { name: nm } if nm == name => Some(n.id),
212 _ => None,
213 })
214 }
215
216 pub fn input_id(&self, name: &str) -> Option<NodeId> {
218 self.nodes.iter().find_map(|n| match &n.op {
219 Op::Input { name: nm } if nm == name => Some(n.id),
220 _ => None,
221 })
222 }
223
224 pub fn param_id(&self, name: &str) -> Option<NodeId> {
226 self.nodes.iter().find_map(|n| match &n.op {
227 Op::Param { name: nm } if nm == name => Some(n.id),
228 _ => None,
229 })
230 }
231
232 pub fn topo_order(&self) -> impl Iterator<Item = NodeId> + '_ {
234 (0..self.nodes.len()).map(|i| NodeId(i as u32))
235 }
236
237 pub fn reverse_topo(&self) -> impl Iterator<Item = NodeId> + '_ {
239 (0..self.nodes.len()).rev().map(|i| NodeId(i as u32))
240 }
241
242 pub fn define(
249 name: impl Into<String>,
250 build: impl FnOnce(&mut crate::hir::HirModule) -> crate::hir::HirNodeId,
251 ) -> crate::GraphModule {
252 crate::GraphModule::define(name, build)
253 }
254
255 pub fn hir(name: impl Into<String>) -> crate::GraphModule {
257 crate::GraphModule::hir(name)
258 }
259
260 pub fn module(self) -> crate::GraphModule {
262 crate::GraphModule::from_graph(self)
263 }
264
265 pub fn from_hir(hir: crate::hir::HirModule) -> Result<Self, crate::hir::LowerError> {
267 hir.lower_to_mir().map(|m| m.into_graph())
268 }
269
270 pub fn to_mir(self) -> crate::MirModule {
272 crate::MirModule::from_graph(self)
273 }
274
275 pub fn from_lir(lir: crate::LirModule) -> Self {
277 lir.into_graph()
278 }
279
280 pub fn inspect(&self) -> String {
282 crate::inspect_graph(self)
283 }
284
285 pub fn has_dynamic_dims(&self) -> bool {
287 crate::dynamic::has_dynamic_dims(self)
288 }
289
290 pub fn dynamic_symbols(&self) -> Vec<u32> {
292 crate::dynamic::collect_dynamic_symbols(self)
293 }
294
295 pub fn bind(&self, bindings: &crate::DimBinding) -> Self {
297 crate::dynamic::bind_graph(self, bindings)
298 }
299
300 pub fn inspect_module(module: &crate::GraphModule) -> String {
302 module.inspect()
303 }
304}
305
306impl std::fmt::Display for Graph {
308 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
309 writeln!(f, "graph @{} {{", self.name)?;
310 for node in &self.nodes {
311 write!(f, " {} = {}", node.id, node.op)?;
312 if !node.inputs.is_empty() {
313 write!(f, "(")?;
314 for (i, inp) in node.inputs.iter().enumerate() {
315 if i > 0 {
316 write!(f, ", ")?;
317 }
318 write!(f, "{inp}")?;
319 }
320 write!(f, ")")?;
321 }
322 writeln!(f, " : {}", node.shape)?;
323 }
324 if !self.outputs.is_empty() {
325 write!(f, " return ")?;
326 for (i, o) in self.outputs.iter().enumerate() {
327 if i > 0 {
328 write!(f, ", ")?;
329 }
330 write!(f, "{o}")?;
331 }
332 writeln!(f)?;
333 }
334 writeln!(f, "}}")
335 }
336}
337
338#[cfg(test)]
339mod tests {
340 use super::*;
341 use crate::{
342 DType,
343 op::{Activation, BinaryOp},
344 };
345
346 #[test]
347 fn build_simple_graph() {
348 let mut g = Graph::new("test");
349
350 let x = g.input("x", Shape::new(&[4, 15, 384], DType::F32));
351 let w = g.param("weight", Shape::new(&[384, 1536], DType::F32));
352 let b = g.param("bias", Shape::new(&[1536], DType::F32));
353
354 let mm = g.matmul(x, w, Shape::new(&[4, 15, 1536], DType::F32));
355 let add = g.binary(BinaryOp::Add, mm, b, Shape::new(&[4, 15, 1536], DType::F32));
356 let out = g.activation(
357 Activation::Gelu,
358 add,
359 Shape::new(&[4, 15, 1536], DType::F32),
360 );
361
362 g.set_outputs(vec![out]);
363
364 assert_eq!(g.len(), 6);
365 assert_eq!(g.use_count(mm), 1); assert_eq!(g.use_count(x), 1); let printed = format!("{g}");
369 assert!(printed.contains("matmul(%0, %1)"));
370 assert!(printed.contains("Gelu(%4)"));
371 assert!(printed.contains("return %5"));
372 }
373
374 #[test]
376 fn bert_layer_graph() {
377 let mut g = Graph::new("bert_layer");
378 let f = DType::F32;
379 let h = 384;
380 let int = 1536;
381
382 let x = g.input("hidden", Shape::new(&[4, 15, h], f));
384
385 let qkv_w = g.param("qkv.weight", Shape::new(&[h, 3 * h], f));
387 let qkv_b = g.param("qkv.bias", Shape::new(&[3 * h], f));
388 let qkv = g.matmul(x, qkv_w, Shape::new(&[4, 15, 3 * h], f));
389 let _qkv = g.binary(BinaryOp::Add, qkv, qkv_b, Shape::new(&[4, 15, 3 * h], f));
390
391 let int_w = g.param("ffn.weight", Shape::new(&[h, int], f));
395 let int_b = g.param("ffn.bias", Shape::new(&[int], f));
396 let ffn = g.matmul(x, int_w, Shape::new(&[4, 15, int], f));
397 let ffn = g.binary(BinaryOp::Add, ffn, int_b, Shape::new(&[4, 15, int], f));
398 let ffn = g.activation(Activation::Gelu, ffn, Shape::new(&[4, 15, int], f));
399
400 let out_w = g.param("ffn_out.weight", Shape::new(&[int, h], f));
401 let ffn_out = g.matmul(ffn, out_w, Shape::new(&[4, 15, h], f));
402
403 g.set_outputs(vec![ffn_out]);
404
405 assert!(g.len() > 10);
406 println!("{g}");
407 }
408}