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().expect("test graph builds successfully");
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().expect("test graph builds successfully");
369 assert_eq!(g.buffer_count(), 2);
370 assert_eq!(
371 g.buffer(buf0).expect("buf0 registered in graph").size_bytes,
372 1024
373 );
374 }
375
376 #[test]
377 fn builder_add_kernel_chain() {
378 let mut b = GraphBuilder::new();
379 let k0 = b.add_kernel("k0", 4, 256, 0).finish();
380 let k1 = b.add_kernel("k1", 4, 256, 0).finish();
381 let k2 = b.add_kernel("k2", 4, 256, 0).finish();
382 b.chain(&[k0, k1, k2]);
383 let g = b.build().expect("test graph builds successfully");
384 assert_eq!(g.node_count(), 3);
385 assert_eq!(g.edge_count(), 2);
386 assert!(g.is_reachable(k0, k2));
387 }
388
389 #[test]
390 fn builder_kernel_fusible_flag() {
391 let mut b = GraphBuilder::new();
392 let id = b.add_kernel("custom", 1, 32, 0).fusible(false).finish();
393 let g = b.build().expect("test graph builds successfully");
394 assert!(
395 !g.node(id)
396 .expect("node registered in graph")
397 .kind
398 .is_fusible()
399 );
400 }
401
402 #[test]
403 fn builder_add_memcpy() {
404 let mut b = GraphBuilder::new();
405 let up = b.add_memcpy("upload", MemcpyDir::HostToDevice, 4096);
406 let g = b.build().expect("test graph builds successfully");
407 assert_eq!(g.node_count(), 1);
408 let node = g.node(up).expect("memcpy node registered in graph");
409 assert!(node.kind.is_memory_op());
410 }
411
412 #[test]
413 fn builder_add_memset() {
414 let mut b = GraphBuilder::new();
415 let ms = b.add_memset("zero", 8192, 0x00);
416 let g = b.build().expect("test graph builds successfully");
417 let node = g.node(ms).expect("memset node registered in graph");
418 if let NodeKind::Memset { value, .. } = node.kind {
419 assert_eq!(value, 0x00);
420 } else {
421 panic!("expected Memset");
422 }
423 }
424
425 #[test]
426 fn builder_add_barrier_and_event() {
427 let mut b = GraphBuilder::new();
428 let barrier = b.add_barrier("sync");
429 let ev = b.add_event_record("ev0");
430 let ew = b.add_event_wait("ew0");
431 b.chain(&[barrier, ev, ew]);
432 let g = b.build().expect("test graph builds successfully");
433 assert_eq!(g.node_count(), 3);
434 }
435
436 #[test]
437 fn builder_fan_in_fan_out() {
438 let mut b = GraphBuilder::new();
439 let src = b.add_barrier("src");
440 let a = b.add_kernel("a", 1, 32, 0).finish();
441 let c = b.add_kernel("c", 1, 32, 0).finish();
442 let d = b.add_kernel("d", 1, 32, 0).finish();
443 let sink = b.add_barrier("sink");
444 b.fan_out(src, &[a, c, d]);
445 b.fan_in(&[a, c, d], sink);
446 let g = b.build().expect("test graph builds successfully");
447 assert_eq!(g.edge_count(), 6);
448 assert!(g.is_reachable(src, sink));
449 assert_eq!(g.sources(), vec![src]);
450 assert_eq!(g.sinks(), vec![sink]);
451 }
452
453 #[test]
454 fn builder_dep_returns_self_for_chaining() {
455 let mut b = GraphBuilder::new();
456 let a = b.add_barrier("a");
457 let b_node = b.add_barrier("b");
458 let c = b.add_barrier("c");
459 b.dep(a, b_node).dep(b_node, c);
460 let g = b.build().expect("test graph builds successfully");
461 assert_eq!(g.edge_count(), 2);
462 }
463
464 #[test]
465 fn builder_auto_infer_edges_from_buffers() {
466 let mut b = GraphBuilder::new();
467 let buf = b.alloc_buffer("tmp", 512);
468 let writer = b.add_barrier("writer");
469 let reader = b.add_barrier("reader");
470 b.set_outputs(writer, [buf]);
471 b.set_inputs(reader, [buf]);
472 let g = b.build().expect("test graph builds successfully"); assert!(g.is_reachable(writer, reader));
474 }
475
476 #[test]
477 fn builder_no_auto_infer_edges_disabled() {
478 let mut b = GraphBuilder::new().with_auto_infer_edges(false);
479 let buf = b.alloc_buffer("tmp", 512);
480 let writer = b.add_barrier("writer");
481 let reader = b.add_barrier("reader");
482 b.set_outputs(writer, [buf]);
483 b.set_inputs(reader, [buf]);
484 let g = b.build().expect("test graph builds successfully");
485 assert!(!g.is_reachable(writer, reader));
487 }
488
489 #[test]
490 fn builder_add_raw_node() {
491 let mut b = GraphBuilder::new();
492 let n = GraphNode::new(NodeId(0), NodeKind::Barrier)
493 .with_name("raw_barrier")
494 .with_cost(42);
495 let id = b.add_raw(n);
496 let g = b.build().expect("test graph builds successfully");
497 assert_eq!(g.node(id).expect("node registered in graph").cost_hint, 42);
498 }
499
500 #[test]
501 fn builder_add_host_callback() {
502 let mut b = GraphBuilder::new();
503 let cb = b.add_host_callback("sync_point");
504 let g = b.build().expect("test graph builds successfully");
505 if let NodeKind::HostCallback { label } =
506 &g.node(cb).expect("callback node registered in graph").kind
507 {
508 assert_eq!(label, "sync_point");
509 } else {
510 panic!("expected HostCallback");
511 }
512 }
513
514 #[test]
515 fn builder_set_inputs_and_outputs() {
516 let mut b = GraphBuilder::new();
517 let buf0 = b.alloc_buffer("a", 1);
518 let buf1 = b.alloc_buffer("b", 1);
519 let node = b.add_barrier("n");
520 b.set_inputs(node, [buf0]);
521 b.set_outputs(node, [buf1]);
522 let g = b.build().expect("test graph builds successfully");
523 let n = g.node(node).expect("barrier node registered in graph");
524 assert!(n.inputs.contains(&buf0));
525 assert!(n.outputs.contains(&buf1));
526 }
527
528 #[test]
529 fn builder_add_kernel_with_3d_grid() {
530 let mut b = GraphBuilder::new();
531 let id = b.add_kernel_3d("conv2d", (4, 4, 1), (8, 8, 1), 0);
532 let g = b.build().expect("test graph builds successfully");
533 if let NodeKind::KernelLaunch { config, .. } =
534 g.node(id).expect("kernel node registered in graph").kind
535 {
536 assert_eq!(config.grid, (4, 4, 1));
537 assert_eq!(config.block, (8, 8, 1));
538 } else {
539 panic!("expected KernelLaunch");
540 }
541 }
542
543 #[test]
544 fn builder_alloc_external_buffer() {
545 let mut b = GraphBuilder::new();
546 let id = b.alloc_external_buffer("model_weights", 65536);
547 let g = b.build().expect("test graph builds successfully");
548 assert!(
549 g.buffer(id)
550 .expect("external buffer registered in graph")
551 .external
552 );
553 }
554
555 #[test]
556 fn builder_kernel_with_buffers_and_cost() {
557 let mut b = GraphBuilder::new();
558 let inp = b.alloc_buffer("input", 4096);
559 let out = b.alloc_buffer("output", 4096);
560 let id = b
561 .add_kernel("scale", 8, 128, 0)
562 .inputs([inp])
563 .outputs([out])
564 .cost(100)
565 .finish();
566 let g = b.build().expect("test graph builds successfully");
567 let n = g.node(id).expect("node registered in graph");
568 assert_eq!(n.cost_hint, 100);
569 assert!(n.inputs.contains(&inp));
570 assert!(n.outputs.contains(&out));
571 }
572}