1use crate::{Op, Shape};
23
24use crate::provenance::NodeOrigin;
25
26#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
28#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
29pub struct NodeId(pub u32);
30
31impl std::fmt::Display for NodeId {
32 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
33 write!(f, "%{}", self.0)
34 }
35}
36
37#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
39#[derive(Debug, Clone)]
40pub struct Node {
41 pub id: NodeId,
42 pub op: Op,
44 pub inputs: Vec<NodeId>,
46 pub shape: Shape,
48 pub name: Option<String>,
50 pub origin: Option<NodeOrigin>,
52}
53
54#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
75#[derive(Clone, Debug)]
76pub struct Graph {
77 pub name: String,
78 nodes: Vec<Node>,
79 pub outputs: Vec<NodeId>,
81}
82
83impl PartialEq for Graph {
87 fn eq(&self, other: &Self) -> bool {
88 self.name == other.name
89 && self.nodes.len() == other.nodes.len()
90 && self.outputs == other.outputs
91 }
92}
93
94impl Graph {
95 pub fn new(name: impl Into<String>) -> Self {
96 Self {
97 name: name.into(),
98 nodes: Vec::new(),
99 outputs: Vec::new(),
100 }
101 }
102
103 pub fn len(&self) -> usize {
105 self.nodes.len()
106 }
107 pub fn is_empty(&self) -> bool {
108 self.nodes.is_empty()
109 }
110
111 pub fn node(&self, id: NodeId) -> &Node {
113 &self.nodes[id.0 as usize]
114 }
115
116 pub fn nodes(&self) -> &[Node] {
118 &self.nodes
119 }
120
121 pub fn shape(&self, id: NodeId) -> &Shape {
123 &self.nodes[id.0 as usize].shape
124 }
125
126 pub fn set_outputs(&mut self, outputs: Vec<NodeId>) {
128 self.outputs = outputs;
129 }
130
131 pub fn set_inputs(&mut self, id: NodeId, inputs: Vec<NodeId>) {
137 self.nodes[id.0 as usize].inputs = inputs;
138 }
139
140 pub fn node_mut(&mut self, id: NodeId) -> &mut Node {
141 &mut self.nodes[id.0 as usize]
142 }
143
144 pub fn nodes_mut(&mut self) -> &mut [Node] {
145 &mut self.nodes
146 }
147
148 pub fn append_node(
154 &mut self,
155 op: Op,
156 inputs: Vec<NodeId>,
157 shape: Shape,
158 name: Option<String>,
159 ) -> NodeId {
160 self.push(op, inputs, shape, name)
161 }
162
163 pub(crate) fn push(
164 &mut self,
165 op: Op,
166 inputs: Vec<NodeId>,
167 shape: Shape,
168 name: Option<String>,
169 ) -> NodeId {
170 self.push_ext(op, inputs, shape, name, None)
171 }
172
173 pub(crate) fn push_ext(
174 &mut self,
175 op: Op,
176 inputs: Vec<NodeId>,
177 shape: Shape,
178 name: Option<String>,
179 origin: Option<NodeOrigin>,
180 ) -> NodeId {
181 let id = NodeId(self.nodes.len() as u32);
182 self.nodes.push(Node {
183 id,
184 op,
185 inputs,
186 shape,
187 name,
188 origin,
189 });
190 id
191 }
192
193 pub fn users(&self, id: NodeId) -> Vec<NodeId> {
200 self.nodes
201 .iter()
202 .filter(|n| n.inputs.contains(&id))
203 .map(|n| n.id)
204 .collect()
205 }
206
207 pub fn use_count(&self, id: NodeId) -> usize {
209 self.nodes.iter().filter(|n| n.inputs.contains(&id)).count()
210 }
211
212 pub fn node_id_by_name(&self, name: &str) -> Option<NodeId> {
221 self.nodes.iter().find_map(|n| match &n.op {
222 Op::Input { name: nm } | Op::Param { name: nm } if nm == name => Some(n.id),
223 _ => None,
224 })
225 }
226
227 pub fn input_id(&self, name: &str) -> Option<NodeId> {
229 self.nodes.iter().find_map(|n| match &n.op {
230 Op::Input { name: nm } if nm == name => Some(n.id),
231 _ => None,
232 })
233 }
234
235 pub fn param_id(&self, name: &str) -> Option<NodeId> {
237 self.nodes.iter().find_map(|n| match &n.op {
238 Op::Param { name: nm } if nm == name => Some(n.id),
239 _ => None,
240 })
241 }
242
243 pub fn topo_order(&self) -> impl Iterator<Item = NodeId> + '_ {
245 (0..self.nodes.len()).map(|i| NodeId(i as u32))
246 }
247
248 pub fn reverse_topo(&self) -> impl Iterator<Item = NodeId> + '_ {
250 (0..self.nodes.len()).rev().map(|i| NodeId(i as u32))
251 }
252
253 pub fn define(
260 name: impl Into<String>,
261 build: impl FnOnce(&mut crate::hir::HirModule) -> crate::hir::HirNodeId,
262 ) -> crate::GraphModule {
263 crate::GraphModule::define(name, build)
264 }
265
266 pub fn hir(name: impl Into<String>) -> crate::GraphModule {
268 crate::GraphModule::hir(name)
269 }
270
271 pub fn module(self) -> crate::GraphModule {
273 crate::GraphModule::from_graph(self)
274 }
275
276 pub fn from_hir(hir: crate::hir::HirModule) -> Result<Self, crate::hir::LowerError> {
278 hir.lower_to_mir().map(|m| m.into_graph())
279 }
280
281 pub fn to_mir(self) -> crate::MirModule {
283 crate::MirModule::from_graph(self)
284 }
285
286 pub fn from_lir(lir: crate::LirModule) -> Self {
288 lir.into_graph()
289 }
290
291 pub fn inspect(&self) -> String {
293 crate::inspect_graph(self)
294 }
295
296 pub fn has_dynamic_dims(&self) -> bool {
298 crate::dynamic::has_dynamic_dims(self)
299 }
300
301 pub fn dynamic_symbols(&self) -> Vec<u32> {
303 crate::dynamic::collect_dynamic_symbols(self)
304 }
305
306 pub fn bind(&self, bindings: &crate::DimBinding) -> Self {
308 crate::dynamic::bind_graph(self, bindings)
309 }
310
311 pub fn inspect_module(module: &crate::GraphModule) -> String {
313 module.inspect()
314 }
315}
316
317impl std::fmt::Display for Graph {
319 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
320 writeln!(f, "graph @{} {{", self.name)?;
321 for node in &self.nodes {
322 write!(f, " {} = {}", node.id, node.op)?;
323 if !node.inputs.is_empty() {
324 write!(f, "(")?;
325 for (i, inp) in node.inputs.iter().enumerate() {
326 if i > 0 {
327 write!(f, ", ")?;
328 }
329 write!(f, "{inp}")?;
330 }
331 write!(f, ")")?;
332 }
333 writeln!(f, " : {}", node.shape)?;
334 }
335 if !self.outputs.is_empty() {
336 write!(f, " return ")?;
337 for (i, o) in self.outputs.iter().enumerate() {
338 if i > 0 {
339 write!(f, ", ")?;
340 }
341 write!(f, "{o}")?;
342 }
343 writeln!(f)?;
344 }
345 writeln!(f, "}}")
346 }
347}
348
349#[cfg(test)]
350mod tests {
351 use super::*;
352 use crate::{
353 DType,
354 op::{Activation, BinaryOp},
355 };
356
357 #[test]
358 fn build_simple_graph() {
359 let mut g = Graph::new("test");
360
361 let x = g.input("x", Shape::new(&[4, 15, 384], DType::F32));
362 let w = g.param("weight", Shape::new(&[384, 1536], DType::F32));
363 let b = g.param("bias", Shape::new(&[1536], DType::F32));
364
365 let mm = g.matmul(x, w, Shape::new(&[4, 15, 1536], DType::F32));
366 let add = g.binary(BinaryOp::Add, mm, b, Shape::new(&[4, 15, 1536], DType::F32));
367 let out = g.activation(
368 Activation::Gelu,
369 add,
370 Shape::new(&[4, 15, 1536], DType::F32),
371 );
372
373 g.set_outputs(vec![out]);
374
375 assert_eq!(g.len(), 6);
376 assert_eq!(g.use_count(mm), 1); assert_eq!(g.use_count(x), 1); let printed = format!("{g}");
380 assert!(printed.contains("matmul(%0, %1)"));
381 assert!(printed.contains("Gelu(%4)"));
382 assert!(printed.contains("return %5"));
383 }
384
385 #[test]
387 fn bert_layer_graph() {
388 let mut g = Graph::new("bert_layer");
389 let f = DType::F32;
390 let h = 384;
391 let int = 1536;
392
393 let x = g.input("hidden", Shape::new(&[4, 15, h], f));
395
396 let qkv_w = g.param("qkv.weight", Shape::new(&[h, 3 * h], f));
398 let qkv_b = g.param("qkv.bias", Shape::new(&[3 * h], f));
399 let qkv = g.matmul(x, qkv_w, Shape::new(&[4, 15, 3 * h], f));
400 let _qkv = g.binary(BinaryOp::Add, qkv, qkv_b, Shape::new(&[4, 15, 3 * h], f));
401
402 let int_w = g.param("ffn.weight", Shape::new(&[h, int], f));
406 let int_b = g.param("ffn.bias", Shape::new(&[int], f));
407 let ffn = g.matmul(x, int_w, Shape::new(&[4, 15, int], f));
408 let ffn = g.binary(BinaryOp::Add, ffn, int_b, Shape::new(&[4, 15, int], f));
409 let ffn = g.activation(Activation::Gelu, ffn, Shape::new(&[4, 15, int], f));
410
411 let out_w = g.param("ffn_out.weight", Shape::new(&[int, h], f));
412 let ffn_out = g.matmul(ffn, out_w, Shape::new(&[4, 15, h], f));
413
414 g.set_outputs(vec![ffn_out]);
415
416 assert!(g.len() > 10);
417 println!("{g}");
418 }
419}