Skip to main content

vyre_runtime/megakernel/execution/
types.rs

1use crate::PipelineError;
2use vyre_driver::backend::{OutputBuffers, Resource};
3
4/// Per-dispatch host-side runtime instrumentation.
5#[derive(Debug, Clone, Copy, PartialEq, Eq)]
6pub struct MegakernelDispatchStats {
7    /// Bytes supplied to the backend across control, ring, debug, and IO buffers.
8    pub input_bytes: u64,
9    /// Bytes returned by the backend across all output buffers.
10    pub output_bytes: u64,
11    /// Host-visible readback bytes returned by this dispatch.
12    pub readback_bytes: u64,
13    /// Total host-visible bytes moved for this dispatch.
14    pub bytes_moved: u64,
15    /// Conservative host-visible device allocation volume for this dispatch.
16    pub device_allocation_bytes: u64,
17    /// Conservative count of fresh host-visible device buffer allocations.
18    pub device_allocation_events: u32,
19    /// Host-observed dispatch latency in nanoseconds.
20    pub latency_ns: u64,
21    /// Number of output buffers returned by the backend.
22    pub output_buffers: u32,
23    /// Number of resident megakernel resource rows submitted to the backend.
24    pub resident_resource_rows: u32,
25    /// Number of resident resource handles submitted across all rows.
26    pub resident_resource_handles: u32,
27    /// Number of kernel launches issued for this logical megakernel dispatch.
28    pub kernel_launches: u32,
29    /// Number of host-visible synchronization points needed to collect outputs.
30    pub sync_points: u32,
31    /// True when the first dispatch failed with device-loss symptoms and the
32    /// runtime rebuilt the compiled pipeline before retrying.
33    pub recovered_after_device_loss: bool,
34}
35
36impl MegakernelDispatchStats {
37    /// Throughput over returned output bytes in bytes per second.
38    #[must_use]
39    pub fn output_bytes_per_second(&self) -> u64 {
40        bytes_per_second_or_panic(self.output_bytes, self.latency_ns, "output bytes")
41    }
42
43    /// Throughput over host-visible readback bytes in bytes per second.
44    #[must_use]
45    pub fn readback_bytes_per_second(&self) -> u64 {
46        bytes_per_second_or_panic(self.readback_bytes, self.latency_ns, "readback bytes")
47    }
48
49    /// Total host-visible byte movement rate in bytes per second.
50    #[must_use]
51    pub fn bytes_moved_per_second(&self) -> u64 {
52        bytes_per_second_or_panic(self.bytes_moved, self.latency_ns, "moved bytes")
53    }
54
55    /// Conservative allocation volume rate in bytes per second.
56    #[must_use]
57    pub fn device_allocation_bytes_per_second(&self) -> u64 {
58        bytes_per_second_or_panic(
59            self.device_allocation_bytes,
60            self.latency_ns,
61            "device allocation bytes",
62        )
63    }
64}
65
66fn bytes_per_second_or_panic(bytes: u64, latency_ns: u64, _label: &'static str) -> u64 {
67    if latency_ns == 0 {
68        return 0;
69    }
70    let scaled = (bytes as u128) * 1_000_000_000u128;
71    let rate = scaled / u128::from(latency_ns);
72    rate.min(u128::from(u64::MAX)) as u64
73}
74
75/// Backend outputs paired with host-side dispatch instrumentation.
76#[derive(Debug, Clone, PartialEq, Eq)]
77pub struct MegakernelDispatchOutput {
78    /// Backend output buffers.
79    pub buffers: Vec<Vec<u8>>,
80    /// Host-side dispatch instrumentation.
81    pub stats: MegakernelDispatchStats,
82}
83
84/// Backend outputs for a resident-handle batch plus aggregate host-side
85/// instrumentation.
86#[derive(Debug, Clone, PartialEq, Eq)]
87pub struct MegakernelBatchDispatchOutput {
88    /// One output-buffer set per submitted resident handle tuple.
89    pub batches: Vec<Vec<Vec<u8>>>,
90    /// Aggregate host-side dispatch instrumentation for the whole batch.
91    pub stats: MegakernelDispatchStats,
92}
93
94/// Reusable host scratch for batched resident-handle megakernel dispatch.
95///
96/// This scratch owns the transient resource rows submitted to the backend and
97/// the nested host readback buffers returned by batched dispatch. Reusing one
98/// value across repeated batches avoids rebuilding `Vec<[Resource; 4]>`,
99/// `Vec<Vec<Vec<u8>>>`, and per-output byte slots in many-small-launch loops.
100#[derive(Debug, Default)]
101pub struct MegakernelResidentBatchScratch {
102    pub(super) resources: Vec<[Resource; 4]>,
103    pub(super) batches: Vec<OutputBuffers>,
104    pub(super) active_batches: usize,
105}
106
107impl MegakernelResidentBatchScratch {
108    /// Create empty resident-batch scratch.
109    #[must_use]
110    pub fn new() -> Self {
111        Self::default()
112    }
113
114    /// Preallocate scratch for a known hot batch shape.
115    ///
116    /// # Errors
117    ///
118    /// Returns [`PipelineError::Backend`] when OOM prevents reserving the
119    /// requested capacity. Callers must handle the error; the old infallible
120    /// wrapper that silently returned empty scratch on failure has been removed.
121    pub fn with_capacity(
122        batch_count: usize,
123        output_slots_per_batch: usize,
124    ) -> Result<Self, PipelineError> {
125        let mut resources = Vec::new();
126        vyre_foundation::allocation::try_reserve_vec_to_capacity(&mut resources, batch_count)
127            .map_err(|error| {
128                PipelineError::Backend(format!(
129                    "megakernel resident batch scratch could not reserve {batch_count} resource row(s): {error}. Fix: split persistent-handle batches before dispatch."
130                ))
131            })?;
132        let mut batches = Vec::new();
133        vyre_foundation::allocation::try_reserve_vec_to_capacity(&mut batches, batch_count)
134            .map_err(|error| {
135                PipelineError::Backend(format!(
136                    "megakernel resident batch scratch could not reserve {batch_count} batch row(s): {error}. Fix: split persistent-handle batches before dispatch."
137                ))
138            })?;
139        for _ in 0..batch_count {
140            let mut outputs = Vec::new();
141            vyre_foundation::allocation::try_reserve_vec_to_capacity(
142                &mut outputs,
143                output_slots_per_batch,
144            )
145            .map_err(|error| {
146                PipelineError::Backend(format!(
147                    "megakernel resident batch scratch could not reserve {output_slots_per_batch} output slot(s): {error}. Fix: reduce resident output fanout or split persistent-handle batches."
148                ))
149            })?;
150            outputs.resize_with(output_slots_per_batch, Vec::new);
151            batches.push(outputs);
152        }
153        Ok(Self {
154            resources,
155            batches,
156            active_batches: 0,
157        })
158    }
159
160    /// Retained decoded output batches from the most recent dispatch.
161    #[must_use]
162    pub fn batches(&self) -> &[OutputBuffers] {
163        &self.batches[..self.active_batches.min(self.batches.len())]
164    }
165
166    /// Mutable retained output batches for callers that want to drain or
167    /// decode in place after dispatch.
168    pub fn batches_mut(&mut self) -> &mut Vec<OutputBuffers> {
169        &mut self.batches
170    }
171
172    /// Clear logical scratch contents while retaining allocations.
173    pub fn clear(&mut self) {
174        self.resources.clear();
175        self.active_batches = 0;
176        for batch in &mut self.batches {
177            for output in batch {
178                output.clear();
179            }
180        }
181    }
182
183    /// Current retained resource-row capacity.
184    #[must_use]
185    pub fn resource_capacity(&self) -> usize {
186        self.resources.capacity()
187    }
188
189    /// Current retained batch-row capacity.
190    #[must_use]
191    pub fn batch_capacity(&self) -> usize {
192        self.batches.capacity()
193    }
194}
195
196/// GPU-resident buffer handles for the four-buffer megakernel ABI.
197///
198/// Backends that implement persistent handles can keep control, ring, debug,
199/// and IO queue buffers resident across launches. Runtime callers use this
200/// type when a host byte mirror would force avoidable copies on the hot path.
201#[derive(Debug, Clone, Copy, PartialEq, Eq)]
202pub struct MegakernelResidentHandles {
203    /// Resident control-buffer handle.
204    pub control: u64,
205    /// Resident ring-buffer handle.
206    pub ring: u64,
207    /// Resident debug-log buffer handle.
208    pub debug_log: u64,
209    /// Resident IO-queue buffer handle.
210    pub io_queue: u64,
211}
212
213impl MegakernelResidentHandles {
214    /// Number of resident ABI resources passed to one persistent megakernel dispatch.
215    pub const ABI_RESOURCE_COUNT: usize = 4;
216
217    /// Construct resident handles in megakernel ABI binding order.
218    #[must_use]
219    pub const fn new(control: u64, ring: u64, debug_log: u64, io_queue: u64) -> Self {
220        Self {
221            control,
222            ring,
223            debug_log,
224            io_queue,
225        }
226    }
227
228    pub(super) fn resources(self) -> [Resource; Self::ABI_RESOURCE_COUNT] {
229        [
230            Resource::Resident(self.control),
231            Resource::Resident(self.ring),
232            Resource::Resident(self.debug_log),
233            Resource::Resident(self.io_queue),
234        ]
235    }
236}