1pub const HEAVY_ENTER_PCT: u64 = 10; pub const HEAVY_EXIT_PCT: u64 = 25; pub const LIGHT_ENTER_PCT: u64 = 50; pub const LIGHT_EXIT_PCT: u64 = 30; const LIGHT_SLICE_NS: u64 = 2_000_000; const LIGHT_PREEMPT_NS: u64 = 1_000_000; const LIGHT_LAG_SCALE: u64 = 6;
22const LIGHT_BATCH_NS: u64 = 20_000_000; const MIXED_SLICE_NS: u64 = 1_000_000; const MIXED_PREEMPT_NS: u64 = 1_000_000; const MIXED_LAG_SCALE: u64 = 4;
27const MIXED_BATCH_NS: u64 = 20_000_000; const HEAVY_SLICE_NS: u64 = 4_000_000; const HEAVY_PREEMPT_NS: u64 = 2_000_000; const HEAVY_LAG_SCALE: u64 = 2;
32const HEAVY_BATCH_NS: u64 = 20_000_000; const LIGHT_P99_CEIL_NS: u64 = 3_000_000; const MIXED_P99_CEIL_NS: u64 = 5_000_000; const HEAVY_P99_CEIL_NS: u64 = 10_000_000; pub const LIGHT_DEMOTION_NS: u64 = 3_500_000; pub const MIXED_DEMOTION_NS: u64 = 2_500_000; pub const HEAVY_DEMOTION_NS: u64 = 2_000_000; pub const DEFAULT_LAT_CRI_THRESH_HIGH: u64 = 32; pub const DEFAULT_LAT_CRI_THRESH_LOW: u64 = 8; pub const AFFINITY_OFF: u64 = 0;
59pub const AFFINITY_WEAK: u64 = 1;
60pub const AFFINITY_STRONG: u64 = 2;
61
62#[repr(C)]
63#[derive(Clone, Copy)]
64pub struct TuningKnobs {
65 pub slice_ns: u64,
66 pub preempt_thresh_ns: u64,
67 pub lag_scale: u64,
68 pub batch_slice_ns: u64,
69 pub cpu_bound_thresh_ns: u64,
70 pub lat_cri_thresh_high: u64,
71 pub lat_cri_thresh_low: u64,
72 pub affinity_mode: u64,
73 pub sojourn_thresh_ns: u64,
74 pub burst_slice_ns: u64,
75}
76
77impl Default for TuningKnobs {
78 fn default() -> Self {
79 Self {
80 slice_ns: 1_000_000,
81 preempt_thresh_ns: 1_000_000,
82 lag_scale: 4,
83 batch_slice_ns: 20_000_000,
84 cpu_bound_thresh_ns: MIXED_DEMOTION_NS,
85 lat_cri_thresh_high: DEFAULT_LAT_CRI_THRESH_HIGH,
86 lat_cri_thresh_low: DEFAULT_LAT_CRI_THRESH_LOW,
87 affinity_mode: AFFINITY_OFF,
88 sojourn_thresh_ns: 5_000_000,
89 burst_slice_ns: 1_000_000,
90 }
91 }
92}
93
94#[repr(u8)]
97#[derive(Clone, Copy, PartialEq, Eq, Debug)]
98pub enum Regime {
99 Light = 0,
100 Mixed = 1,
101 Heavy = 2,
102}
103
104impl Regime {
105 pub fn label(self) -> &'static str {
106 match self {
107 Self::Light => "LIGHT",
108 Self::Mixed => "MIXED",
109 Self::Heavy => "HEAVY",
110 }
111 }
112
113 pub fn p99_ceiling(self) -> u64 {
114 match self {
115 Self::Light => LIGHT_P99_CEIL_NS,
116 Self::Mixed => MIXED_P99_CEIL_NS,
117 Self::Heavy => HEAVY_P99_CEIL_NS,
118 }
119 }
120}
121
122pub fn regime_knobs(r: Regime) -> TuningKnobs {
125 match r {
126 Regime::Light => TuningKnobs {
127 slice_ns: LIGHT_SLICE_NS,
128 preempt_thresh_ns: LIGHT_PREEMPT_NS,
129 lag_scale: LIGHT_LAG_SCALE,
130 batch_slice_ns: LIGHT_BATCH_NS,
131 cpu_bound_thresh_ns: LIGHT_DEMOTION_NS,
132 lat_cri_thresh_high: DEFAULT_LAT_CRI_THRESH_HIGH,
133 lat_cri_thresh_low: DEFAULT_LAT_CRI_THRESH_LOW,
134 affinity_mode: AFFINITY_WEAK,
135 sojourn_thresh_ns: 5_000_000,
136 burst_slice_ns: 1_000_000,
137 },
138 Regime::Mixed => TuningKnobs {
139 slice_ns: MIXED_SLICE_NS,
140 preempt_thresh_ns: MIXED_PREEMPT_NS,
141 lag_scale: MIXED_LAG_SCALE,
142 batch_slice_ns: MIXED_BATCH_NS,
143 cpu_bound_thresh_ns: MIXED_DEMOTION_NS,
144 lat_cri_thresh_high: DEFAULT_LAT_CRI_THRESH_HIGH,
145 lat_cri_thresh_low: DEFAULT_LAT_CRI_THRESH_LOW,
146 affinity_mode: AFFINITY_STRONG,
147 sojourn_thresh_ns: 5_000_000,
148 burst_slice_ns: 1_000_000,
149 },
150 Regime::Heavy => TuningKnobs {
151 slice_ns: HEAVY_SLICE_NS,
152 preempt_thresh_ns: HEAVY_PREEMPT_NS,
153 lag_scale: HEAVY_LAG_SCALE,
154 batch_slice_ns: HEAVY_BATCH_NS,
155 cpu_bound_thresh_ns: HEAVY_DEMOTION_NS,
156 lat_cri_thresh_high: DEFAULT_LAT_CRI_THRESH_HIGH,
157 lat_cri_thresh_low: DEFAULT_LAT_CRI_THRESH_LOW,
158 affinity_mode: AFFINITY_WEAK,
159 sojourn_thresh_ns: 5_000_000,
160 burst_slice_ns: 1_000_000,
161 },
162 }
163}
164
165pub fn scaled_regime_knobs(r: Regime, nr_cpus: u64) -> TuningKnobs {
173 let mut knobs = regime_knobs(r);
174 match r {
175 Regime::Heavy | Regime::Light => {
176 let slice_cap = nr_cpus * 500_000;
177 let preempt_cap = (nr_cpus * 250_000).max(1_000_000);
178 knobs.slice_ns = knobs.slice_ns.min(slice_cap);
179 knobs.preempt_thresh_ns = knobs.preempt_thresh_ns.min(preempt_cap);
180 }
181 Regime::Mixed => {
182 let slice_cap = nr_cpus * 500_000;
183 let preempt_cap = nr_cpus * 500_000;
184 knobs.slice_ns = knobs.slice_ns.min(slice_cap);
185 knobs.preempt_thresh_ns = knobs.preempt_thresh_ns.min(preempt_cap);
186 let batch_cap = nr_cpus * 5_000_000;
187 knobs.batch_slice_ns = knobs.batch_slice_ns.min(batch_cap);
188 }
189 }
190 knobs.sojourn_thresh_ns = (nr_cpus * 1_000_000).clamp(2_000_000, 6_000_000);
192 knobs
193}
194
195pub fn detect_regime(current: Regime, idle_pct: u64) -> Regime {
200 match current {
201 Regime::Light => {
202 if idle_pct < LIGHT_EXIT_PCT {
203 Regime::Mixed
204 } else {
205 Regime::Light
206 }
207 }
208 Regime::Mixed => {
209 if idle_pct > LIGHT_ENTER_PCT {
210 Regime::Light
211 } else if idle_pct < HEAVY_ENTER_PCT {
212 Regime::Heavy
213 } else {
214 Regime::Mixed
215 }
216 }
217 Regime::Heavy => {
218 if idle_pct > HEAVY_EXIT_PCT {
219 Regime::Mixed
220 } else {
221 Regime::Heavy
222 }
223 }
224 }
225}
226
227pub const STABILITY_THRESHOLD: u32 = 10; pub fn compute_stability_score(
232 prev_score: u32,
233 regime_changed: bool,
234 reflex_events_delta: u64,
235 p99_ns: u64,
236 p99_ceiling_ns: u64,
237) -> u32 {
238 if regime_changed || reflex_events_delta > 0 || p99_ns > p99_ceiling_ns / 2 {
239 return 0;
240 }
241 (prev_score + 1).min(STABILITY_THRESHOLD)
242}
243
244pub fn should_print_telemetry(tick_counter: u64, stability_score: u32) -> bool {
247 if stability_score >= STABILITY_THRESHOLD {
248 tick_counter % 2 == 0
249 } else {
250 true
251 }
252}
253
254pub const HIST_BUCKETS: usize = 12;
257pub const HIST_EDGES_NS: [u64; HIST_BUCKETS] = [
258 10_000, 25_000, 50_000, 100_000, 250_000, 500_000, 1_000_000, 2_000_000, 5_000_000, 10_000_000, 20_000_000, u64::MAX, ];
271
272pub fn compute_p99_from_histogram(counts: &[u64; HIST_BUCKETS]) -> u64 {
275 let total: u64 = counts.iter().sum();
276 if total == 0 {
277 return 0;
278 }
279 let threshold = (total * 99 + 99) / 100;
280 let mut cumulative = 0u64;
281 for i in 0..HIST_BUCKETS {
282 cumulative += counts[i];
283 if cumulative >= threshold {
284 return HIST_EDGES_NS[i].min(HIST_EDGES_NS[HIST_BUCKETS - 2]);
285 }
286 }
287 HIST_EDGES_NS[HIST_BUCKETS - 2]
288}
289
290#[allow(dead_code)]
293pub fn should_reflex_tighten(aggregate_p99: u64, interactive_p99: u64, ceiling: u64) -> bool {
294 aggregate_p99 > ceiling || interactive_p99 > ceiling
295}
296
297#[allow(dead_code)]
302pub const BATCH_MAX_NS: u64 = 25_000_000; #[allow(dead_code)]
305pub fn sleep_adjust_batch_ns(base_batch_ns: u64, io_pct: u64) -> u64 {
306 if io_pct > 60 {
307 (base_batch_ns * 5 / 4).min(BATCH_MAX_NS)
309 } else if io_pct < 15 {
310 (base_batch_ns * 3 / 4).max(base_batch_ns / 2)
312 } else {
313 base_batch_ns
314 }
315}
316
317const N_EXPERTS: usize = 6;
326const ETA: f64 = 8.0;
327const RELAX_RATE: f64 = 0.80;
328const SPIKE_CONFIRM: u32 = 2;
329const RELAX_HOLD: u32 = 2;
330const RELAX_CEIL_PCT: f64 = 0.70;
331const EQUILIBRIUM: [f64; N_EXPERTS] = [0.08, 0.44, 0.12, 0.12, 0.12, 0.12];
332const WEIGHT_FLOOR: f64 = 1e-6;
333
334const EX_LATENCY: usize = 0;
335const EX_BALANCED: usize = 1;
336const EX_THROUGHPUT: usize = 2;
337const EX_IO_HEAVY: usize = 3;
338const EX_FORK_STORM: usize = 4;
339const EX_SATURATED: usize = 5;
340
341const SC_SLICE: [f64; 6] = [0.74, 1.00, 1.23, 0.98, 0.49, 1.47];
345const SC_PREEMPT: [f64; 6] = [0.74, 1.00, 1.23, 0.98, 0.49, 1.47];
346const SC_BATCH: [f64; 6] = [0.78, 1.00, 1.30, 1.30, 0.52, 1.04];
347const SC_DEMOTE: [f64; 6] = [0.86, 1.00, 1.29, 1.08, 0.86, 0.86];
348const SC_LCRI_HI: [f64; 6] = [0.74, 1.00, 1.23, 0.98, 0.98, 0.98];
349const SC_LCRI_LO: [f64; 6] = [0.70, 1.00, 1.40, 0.93, 0.93, 0.93];
350const SC_SOJOURN: [f64; 6] = [0.80, 1.00, 1.60, 0.93, 0.53, 1.07];
351const SC_BURST: [f64; 6] = [0.74, 1.00, 1.47, 0.98, 0.49, 1.23];
352
353const DV_LAG: [u64; 6] = [6, 4, 3, 4, 4, 3];
355const DV_AFFINITY: [u64; 6] = [
356 AFFINITY_STRONG,
357 AFFINITY_STRONG,
358 AFFINITY_WEAK,
359 AFFINITY_WEAK,
360 AFFINITY_OFF,
361 AFFINITY_WEAK,
362];
363fn blend_continuous(base: u64, scales: &[f64; 6], w: &[f64; N_EXPERTS]) -> u64 {
364 let v: f64 = (0..N_EXPERTS).map(|i| w[i] * base as f64 * scales[i]).sum();
365 (v.round() as u64).max(1)
366}
367
368fn majority_discrete(values: &[u64; 6], w: &[f64; N_EXPERTS]) -> u64 {
369 let mut best_val = values[0];
371 let mut best_w = 0.0f64;
372 for &v in values.iter() {
373 let total: f64 = (0..N_EXPERTS)
374 .filter(|&i| values[i] == v)
375 .map(|i| w[i])
376 .sum();
377 if total > best_w {
378 best_w = total;
379 best_val = v;
380 }
381 }
382 best_val
383}
384
385#[derive(Clone, Copy, PartialEq, Eq, Debug)]
386pub enum IoBucket {
387 Low,
388 Mid,
389 High,
390}
391
392pub fn io_bucket(io_pct: u64) -> IoBucket {
393 if io_pct > 60 {
394 IoBucket::High
395 } else if io_pct < 15 {
396 IoBucket::Low
397 } else {
398 IoBucket::Mid
399 }
400}
401
402pub struct MwuSignals {
403 pub p99_ns: u64,
404 pub interactive_p99_ns: u64,
405 pub io_pct: u64,
406 pub rescue_count: u64,
407 pub wakeup_rate: u64,
408}
409
410pub struct MwuController {
411 weights: [f64; N_EXPERTS],
412 baseline: TuningKnobs,
413 spike_streak: u32,
414 healthy_streak: u32,
415 fork_streak: u32,
416 prev_io_bucket: IoBucket,
417 prev_rescuing: bool,
418 losses_applied: bool,
419}
420
421impl MwuController {
422 pub fn new(baseline: TuningKnobs) -> Self {
423 Self {
424 weights: EQUILIBRIUM,
425 baseline,
426 spike_streak: 0,
427 healthy_streak: 0,
428 fork_streak: 0,
429 prev_io_bucket: IoBucket::Mid,
430 prev_rescuing: false,
431 losses_applied: false,
432 }
433 }
434
435 pub fn reset(&mut self) {
436 self.weights = EQUILIBRIUM;
437 self.spike_streak = 0;
438 self.healthy_streak = 0;
439 self.fork_streak = 0;
440 self.prev_io_bucket = IoBucket::Mid;
441 self.prev_rescuing = false;
442 self.losses_applied = false;
443 }
444
445 pub fn set_baseline(&mut self, baseline: TuningKnobs) {
446 self.baseline = baseline;
447 }
448
449 pub fn update(&mut self, sig: &MwuSignals, ceiling: u64, nr_cpus: u64) -> TuningKnobs {
450 let worst = sig.p99_ns.max(sig.interactive_p99_ns);
451 let above = worst > ceiling;
452 let below_relax = (worst as f64) < (ceiling as f64 * RELAX_CEIL_PCT);
453
454 let mut losses = [0.0f64; N_EXPERTS];
455 let mut has_loss = false;
456
457 if above {
459 self.healthy_streak = 0;
460 self.spike_streak += 1;
461 if self.spike_streak >= SPIKE_CONFIRM {
462 let v = ((worst - ceiling) as f64 / ceiling as f64).min(3.0);
463 losses[EX_BALANCED] += v * 0.5;
464 losses[EX_THROUGHPUT] += v * 1.0;
465 losses[EX_IO_HEAVY] += v * 0.6;
466 losses[EX_FORK_STORM] += v * 0.3;
467 losses[EX_SATURATED] += v * 0.9;
468 has_loss = true;
469 }
470 } else {
471 self.spike_streak = 0;
472 }
473
474 let rescuing = sig.rescue_count > 0;
480 if rescuing && !self.prev_rescuing {
481 let v = (sig.rescue_count as f64 * 1.5).min(3.0);
482 losses[EX_LATENCY] += v * 0.4;
483 losses[EX_THROUGHPUT] += v * 0.6;
484 losses[EX_SATURATED] += v * 0.6;
485 losses[EX_IO_HEAVY] += v * 0.4;
486 losses[EX_BALANCED] += v * 0.2;
487 has_loss = true;
488 }
489 self.prev_rescuing = rescuing;
490
491 let cur_io = io_bucket(sig.io_pct);
493 if cur_io != self.prev_io_bucket {
494 match cur_io {
495 IoBucket::High => {
496 let v = ((sig.io_pct as f64 - 60.0) / 40.0).min(1.0);
497 for i in 0..N_EXPERTS {
498 if i != EX_IO_HEAVY {
499 losses[i] += v * 0.8;
500 }
501 }
502 has_loss = true;
503 }
504 IoBucket::Low => {
505 let v = ((15.0 - sig.io_pct as f64) / 15.0).clamp(0.0, 1.0);
506 losses[EX_IO_HEAVY] += v * 1.0;
507 has_loss = true;
508 }
509 IoBucket::Mid => {}
510 }
511 }
512 self.prev_io_bucket = cur_io;
513
514 let fork_storm = sig.wakeup_rate > nr_cpus * 2;
516 if fork_storm {
517 self.fork_streak += 1;
518 if self.fork_streak >= SPIKE_CONFIRM {
519 losses[EX_LATENCY] += 0.05;
520 losses[EX_BALANCED] += 0.15;
521 losses[EX_THROUGHPUT] += 0.30;
522 losses[EX_IO_HEAVY] += 0.25;
523 losses[EX_SATURATED] += 0.20;
524 has_loss = true;
525 }
526 } else {
527 self.fork_streak = 0;
528 }
529
530 if has_loss {
532 for i in 0..N_EXPERTS {
533 if losses[i] > 0.0 {
534 self.weights[i] *= (-ETA * losses[i]).exp();
535 }
536 if self.weights[i] < WEIGHT_FLOOR {
537 self.weights[i] = WEIGHT_FLOOR;
538 }
539 }
540 let sum: f64 = self.weights.iter().sum();
541 for w in self.weights.iter_mut() {
542 *w /= sum;
543 }
544 }
545
546 if !has_loss && below_relax {
548 self.healthy_streak += 1;
549 if self.healthy_streak >= RELAX_HOLD {
550 for i in 0..N_EXPERTS {
551 self.weights[i] =
552 (1.0 - RELAX_RATE) * self.weights[i] + RELAX_RATE * EQUILIBRIUM[i];
553 }
554 }
555 } else if !has_loss {
556 self.healthy_streak = 0;
557 }
558
559 self.losses_applied = has_loss;
560
561 let b = &self.baseline;
563 TuningKnobs {
564 slice_ns: blend_continuous(b.slice_ns, &SC_SLICE, &self.weights),
565 preempt_thresh_ns: blend_continuous(b.preempt_thresh_ns, &SC_PREEMPT, &self.weights),
566 lag_scale: majority_discrete(&DV_LAG, &self.weights),
567 batch_slice_ns: blend_continuous(b.batch_slice_ns, &SC_BATCH, &self.weights),
568 cpu_bound_thresh_ns: blend_continuous(b.cpu_bound_thresh_ns, &SC_DEMOTE, &self.weights),
569 lat_cri_thresh_high: blend_continuous(
570 b.lat_cri_thresh_high,
571 &SC_LCRI_HI,
572 &self.weights,
573 ),
574 lat_cri_thresh_low: blend_continuous(b.lat_cri_thresh_low, &SC_LCRI_LO, &self.weights),
575 affinity_mode: majority_discrete(&DV_AFFINITY, &self.weights),
576 sojourn_thresh_ns: blend_continuous(b.sojourn_thresh_ns, &SC_SOJOURN, &self.weights),
577 burst_slice_ns: blend_continuous(b.burst_slice_ns, &SC_BURST, &self.weights),
578 }
579 }
580
581 pub fn had_losses(&self) -> bool {
582 self.losses_applied
583 }
584
585 pub fn scale(&self) -> f64 {
586 let s: f64 = (0..N_EXPERTS).map(|i| self.weights[i] * SC_SLICE[i]).sum();
587 s
588 }
589}