1use std::collections::HashSet;
23
24use crate::error::{GraphError, GraphResult};
25use crate::executor::plan::{ExecutionPlan, PlanStep};
26use crate::node::StreamId;
27
28#[derive(Debug, Clone, Default)]
34pub struct ExecutionStats {
35 pub kernels_launched: usize,
37 pub bytes_copied: usize,
39 pub bytes_set: usize,
41 pub events_recorded: usize,
43 pub events_waited: usize,
45 pub host_callbacks: usize,
47 pub barriers: usize,
49 pub steps_on_stream0: usize,
51 pub steps_on_other_streams: usize,
53}
54
55impl ExecutionStats {
56 pub fn total_steps(&self) -> usize {
58 self.kernels_launched
59 + self.events_recorded
60 + self.events_waited
61 + self.host_callbacks
62 + self.barriers
63 + self.steps_on_stream0
66 + self.steps_on_other_streams
67 }
68}
69
70pub struct SequentialExecutor<'a> {
78 plan: &'a ExecutionPlan,
79}
80
81impl<'a> SequentialExecutor<'a> {
82 pub fn new(plan: &'a ExecutionPlan) -> Self {
84 Self { plan }
85 }
86
87 pub fn run(&self) -> GraphResult<ExecutionStats> {
96 let mut stats = ExecutionStats::default();
97 let mut fired_events: HashSet<usize> = HashSet::new();
98 let mut memcpy_bytes = 0usize;
99 let mut memset_bytes = 0usize;
100
101 for step in &self.plan.steps {
102 match step.stream() {
104 StreamId(0) => stats.steps_on_stream0 += 1,
105 _ => stats.steps_on_other_streams += 1,
106 }
107
108 match step {
109 PlanStep::KernelLaunch { .. } => {
110 stats.kernels_launched += 1;
111 }
112 PlanStep::Memcpy { size_bytes, .. } => {
113 memcpy_bytes += size_bytes;
114 }
115 PlanStep::Memset { size_bytes, .. } => {
116 memset_bytes += size_bytes;
117 }
118 PlanStep::EventRecord { event_id, .. } => {
119 fired_events.insert(*event_id);
120 stats.events_recorded += 1;
121 }
122 PlanStep::EventWait { event_id, stream } => {
123 if !fired_events.contains(event_id) {
124 return Err(GraphError::InvalidPlan(format!(
125 "EventWait for event {event_id} on stream {stream} but event was never recorded"
126 )));
127 }
128 stats.events_waited += 1;
129 }
130 PlanStep::HostCallback { .. } => {
131 stats.host_callbacks += 1;
132 }
133 PlanStep::Barrier { .. } => {
134 stats.barriers += 1;
135 }
136 }
137 }
138
139 stats.bytes_copied = memcpy_bytes;
140 stats.bytes_set = memset_bytes;
141
142 Ok(stats)
143 }
144
145 pub fn validate(&self) -> GraphResult<usize> {
153 let mut recorded: HashSet<usize> = HashSet::new();
154 let mut issues = 0usize;
155 for step in &self.plan.steps {
156 match step {
157 PlanStep::EventRecord { event_id, .. } if !recorded.insert(*event_id) => {
158 issues += 1;
160 }
161 PlanStep::EventRecord { .. } => {}
162 PlanStep::EventWait { event_id, stream } if !recorded.contains(event_id) => {
163 return Err(GraphError::InvalidPlan(format!(
164 "EventWait({event_id}) on stream {stream} has no prior EventRecord"
165 )));
166 }
167 PlanStep::EventWait { .. } => {}
168 _ => {}
169 }
170 }
171 Ok(issues)
172 }
173}
174
175#[cfg(test)]
180mod tests {
181 use super::*;
182 use crate::builder::GraphBuilder;
183 use crate::executor::plan::ExecutionPlan;
184 use crate::graph::ComputeGraph;
185 use crate::node::MemcpyDir;
186
187 fn simple_chain_graph() -> ComputeGraph {
188 let mut b = GraphBuilder::new().with_auto_infer_edges(false);
189 let up = b.add_memcpy("up", MemcpyDir::HostToDevice, 2048);
190 let k = b.add_kernel("k", 4, 256, 0).fusible(false).finish();
191 let dn = b.add_memcpy("dn", MemcpyDir::DeviceToHost, 2048);
192 b.chain(&[up, k, dn]);
193 b.build().expect("test graph builds successfully")
194 }
195
196 fn build_and_execute(graph: &ComputeGraph) -> GraphResult<ExecutionStats> {
197 let plan = ExecutionPlan::build(graph, 4)?;
198 SequentialExecutor::new(&plan).run()
199 }
200
201 #[test]
202 fn seq_simple_chain_runs() {
203 let g = simple_chain_graph();
204 let stats = build_and_execute(&g).expect("sequential execution of valid graph succeeds");
205 assert_eq!(stats.kernels_launched, 1);
206 assert_eq!(stats.bytes_copied, 2048 * 2);
207 }
208
209 #[test]
210 fn seq_memset_counted() {
211 let mut b = GraphBuilder::new().with_auto_infer_edges(false);
212 b.add_memset("zero", 8192, 0x00);
213 let g = b.build().expect("test graph builds successfully");
214 let stats = build_and_execute(&g).expect("sequential execution of valid graph succeeds");
215 assert_eq!(stats.bytes_set, 8192);
216 }
217
218 #[test]
219 fn seq_barrier_counted() {
220 let mut b = GraphBuilder::new().with_auto_infer_edges(false);
221 b.add_barrier("sync");
222 let g = b.build().expect("test graph builds successfully");
223 let stats = build_and_execute(&g).expect("sequential execution of valid graph succeeds");
224 assert_eq!(stats.barriers, 1);
225 }
226
227 #[test]
228 fn seq_host_callback_counted() {
229 let mut b = GraphBuilder::new().with_auto_infer_edges(false);
230 b.add_host_callback("checkpoint");
231 let g = b.build().expect("test graph builds successfully");
232 let stats = build_and_execute(&g).expect("sequential execution of valid graph succeeds");
233 assert_eq!(stats.host_callbacks, 1);
234 }
235
236 #[test]
237 fn seq_valid_event_record_wait() {
238 let steps = vec![
240 PlanStep::EventRecord {
241 event_id: 0,
242 stream: StreamId(0),
243 },
244 PlanStep::EventWait {
245 event_id: 0,
246 stream: StreamId(1),
247 },
248 ];
249 let plan = ExecutionPlan {
250 steps,
251 num_streams: 2,
252 pool_bytes: 0,
253 kernel_count_original: 0,
254 kernel_count_fused: 0,
255 event_count: 1,
256 };
257 let stats = SequentialExecutor::new(&plan)
258 .run()
259 .expect("sequential executor runs a valid plan");
260 assert_eq!(stats.events_recorded, 1);
261 assert_eq!(stats.events_waited, 1);
262 }
263
264 #[test]
265 fn seq_event_wait_without_record_fails() {
266 let steps = vec![PlanStep::EventWait {
267 event_id: 99,
268 stream: StreamId(0),
269 }];
270 let plan = ExecutionPlan {
271 steps,
272 num_streams: 1,
273 pool_bytes: 0,
274 kernel_count_original: 0,
275 kernel_count_fused: 0,
276 event_count: 1,
277 };
278 let result = SequentialExecutor::new(&plan).run();
279 assert!(matches!(result, Err(GraphError::InvalidPlan(_))));
280 }
281
282 #[test]
283 fn seq_validate_ok() {
284 let steps = vec![
285 PlanStep::EventRecord {
286 event_id: 0,
287 stream: StreamId(0),
288 },
289 PlanStep::EventWait {
290 event_id: 0,
291 stream: StreamId(1),
292 },
293 ];
294 let plan = ExecutionPlan {
295 steps,
296 num_streams: 2,
297 pool_bytes: 0,
298 kernel_count_original: 0,
299 kernel_count_fused: 0,
300 event_count: 1,
301 };
302 let issues = SequentialExecutor::new(&plan)
303 .validate()
304 .expect("plan validation succeeds on well-formed plan");
305 assert_eq!(issues, 0);
306 }
307
308 #[test]
309 fn seq_validate_missing_record_fails() {
310 let steps = vec![PlanStep::EventWait {
311 event_id: 5,
312 stream: StreamId(0),
313 }];
314 let plan = ExecutionPlan {
315 steps,
316 num_streams: 1,
317 pool_bytes: 0,
318 kernel_count_original: 0,
319 kernel_count_fused: 0,
320 event_count: 1,
321 };
322 assert!(matches!(
323 SequentialExecutor::new(&plan).validate(),
324 Err(GraphError::InvalidPlan(_))
325 ));
326 }
327
328 #[test]
329 fn seq_stream0_step_counted() {
330 let steps = vec![PlanStep::Barrier {
331 node: crate::node::NodeId(0),
332 stream: StreamId(0),
333 }];
334 let plan = ExecutionPlan {
335 steps,
336 num_streams: 1,
337 pool_bytes: 0,
338 kernel_count_original: 0,
339 kernel_count_fused: 0,
340 event_count: 0,
341 };
342 let stats = SequentialExecutor::new(&plan)
343 .run()
344 .expect("sequential executor runs a valid plan");
345 assert_eq!(stats.steps_on_stream0, 1);
346 assert_eq!(stats.steps_on_other_streams, 0);
347 }
348
349 #[test]
350 fn seq_other_stream_step_counted() {
351 let steps = vec![PlanStep::Barrier {
352 node: crate::node::NodeId(0),
353 stream: StreamId(2),
354 }];
355 let plan = ExecutionPlan {
356 steps,
357 num_streams: 3,
358 pool_bytes: 0,
359 kernel_count_original: 0,
360 kernel_count_fused: 0,
361 event_count: 0,
362 };
363 let stats = SequentialExecutor::new(&plan)
364 .run()
365 .expect("sequential executor runs a valid plan");
366 assert_eq!(stats.steps_on_other_streams, 1);
367 }
368
369 #[test]
370 fn seq_multiple_kernels_fused_counted() {
371 let mut b = GraphBuilder::new().with_auto_infer_edges(false);
373 let k0 = b.add_kernel("add", 4, 256, 0).fusible(true).finish();
374 let k1 = b.add_kernel("relu", 4, 256, 0).fusible(true).finish();
375 let k2 = b.add_kernel("scale", 4, 256, 0).fusible(true).finish();
376 b.chain(&[k0, k1, k2]);
377 let g = b.build().expect("test graph builds successfully");
378 let plan = ExecutionPlan::build(&g, 1).expect("execution plan builds from valid graph");
379 let stats = SequentialExecutor::new(&plan)
380 .run()
381 .expect("sequential executor runs a valid plan");
382 assert_eq!(stats.kernels_launched, 1);
384 }
385}