1mod persistent_handles;
4mod readback_dispatch;
5mod types;
6
7use super::builder::{build_program_jit_slots, build_program_sharded_slots_shared};
8use super::handlers::OpcodeHandler;
9use super::io;
10use super::planner::MegakernelLaunchGeometry;
11use super::protocol;
12use super::protocol_api::{validate_control_bytes, validate_debug_log_bytes};
13use super::recovery::{
14 backend_error_indicates_device_loss, recover_compiled_pipeline, MegakernelRecoveryDecision,
15 MegakernelRecoveryPolicy,
16};
17use super::staging_reserve::reserve_vec_capacity;
18use crate::PipelineError;
19use arc_swap::ArcSwap;
20use std::sync::Arc;
21use std::time::Instant;
22use vyre_driver::backend::{
23 CompiledPipeline, DispatchConfig, OutputBuffers, Resource, VyreBackend,
24};
25use vyre_foundation::ir::Program;
26
27pub use types::{
28 MegakernelBatchDispatchOutput, MegakernelDispatchOutput, MegakernelDispatchStats,
29 MegakernelResidentBatchScratch, MegakernelResidentHandles,
30};
31
32pub struct Megakernel {
38 backend: Arc<dyn VyreBackend>,
39 pipeline: ArcSwap<PipelineSlot>,
40 pipeline_id: ArcSwap<String>,
42 program: Arc<Program>,
43 has_grid_sync: bool,
44 empty_io_queue_bytes: Arc<[u8]>,
45 slot_count: u32,
46 workgroup_size_x: u32,
47 recovery_policy: MegakernelRecoveryPolicy,
48}
49
50struct PipelineSlot {
51 inner: Arc<dyn CompiledPipeline>,
52}
53
54impl Megakernel {
55 pub fn bootstrap(backend: Arc<dyn VyreBackend>) -> Result<Self, PipelineError> {
61 Self::bootstrap_sharded(backend, 256, 256, Vec::new())
62 }
63
64 pub fn bootstrap_with_opcodes(
70 backend: Arc<dyn VyreBackend>,
71 opcodes: Vec<OpcodeHandler>,
72 ) -> Result<Self, PipelineError> {
73 Self::bootstrap_sharded(backend, 256, 256, opcodes)
74 }
75
76 pub fn worker_groups_for_geometry(
83 slot_count: u32,
84 workgroup_size_x: u32,
85 ) -> Result<u32, PipelineError> {
86 validate_bootstrap_geometry(slot_count, workgroup_size_x)?;
87 Ok(slot_count / workgroup_size_x)
88 }
89
90 pub fn bootstrap_sharded(
97 backend: Arc<dyn VyreBackend>,
98 slot_count: u32,
99 workgroup_size_x: u32,
100 opcodes: Vec<OpcodeHandler>,
101 ) -> Result<Self, PipelineError> {
102 validate_bootstrap_geometry(slot_count, workgroup_size_x)?;
103 let program = build_program_sharded_slots_shared(workgroup_size_x, slot_count, &opcodes);
104 Self::compile_bootstrap_shared(backend, slot_count, workgroup_size_x, program)
105 }
106
107 pub fn bootstrap_jit(
113 backend: Arc<dyn VyreBackend>,
114 slot_count: u32,
115 workgroup_size_x: u32,
116 payload_processor: &[vyre_foundation::ir::Node],
117 ) -> Result<Self, PipelineError> {
118 validate_bootstrap_geometry(slot_count, workgroup_size_x)?;
119 let program = build_program_jit_slots(workgroup_size_x, slot_count, payload_processor);
120 Self::compile_bootstrap(backend, slot_count, workgroup_size_x, program)
121 }
122
123 fn compile_bootstrap(
124 backend: Arc<dyn VyreBackend>,
125 slot_count: u32,
126 workgroup_size_x: u32,
127 program: Program,
128 ) -> Result<Self, PipelineError> {
129 Self::compile_bootstrap_shared(backend, slot_count, workgroup_size_x, Arc::new(program))
130 }
131
132 fn compile_bootstrap_shared(
133 backend: Arc<dyn VyreBackend>,
134 slot_count: u32,
135 workgroup_size_x: u32,
136 program: Arc<Program>,
137 ) -> Result<Self, PipelineError> {
138 validate_bootstrap_geometry(slot_count, workgroup_size_x)?;
139 let config = DispatchConfig::default();
140 let pipeline = vyre_driver::pipeline::compile_shared(
141 Arc::clone(&backend),
142 Arc::clone(&program),
143 &config,
144 )?;
145 let pipeline_id = pipeline.id().to_string();
146 let has_grid_sync = vyre_driver::grid_sync::contains_grid_sync(&program);
147 let empty_io_queue_bytes =
148 Arc::<[u8]>::from(io::try_encode_empty_io_queue(io::IO_SLOT_COUNT)?.into_boxed_slice());
149 Ok(Self {
150 backend,
151 pipeline: ArcSwap::from(Arc::new(PipelineSlot { inner: pipeline })),
152 pipeline_id: ArcSwap::from(Arc::new(pipeline_id)),
153 program,
154 has_grid_sync,
155 empty_io_queue_bytes,
156 slot_count,
157 workgroup_size_x,
158 recovery_policy: MegakernelRecoveryPolicy::default(),
159 })
160 }
161
162 pub fn dispatch(
169 &self,
170 control_bytes: Vec<u8>,
171 ring_bytes: Vec<u8>,
172 debug_log_bytes: Vec<u8>,
173 ) -> Result<Vec<Vec<u8>>, PipelineError> {
174 self.dispatch_borrowed(&control_bytes, &ring_bytes, &debug_log_bytes)
175 }
176
177 pub fn dispatch_borrowed(
184 &self,
185 control_bytes: &[u8],
186 ring_bytes: &[u8],
187 debug_log_bytes: &[u8],
188 ) -> Result<Vec<Vec<u8>>, PipelineError> {
189 Ok(self
190 .dispatch_borrowed_observed(control_bytes, ring_bytes, debug_log_bytes)?
191 .buffers)
192 }
193
194 pub fn dispatch_observed(
200 &self,
201 control_bytes: Vec<u8>,
202 ring_bytes: Vec<u8>,
203 debug_log_bytes: Vec<u8>,
204 ) -> Result<MegakernelDispatchOutput, PipelineError> {
205 self.dispatch_with_io_queue_borrowed_observed(
206 &control_bytes,
207 &ring_bytes,
208 &debug_log_bytes,
209 &self.empty_io_queue_bytes,
210 )
211 }
212
213 pub fn dispatch_borrowed_observed(
220 &self,
221 control_bytes: &[u8],
222 ring_bytes: &[u8],
223 debug_log_bytes: &[u8],
224 ) -> Result<MegakernelDispatchOutput, PipelineError> {
225 self.dispatch_with_io_queue_borrowed_observed(
226 control_bytes,
227 ring_bytes,
228 debug_log_bytes,
229 &self.empty_io_queue_bytes,
230 )
231 }
232
233 pub fn dispatch_with_io_queue(
240 &self,
241 control_bytes: Vec<u8>,
242 ring_bytes: Vec<u8>,
243 debug_log_bytes: Vec<u8>,
244 io_queue_bytes: Vec<u8>,
245 ) -> Result<Vec<Vec<u8>>, PipelineError> {
246 self.dispatch_with_io_queue_borrowed(
247 &control_bytes,
248 &ring_bytes,
249 &debug_log_bytes,
250 &io_queue_bytes,
251 )
252 }
253
254 pub fn dispatch_with_io_queue_borrowed(
260 &self,
261 control_bytes: &[u8],
262 ring_bytes: &[u8],
263 debug_log_bytes: &[u8],
264 io_queue_bytes: &[u8],
265 ) -> Result<Vec<Vec<u8>>, PipelineError> {
266 Ok(self
267 .dispatch_with_io_queue_borrowed_observed(
268 control_bytes,
269 ring_bytes,
270 debug_log_bytes,
271 io_queue_bytes,
272 )?
273 .buffers)
274 }
275
276 pub fn dispatch_with_io_queue_observed(
282 &self,
283 control_bytes: Vec<u8>,
284 ring_bytes: Vec<u8>,
285 debug_log_bytes: Vec<u8>,
286 io_queue_bytes: Vec<u8>,
287 ) -> Result<MegakernelDispatchOutput, PipelineError> {
288 self.dispatch_with_io_queue_borrowed_observed(
289 &control_bytes,
290 &ring_bytes,
291 &debug_log_bytes,
292 &io_queue_bytes,
293 )
294 }
295
296 pub fn dispatch_with_io_queue_borrowed_observed(
303 &self,
304 control_bytes: &[u8],
305 ring_bytes: &[u8],
306 debug_log_bytes: &[u8],
307 io_queue_bytes: &[u8],
308 ) -> Result<MegakernelDispatchOutput, PipelineError> {
309 let mut buffers = Vec::new();
310 reserve_output_shell(
311 &mut buffers,
312 MegakernelResidentHandles::ABI_RESOURCE_COUNT,
313 "borrowed megakernel output shell",
314 )?;
315 let stats = self.dispatch_with_io_queue_borrowed_into(
316 control_bytes,
317 ring_bytes,
318 debug_log_bytes,
319 io_queue_bytes,
320 &mut buffers,
321 )?;
322 Ok(MegakernelDispatchOutput { buffers, stats })
323 }
324
325 pub fn dispatch_with_io_queue_borrowed_into(
332 &self,
333 control_bytes: &[u8],
334 ring_bytes: &[u8],
335 debug_log_bytes: &[u8],
336 io_queue_bytes: &[u8],
337 outputs: &mut OutputBuffers,
338 ) -> Result<MegakernelDispatchStats, PipelineError> {
339 validate_control_bytes(control_bytes)?;
340 validate_debug_log_bytes(debug_log_bytes)?;
341 io::validate_io_queue_bytes(io_queue_bytes)?;
342 self.validate_ring_bytes(ring_bytes)?;
343
344 let input_bytes = total_len([control_bytes, ring_bytes, debug_log_bytes, io_queue_bytes])?;
345 let inputs = [control_bytes, ring_bytes, debug_log_bytes, io_queue_bytes];
346 let config = self.launch_geometry().dispatch_config(None);
347 let started = Instant::now();
348 let mut recovered = false;
349 match self.dispatch_once_into(&inputs, &config, outputs) {
350 Ok(()) => {}
351 Err(error) if self.recovery_policy.allows_retry(&error) => {
352 self.recover_after_device_loss()?;
353 recovered = true;
354 self.dispatch_once_into(&inputs, &config, outputs)?
355 }
356 Err(error) => return Err(error.into()),
357 }
358 let latency_ns = nanos_u64(started.elapsed().as_nanos())?;
359 let output_bytes = output_bytes(outputs)?;
360 let readback_bytes = output_bytes;
361 let bytes_moved = checked_add_u64(input_bytes, readback_bytes, "megakernel bytes moved")?;
362 let device_allocation_bytes = checked_add_u64(
363 input_bytes,
364 output_bytes,
365 "megakernel host-visible device allocation bytes",
366 )?;
367 let output_buffers = count_u32(outputs.len(), "megakernel output buffer count")?;
368 let device_allocation_events =
369 checked_add_u32(4, output_buffers, "megakernel allocation event count")?;
370 Ok(MegakernelDispatchStats {
371 input_bytes,
372 output_bytes,
373 readback_bytes,
374 bytes_moved,
375 device_allocation_bytes,
376 device_allocation_events,
377 latency_ns,
378 output_buffers,
379 resident_resource_rows: 0,
380 resident_resource_handles: 0,
381 kernel_launches: if recovered { 2 } else { 1 },
382 sync_points: 1,
383 recovered_after_device_loss: recovered,
384 })
385 }
386
387 pub fn recover_after_device_loss(&self) -> Result<MegakernelRecoveryDecision, PipelineError> {
396 let config = self.launch_geometry().dispatch_config(None);
397 let rebuilt = recover_compiled_pipeline(&self.backend, Arc::clone(&self.program), &config)?;
398 let new_id = rebuilt.id().to_string();
399 self.pipeline
400 .store(Arc::new(PipelineSlot { inner: rebuilt }));
401 self.pipeline_id.store(Arc::new(new_id));
402 Ok(MegakernelRecoveryDecision::RecompiledPipeline)
403 }
404
405 #[must_use]
411 pub fn pipeline_id(&self) -> String {
412 (**self.pipeline_id.load()).clone()
413 }
414
415 #[must_use]
417 pub const fn slot_count(&self) -> u32 {
418 self.slot_count
419 }
420
421 #[must_use]
423 pub const fn workgroup_size_x(&self) -> u32 {
424 self.workgroup_size_x
425 }
426
427 #[must_use]
429 pub fn worker_groups(&self) -> u32 {
430 self.slot_count / self.workgroup_size_x
431 }
432
433 pub(super) fn validate_ring_bytes(&self, ring_bytes: &[u8]) -> Result<(), PipelineError> {
434 let expected_ring_bytes = protocol::ring_byte_len(self.slot_count).ok_or_else(|| {
435 PipelineError::Backend(
436 "megakernel ring byte length overflowed usize. Fix: split the ring into smaller dispatch shards."
437 .to_string(),
438 )
439 })?;
440 if ring_bytes.len() != expected_ring_bytes {
441 return Err(PipelineError::Backend(format!(
442 "megakernel ring buffer has {} bytes, expected {expected_ring_bytes} for {} slots. Fix: build ring bytes with Megakernel::encode_empty_ring(slot_count) for this handle.",
443 ring_bytes.len(),
444 self.slot_count
445 )));
446 }
447 Ok(())
448 }
449
450 pub(super) fn launch_geometry(&self) -> MegakernelLaunchGeometry {
451 MegakernelLaunchGeometry {
452 workgroup_size_x: self.workgroup_size_x,
453 slot_count: self.slot_count,
454 dispatch_grid: [self.slot_count / self.workgroup_size_x, 1, 1],
455 }
456 }
457
458 fn dispatch_once_into(
459 &self,
460 inputs: &[&[u8]; 4],
461 config: &DispatchConfig,
462 outputs: &mut OutputBuffers,
463 ) -> Result<(), vyre_driver::BackendError> {
464 if self.has_grid_sync && !self.backend.supports_grid_sync() {
465 return vyre_driver::grid_sync::dispatch_with_grid_sync_split_into(
466 self.backend.as_ref(),
467 &self.program,
468 inputs,
469 config,
470 outputs,
471 );
472 }
473 let pipeline = self.pipeline.load();
474 pipeline
475 .inner
476 .dispatch_borrowed_into(inputs, config, outputs)
477 }
478
479 fn dispatch_persistent_handles_once_into(
480 &self,
481 inputs: &[Resource; 4],
482 config: &DispatchConfig,
483 outputs: &mut OutputBuffers,
484 ) -> Result<(), vyre_driver::BackendError> {
485 let pipeline = self.pipeline.load();
486 pipeline
487 .inner
488 .dispatch_persistent_handles_into(inputs, config, outputs)
489 }
490
491 fn dispatch_persistent_handle_rows_once_into(
492 &self,
493 rows: &[[Resource; 4]],
494 config: &DispatchConfig,
495 outputs: &mut Vec<OutputBuffers>,
496 ) -> Result<(), vyre_driver::BackendError> {
497 let pipeline = self.pipeline.load();
498 pipeline
499 .inner
500 .dispatch_persistent_handle_rows_into(rows, config, outputs)
501 }
502}
503
504impl MegakernelRecoveryPolicy {
505 fn allows_retry(self, error: &vyre_driver::BackendError) -> bool {
506 self.retry_device_loss_once && backend_error_indicates_device_loss(error)
507 }
508}
509
510fn validate_bootstrap_geometry(
511 slot_count: u32,
512 workgroup_size_x: u32,
513) -> Result<(), PipelineError> {
514 if slot_count == 0 || workgroup_size_x == 0 || slot_count % workgroup_size_x != 0 {
515 return Err(PipelineError::QueueFull {
516 queue: "submission",
517 fix: "slot_count must be a non-zero multiple of workgroup_size_x",
518 });
519 }
520 Ok(())
521}
522
523pub(super) fn total_len<const N: usize>(buffers: [&[u8]; N]) -> Result<u64, PipelineError> {
524 let mut total = 0u64;
525 for buffer in buffers {
526 total = checked_add_u64(
527 total,
528 usize_to_u64(buffer.len(), "megakernel input buffer length")?,
529 "megakernel input byte total",
530 )?;
531 }
532 Ok(total)
533}
534
535pub(super) fn output_bytes(outputs: &[Vec<u8>]) -> Result<u64, PipelineError> {
536 let mut total = 0u64;
537 for output in outputs {
538 total = checked_add_u64(
539 total,
540 usize_to_u64(output.len(), "megakernel output buffer length")?,
541 "megakernel output byte total",
542 )?;
543 }
544 Ok(total)
545}
546
547pub(super) fn nested_output_bytes(outputs: &[Vec<Vec<u8>>]) -> Result<u64, PipelineError> {
548 let mut total = 0u64;
549 for row in outputs {
550 total = checked_add_u64(
551 total,
552 output_bytes(row)?,
553 "megakernel nested output byte total",
554 )?;
555 }
556 Ok(total)
557}
558
559pub(super) fn output_count_u32(outputs: &[Vec<u8>]) -> Result<u32, PipelineError> {
560 count_u32(outputs.len(), "megakernel output buffer count")
561}
562
563pub(super) fn nested_output_count_u32(outputs: &[Vec<Vec<u8>>]) -> Result<u32, PipelineError> {
564 let mut total = 0usize;
565 for row in outputs {
566 total = total.checked_add(row.len()).ok_or_else(|| {
567 PipelineError::Backend(
568 "megakernel nested output buffer count overflowed usize. Fix: split resident rows before dispatch.".to_string(),
569 )
570 })?;
571 }
572 count_u32(total, "megakernel nested output buffer count")
573}
574
575pub(super) fn resident_row_count_u32(rows: usize) -> Result<u32, PipelineError> {
576 count_u32(rows, "megakernel resident resource row count")
577}
578
579pub(super) fn resident_handle_count_u32(rows: usize) -> Result<u32, PipelineError> {
580 let handles = rows
581 .checked_mul(MegakernelResidentHandles::ABI_RESOURCE_COUNT)
582 .ok_or_else(|| {
583 PipelineError::Backend(
584 "megakernel resident resource handle count overflowed usize. Fix: split resident rows before dispatch.".to_string(),
585 )
586 })?;
587 count_u32(handles, "megakernel resident resource handle count")
588}
589
590pub(super) fn reserve_output_shell<T>(
591 out: &mut Vec<T>,
592 capacity: usize,
593 label: &'static str,
594) -> Result<(), PipelineError> {
595 reserve_vec_capacity(out, capacity, label)
596}
597
598pub(super) fn nanos_u64(nanos: u128) -> Result<u64, PipelineError> {
599 u64::try_from(nanos).map_err(|source| {
600 PipelineError::Backend(format!(
601 "megakernel latency cannot fit u64 nanoseconds: {source}. Fix: timeout or split the dispatch before telemetry overflows."
602 ))
603 })
604}
605
606fn usize_to_u64(value: usize, label: &str) -> Result<u64, PipelineError> {
607 u64::try_from(value).map_err(|source| {
608 PipelineError::Backend(format!(
609 "{label} cannot fit u64: {source}. Fix: split the megakernel dispatch before telemetry/accounting."
610 ))
611 })
612}
613
614fn count_u32(value: usize, label: &str) -> Result<u32, PipelineError> {
615 u32::try_from(value).map_err(|source| {
616 PipelineError::Backend(format!(
617 "{label} cannot fit u32: {source}. Fix: split the megakernel dispatch before telemetry/accounting."
618 ))
619 })
620}
621
622fn checked_add_u64(left: u64, right: u64, label: &str) -> Result<u64, PipelineError> {
623 left.checked_add(right).ok_or_else(|| {
624 PipelineError::Backend(format!(
625 "{label} overflowed u64. Fix: split the megakernel dispatch before telemetry/accounting."
626 ))
627 })
628}
629
630fn checked_add_u32(left: u32, right: u32, label: &str) -> Result<u32, PipelineError> {
631 left.checked_add(right).ok_or_else(|| {
632 PipelineError::Backend(format!(
633 "{label} overflowed u32. Fix: split the megakernel dispatch before telemetry/accounting."
634 ))
635 })
636}
637
638#[cfg(test)]
639mod tests;