1const WARP_SPARSE_DENSITY: f64 = 0.03125;
18const SPARSE_DENSITY: f64 = 0.125;
19const DENSE_DENSITY: f64 = 0.70;
20const BLOCK_DENSE_DENSITY: f64 = 0.85;
21const FUSION_PRESSURE: f64 = 0.70;
22const FUSION_PRESSURE_HYSTERESIS: f64 = 0.10;
23const FRONTIER_HYSTERESIS: f64 = 0.025;
24const MEMORY_RED_ZONE_BPS: u32 = 9_000;
25const MEMORY_HYSTERESIS_BPS: u32 = 250;
26const LAUNCH_PRESSURE_BPS: u32 = 1_500;
27const LAUNCH_HYSTERESIS_BPS: u32 = 250;
28const FUSION_READBACK_BYTES: u64 = 4_096;
29const DENSE_AVERAGE_DEGREE_BPS: u64 = 20_000;
30const WARP_SPARSE_AVERAGE_DEGREE_BPS: u64 = 80_000;
31
32#[derive(Clone, Copy, Debug, Eq, PartialEq)]
34pub struct MegakernelDeviceCapabilities {
35 pub supports_device_wide_barrier: bool,
37}
38
39impl MegakernelDeviceCapabilities {
40 pub const FUSION_CAPABLE: Self = Self {
42 supports_device_wide_barrier: true,
43 };
44 pub const FUSION_INCAPABLE: Self = Self {
46 supports_device_wide_barrier: false,
47 };
48
49 #[must_use]
54 pub fn admissible_fusion_pressure(self, fusion_pressure: f64) -> f64 {
55 if self.supports_device_wide_barrier {
56 fusion_pressure
57 } else {
58 0.0
59 }
60 }
61}
62
63#[derive(Clone, Copy, Debug, PartialEq)]
65pub struct MegakernelExecutionSample {
66 pub dispatch_cost_ns: f64,
68 pub frontier_density: f64,
70 pub readback_bytes: u64,
72}
73
74#[derive(Clone, Copy, Debug, Eq, PartialEq)]
76pub enum MegakernelExecutionTopology {
77 WarpSparseFrontier,
80 SparseFrontier,
82 BlockDenseFrontier,
85 DenseFrontier,
87 HybridFrontier,
89 FusedWave,
92}
93
94#[derive(Clone, Copy, Debug, Eq, PartialEq)]
96pub struct MegakernelGraphShape {
97 pub node_count: u64,
99 pub edge_count: u64,
101}
102
103#[derive(Clone, Copy, Debug, Eq, PartialEq)]
105pub struct MegakernelMemoryBudget {
106 pub required_bytes: u64,
108 pub budget_bytes: u64,
110}
111
112#[derive(Clone, Copy, Debug, Eq, PartialEq)]
114pub struct MegakernelMemoryPlan {
115 pub graph_bytes: u64,
117 pub frontier_bytes: u64,
119 pub scratch_bytes: u64,
121 pub output_bytes: u64,
123 pub required_bytes: u64,
125 pub budget_bytes: u64,
127 pub memory_pressure_bps: u32,
129}
130
131#[derive(Clone, Copy, Debug, Eq, PartialEq)]
133pub struct MegakernelExecutionPlan {
134 pub topology: MegakernelExecutionTopology,
136 pub memory: MegakernelMemoryPlan,
138 pub downgraded_to_sparse: bool,
141}
142
143#[derive(Clone, Debug, Eq, PartialEq)]
145pub enum MegakernelMemoryError {
146 ByteCountOverflow {
148 field: &'static str,
150 },
151 OverBudget {
153 topology: MegakernelExecutionTopology,
155 required_bytes: u64,
157 budget_bytes: u64,
159 node_count: u64,
161 edge_count: u64,
163 },
164}
165
166impl std::fmt::Display for MegakernelMemoryError {
167 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
168 match self {
169 Self::ByteCountOverflow { field } => write!(
170 f,
171 "megakernel memory planner overflowed while computing {field}. Fix: shard the graph or lower the candidate topology before planning device residency."
172 ),
173 Self::OverBudget {
174 topology,
175 required_bytes,
176 budget_bytes,
177 node_count,
178 edge_count,
179 } => write!(
180 f,
181 "megakernel {topology:?} plan requires {required_bytes} bytes but budget allows {budget_bytes} bytes for graph nodes={node_count} edges={edge_count}. Fix: choose a sparse topology, reduce fusion pressure, shard the graph, or raise the explicit device-memory budget."
182 ),
183 }
184 }
185}
186
187impl std::error::Error for MegakernelMemoryError {}
188
189#[derive(Clone, Copy, Debug, PartialEq)]
191pub struct MegakernelTopologyDecision {
192 pub topology: MegakernelExecutionTopology,
194 pub memory_pressure_bps: u32,
196 pub average_degree_bps: u64,
198 pub launch_pressure_bps: u32,
200}
201
202impl MegakernelTopologyDecision {
203 #[must_use]
205 pub fn stable_explanation(&self) -> String {
206 format!(
207 "megakernel-topology-v1|topology={:?}|memory_pressure_bps={}|average_degree_bps={}|launch_pressure_bps={}|reason={}",
208 self.topology,
209 self.memory_pressure_bps,
210 self.average_degree_bps,
211 self.launch_pressure_bps,
212 self.reason_code()
213 )
214 }
215
216 fn reason_code(&self) -> &'static str {
217 match self.topology {
218 MegakernelExecutionTopology::WarpSparseFrontier => "ultra_sparse_warp_specialized",
219 MegakernelExecutionTopology::SparseFrontier if self.memory_pressure_bps >= 9_000 => {
220 "memory_pressure_sparse_safety"
221 }
222 MegakernelExecutionTopology::SparseFrontier => "low_density_sparse_queue",
223 MegakernelExecutionTopology::BlockDenseFrontier => "high_density_block_specialized",
224 MegakernelExecutionTopology::DenseFrontier => "dense_coalesced_frontier",
225 MegakernelExecutionTopology::HybridFrontier => "transition_band_hybrid",
226 MegakernelExecutionTopology::FusedWave => "launch_and_readback_pressure_fused",
227 }
228 }
229}
230
231#[must_use]
233pub fn select_megakernel_topology(
234 sample: MegakernelExecutionSample,
235 graph: MegakernelGraphShape,
236 memory: MegakernelMemoryBudget,
237 launch_overhead_ns: f64,
238 fusion_pressure: f64,
239 capabilities: MegakernelDeviceCapabilities,
240) -> MegakernelTopologyDecision {
241 let memory_pressure_bps = pressure_bps(memory.required_bytes, memory.budget_bytes);
242 let average_degree_bps = pressure_bps_u64(graph.edge_count, graph.node_count);
243 let launch_pressure_bps =
244 if sample.dispatch_cost_ns <= 0.0 || !sample.dispatch_cost_ns.is_finite() {
245 0
246 } else {
247 finite_ratio_bps(
248 launch_overhead_ns.max(0.0),
249 sample.dispatch_cost_ns,
250 "launch overhead pressure",
251 )
252 };
253 let density = finite_unit(sample.frontier_density);
254 let fusion = finite_unit(capabilities.admissible_fusion_pressure(fusion_pressure));
255 let topology = if memory_pressure_bps >= MEMORY_RED_ZONE_BPS {
256 MegakernelExecutionTopology::SparseFrontier
257 } else if fusion >= FUSION_PRESSURE
258 && launch_pressure_bps >= LAUNCH_PRESSURE_BPS
259 && sample.readback_bytes >= FUSION_READBACK_BYTES
260 && memory_pressure_bps
261 <= checked_bps_sub(MEMORY_RED_ZONE_BPS, 500, "fusion memory red-zone margin")
262 {
263 MegakernelExecutionTopology::FusedWave
264 } else if density <= WARP_SPARSE_DENSITY && average_degree_bps <= WARP_SPARSE_AVERAGE_DEGREE_BPS
265 {
266 MegakernelExecutionTopology::WarpSparseFrontier
267 } else if density <= SPARSE_DENSITY {
268 MegakernelExecutionTopology::SparseFrontier
269 } else if density >= BLOCK_DENSE_DENSITY && average_degree_bps >= DENSE_AVERAGE_DEGREE_BPS {
270 MegakernelExecutionTopology::BlockDenseFrontier
271 } else if density >= DENSE_DENSITY && average_degree_bps >= DENSE_AVERAGE_DEGREE_BPS {
272 MegakernelExecutionTopology::DenseFrontier
273 } else {
274 MegakernelExecutionTopology::HybridFrontier
275 };
276 MegakernelTopologyDecision {
277 topology,
278 memory_pressure_bps,
279 average_degree_bps,
280 launch_pressure_bps,
281 }
282}
283
284#[must_use]
286pub fn select_megakernel_topology_stable(
287 sample: MegakernelExecutionSample,
288 graph: MegakernelGraphShape,
289 memory: MegakernelMemoryBudget,
290 launch_overhead_ns: f64,
291 fusion_pressure: f64,
292 previous_topology: MegakernelExecutionTopology,
293 capabilities: MegakernelDeviceCapabilities,
294) -> MegakernelTopologyDecision {
295 let mut decision = select_megakernel_topology(
296 sample,
297 graph,
298 memory,
299 launch_overhead_ns,
300 fusion_pressure,
301 capabilities,
302 );
303 decision.topology = stabilize_topology(
304 decision,
305 sample,
306 capabilities.admissible_fusion_pressure(fusion_pressure),
307 previous_topology,
308 );
309 decision
310}
311
312fn stabilize_topology(
313 decision: MegakernelTopologyDecision,
314 sample: MegakernelExecutionSample,
315 fusion_pressure: f64,
316 previous_topology: MegakernelExecutionTopology,
317) -> MegakernelExecutionTopology {
318 if decision.memory_pressure_bps >= MEMORY_RED_ZONE_BPS {
319 return decision.topology;
320 }
321 let density = finite_unit(sample.frontier_density);
322 let fusion = finite_unit(fusion_pressure);
323 if matches!(
324 previous_topology,
325 MegakernelExecutionTopology::SparseFrontier
326 | MegakernelExecutionTopology::WarpSparseFrontier
327 ) && decision.memory_pressure_bps
328 >= checked_bps_sub(
329 MEMORY_RED_ZONE_BPS,
330 MEMORY_HYSTERESIS_BPS,
331 "memory hysteresis floor",
332 )
333 {
334 return MegakernelExecutionTopology::SparseFrontier;
335 }
336
337 match previous_topology {
338 MegakernelExecutionTopology::WarpSparseFrontier
339 if density <= WARP_SPARSE_DENSITY + FRONTIER_HYSTERESIS
340 && decision.average_degree_bps <= WARP_SPARSE_AVERAGE_DEGREE_BPS =>
341 {
342 MegakernelExecutionTopology::WarpSparseFrontier
343 }
344 MegakernelExecutionTopology::SparseFrontier
345 if density <= SPARSE_DENSITY + FRONTIER_HYSTERESIS =>
346 {
347 MegakernelExecutionTopology::SparseFrontier
348 }
349 MegakernelExecutionTopology::HybridFrontier
350 if decision.topology == MegakernelExecutionTopology::SparseFrontier
351 && density >= SPARSE_DENSITY - FRONTIER_HYSTERESIS =>
352 {
353 MegakernelExecutionTopology::HybridFrontier
354 }
355 MegakernelExecutionTopology::HybridFrontier
356 if matches!(
357 decision.topology,
358 MegakernelExecutionTopology::DenseFrontier
359 | MegakernelExecutionTopology::BlockDenseFrontier
360 ) && density <= DENSE_DENSITY + FRONTIER_HYSTERESIS =>
361 {
362 MegakernelExecutionTopology::HybridFrontier
363 }
364 MegakernelExecutionTopology::DenseFrontier
365 if density >= DENSE_DENSITY - FRONTIER_HYSTERESIS
366 && decision.average_degree_bps >= DENSE_AVERAGE_DEGREE_BPS =>
367 {
368 MegakernelExecutionTopology::DenseFrontier
369 }
370 MegakernelExecutionTopology::BlockDenseFrontier
371 if density >= BLOCK_DENSE_DENSITY - FRONTIER_HYSTERESIS
372 && decision.average_degree_bps >= DENSE_AVERAGE_DEGREE_BPS =>
373 {
374 MegakernelExecutionTopology::BlockDenseFrontier
375 }
376 MegakernelExecutionTopology::FusedWave
377 if fusion >= FUSION_PRESSURE - FUSION_PRESSURE_HYSTERESIS
378 && decision.launch_pressure_bps
379 >= checked_bps_sub(
380 LAUNCH_PRESSURE_BPS,
381 LAUNCH_HYSTERESIS_BPS,
382 "launch hysteresis floor",
383 )
384 && sample.readback_bytes >= FUSION_READBACK_BYTES
385 && decision.memory_pressure_bps
386 <= checked_bps_sub(
387 MEMORY_RED_ZONE_BPS,
388 MEMORY_HYSTERESIS_BPS,
389 "memory hysteresis floor",
390 ) =>
391 {
392 MegakernelExecutionTopology::FusedWave
393 }
394 _ => decision.topology,
395 }
396}
397
398pub fn megakernel_resident_graph_bytes(
405 graph: MegakernelGraphShape,
406 bytes_per_node: u64,
407 bytes_per_edge: u64,
408) -> Result<u64, MegakernelMemoryError> {
409 let node_bytes = checked_mul(graph.node_count, bytes_per_node, "node layout bytes")?;
410 let edge_bytes = checked_mul(graph.edge_count, bytes_per_edge, "edge layout bytes")?;
411 checked_add(node_bytes, edge_bytes, "graph layout bytes")
412}
413
414pub fn plan_megakernel_memory_budget(
416 topology: MegakernelExecutionTopology,
417 graph: MegakernelGraphShape,
418 bytes_per_node: u64,
419 bytes_per_edge: u64,
420 frontier_bytes: u64,
421 scratch_bytes: u64,
422 output_bytes: u64,
423 budget_bytes: u64,
424) -> Result<MegakernelMemoryPlan, MegakernelMemoryError> {
425 let graph_bytes = megakernel_resident_graph_bytes(graph, bytes_per_node, bytes_per_edge)?;
426 let topology_scratch_bytes = topology_scratch_bytes(topology, scratch_bytes)?;
427 let required_without_output =
428 checked_add(graph_bytes, frontier_bytes, "graph plus frontier bytes")?;
429 let required_without_output = checked_add(
430 required_without_output,
431 topology_scratch_bytes,
432 "scratch bytes",
433 )?;
434 let required_bytes = checked_add(required_without_output, output_bytes, "output bytes")?;
435 if required_bytes > budget_bytes {
436 return Err(MegakernelMemoryError::OverBudget {
437 topology,
438 required_bytes,
439 budget_bytes,
440 node_count: graph.node_count,
441 edge_count: graph.edge_count,
442 });
443 }
444 Ok(MegakernelMemoryPlan {
445 graph_bytes,
446 frontier_bytes,
447 scratch_bytes: topology_scratch_bytes,
448 output_bytes,
449 required_bytes,
450 budget_bytes,
451 memory_pressure_bps: pressure_bps(required_bytes, budget_bytes),
452 })
453}
454
455pub fn plan_megakernel_execution(
457 sample: MegakernelExecutionSample,
458 graph: MegakernelGraphShape,
459 bytes_per_node: u64,
460 bytes_per_edge: u64,
461 frontier_bytes: u64,
462 scratch_bytes: u64,
463 output_bytes: u64,
464 budget_bytes: u64,
465 launch_overhead_ns: f64,
466 fusion_pressure: f64,
467 capabilities: MegakernelDeviceCapabilities,
468) -> Result<MegakernelExecutionPlan, MegakernelMemoryError> {
469 let sparse_memory = plan_megakernel_memory_budget(
470 MegakernelExecutionTopology::SparseFrontier,
471 graph,
472 bytes_per_node,
473 bytes_per_edge,
474 frontier_bytes,
475 scratch_bytes,
476 output_bytes,
477 budget_bytes,
478 )?;
479 let decision = select_megakernel_topology(
480 sample,
481 graph,
482 MegakernelMemoryBudget {
483 required_bytes: sparse_memory.required_bytes,
484 budget_bytes,
485 },
486 launch_overhead_ns,
487 fusion_pressure,
488 capabilities,
489 );
490 match plan_megakernel_memory_budget(
491 decision.topology,
492 graph,
493 bytes_per_node,
494 bytes_per_edge,
495 frontier_bytes,
496 scratch_bytes,
497 output_bytes,
498 budget_bytes,
499 ) {
500 Ok(memory) => Ok(MegakernelExecutionPlan {
501 topology: decision.topology,
502 memory,
503 downgraded_to_sparse: false,
504 }),
505 Err(MegakernelMemoryError::OverBudget { .. })
506 if decision.topology != MegakernelExecutionTopology::SparseFrontier =>
507 {
508 Ok(MegakernelExecutionPlan {
509 topology: MegakernelExecutionTopology::SparseFrontier,
510 memory: sparse_memory,
511 downgraded_to_sparse: true,
512 })
513 }
514 Err(error) => Err(error),
515 }
516}
517
518#[derive(Clone, Copy, Debug, PartialEq)]
523pub struct MegakernelExecutionRequest {
524 pub sample: MegakernelExecutionSample,
526 pub graph: MegakernelGraphShape,
528 pub bytes_per_node: u64,
530 pub bytes_per_edge: u64,
532 pub frontier_bytes: u64,
534 pub scratch_bytes: u64,
536 pub output_bytes: u64,
538 pub budget_bytes: u64,
540 pub launch_overhead_ns: f64,
542 pub fusion_pressure: f64,
544 pub capabilities: MegakernelDeviceCapabilities,
546}
547
548pub trait MegakernelExecutionPlanner {
554 fn plan_execution(
561 &mut self,
562 request: MegakernelExecutionRequest,
563 ) -> Result<MegakernelExecutionPlan, MegakernelMemoryError>;
564}
565
566#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
568pub struct NeutralMegakernelExecutionPlanner;
569
570impl MegakernelExecutionPlanner for NeutralMegakernelExecutionPlanner {
571 fn plan_execution(
572 &mut self,
573 request: MegakernelExecutionRequest,
574 ) -> Result<MegakernelExecutionPlan, MegakernelMemoryError> {
575 plan_megakernel_execution(
576 request.sample,
577 request.graph,
578 request.bytes_per_node,
579 request.bytes_per_edge,
580 request.frontier_bytes,
581 request.scratch_bytes,
582 request.output_bytes,
583 request.budget_bytes,
584 request.launch_overhead_ns,
585 request.fusion_pressure,
586 request.capabilities,
587 )
588 }
589}
590
591fn finite_unit(value: f64) -> f64 {
592 if value.is_finite() {
593 value.clamp(0.0, 1.0)
594 } else {
595 0.0
596 }
597}
598
599fn pressure_bps(numerator: u64, denominator: u64) -> u32 {
600 let clamped = pressure_bps_u64(numerator, denominator).min(10_000);
601 match u32::try_from(clamped) {
602 Ok(value) => value,
603 Err(error) => {
604 tracing::error!(
605 "megakernel pressure conversion failed after clamping value {clamped}: {error}. Fix: inspect ratio/clamp invariants before topology selection."
606 );
607 10_000
608 }
609 }
610}
611
612fn pressure_bps_u64(numerator: u64, denominator: u64) -> u64 {
613 crate::numeric::ratio_basis_points_u64_wide(
614 numerator,
615 denominator,
616 if numerator == 0 { 0 } else { u64::MAX },
617 "megakernel scheduler pressure",
618 "megakernel execution",
619 )
620}
621
622fn finite_ratio_bps(numerator: f64, denominator: f64, label: &'static str) -> u32 {
623 crate::numeric::finite_f64_ratio_basis_points_round(
624 numerator,
625 denominator,
626 u32::MAX,
627 u32::MAX,
628 label,
629 "megakernel execution",
630 )
631}
632
633fn checked_bps_sub(value: u32, margin: u32, label: &'static str) -> u32 {
634 if let Some(result) = value.checked_sub(margin) {
635 return result;
636 }
637 tracing::error!(
638 "megakernel {label} underflowed basis-point threshold. Fix: configure hysteresis below the threshold."
639 );
640 0
641}
642
643fn topology_scratch_bytes(
644 topology: MegakernelExecutionTopology,
645 base_scratch_bytes: u64,
646) -> Result<u64, MegakernelMemoryError> {
647 match topology {
648 MegakernelExecutionTopology::WarpSparseFrontier => Ok(base_scratch_bytes.max(32)),
649 MegakernelExecutionTopology::SparseFrontier => Ok(base_scratch_bytes),
650 MegakernelExecutionTopology::BlockDenseFrontier => checked_mul(
651 base_scratch_bytes.max(1024),
652 2,
653 "block dense topology scratch bytes",
654 ),
655 MegakernelExecutionTopology::DenseFrontier => {
656 checked_mul(base_scratch_bytes, 2, "dense topology scratch bytes")
657 }
658 MegakernelExecutionTopology::HybridFrontier => {
659 checked_mul(base_scratch_bytes, 3, "hybrid topology scratch bytes")
660 }
661 MegakernelExecutionTopology::FusedWave => {
662 checked_mul(base_scratch_bytes, 4, "fused topology scratch bytes")
663 }
664 }
665}
666
667fn checked_add(lhs: u64, rhs: u64, field: &'static str) -> Result<u64, MegakernelMemoryError> {
668 lhs.checked_add(rhs)
669 .ok_or(MegakernelMemoryError::ByteCountOverflow { field })
670}
671
672fn checked_mul(lhs: u64, rhs: u64, field: &'static str) -> Result<u64, MegakernelMemoryError> {
673 lhs.checked_mul(rhs)
674 .ok_or(MegakernelMemoryError::ByteCountOverflow { field })
675}
676
677#[cfg(test)]
678mod tests {
679 use super::{
680 plan_megakernel_execution, plan_megakernel_memory_budget, select_megakernel_topology,
681 select_megakernel_topology_stable, MegakernelDeviceCapabilities, MegakernelExecutionSample,
682 MegakernelExecutionTopology, MegakernelGraphShape, MegakernelMemoryBudget,
683 MegakernelMemoryError,
684 };
685
686 #[test]
687 fn topology_selector_uses_sparse_dense_hybrid_and_fused_bands() {
688 let graph = MegakernelGraphShape {
689 node_count: 1_000,
690 edge_count: 4_000,
691 };
692 let memory = MegakernelMemoryBudget {
693 required_bytes: 1_000,
694 budget_bytes: 10_000,
695 };
696 let warp_sparse = select_megakernel_topology(
697 MegakernelExecutionSample {
698 dispatch_cost_ns: 1_000.0,
699 frontier_density: 0.01,
700 readback_bytes: 256,
701 },
702 graph,
703 memory,
704 100.0,
705 0.0,
706 MegakernelDeviceCapabilities::FUSION_CAPABLE,
707 );
708 assert_eq!(
709 warp_sparse.topology,
710 MegakernelExecutionTopology::WarpSparseFrontier
711 );
712 assert_eq!(
713 warp_sparse.stable_explanation(),
714 "megakernel-topology-v1|topology=WarpSparseFrontier|memory_pressure_bps=1000|average_degree_bps=40000|launch_pressure_bps=1000|reason=ultra_sparse_warp_specialized"
715 );
716
717 let block_dense = select_megakernel_topology(
718 MegakernelExecutionSample {
719 dispatch_cost_ns: 1_000.0,
720 frontier_density: 0.90,
721 readback_bytes: 512,
722 },
723 graph,
724 memory,
725 100.0,
726 0.0,
727 MegakernelDeviceCapabilities::FUSION_CAPABLE,
728 );
729 assert_eq!(
730 block_dense.topology,
731 MegakernelExecutionTopology::BlockDenseFrontier
732 );
733
734 let hybrid = select_megakernel_topology(
735 MegakernelExecutionSample {
736 dispatch_cost_ns: 1_000.0,
737 frontier_density: 0.35,
738 readback_bytes: 512,
739 },
740 graph,
741 memory,
742 100.0,
743 0.0,
744 MegakernelDeviceCapabilities::FUSION_CAPABLE,
745 );
746 assert_eq!(hybrid.topology, MegakernelExecutionTopology::HybridFrontier);
747
748 let fused = select_megakernel_topology(
749 MegakernelExecutionSample {
750 dispatch_cost_ns: 1_000.0,
751 frontier_density: 0.50,
752 readback_bytes: 1 << 20,
753 },
754 graph,
755 memory,
756 250.0,
757 0.90,
758 MegakernelDeviceCapabilities::FUSION_CAPABLE,
759 );
760 assert_eq!(fused.topology, MegakernelExecutionTopology::FusedWave);
761 assert_eq!(fused.launch_pressure_bps, 2_500);
762
763 let unfusable = select_megakernel_topology(
764 MegakernelExecutionSample {
765 dispatch_cost_ns: 1_000.0,
766 frontier_density: 0.50,
767 readback_bytes: 1 << 20,
768 },
769 graph,
770 memory,
771 250.0,
772 0.90,
773 MegakernelDeviceCapabilities::FUSION_INCAPABLE,
774 );
775 assert_eq!(
776 unfusable.topology,
777 MegakernelExecutionTopology::HybridFrontier,
778 "Fix: a fused wave crosses wave boundaries inside one launch, so a device without a \
779 device-wide barrier cannot run it however high the measured fusion pressure is."
780 );
781 }
782
783 #[test]
784 fn stable_topology_selector_prevents_variant_flapping_near_thresholds() {
785 let graph = MegakernelGraphShape {
786 node_count: 1_000,
787 edge_count: 4_000,
788 };
789 let memory = MegakernelMemoryBudget {
790 required_bytes: 1_000,
791 budget_bytes: 10_000,
792 };
793 let sparse_to_hybrid = select_megakernel_topology_stable(
794 MegakernelExecutionSample {
795 dispatch_cost_ns: 1_000.0,
796 frontier_density: 0.14,
797 readback_bytes: 512,
798 },
799 graph,
800 memory,
801 100.0,
802 0.0,
803 MegakernelExecutionTopology::SparseFrontier,
804 MegakernelDeviceCapabilities::FUSION_CAPABLE,
805 );
806 assert_eq!(
807 sparse_to_hybrid.topology,
808 MegakernelExecutionTopology::SparseFrontier
809 );
810
811 let held_fusion = select_megakernel_topology_stable(
812 MegakernelExecutionSample {
813 dispatch_cost_ns: 1_000.0,
814 frontier_density: 0.50,
815 readback_bytes: 1 << 20,
816 },
817 graph,
818 memory,
819 250.0,
820 0.65,
821 MegakernelExecutionTopology::FusedWave,
822 MegakernelDeviceCapabilities::FUSION_CAPABLE,
823 );
824 assert_eq!(
825 held_fusion.topology,
826 MegakernelExecutionTopology::FusedWave
827 );
828
829 let released_fusion = select_megakernel_topology_stable(
830 MegakernelExecutionSample {
831 dispatch_cost_ns: 1_000.0,
832 frontier_density: 0.50,
833 readback_bytes: 1 << 20,
834 },
835 graph,
836 memory,
837 250.0,
838 0.65,
839 MegakernelExecutionTopology::FusedWave,
840 MegakernelDeviceCapabilities::FUSION_INCAPABLE,
841 );
842 assert_ne!(
843 released_fusion.topology,
844 MegakernelExecutionTopology::FusedWave,
845 "Fix: hysteresis must not hold a fused wave on a device that cannot run one."
846 );
847 }
848
849 #[test]
850 fn memory_planner_bounds_peak_bytes_by_topology() {
851 let graph = MegakernelGraphShape {
852 node_count: 1_000,
853 edge_count: 4_000,
854 };
855 let plan = plan_megakernel_memory_budget(
856 MegakernelExecutionTopology::FusedWave,
857 graph,
858 16,
859 8,
860 4_096,
861 2_048,
862 512,
863 128 * 1024,
864 )
865 .expect("Fix: valid fused plan should fit the explicit device-memory budget");
866
867 assert_eq!(plan.graph_bytes, 48_000);
868 assert_eq!(plan.scratch_bytes, 8_192);
869 assert_eq!(plan.required_bytes, 60_800);
870 assert!(plan.memory_pressure_bps > 0);
871 }
872
873 #[test]
874 fn memory_planner_rejects_budget_and_overflow_failures() {
875 let graph = MegakernelGraphShape {
876 node_count: 1_000,
877 edge_count: 4_000,
878 };
879 let err = plan_megakernel_memory_budget(
880 MegakernelExecutionTopology::DenseFrontier,
881 graph,
882 16,
883 8,
884 4_096,
885 2_048,
886 512,
887 32 * 1024,
888 )
889 .expect_err("over-budget dense plan must fail before allocation");
890 assert!(matches!(
891 err,
892 MegakernelMemoryError::OverBudget {
893 topology: MegakernelExecutionTopology::DenseFrontier,
894 ..
895 }
896 ));
897 assert!(err.to_string().contains("Fix: choose a sparse topology"));
898
899 let overflow = plan_megakernel_memory_budget(
900 MegakernelExecutionTopology::SparseFrontier,
901 MegakernelGraphShape {
902 node_count: u64::MAX,
903 edge_count: 0,
904 },
905 2,
906 0,
907 0,
908 0,
909 0,
910 u64::MAX,
911 )
912 .expect_err("overflowing graph byte count must be rejected");
913 assert!(matches!(
914 overflow,
915 MegakernelMemoryError::ByteCountOverflow {
916 field: "node layout bytes"
917 }
918 ));
919 }
920
921 #[test]
922 fn generated_execution_plans_never_exceed_budget_or_hide_overflow() {
923 let mut state = 0x4d59_5df4_d0f3_3173_u64;
924 for case_index in 0..1024usize {
925 let node_count = 1 + next_u64(&mut state) % 8_192;
926 let edge_count = node_count + next_u64(&mut state) % 65_536;
927 let bytes_per_node = 1 + next_u64(&mut state) % 64;
928 let bytes_per_edge = 1 + next_u64(&mut state) % 32;
929 let frontier_bytes = next_u64(&mut state) % 65_536;
930 let scratch_bytes = next_u64(&mut state) % 16_384;
931 let output_bytes = next_u64(&mut state) % 8_192;
932 let budget_bytes = 64 * 1024 + next_u64(&mut state) % (4 * 1024 * 1024);
933 let sample = MegakernelExecutionSample {
934 dispatch_cost_ns: 100.0 + (next_u64(&mut state) % 10_000) as f64,
935 frontier_density: (next_u64(&mut state) % 10_001) as f64 / 10_000.0,
936 readback_bytes: next_u64(&mut state) % (1 << 20),
937 };
938
939 let result = plan_megakernel_execution(
940 sample,
941 MegakernelGraphShape {
942 node_count,
943 edge_count,
944 },
945 bytes_per_node,
946 bytes_per_edge,
947 frontier_bytes,
948 scratch_bytes,
949 output_bytes,
950 budget_bytes,
951 250.0,
952 0.85,
953 MegakernelDeviceCapabilities::FUSION_CAPABLE,
954 );
955 match result {
956 Ok(plan) => {
957 assert!(
958 plan.memory.required_bytes <= plan.memory.budget_bytes,
959 "case {case_index}"
960 );
961 assert!(plan.memory.memory_pressure_bps <= 10_000);
962 }
963 Err(MegakernelMemoryError::OverBudget {
964 required_bytes,
965 budget_bytes,
966 ..
967 }) => assert!(required_bytes > budget_bytes, "case {case_index}"),
968 Err(MegakernelMemoryError::ByteCountOverflow { .. }) => {}
969 }
970 }
971 }
972
973 fn next_u64(state: &mut u64) -> u64 {
974 *state = state
975 .wrapping_mul(6_364_136_223_846_793_005)
976 .wrapping_add(1_442_695_040_888_963_407);
977 *state
978 }
979}