weir/ifds_resident_types/dispatch.rs
1use smallvec::SmallVec;
2use vyre_foundation::ir::Program;
3
4/// Backend-neutral resident dispatch hooks for IFDS.
5///
6/// Weir owns this trait so production dataflow APIs do not depend on concrete
7/// driver crates. A Vyre/CUDA adapter can implement it by forwarding to
8/// `VyreBackend::{allocate_resident, upload_resident, dispatch_resident_timed,
9/// download_resident, free_resident}`.
10pub trait IfdsResidentDispatch {
11 /// Opaque backend-owned resident handle.
12 type Resource: Clone + PartialEq;
13
14 /// Stable backend/device identity for resident resource ownership.
15 fn resident_backend_id(&self) -> &'static str;
16
17 /// Stable backend implementation version for resident cache compatibility.
18 ///
19 /// Implementers backed by `vyre::VyreBackend` should forward
20 /// `vyre::VyreBackend::version`. The default keeps existing non-Vyre
21 /// test dispatchers source-compatible while preventing accidental reuse
22 /// across dispatchers that opt into versioned identity.
23 fn resident_backend_version(&self) -> &'static str {
24 "unspecified"
25 }
26
27 /// Allocate a resident buffer of exactly `byte_len` bytes.
28 fn allocate_resident(&self, byte_len: usize) -> Result<Self::Resource, String>;
29
30 /// Upload bytes into a resident buffer.
31 fn upload_resident(&self, resource: &Self::Resource, bytes: &[u8]) -> Result<(), String>;
32
33 /// Upload several resident buffers as one logical staging operation.
34 ///
35 /// Implementers must provide the batching boundary explicitly. A default
36 /// per-buffer loop hides stream synchronization and defeats the resident
37 /// seed-scatter path's upload amortization contract.
38 fn upload_resident_many(&self, uploads: &[(&Self::Resource, &[u8])]) -> Result<(), String>;
39
40 /// Upload byte ranges into resident buffers as one logical staging
41 /// operation.
42 ///
43 /// Backends with true ranged resident uploads should override this method.
44 /// The default only supports zero-offset uploads and forwards them through
45 /// [`Self::upload_resident_many`], preserving existing test dispatchers
46 /// while allowing CUDA-backed paths to avoid padding small seed batches to
47 /// the full retained capacity.
48 fn upload_resident_at_many(
49 &self,
50 uploads: &[(&Self::Resource, usize, &[u8])],
51 ) -> Result<(), String> {
52 if uploads.iter().any(|(_, offset, _)| *offset != 0) {
53 return Err(
54 "weir IFDS resident dispatch does not implement ranged uploads. Fix: provide upload_resident_at_many for this backend or use zero-offset uploads only."
55 .to_string(),
56 );
57 }
58 let mut zero_offset_uploads = SmallVec::<[(&Self::Resource, &[u8]); 8]>::new();
59 zero_offset_uploads.try_reserve(uploads.len()).map_err(|error| {
60 format!(
61 "weir IFDS resident dispatch could not reserve {} zero-offset upload tuple(s): {error}. Fix: shard the resident upload batch before dispatch.",
62 uploads.len()
63 )
64 })?;
65 for &(resource, _, bytes) in uploads {
66 zero_offset_uploads.push((resource, bytes));
67 }
68 self.upload_resident_many(&zero_offset_uploads)
69 }
70
71 /// Download a resident buffer.
72 fn download_resident(&self, resource: &Self::Resource) -> Result<Vec<u8>, String>;
73
74 /// Download a resident buffer into caller-owned host storage.
75 fn download_resident_into(
76 &self,
77 resource: &Self::Resource,
78 output: &mut Vec<u8>,
79 ) -> Result<(), String>;
80
81 /// Download one byte range from a resident buffer.
82 ///
83 /// Implementers must provide a real ranged transfer. A default full-buffer
84 /// download would turn every convergence poll into an O(frontier) copy and
85 /// hide the exact production regression this resident API exists to avoid.
86 fn download_resident_range(
87 &self,
88 resource: &Self::Resource,
89 byte_offset: usize,
90 byte_len: usize,
91 ) -> Result<Vec<u8>, String>;
92
93 /// Download one byte range from a resident buffer into caller-owned host
94 /// storage.
95 fn download_resident_range_into(
96 &self,
97 resource: &Self::Resource,
98 byte_offset: usize,
99 byte_len: usize,
100 output: &mut Vec<u8>,
101 ) -> Result<(), String>;
102
103 /// Download several resident byte ranges into caller-owned host buffers as
104 /// one logical readback operation.
105 fn download_resident_ranges_into(
106 &self,
107 ranges: &[(&Self::Resource, usize, usize)],
108 outputs: &mut [&mut Vec<u8>],
109 ) -> Result<(), String> {
110 if ranges.len() != outputs.len() {
111 return Err(format!(
112 "weir IFDS resident ranged batch download expected matching range/output counts but got {} range(s) and {} output(s). Fix: pass one caller-owned output Vec per readback range.",
113 ranges.len(),
114 outputs.len()
115 ));
116 }
117 for ((resource, byte_offset, byte_len), output) in ranges.iter().zip(outputs.iter_mut()) {
118 self.download_resident_range_into(resource, *byte_offset, *byte_len, output)?;
119 }
120 Ok(())
121 }
122
123 /// Free a resident buffer.
124 fn free_resident(&self, resource: Self::Resource) -> Result<(), String>;
125
126 /// Dispatch `program` with resident buffers in binding order.
127 fn dispatch_resident(
128 &self,
129 program: &Program,
130 resources: &[Self::Resource],
131 grid_override: Option<[u32; 3]>,
132 ) -> Result<(), String>;
133
134 /// Dispatch an ordered sequence of resident-buffer programs and read
135 /// selected resident byte ranges into caller-owned host buffers.
136 ///
137 /// The default preserves correctness through `dispatch_resident` plus
138 /// `download_resident_ranges_into`. CUDA-backed adapters override this so
139 /// a fixed-point window and final compact readback are enqueued behind one
140 /// device stream fence instead of synchronizing once per iteration.
141 #[allow(clippy::type_complexity)]
142 fn dispatch_resident_sequence_read_ranges_into(
143 &self,
144 steps: &[(&Program, &[Self::Resource], Option<[u32; 3]>)],
145 read_ranges: &[(&Self::Resource, usize, usize)],
146 outputs: &mut [&mut Vec<u8>],
147 ) -> Result<(), String> {
148 for (program, resources, grid_override) in steps {
149 self.dispatch_resident(program, resources, *grid_override)?;
150 }
151 self.download_resident_ranges_into(read_ranges, outputs)
152 }
153
154 /// Dispatch a resident sequence prefix, repeat a resident sub-sequence, and
155 /// read selected resident byte ranges into caller-owned host buffers.
156 ///
157 /// The default materializes no extra host sequence vector; it loops the
158 /// repeated kernel group directly. CUDA-backed adapters can override this
159 /// to forward repeat structure to the backend, avoiding per-iteration
160 /// sequence construction and repeated launch preparation.
161 #[allow(clippy::type_complexity)]
162 fn dispatch_resident_repeated_sequence_read_ranges_into(
163 &self,
164 prefix_steps: &[(&Program, &[Self::Resource], Option<[u32; 3]>)],
165 repeated_steps: &[(&Program, &[Self::Resource], Option<[u32; 3]>)],
166 repeat_count: u32,
167 read_ranges: &[(&Self::Resource, usize, usize)],
168 outputs: &mut [&mut Vec<u8>],
169 ) -> Result<(), String> {
170 for (program, resources, grid_override) in prefix_steps {
171 self.dispatch_resident(program, resources, *grid_override)?;
172 }
173 for _ in 0..repeat_count {
174 for (program, resources, grid_override) in repeated_steps {
175 self.dispatch_resident(program, resources, *grid_override)?;
176 }
177 }
178 self.download_resident_ranges_into(read_ranges, outputs)
179 }
180}