1use crate::error::GraphResult;
26use crate::graph::ComputeGraph;
27use crate::node::{
28 BufferDescriptor, BufferId, GraphNode, KernelConfig, MemcpyDir, NodeId, NodeKind, StreamId,
29};
30
31pub struct KernelNodeBuilder<'a> {
39 parent: &'a mut GraphBuilder,
40 name: String,
41 function_name: String,
42 config: KernelConfig,
43 fusible: bool,
44 inputs: Vec<BufferId>,
45 outputs: Vec<BufferId>,
46 stream: Option<StreamId>,
47 cost: u64,
48}
49
50impl<'a> KernelNodeBuilder<'a> {
51 #[must_use]
53 pub fn fusible(mut self, v: bool) -> Self {
54 self.fusible = v;
55 self
56 }
57
58 #[must_use]
60 pub fn inputs(mut self, ids: impl IntoIterator<Item = BufferId>) -> Self {
61 self.inputs.extend(ids);
62 self
63 }
64
65 #[must_use]
67 pub fn outputs(mut self, ids: impl IntoIterator<Item = BufferId>) -> Self {
68 self.outputs.extend(ids);
69 self
70 }
71
72 #[must_use]
74 pub fn on_stream(mut self, s: StreamId) -> Self {
75 self.stream = Some(s);
76 self
77 }
78
79 #[must_use]
81 pub fn cost(mut self, c: u64) -> Self {
82 self.cost = c;
83 self
84 }
85
86 pub fn finish(self) -> NodeId {
90 let kind = NodeKind::KernelLaunch {
91 function_name: self.function_name,
92 config: self.config,
93 fusible: self.fusible,
94 };
95 let mut node = GraphNode::new(NodeId(0), kind)
96 .with_inputs(self.inputs)
97 .with_outputs(self.outputs)
98 .with_name(self.name)
99 .with_cost(self.cost);
100 if let Some(s) = self.stream {
101 node = node.with_stream(s);
102 }
103 self.parent.graph.add_node(node)
104 }
105}
106
107#[derive(Debug, Default)]
117pub struct GraphBuilder {
118 graph: ComputeGraph,
119 auto_infer_edges: bool,
121}
122
123impl GraphBuilder {
124 #[must_use]
126 pub fn new() -> Self {
127 Self {
128 graph: ComputeGraph::new(),
129 auto_infer_edges: true,
130 }
131 }
132
133 #[must_use]
137 pub fn with_auto_infer_edges(mut self, v: bool) -> Self {
138 self.auto_infer_edges = v;
139 self
140 }
141
142 pub fn alloc_buffer(&mut self, name: &str, size_bytes: usize) -> BufferId {
148 self.graph
149 .add_buffer(BufferDescriptor::new(BufferId(0), size_bytes).with_name(name))
150 }
151
152 pub fn alloc_external_buffer(&mut self, name: &str, size_bytes: usize) -> BufferId {
154 self.graph.add_buffer(
155 BufferDescriptor::new(BufferId(0), size_bytes)
156 .with_name(name)
157 .external(),
158 )
159 }
160
161 pub fn add_kernel(
169 &mut self,
170 name: &str,
171 num_blocks: u32,
172 threads_per_block: u32,
173 shared_mem: u32,
174 ) -> KernelNodeBuilder<'_> {
175 KernelNodeBuilder {
176 parent: self,
177 name: name.to_owned(),
178 function_name: name.to_owned(),
179 config: KernelConfig::linear(num_blocks, threads_per_block, shared_mem),
180 fusible: true,
181 inputs: Vec::new(),
182 outputs: Vec::new(),
183 stream: None,
184 cost: 1,
185 }
186 }
187
188 pub fn add_kernel_3d(
190 &mut self,
191 name: &str,
192 grid: (u32, u32, u32),
193 block: (u32, u32, u32),
194 shared_mem: u32,
195 ) -> NodeId {
196 let kind = NodeKind::KernelLaunch {
197 function_name: name.to_owned(),
198 config: KernelConfig {
199 grid,
200 block,
201 shared_mem_bytes: shared_mem,
202 },
203 fusible: false,
204 };
205 self.graph
206 .add_node(GraphNode::new(NodeId(0), kind).with_name(name))
207 }
208
209 pub fn add_memcpy(&mut self, name: &str, dir: MemcpyDir, size_bytes: usize) -> NodeId {
211 let kind = NodeKind::Memcpy { dir, size_bytes };
212 self.graph
213 .add_node(GraphNode::new(NodeId(0), kind).with_name(name))
214 }
215
216 pub fn add_memset(&mut self, name: &str, size_bytes: usize, value: u8) -> NodeId {
218 let kind = NodeKind::Memset { size_bytes, value };
219 self.graph
220 .add_node(GraphNode::new(NodeId(0), kind).with_name(name))
221 }
222
223 pub fn add_event_record(&mut self, name: &str) -> NodeId {
225 self.graph
226 .add_node(GraphNode::new(NodeId(0), NodeKind::EventRecord).with_name(name))
227 }
228
229 pub fn add_event_wait(&mut self, name: &str) -> NodeId {
231 self.graph
232 .add_node(GraphNode::new(NodeId(0), NodeKind::EventWait).with_name(name))
233 }
234
235 pub fn add_barrier(&mut self, name: &str) -> NodeId {
237 self.graph
238 .add_node(GraphNode::new(NodeId(0), NodeKind::Barrier).with_name(name))
239 }
240
241 pub fn add_host_callback(&mut self, name: &str) -> NodeId {
243 let kind = NodeKind::HostCallback {
244 label: name.to_owned(),
245 };
246 self.graph
247 .add_node(GraphNode::new(NodeId(0), kind).with_name(name))
248 }
249
250 pub fn add_raw(&mut self, node: GraphNode) -> NodeId {
252 self.graph.add_node(node)
253 }
254
255 pub fn set_inputs(
263 &mut self,
264 node: NodeId,
265 inputs: impl IntoIterator<Item = BufferId>,
266 ) -> &mut Self {
267 if let Ok(n) = self.graph.node_mut(node) {
268 n.inputs.extend(inputs);
269 }
270 self
271 }
272
273 pub fn set_outputs(
275 &mut self,
276 node: NodeId,
277 outputs: impl IntoIterator<Item = BufferId>,
278 ) -> &mut Self {
279 if let Ok(n) = self.graph.node_mut(node) {
280 n.outputs.extend(outputs);
281 }
282 self
283 }
284
285 pub fn dep(&mut self, from: NodeId, to: NodeId) -> &mut Self {
294 let _ = self.graph.add_edge(from, to);
296 self
297 }
298
299 pub fn chain(&mut self, nodes: &[NodeId]) -> &mut Self {
301 for w in nodes.windows(2) {
302 self.dep(w[0], w[1]);
303 }
304 self
305 }
306
307 pub fn fan_in(&mut self, predecessors: &[NodeId], join: NodeId) -> &mut Self {
309 for &p in predecessors {
310 self.dep(p, join);
311 }
312 self
313 }
314
315 pub fn fan_out(&mut self, fork: NodeId, successors: &[NodeId]) -> &mut Self {
317 for &s in successors {
318 self.dep(fork, s);
319 }
320 self
321 }
322
323 pub fn build(mut self) -> GraphResult<ComputeGraph> {
338 if self.auto_infer_edges {
339 self.graph.infer_data_edges()?;
340 }
341 Ok(self.graph)
342 }
343}
344
345#[cfg(test)]
350mod tests {
351 use super::*;
352 use crate::node::MemcpyDir;
353
354 #[test]
355 fn builder_empty_build() {
356 let b = GraphBuilder::new();
357 let g = b.build().unwrap();
358 assert!(g.is_empty());
359 }
360
361 #[test]
362 fn builder_alloc_buffer() {
363 let mut b = GraphBuilder::new();
364 let buf0 = b.alloc_buffer("input", 1024);
365 let buf1 = b.alloc_buffer("output", 2048);
366 assert_eq!(buf0, BufferId(0));
367 assert_eq!(buf1, BufferId(1));
368 let g = b.build().unwrap();
369 assert_eq!(g.buffer_count(), 2);
370 assert_eq!(g.buffer(buf0).unwrap().size_bytes, 1024);
371 }
372
373 #[test]
374 fn builder_add_kernel_chain() {
375 let mut b = GraphBuilder::new();
376 let k0 = b.add_kernel("k0", 4, 256, 0).finish();
377 let k1 = b.add_kernel("k1", 4, 256, 0).finish();
378 let k2 = b.add_kernel("k2", 4, 256, 0).finish();
379 b.chain(&[k0, k1, k2]);
380 let g = b.build().unwrap();
381 assert_eq!(g.node_count(), 3);
382 assert_eq!(g.edge_count(), 2);
383 assert!(g.is_reachable(k0, k2));
384 }
385
386 #[test]
387 fn builder_kernel_fusible_flag() {
388 let mut b = GraphBuilder::new();
389 let id = b.add_kernel("custom", 1, 32, 0).fusible(false).finish();
390 let g = b.build().unwrap();
391 assert!(!g.node(id).unwrap().kind.is_fusible());
392 }
393
394 #[test]
395 fn builder_add_memcpy() {
396 let mut b = GraphBuilder::new();
397 let up = b.add_memcpy("upload", MemcpyDir::HostToDevice, 4096);
398 let g = b.build().unwrap();
399 assert_eq!(g.node_count(), 1);
400 let node = g.node(up).unwrap();
401 assert!(node.kind.is_memory_op());
402 }
403
404 #[test]
405 fn builder_add_memset() {
406 let mut b = GraphBuilder::new();
407 let ms = b.add_memset("zero", 8192, 0x00);
408 let g = b.build().unwrap();
409 let node = g.node(ms).unwrap();
410 if let NodeKind::Memset { value, .. } = node.kind {
411 assert_eq!(value, 0x00);
412 } else {
413 panic!("expected Memset");
414 }
415 }
416
417 #[test]
418 fn builder_add_barrier_and_event() {
419 let mut b = GraphBuilder::new();
420 let barrier = b.add_barrier("sync");
421 let ev = b.add_event_record("ev0");
422 let ew = b.add_event_wait("ew0");
423 b.chain(&[barrier, ev, ew]);
424 let g = b.build().unwrap();
425 assert_eq!(g.node_count(), 3);
426 }
427
428 #[test]
429 fn builder_fan_in_fan_out() {
430 let mut b = GraphBuilder::new();
431 let src = b.add_barrier("src");
432 let a = b.add_kernel("a", 1, 32, 0).finish();
433 let c = b.add_kernel("c", 1, 32, 0).finish();
434 let d = b.add_kernel("d", 1, 32, 0).finish();
435 let sink = b.add_barrier("sink");
436 b.fan_out(src, &[a, c, d]);
437 b.fan_in(&[a, c, d], sink);
438 let g = b.build().unwrap();
439 assert_eq!(g.edge_count(), 6);
440 assert!(g.is_reachable(src, sink));
441 assert_eq!(g.sources(), vec![src]);
442 assert_eq!(g.sinks(), vec![sink]);
443 }
444
445 #[test]
446 fn builder_dep_returns_self_for_chaining() {
447 let mut b = GraphBuilder::new();
448 let a = b.add_barrier("a");
449 let b_node = b.add_barrier("b");
450 let c = b.add_barrier("c");
451 b.dep(a, b_node).dep(b_node, c);
452 let g = b.build().unwrap();
453 assert_eq!(g.edge_count(), 2);
454 }
455
456 #[test]
457 fn builder_auto_infer_edges_from_buffers() {
458 let mut b = GraphBuilder::new();
459 let buf = b.alloc_buffer("tmp", 512);
460 let writer = b.add_barrier("writer");
461 let reader = b.add_barrier("reader");
462 b.set_outputs(writer, [buf]);
463 b.set_inputs(reader, [buf]);
464 let g = b.build().unwrap(); assert!(g.is_reachable(writer, reader));
466 }
467
468 #[test]
469 fn builder_no_auto_infer_edges_disabled() {
470 let mut b = GraphBuilder::new().with_auto_infer_edges(false);
471 let buf = b.alloc_buffer("tmp", 512);
472 let writer = b.add_barrier("writer");
473 let reader = b.add_barrier("reader");
474 b.set_outputs(writer, [buf]);
475 b.set_inputs(reader, [buf]);
476 let g = b.build().unwrap();
477 assert!(!g.is_reachable(writer, reader));
479 }
480
481 #[test]
482 fn builder_add_raw_node() {
483 let mut b = GraphBuilder::new();
484 let n = GraphNode::new(NodeId(0), NodeKind::Barrier)
485 .with_name("raw_barrier")
486 .with_cost(42);
487 let id = b.add_raw(n);
488 let g = b.build().unwrap();
489 assert_eq!(g.node(id).unwrap().cost_hint, 42);
490 }
491
492 #[test]
493 fn builder_add_host_callback() {
494 let mut b = GraphBuilder::new();
495 let cb = b.add_host_callback("sync_point");
496 let g = b.build().unwrap();
497 if let NodeKind::HostCallback { label } = &g.node(cb).unwrap().kind {
498 assert_eq!(label, "sync_point");
499 } else {
500 panic!("expected HostCallback");
501 }
502 }
503
504 #[test]
505 fn builder_set_inputs_and_outputs() {
506 let mut b = GraphBuilder::new();
507 let buf0 = b.alloc_buffer("a", 1);
508 let buf1 = b.alloc_buffer("b", 1);
509 let node = b.add_barrier("n");
510 b.set_inputs(node, [buf0]);
511 b.set_outputs(node, [buf1]);
512 let g = b.build().unwrap();
513 let n = g.node(node).unwrap();
514 assert!(n.inputs.contains(&buf0));
515 assert!(n.outputs.contains(&buf1));
516 }
517
518 #[test]
519 fn builder_add_kernel_with_3d_grid() {
520 let mut b = GraphBuilder::new();
521 let id = b.add_kernel_3d("conv2d", (4, 4, 1), (8, 8, 1), 0);
522 let g = b.build().unwrap();
523 if let NodeKind::KernelLaunch { config, .. } = g.node(id).unwrap().kind {
524 assert_eq!(config.grid, (4, 4, 1));
525 assert_eq!(config.block, (8, 8, 1));
526 } else {
527 panic!("expected KernelLaunch");
528 }
529 }
530
531 #[test]
532 fn builder_alloc_external_buffer() {
533 let mut b = GraphBuilder::new();
534 let id = b.alloc_external_buffer("model_weights", 65536);
535 let g = b.build().unwrap();
536 assert!(g.buffer(id).unwrap().external);
537 }
538
539 #[test]
540 fn builder_kernel_with_buffers_and_cost() {
541 let mut b = GraphBuilder::new();
542 let inp = b.alloc_buffer("input", 4096);
543 let out = b.alloc_buffer("output", 4096);
544 let id = b
545 .add_kernel("scale", 8, 128, 0)
546 .inputs([inp])
547 .outputs([out])
548 .cost(100)
549 .finish();
550 let g = b.build().unwrap();
551 let n = g.node(id).unwrap();
552 assert_eq!(n.cost_hint, 100);
553 assert!(n.inputs.contains(&inp));
554 assert!(n.outputs.contains(&out));
555 }
556}