vyre_runtime/megakernel/planner/
grid.rs1use std::cell::RefCell;
4
5use rustc_hash::FxHashMap;
6use vyre_driver::backend::BackendError;
7
8use super::geometry::ResidentLaunchGeometry;
9use super::sizing::ResidentSizingPolicy;
10
11#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
13pub struct ResidentGridLimits {
14 pub max_workgroup_size_x: u32,
16 pub max_compute_workgroups_per_dimension: u32,
18 pub max_compute_invocations_per_workgroup: u32,
20}
21
22const GRID_PLAN_CACHE_CAP: usize = 128;
23
24#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
25struct GeometryCacheKey {
26 slot_count: u32,
27 worker_count: u32,
28 max_workgroup_size_x: u32,
29}
30
31struct MegakernelPlannerCache {
32 grid_plans: FxHashMap<(ResidentGridRequest, ResidentGridLimits), CacheEntry<ResidentGridPlan>>,
33 geometries: FxHashMap<GeometryCacheKey, CacheEntry<ResidentLaunchGeometry>>,
34 clock: u64,
35}
36
37struct CacheEntry<T> {
38 value: T,
39 last_seen: u64,
40}
41
42impl MegakernelPlannerCache {
43 fn get_grid_plan(
44 &mut self,
45 key: &(ResidentGridRequest, ResidentGridLimits),
46 ) -> Option<ResidentGridPlan> {
47 self.prepare_cache_hit_tick();
48 let entry = self.grid_plans.get_mut(key)?;
49 self.clock += 1;
50 entry.last_seen = self.clock;
51 Some(entry.value)
52 }
53
54 fn insert_grid_plan(
55 &mut self,
56 key: (ResidentGridRequest, ResidentGridLimits),
57 value: ResidentGridPlan,
58 ) {
59 let tick = self.next_tick();
60 self.grid_plans.insert(
61 key,
62 CacheEntry {
63 value,
64 last_seen: tick,
65 },
66 );
67 self.evict_grid_plans_to_cap();
68 }
69
70 fn get_geometry(&mut self, key: &GeometryCacheKey) -> Option<ResidentLaunchGeometry> {
71 self.prepare_cache_hit_tick();
72 let entry = self.geometries.get_mut(key)?;
73 self.clock += 1;
74 entry.last_seen = self.clock;
75 Some(entry.value)
76 }
77
78 fn insert_geometry(&mut self, key: GeometryCacheKey, value: ResidentLaunchGeometry) {
79 let tick = self.next_tick();
80 self.geometries.insert(
81 key,
82 CacheEntry {
83 value,
84 last_seen: tick,
85 },
86 );
87 self.evict_geometries_to_cap();
88 }
89
90 fn evict_grid_plans_to_cap(&mut self) {
91 while self.grid_plans.len() > GRID_PLAN_CACHE_CAP {
92 let Some(evicted) = self
93 .grid_plans
94 .iter()
95 .min_by_key(|(_, entry)| entry.last_seen)
96 .map(|(key, _)| *key)
97 else {
98 break;
99 };
100 self.grid_plans.remove(&evicted);
101 }
102 }
103
104 fn evict_geometries_to_cap(&mut self) {
105 while self.geometries.len() > GRID_PLAN_CACHE_CAP {
106 let Some(evicted) = self
107 .geometries
108 .iter()
109 .min_by_key(|(_, entry)| entry.last_seen)
110 .map(|(key, _)| *key)
111 else {
112 break;
113 };
114 self.geometries.remove(&evicted);
115 }
116 }
117
118 fn next_tick(&mut self) -> u64 {
119 self.prepare_cache_hit_tick();
120 self.clock += 1;
121 self.clock
122 }
123
124 fn prepare_cache_hit_tick(&mut self) {
125 if self.clock == u64::MAX {
126 self.clock = 0;
127 for entry in self.grid_plans.values_mut() {
128 entry.last_seen = 0;
129 }
130 for entry in self.geometries.values_mut() {
131 entry.last_seen = 0;
132 }
133 }
134 }
135}
136
137impl Default for MegakernelPlannerCache {
138 fn default() -> Self {
139 Self {
140 grid_plans: FxHashMap::with_capacity_and_hasher(
141 GRID_PLAN_CACHE_CAP,
142 Default::default(),
143 ),
144 geometries: FxHashMap::with_capacity_and_hasher(
145 GRID_PLAN_CACHE_CAP,
146 Default::default(),
147 ),
148 clock: 0,
149 }
150 }
151}
152
153thread_local! {
154 static PLANNER_CACHE: RefCell<MegakernelPlannerCache> = RefCell::new(MegakernelPlannerCache::default());
155}
156
157fn cached_grid_plan(
158 request: ResidentGridRequest,
159 limits: ResidentGridLimits,
160) -> Result<ResidentGridPlan, BackendError> {
161 if let Some(plan) =
162 PLANNER_CACHE.with(|cache| cache.borrow_mut().get_grid_plan(&(request, limits)))
163 {
164 return Ok(plan);
165 }
166
167 let plan = ResidentSizingPolicy::standard().calculate_optimal_grid(request, limits)?;
168 PLANNER_CACHE.with(|cache| {
169 cache.borrow_mut().insert_grid_plan((request, limits), plan);
170 });
171 Ok(plan)
172}
173
174pub(super) fn cached_geometry_from_slots(
175 slot_count: u32,
176 worker_count: u32,
177 max_workgroup_size_x: u32,
178) -> ResidentLaunchGeometry {
179 let key = GeometryCacheKey {
180 slot_count,
181 worker_count,
182 max_workgroup_size_x,
183 };
184 if let Some(geometry) = PLANNER_CACHE.with(|cache| cache.borrow_mut().get_geometry(&key)) {
185 return geometry;
186 }
187
188 let geometry = ResidentSizingPolicy::standard().geometry_from_slots(
189 slot_count,
190 worker_count,
191 max_workgroup_size_x,
192 );
193 PLANNER_CACHE.with(|cache| {
194 cache.borrow_mut().insert_geometry(key, geometry);
195 });
196 geometry
197}
198
199impl ResidentGridLimits {
200 #[must_use]
202 pub const fn new(
203 max_workgroup_size_x: u32,
204 max_compute_workgroups_per_dimension: u32,
205 max_compute_invocations_per_workgroup: u32,
206 ) -> Self {
207 Self {
208 max_workgroup_size_x,
209 max_compute_workgroups_per_dimension,
210 max_compute_invocations_per_workgroup,
211 }
212 }
213
214 pub(super) fn validate(self) -> Result<(), BackendError> {
215 if self.max_workgroup_size_x == 0 {
216 return Err(BackendError::new(
217 "megakernel max_workgroup_size_x must be non-zero. Fix: pass live adapter limits instead of a zero limit.",
218 ));
219 }
220 if self.max_compute_workgroups_per_dimension == 0 {
221 return Err(BackendError::new(
222 "megakernel max_compute_workgroups_per_dimension must be non-zero. Fix: pass live adapter limits instead of a zero limit.",
223 ));
224 }
225 if self.max_compute_invocations_per_workgroup == 0 {
226 return Err(BackendError::new(
227 "megakernel max_compute_invocations_per_workgroup must be non-zero. Fix: pass live adapter limits instead of a zero limit.",
228 ));
229 }
230 Ok(())
231 }
232}
233
234#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
236pub struct ResidentGridRequest {
237 pub queue_len: u32,
239 pub requested_worker_groups: u32,
241}
242
243impl ResidentGridRequest {
244 #[must_use]
246 pub const fn new(queue_len: u32, requested_worker_groups: u32) -> Self {
247 Self {
248 queue_len,
249 requested_worker_groups,
250 }
251 }
252}
253
254#[derive(Debug, Clone, Copy, PartialEq, Eq)]
256pub struct ResidentGridPlan {
257 pub geometry: ResidentLaunchGeometry,
259 pub worker_groups: u32,
261}
262
263impl ResidentGridPlan {
264 pub fn recommend(
270 request: ResidentGridRequest,
271 limits: ResidentGridLimits,
272 ) -> Result<Self, BackendError> {
273 cached_grid_plan(request, limits)
274 }
275}
276
277#[cfg(test)]
278mod tests {
279 use super::*;
280
281 fn limits() -> ResidentGridLimits {
282 ResidentGridLimits::new(256, 65_535, 256)
283 }
284
285 fn request(queue_len: u32) -> ResidentGridRequest {
286 ResidentGridRequest::new(queue_len, 0)
287 }
288
289 fn geometry(slot_count: u32) -> ResidentLaunchGeometry {
290 ResidentLaunchGeometry {
291 workgroup_size_x: 1,
292 slot_count,
293 dispatch_grid: [1, 1, 1],
294 }
295 }
296
297 #[test]
298 fn planner_grid_cache_refreshes_hot_plan_on_hit() {
299 let mut cache = MegakernelPlannerCache::default();
300 let limits = limits();
301 let hot_key = (request(1), limits);
302 let hot_plan = ResidentGridPlan {
303 geometry: geometry(1),
304 worker_groups: 1,
305 };
306 cache.insert_grid_plan(hot_key, hot_plan);
307 for queue_len in 2..=GRID_PLAN_CACHE_CAP as u32 {
308 cache.insert_grid_plan(
309 (request(queue_len), limits),
310 ResidentGridPlan {
311 geometry: geometry(queue_len),
312 worker_groups: 1,
313 },
314 );
315 }
316 assert_eq!(cache.get_grid_plan(&hot_key), Some(hot_plan));
317 cache.insert_grid_plan(
318 (request((GRID_PLAN_CACHE_CAP + 1) as u32), limits),
319 ResidentGridPlan {
320 geometry: geometry((GRID_PLAN_CACHE_CAP + 1) as u32),
321 worker_groups: 1,
322 },
323 );
324 assert_eq!(cache.get_grid_plan(&hot_key), Some(hot_plan));
325 }
326
327 #[test]
328 fn planner_geometry_cache_refreshes_hot_geometry_on_hit() {
329 let mut cache = MegakernelPlannerCache::default();
330 let hot_key = GeometryCacheKey {
331 slot_count: 1,
332 worker_count: 1,
333 max_workgroup_size_x: 256,
334 };
335 let hot_geometry = geometry(1);
336 cache.insert_geometry(hot_key, hot_geometry);
337 for slot_count in 2..=GRID_PLAN_CACHE_CAP as u32 {
338 cache.insert_geometry(
339 GeometryCacheKey {
340 slot_count,
341 worker_count: 1,
342 max_workgroup_size_x: 256,
343 },
344 geometry(slot_count),
345 );
346 }
347 assert_eq!(cache.get_geometry(&hot_key), Some(hot_geometry));
348 cache.insert_geometry(
349 GeometryCacheKey {
350 slot_count: (GRID_PLAN_CACHE_CAP + 1) as u32,
351 worker_count: 1,
352 max_workgroup_size_x: 256,
353 },
354 geometry((GRID_PLAN_CACHE_CAP + 1) as u32),
355 );
356 assert_eq!(cache.get_geometry(&hot_key), Some(hot_geometry));
357 }
358}