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().unwrap()
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).unwrap();
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().unwrap();
214 let stats = build_and_execute(&g).unwrap();
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().unwrap();
223 let stats = build_and_execute(&g).unwrap();
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().unwrap();
232 let stats = build_and_execute(&g).unwrap();
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).run().unwrap();
258 assert_eq!(stats.events_recorded, 1);
259 assert_eq!(stats.events_waited, 1);
260 }
261
262 #[test]
263 fn seq_event_wait_without_record_fails() {
264 let steps = vec![PlanStep::EventWait {
265 event_id: 99,
266 stream: StreamId(0),
267 }];
268 let plan = ExecutionPlan {
269 steps,
270 num_streams: 1,
271 pool_bytes: 0,
272 kernel_count_original: 0,
273 kernel_count_fused: 0,
274 event_count: 1,
275 };
276 let result = SequentialExecutor::new(&plan).run();
277 assert!(matches!(result, Err(GraphError::InvalidPlan(_))));
278 }
279
280 #[test]
281 fn seq_validate_ok() {
282 let steps = vec![
283 PlanStep::EventRecord {
284 event_id: 0,
285 stream: StreamId(0),
286 },
287 PlanStep::EventWait {
288 event_id: 0,
289 stream: StreamId(1),
290 },
291 ];
292 let plan = ExecutionPlan {
293 steps,
294 num_streams: 2,
295 pool_bytes: 0,
296 kernel_count_original: 0,
297 kernel_count_fused: 0,
298 event_count: 1,
299 };
300 let issues = SequentialExecutor::new(&plan).validate().unwrap();
301 assert_eq!(issues, 0);
302 }
303
304 #[test]
305 fn seq_validate_missing_record_fails() {
306 let steps = vec![PlanStep::EventWait {
307 event_id: 5,
308 stream: StreamId(0),
309 }];
310 let plan = ExecutionPlan {
311 steps,
312 num_streams: 1,
313 pool_bytes: 0,
314 kernel_count_original: 0,
315 kernel_count_fused: 0,
316 event_count: 1,
317 };
318 assert!(matches!(
319 SequentialExecutor::new(&plan).validate(),
320 Err(GraphError::InvalidPlan(_))
321 ));
322 }
323
324 #[test]
325 fn seq_stream0_step_counted() {
326 let steps = vec![PlanStep::Barrier {
327 node: crate::node::NodeId(0),
328 stream: StreamId(0),
329 }];
330 let plan = ExecutionPlan {
331 steps,
332 num_streams: 1,
333 pool_bytes: 0,
334 kernel_count_original: 0,
335 kernel_count_fused: 0,
336 event_count: 0,
337 };
338 let stats = SequentialExecutor::new(&plan).run().unwrap();
339 assert_eq!(stats.steps_on_stream0, 1);
340 assert_eq!(stats.steps_on_other_streams, 0);
341 }
342
343 #[test]
344 fn seq_other_stream_step_counted() {
345 let steps = vec![PlanStep::Barrier {
346 node: crate::node::NodeId(0),
347 stream: StreamId(2),
348 }];
349 let plan = ExecutionPlan {
350 steps,
351 num_streams: 3,
352 pool_bytes: 0,
353 kernel_count_original: 0,
354 kernel_count_fused: 0,
355 event_count: 0,
356 };
357 let stats = SequentialExecutor::new(&plan).run().unwrap();
358 assert_eq!(stats.steps_on_other_streams, 1);
359 }
360
361 #[test]
362 fn seq_multiple_kernels_fused_counted() {
363 let mut b = GraphBuilder::new().with_auto_infer_edges(false);
365 let k0 = b.add_kernel("add", 4, 256, 0).fusible(true).finish();
366 let k1 = b.add_kernel("relu", 4, 256, 0).fusible(true).finish();
367 let k2 = b.add_kernel("scale", 4, 256, 0).fusible(true).finish();
368 b.chain(&[k0, k1, k2]);
369 let g = b.build().unwrap();
370 let plan = ExecutionPlan::build(&g, 1).unwrap();
371 let stats = SequentialExecutor::new(&plan).run().unwrap();
372 assert_eq!(stats.kernels_launched, 1);
374 }
375}