scx_pandemonium/tuning.rs
1// PANDEMONIUM TUNING TYPES
2// PURE-RUST MODULE: ZERO BPF DEPENDENCIES
3// SHARED BETWEEN BINARY CRATE (scheduler.rs, adaptive.rs) AND LIB CRATE (tests)
4
5// REGIME THRESHOLDS (CHAOS-DRIVEN BANDS)
6// THE SCHMITT-TRIGGER ENTER/EXIT PAIR IS GONE. THESE TWO THRESHOLDS
7// DEFINE THE HIGH/LOW BANDS OF mean_idle_pct USED BY THE CHAOS-DRIVEN
8// REGIME DETECTOR: BOTH ENTRY AND EXIT GO THROUGH THE SAME WINDOWED
9// MEAN, AND THE HVG/BP PRIMITIVES DECIDE WHEN ORDER IS SUFFICIENT TO
10// LATCH LIGHT OR HEAVY (OTHERWISE MIXED).
11
12pub const HEAVY_ENTER_PCT: u64 = 10; // mean_idle <= THIS BAND -> CANDIDATE HEAVY
13pub const LIGHT_ENTER_PCT: u64 = 50; // mean_idle >= THIS BAND -> CANDIDATE LIGHT
14
15// REGIME PROFILES
16// PREEMPT_THRESH CONTROLS WHEN TICK PREEMPTS BATCH TASKS (IF INTERACTIVE WAITING).
17// BATCH_SLICE_NS CONTROLS MAX UNINTERRUPTED BATCH RUN WHEN NO INTERACTIVE WAITING.
18// CPU_BOUND_THRESH_NS CONTROLS DEMOTION THRESHOLD PER REGIME (FEATURE 5).
19
20const LIGHT_SLICE_NS: u64 = 2_000_000; // 2MS
21const LIGHT_PREEMPT_NS: u64 = 1_000_000; // 1MS: AGGRESSIVE
22const LIGHT_BATCH_NS: u64 = 20_000_000; // 20MS: NO CONTENTION, LET BATCH RIP
23
24const MIXED_SLICE_NS: u64 = 1_000_000; // 1MS: TIGHT INTERACTIVE CONTROL
25const MIXED_PREEMPT_NS: u64 = 1_000_000; // 1MS: MATCH FOR CLEAN ENFORCEMENT
26const MIXED_BATCH_NS: u64 = 20_000_000; // 20MS: MATCHES LIGHT/HEAVY/BPF DEFAULT
27
28const HEAVY_SLICE_NS: u64 = 4_000_000; // 4MS: WIDER FOR THROUGHPUT
29const HEAVY_PREEMPT_NS: u64 = 2_000_000; // 2MS: SLIGHTLY RELAXED
30const HEAVY_BATCH_NS: u64 = 20_000_000; // 20MS: LET BATCH RIP
31
32// P99 CEILINGS
33
34const LIGHT_P99_CEIL_NS: u64 = 3_000_000; // 3MS
35const MIXED_P99_CEIL_NS: u64 = 5_000_000; // 5MS: BELOW 16MS FRAME BUDGET
36const HEAVY_P99_CEIL_NS: u64 = 10_000_000; // 10MS: HEAVY LOAD, REALISTIC
37
38// CLASSIFIER THRESHOLDS
39// LAT_CRI SCORE BOUNDARIES FOR TIER CLASSIFICATION
40// EXPOSED AS TUNING KNOBS FOR RUNTIME ADJUSTMENT
41
42pub const DEFAULT_LAT_CRI_THRESH_HIGH: u64 = 32; // >= THIS: LAT_CRITICAL
43pub const DEFAULT_LAT_CRI_THRESH_LOW: u64 = 8; // >= THIS: INTERACTIVE, BELOW: BATCH
44
45// TUNING KNOBS
46// MATCHES struct tuning_knobs IN BPF (intf.h)
47
48// AFFINITY MODE: L2 PLACEMENT STRENGTH
49pub const AFFINITY_OFF: u64 = 0;
50pub const AFFINITY_WEAK: u64 = 1;
51pub const AFFINITY_STRONG: u64 = 2;
52
53#[repr(C)]
54#[derive(Clone, Copy)]
55pub struct TuningKnobs {
56 pub slice_ns: u64,
57 pub preempt_thresh_ns: u64,
58 pub batch_slice_ns: u64,
59 pub lat_cri_thresh_high: u64,
60 pub lat_cri_thresh_low: u64,
61 pub affinity_mode: u64,
62 pub sojourn_thresh_ns: u64,
63 pub burst_slice_ns: u64,
64 // FIEDLER-DERIVED TOPOLOGY TIME CONSTANT (TAU_SCALE_NS / lambda_2).
65 // ZERO MEANS RUST HAS NOT YET WRITTEN tau; BPF USES THE PRE-FIRST-TICK
66 // FALLBACK CONSTANTS UNTIL A NONZERO VALUE LANDS. WRITTEN BY RUST AT
67 // TOPOLOGY DETECT AND ON HOTPLUG; READ BY BPF AT THE FIRST CPU-0 TICK.
68 pub topology_tau_ns: u64,
69 // R_eff-DERIVED CODEL EQUILIBRIUM TARGET (<R_eff> * 2m * tau).
70 // CO-LOCATED WITH topology_tau_ns; SAME ZERO/WRITE/CLAMP SEMANTICS.
71 pub codel_eq_ns: u64,
72}
73
74impl Default for TuningKnobs {
75 fn default() -> Self {
76 Self {
77 slice_ns: 1_000_000,
78 preempt_thresh_ns: 1_000_000,
79 batch_slice_ns: 20_000_000,
80 lat_cri_thresh_high: DEFAULT_LAT_CRI_THRESH_HIGH,
81 lat_cri_thresh_low: DEFAULT_LAT_CRI_THRESH_LOW,
82 affinity_mode: AFFINITY_OFF,
83 sojourn_thresh_ns: 5_000_000,
84 burst_slice_ns: 1_000_000,
85 topology_tau_ns: 0,
86 codel_eq_ns: 0,
87 }
88 }
89}
90
91// REGIME
92
93#[repr(u8)]
94#[derive(Clone, Copy, PartialEq, Eq, Debug)]
95pub enum Regime {
96 Light = 0,
97 Mixed = 1,
98 Heavy = 2,
99}
100
101impl Regime {
102 pub fn label(self) -> &'static str {
103 match self {
104 Self::Light => "LIGHT",
105 Self::Mixed => "MIXED",
106 Self::Heavy => "HEAVY",
107 }
108 }
109
110 pub fn p99_ceiling(self) -> u64 {
111 match self {
112 Self::Light => LIGHT_P99_CEIL_NS,
113 Self::Mixed => MIXED_P99_CEIL_NS,
114 Self::Heavy => HEAVY_P99_CEIL_NS,
115 }
116 }
117}
118
119// REGIME KNOBS
120
121pub fn regime_knobs(r: Regime) -> TuningKnobs {
122 match r {
123 Regime::Light => TuningKnobs {
124 slice_ns: LIGHT_SLICE_NS,
125 preempt_thresh_ns: LIGHT_PREEMPT_NS,
126 batch_slice_ns: LIGHT_BATCH_NS,
127 lat_cri_thresh_high: DEFAULT_LAT_CRI_THRESH_HIGH,
128 lat_cri_thresh_low: DEFAULT_LAT_CRI_THRESH_LOW,
129 affinity_mode: AFFINITY_WEAK,
130 sojourn_thresh_ns: 5_000_000,
131 burst_slice_ns: 1_000_000,
132 topology_tau_ns: 0,
133 codel_eq_ns: 0,
134 },
135 Regime::Mixed => TuningKnobs {
136 slice_ns: MIXED_SLICE_NS,
137 preempt_thresh_ns: MIXED_PREEMPT_NS,
138 batch_slice_ns: MIXED_BATCH_NS,
139 lat_cri_thresh_high: DEFAULT_LAT_CRI_THRESH_HIGH,
140 lat_cri_thresh_low: DEFAULT_LAT_CRI_THRESH_LOW,
141 affinity_mode: AFFINITY_STRONG,
142 sojourn_thresh_ns: 5_000_000,
143 burst_slice_ns: 1_000_000,
144 topology_tau_ns: 0,
145 codel_eq_ns: 0,
146 },
147 Regime::Heavy => TuningKnobs {
148 slice_ns: HEAVY_SLICE_NS,
149 preempt_thresh_ns: HEAVY_PREEMPT_NS,
150 batch_slice_ns: HEAVY_BATCH_NS,
151 lat_cri_thresh_high: DEFAULT_LAT_CRI_THRESH_HIGH,
152 lat_cri_thresh_low: DEFAULT_LAT_CRI_THRESH_LOW,
153 affinity_mode: AFFINITY_WEAK,
154 sojourn_thresh_ns: 5_000_000,
155 burst_slice_ns: 1_000_000,
156 topology_tau_ns: 0,
157 codel_eq_ns: 0,
158 },
159 }
160}
161
162// TAU-SCALED REGIME KNOBS
163// CAPS DIMENSIONED AS Q16 FIXED-POINT MULTIPLIERS OF tau_ns. k_i CALIBRATED
164// AGAINST THE 12C REFERENCE TOPOLOGY (tau ~= 40MS):
165// SLICE_CAP: 0.15 -> 6MS AT tau=40MS
166// PREEMPT_CAP: 0.075 -> 3MS AT tau=40MS
167// BATCH_CAP: 1.5 -> 60MS AT tau=40MS (Mixed ONLY)
168// SOJOURN: 0.15 -> 6MS AT tau=40MS
169// PER-CAP CLAMPS ARE SAFETY RAILS.
170const K_SLICE_CAP_Q16: u64 = 9830; // 0.15
171const K_PREEMPT_CAP_Q16: u64 = 4915; // 0.075
172const K_BATCH_CAP_Q16: u64 = 98304; // 1.5
173const K_SOJOURN_Q16: u64 = 9830; // 0.15
174
175// FORK-STORM RAW-WAKE-RATE THRESHOLD. Q16 RATIO INTERPRETED AS
176// "WAKES/SEC PER MS-OF-tau", SO scale_tau_u64(tau, K) PRODUCES
177// THE TOTAL-WAKE THRESHOLD (NOT PER-CPU). AT THE 12C REFERENCE
178// (tau=40MS) THE GATE FIRES AT ~8000 WAKE/S, TIGHTENING LINEARLY
179// AT LOWER tau (1200/S AT 4C, 400/S AT THE 2C FLOOR).
180const K_FORK_STORM_RATE_Q16: u64 = 13107; // 0.20
181const FORK_STORM_RATE_FLOOR: u64 = 200; // HZ; CLAMPS BELOW tau=1MS
182
183#[inline]
184fn scale_tau_u64(tau_ns: u64, k_q16: u64) -> u64 {
185 (tau_ns as u128 * k_q16 as u128 >> 16) as u64
186}
187
188pub fn scaled_regime_knobs(r: Regime, nr_cpus: u64, tau_ns: u64) -> TuningKnobs {
189 let mut knobs = regime_knobs(r);
190
191 let slice_cap_tau = scale_tau_u64(tau_ns, K_SLICE_CAP_Q16).clamp(500_000, 8_000_000);
192 let preempt_cap_tau = scale_tau_u64(tau_ns, K_PREEMPT_CAP_Q16).clamp(250_000, 4_000_000);
193 let sojourn_tau = scale_tau_u64(tau_ns, K_SOJOURN_Q16).clamp(2_000_000, 6_000_000);
194
195 knobs.slice_ns = knobs.slice_ns.min(slice_cap_tau);
196 knobs.preempt_thresh_ns = knobs.preempt_thresh_ns.min(preempt_cap_tau);
197
198 // LOW-CORE SLICE CAP. tau IS LARGEST AT LOW CORE COUNT (lambda_2 SHRINKS
199 // AS CORES DROP), SO THE tau SLICE CAP ABOVE IS LOOSEST EXACTLY WHERE A
200 // WIDE BATCH SLICE DOES THE MOST DAMAGE: ON 2-4 CORES THE HEAVY PROFILE'S
201 // 4ms SLICE DENIES A LATENCY-SENSITIVE PROBE ACROSS MANY CONSECUTIVE
202 // SLICES, PRODUCING THE 50-200ms LONG-RUN/MIXED P99 TAIL AT LOW CORE.
203 // idle_pct READS LOW AT LOW CORE COUNT -- BECAUSE THE
204 // BPF WARM-STAY/PER-CPU LANDING DELIBERATELY BYPASSES THE IDLE FAST PATH
205 // -- SO THE REGIME FALSELY LATCHES HEAVY AND INHERITS ITS 4ms SLICE. THE
206 // BPF BASELINE RUNS 1ms HERE WITH NO SUCH TAIL, AND THE LONG-RUN WORK
207 // NUMBERS SHOW THE WIDE SLICE BUYS NO THROUGHPUT ON SO FEW CORES. CAP THE
208 // SLICE TO THE MIXED VALUE AT <=4 CORES; 8C/12C (WHERE ADAPTIVE WINS AND
209 // THE WIDER SLICE EARNS THROUGHPUT) ARE UNTOUCHED.
210 if nr_cpus <= 4 {
211 knobs.slice_ns = knobs.slice_ns.min(MIXED_SLICE_NS);
212 }
213 if matches!(r, Regime::Mixed) {
214 let batch_cap_tau = scale_tau_u64(tau_ns, K_BATCH_CAP_Q16).clamp(10_000_000, 80_000_000);
215 knobs.batch_slice_ns = knobs.batch_slice_ns.min(batch_cap_tau);
216 }
217 knobs.sojourn_thresh_ns = sojourn_tau;
218
219 knobs
220}
221
222// REGIME DETECTION (CHAOS-DRIVEN)
223// THE SCHMITT TRIGGER IS GONE. REGIME IS A FUNCTION OF:
224// - mean_idle_pct: WINDOWED MEAN OVER THE LAST N TICKS
225// - hvg_lambda: HVG MEAN DEGREE OF THE idle_pct WINDOW
226// - bp_h: BANDT-POMPE D=3 PERMUTATION ENTROPY OF THE WINDOW
227// HYSTERESIS IS BUILT INTO THE WINDOW: SAMPLES MUST FLOW IN BEFORE THE
228// MEAN MOVES. THE 2-TICK HOLD IN THE MONITOR LOOP STAYS AS ADDITIONAL
229// SMOOTHING.
230//
231// LIGHT := mean_idle HIGH AND CHAOS-LOW (PERIODIC / IDLE-DOMINATED)
232// HEAVY := mean_idle LOW AND CHAOS-LOW (PERIODIC / SATURATED)
233// MIXED := ANYTHING ELSE (REGIME IS UNSTABLE OR IN MID-BAND)
234//
235// THE chaos_low PREDICATE IS lambda < CHAOTIC_MIN OR bp_h < BP_H_HIGH.
236// EITHER PRIMITIVE INDICATING ORDER IS ENOUGH; THEY MEASURE DIFFERENT
237// THINGS (AMPLITUDE-AWARE VS AMPLITUDE-INVARIANT) AND THE FIRST TO
238// FIRE LATCHES THE REGIME TO LIGHT/HEAVY INSTEAD OF MIXED.
239
240pub fn detect_regime(mean_idle_pct: f64, hvg_lambda: f64, bp_h: f64) -> Regime {
241 let chaos_low =
242 hvg_lambda < crate::chaos::HVG_LAMBDA_CHAOTIC_MIN || bp_h < crate::chaos::BP_H_HIGH;
243
244 if chaos_low && mean_idle_pct >= LIGHT_ENTER_PCT as f64 {
245 Regime::Light
246 } else if chaos_low && mean_idle_pct <= HEAVY_ENTER_PCT as f64 {
247 Regime::Heavy
248 } else {
249 Regime::Mixed
250 }
251}
252
253// STABILITY MODE
254
255pub const STABILITY_THRESHOLD: u32 = 10; // CONSECUTIVE STABLE TICKS BEFORE HIBERNATE
256
257pub fn compute_stability_score(
258 prev_score: u32,
259 regime_changed: bool,
260 reflex_events_delta: u64,
261 p99_ns: u64,
262 p99_ceiling_ns: u64,
263) -> u32 {
264 if regime_changed || reflex_events_delta > 0 || p99_ns > p99_ceiling_ns / 2 {
265 return 0;
266 }
267 (prev_score + 1).min(STABILITY_THRESHOLD)
268}
269
270// TELEMETRY GATING
271
272pub fn should_print_telemetry(tick_counter: u64, stability_score: u32) -> bool {
273 if stability_score >= STABILITY_THRESHOLD {
274 tick_counter % 2 == 0
275 } else {
276 true
277 }
278}
279
280// P99 HISTOGRAM
281
282pub const HIST_BUCKETS: usize = 12;
283pub const HIST_EDGES_NS: [u64; HIST_BUCKETS] = [
284 10_000, // 10us
285 25_000, // 25us
286 50_000, // 50us
287 100_000, // 100us
288 250_000, // 250us
289 500_000, // 500us
290 1_000_000, // 1ms
291 2_000_000, // 2ms
292 5_000_000, // 5ms
293 10_000_000, // 10ms
294 20_000_000, // 20ms
295 u64::MAX, // +inf
296];
297
298// COMPUTE P99 FROM DRAINED HISTOGRAM COUNTS. PURE FUNCTION.
299// CAP AT 20MS (LAST REAL BUCKET) -- +INF WOULD POISON EVERY COMPARISON.
300pub fn compute_p99_from_histogram(counts: &[u64; HIST_BUCKETS]) -> u64 {
301 let total: u64 = counts.iter().sum();
302 if total == 0 {
303 return 0;
304 }
305 let threshold = (total * 99 + 99) / 100;
306 let mut cumulative = 0u64;
307 for i in 0..HIST_BUCKETS {
308 cumulative += counts[i];
309 if cumulative >= threshold {
310 return HIST_EDGES_NS[i].min(HIST_EDGES_NS[HIST_BUCKETS - 2]);
311 }
312 }
313 HIST_EDGES_NS[HIST_BUCKETS - 2]
314}
315
316// MWU ORCHESTRATOR
317// MULTIPLICATIVE WEIGHT UPDATES ACROSS ALL 11 TUNING KNOBS.
318// 6 EXPERT PROFILES, EACH A SCALE FACTOR ON THE REGIME BASELINE.
319// DISCRETE KNOBS (LAG, AFFINITY, DEPTH) USE MAJORITY VOTE, NOT WEIGHTED AVERAGE.
320// 5 LOSS PATHWAYS: P99 SPIKE, RESCUE DELTA, IO DELTA, FORK STORM, CHAOS TRANSITION.
321// 1e-6 WEIGHT FLOOR PREVENTS UNDERFLOW (DEAD WEIGHTS CAN'T RECOVER).
322// PATHWAYS FIRE IMMEDIATELY -- NO SCHMITT-STYLE STREAK CONFIRMATION.
323//
324// v5.11.0: WEIGHTS ARE PER-REGIME (ONE VECTOR PER Light/Mixed/Heavy).
325// ON A STEADY WORKLOAD THE REGIME IS CONSTANT, SO THE ACTIVE VECTOR
326// SEES A STATIONARY LOSS STREAM AND CONVERGES HARD INSTEAD OF BEING
327// RE-LITIGATED EVERY TICK. BUTTERWORTH-DAMPED WEIGHT UPDATE, ANCHORED
328// LEARNING RATE, NO-OP-SKEWED INIT + WARM-UP GATE, ADAPTIVE STEP SIZE,
329// COARSE DIRTY-TRACKING, AND A WEIGHT-VARIANCE CONVERGENCE DETECTOR
330// THAT FEEDS THE QUIESCENCE GATE.
331
332const N_EXPERTS: usize = 6;
333
334// ANCHORED LEARNING RATE. ETA = ETA_CONST * sqrt(ln(N_EXPERTS) / T),
335// ETA_CONST = 1.0, T = 16 (CHAOS_WIN horizon). sqrt(ln 6 / 16) =
336// sqrt(1.79176 / 16) = 0.33465. THE OLD FIXED 8.0 OVERSHOT; THIS IS
337// THE THEORY-OPTIMAL HEDGE/EXP3 RATE FOR THE WINDOW HORIZON. IF
338// LATENCY RESPONSE REGRESSES, ETA_CONST IS THE PRIMARY TUNING SURFACE
339// (RAISE TOWARD 2-3 BEFORE TOUCHING THE QUIESCENCE GATE).
340const ETA: f64 = 0.33465;
341
342const RELAX_RATE: f64 = 0.80;
343const RELAX_HOLD: u32 = 2;
344const RELAX_CEIL_PCT: f64 = 0.70;
345
346// NO-OP-SKEWED INIT: DOMINANT MASS ON THE ANCHOR (EX_BALANCED, THE
347// "LEAVE KNOBS AT REGIME BASELINE" EXPERT WITH THE ALL-1.00 SCALE
348// COLUMN). THIS IS BOTH THE INITIAL VECTOR AND THE RELAX TARGET -- A
349// HEALTHY WORKLOAD RELAXES BACK TO "DO NOTHING", NOT TO A CONTESTED
350// MIDDLE. NOTE: THE FULLY-RELAXED BLEND SITS AT ~0.996x BASELINE (THE
351// THIN AGGRESSOR SPREAD), NOT EXACTLY BASELINE -- SUB-1%, WITHIN
352// RUN-TO-RUN NOISE, AND THE COARSE DIRTY-TRACKING MEANS A STEADY
353// WORKLOAD RETURNS THE EXACT CACHED KNOBS ANYWAY.
354const EQUILIBRIUM: [f64; N_EXPERTS] = [0.04, 0.80, 0.04, 0.04, 0.04, 0.04];
355
356const WEIGHT_FLOOR: f64 = 1e-6;
357
358// CROSS-DOMAIN SCATTER THRESHOLD (PATHWAY 6). PLACEMENT-SIDE CROSS-DOMAIN MIGRATION
359// FRACTION ABOVE THIS (PERCENT OF DISPATCHES) IS TREATED AS STORM PRESSURE.
360// EEVDF'S MEASURED BASELINE IS ~14%; 20 LEAVES HEADROOM SO NORMAL CROSS-DOMAIN
361// WORK-CONSERVATION DOES NOT TRIP THE PATHWAY, ONLY A GENUINE SCATTER CLIMB.
362const SCATTER_THRESH_PCT: u64 = 20;
363
364// WARM-UP GATE: FOR THE FIRST WARMUP_TICKS CALLS PER REGIME, SCORE THE
365// LOSS PATHWAYS (KEEP prev_* EDGE STATE CURRENT) BUT SKIP THE WEIGHT
366// UPDATE AND RETURN BASELINE KNOBS. PREVENTS THE FIRST NOISY TICKS
367// AFTER START / REGIME CHANGE FROM YANKING KNOBS.
368const WARMUP_TICKS: u32 = 3;
369
370// DYNAMIC BUTTERWORTH DAMPING ON THE WEIGHT UPDATE. THE POST-LOSS /
371// POST-RELAX VECTOR IS LOW-PASS BLENDED TOWARD ITS ONE-TICK-AGO SELF
372// SO IT APPROACHES THE TARGET WITHOUT RINGING. THE BLEND COEFFICIENT
373// IS DRIVEN BY SIGNAL TRUST -- RQA-DET + HVG-LAMBDA -- NOT CPU COUNT.
374//
375// PRINCIPLE: BUTTERWORTH = MAXIMALLY-FLAT (FAITHFUL) TRACKING.
376// FAITHFUL TRACKING IS CORRECT ONLY WHEN THE SIGNAL IS TRUSTWORTHY;
377// WHEN THE SIGNAL IS ITSELF CHAOTIC, FAITHFUL TRACKING IS THE THING
378// THAT CAUSES THE CONTROLLER TO RING. SO DAMPING IS A FUNCTION OF
379// HOW STEADY THE WORKLOAD APPEARS:
380// HIGH RQA-DET + lambda <= PERIODIC_MAX -> TRUST -> LIGHT DAMP (~0.85)
381// LOW RQA-DET OR lambda >= CHAOTIC_MIN -> NO TRUST -> HEAVY DAMP (0.50)
382//
383// REPLACED THE v5.10.0 CPU-COUNT CLIFF (0.707 BELOW 11 CPUS, 0.5 AT
384// OR ABOVE) WHICH DAMPED HARDER AT EXACTLY THE CORE COUNTS WHERE THE
385// CONTROLLER NEEDS THE MOST RESPONSIVENESS, PRODUCING THE 12C
386// REGRESSION CLUSTER (IPC BPF 12.9x, MIXED BPF 3.4x, LONGRUN
387// ADAPTIVE 2.2x). NR_CPUS NO LONGER PARTICIPATES IN DAMPING.
388const DAMP_LO: f64 = 0.50;
389const DAMP_HI: f64 = 0.85;
390// PENALTY APPLIED TO TRUST WHEN hvg_lambda CROSSES INTO THE
391// TRANSITION BAND (PERIODIC_MAX, CHAOTIC_MIN). LINEAR-RAMPED ACROSS
392// THE BAND; SATURATES AT 1.0 ONCE lambda >= CHAOTIC_MIN.
393const DAMP_LAMBDA_PENALTY: f64 = 0.20;
394// NEUTRAL TRUST WHEN RQA-DET IS NONE (WINDOW NOT FULL: NO RECURRENCE
395// EVIDENCE YET, NEITHER STEADY NOR CHAOTIC -- MIDDLE OF THE RANGE).
396const DAMP_TRUST_NEUTRAL: f64 = 0.5;
397
398// SIGNAL-TRUST -> DAMP COEFFICIENT. PURE FUNCTION OF THE TWO RAW
399// CHAOS SIGNALS ALREADY COMPUTED EVERY TICK FOR THE QUIESCENCE GATE.
400pub fn compute_damp(rqa_det: Option<f64>, hvg_lambda: f64) -> f64 {
401 let rqa_trust = rqa_det.unwrap_or(DAMP_TRUST_NEUTRAL);
402 let band = crate::chaos::HVG_LAMBDA_CHAOTIC_MIN - crate::chaos::HVG_LAMBDA_PERIODIC_MAX;
403 let lambda_penalty = if hvg_lambda > crate::chaos::HVG_LAMBDA_PERIODIC_MAX {
404 ((hvg_lambda - crate::chaos::HVG_LAMBDA_PERIODIC_MAX) / band).clamp(0.0, 1.0)
405 } else {
406 0.0
407 };
408 let trust = (rqa_trust - DAMP_LAMBDA_PENALTY * lambda_penalty).clamp(0.0, 1.0);
409 DAMP_LO + (DAMP_HI - DAMP_LO) * trust
410}
411
412// ADAPTIVE STEP SIZE: step = STEP_BASE / (1 + residual), residual =
413// aggregate loss this tick. BIG DISTURBANCE -> SMALLER CORRECTIVE
414// STEP; STEADY -> step ~= STEP_BASE. FOLDED INTO THE DAMPING COEFF.
415const STEP_BASE: f64 = 1.0;
416
417// WEIGHT-VARIANCE CONVERGENCE DETECTOR. PER-REGIME RING OF THE LAST
418// VAR_HIST L1 WEIGHT-MOVEMENT SAMPLES. CONVERGED WHEN BOTH HALVES'
419// MEAN MOVEMENT ARE BELOW CONVERGE_MOVE_EPS AND THEIR RATIO IS WITHIN
420// [1 - DELTA, 1 + DELTA]. FEEDS THE QUIESCENCE GATE.
421const VAR_HIST: usize = 8;
422const CONVERGE_MOVE_EPS: f64 = 1e-3;
423const CONVERGE_RATIO_DELTA: f64 = 0.25;
424
425// COARSE DIRTY-TRACKING THRESHOLD: WHEN THE ACTIVE WEIGHT VECTOR MOVED
426// LESS THAN THIS (L1) THIS TICK, update() RETURNS THE CACHED BLEND
427// OUTPUT (last_knobs) AND SKIPS THE BLEND ENTIRELY.
428const WEIGHTS_MOVED_EPS: f64 = 1e-9;
429
430const EX_LATENCY: usize = 0;
431const EX_BALANCED: usize = 1;
432const EX_THROUGHPUT: usize = 2;
433const EX_IO_HEAVY: usize = 3;
434const EX_FORK_STORM: usize = 4;
435const EX_SATURATED: usize = 5;
436
437// CORRECTED CONTINUOUS SCALE FACTORS
438// PROPORTIONALLY ADJUSTED SO sum(EQ[i] * SCALE[i]) = 1.0 AT EQUILIBRIUM.
439// [LATENCY, BALANCED, THROUGHPUT, IO_HEAVY, FORK_STORM, SATURATED]
440const SC_SLICE: [f64; 6] = [0.74, 1.00, 1.23, 0.98, 0.49, 1.47];
441const SC_PREEMPT: [f64; 6] = [0.74, 1.00, 1.23, 0.98, 0.49, 1.47];
442const SC_BATCH: [f64; 6] = [0.78, 1.00, 1.30, 1.30, 0.52, 1.04];
443const SC_LCRI_HI: [f64; 6] = [0.74, 1.00, 1.23, 0.98, 0.98, 0.98];
444const SC_LCRI_LO: [f64; 6] = [0.70, 1.00, 1.40, 0.93, 0.93, 0.93];
445const SC_SOJOURN: [f64; 6] = [0.80, 1.00, 1.60, 0.93, 0.53, 1.07];
446const SC_BURST: [f64; 6] = [0.74, 1.00, 1.47, 0.98, 0.49, 1.23];
447
448// DISCRETE KNOB VALUES (ABSOLUTE, NOT SCALE FACTORS)
449// FORK_STORM (INDEX 4) IS WEAK, NOT OFF: THE BPF READS affinity_mode ONLY AS
450// A BINARY GATE (main.bpf.c:1619, > 0) ON find_idle_l2_sibling. OFF DISABLES
451// THE L2 SIBLING PRE-FILTER, SO DURING A STORM WAKEES SEAT TOPOLOGY-BLIND AND
452// SCATTER CROSS-DOMAIN -- FIGHTING THE PHI/WARM-STAY PLACEMENT INSTEAD OF HELPING.
453// WEAK KEEPS THE L2 PRE-FILTER ON. (STRONG VS WEAK IS A NO-OP TO THE BPF; ONLY
454// OFF DIFFERS.)
455const DV_AFFINITY: [u64; 6] = [
456 AFFINITY_STRONG,
457 AFFINITY_STRONG,
458 AFFINITY_WEAK,
459 AFFINITY_WEAK,
460 AFFINITY_WEAK,
461 AFFINITY_WEAK,
462];
463fn blend_continuous(base: u64, scales: &[f64; 6], w: &[f64; N_EXPERTS]) -> u64 {
464 let v: f64 = (0..N_EXPERTS).map(|i| w[i] * base as f64 * scales[i]).sum();
465 (v.round() as u64).max(1)
466}
467
468fn majority_discrete(values: &[u64; 6], w: &[f64; N_EXPERTS]) -> u64 {
469 // GROUP BY VALUE, SUM WEIGHTS, PICK HIGHEST GROUP
470 let mut best_val = values[0];
471 let mut best_w = 0.0f64;
472 for &v in values.iter() {
473 let total: f64 = (0..N_EXPERTS)
474 .filter(|&i| values[i] == v)
475 .map(|i| w[i])
476 .sum();
477 if total > best_w {
478 best_w = total;
479 best_val = v;
480 }
481 }
482 best_val
483}
484
485#[derive(Clone, Copy, PartialEq, Eq, Debug)]
486pub enum IoBucket {
487 Low,
488 Mid,
489 High,
490}
491
492pub fn io_bucket(io_pct: u64) -> IoBucket {
493 if io_pct > 60 {
494 IoBucket::High
495 } else if io_pct < 15 {
496 IoBucket::Low
497 } else {
498 IoBucket::Mid
499 }
500}
501
502pub struct MwuSignals {
503 pub p99_ns: u64,
504 pub interactive_p99_ns: u64,
505 pub io_pct: u64,
506 pub rescue_count: u64,
507 pub wakeup_rate: u64,
508 // CROSS-DOMAIN SCATTER FRACTION THIS TICK: PLACEMENT-SIDE CROSS-DOMAIN LANDINGS
509 // (XDOM_SEL_* + XDOM_ENQ_T1, EXCLUDING THE PHI-CORRECT STEAL/STEP5 WORK-
510 // CONSERVATION PATHS) AS A PERCENTAGE OF DISPATCHES. EEVDF BASELINE ~14%;
511 // PANDEMONIUM'S STORM REGRESSION RAN ~48%. PATHWAY 6 KEYS ON THIS SO MWU
512 // CAN SEE THE MIGRATION STORM IT MIGHT BE INDUCING AND STEER AWAY FROM IT.
513 pub scatter_pct: u64,
514 // CHAOS PRIMITIVES (RAW-WINDOW DERIVED, NO EWMA, NO SCHMITT).
515 // hvg_lambda := HVG MEAN DEGREE OF THE idle_pct WINDOW
516 // bp_h_delta := bp_h(THIS TICK) - bp_h(PREVIOUS TICK), WHERE
517 // bp_h IS BANDT-POMPE D=3 PERMUTATION ENTROPY ON
518 // THE wakeup_rate WINDOW, NORMALIZED TO [0, 1].
519 // POSITIVE = WORKLOAD ENTERED A MORE-DISORDERED
520 // ORDINAL REGIME WITHIN THE LAST SECOND.
521 pub hvg_lambda: f64,
522 pub bp_h_delta: f64,
523 // RQA DETERMINISM OVER THE SAME idle_pct WINDOW HVG SEES.
524 // 1.0 = PERFECTLY RECURRENT, 0.0 = NO RECURRENCE, None = WINDOW
525 // NOT YET FULL. CONSUMED BY compute_damp() AS THE PRIMARY TRUST
526 // SIGNAL FOR THE DYNAMIC BUTTERWORTH BLEND.
527 pub rqa_det: Option<f64>,
528}
529
530// SNAPSHOT OF THE BPF DAMPED-HARMONIC OSCILLATOR'S ADAPTIVE STATE.
531// MWU READS THIS BEFORE COMPUTING PATHWAY LOSSES SO IT CAN AVOID
532// DOUBLE-CORRECTING ON RESCUE PRESSURE: WHEN THE OSCILLATOR HAS
533// ALREADY TIGHTENED codel_target_ns TOWARD THE FLOOR, BPF HAS
534// RESPONDED -- MWU STAYS OUT. WHEN THE OSCILLATOR IS NEAR THE
535// CEILING, NO RESCUE PRESSURE EXISTS FOR MWU TO AMPLIFY.
536//
537// ALL FIELDS ZERO = SENTINEL ("READBACK UNAVAILABLE / INIT") -> NO GATING.
538#[derive(Clone, Copy, Debug, Default)]
539pub struct OscillatorState {
540 pub codel_target_ns: u64,
541 pub codel_target_floor_ns: u64,
542 pub codel_target_max_ns: u64,
543 // HOME NEAREST-PEER PHI HOLD (reff_value SLOT 0, ns). THE BPF WARM-STAY
544 // AND STEP-1 R_eff STEAL RELEASE AT codel_target_ns + THIS, NOT AT THE
545 // BARE CODEL WINDOW. position() MEASURES THE BARE WINDOW; THE ABSOLUTE
546 // CHECKS BELOW ADD THIS TERM SO THE DEFER DECISION IS TAKEN AGAINST THE
547 // EFFECTIVE PHI RELEASE POINT THE BPF ACTUALLY USES. 0 = READBACK
548 // UNAVAILABLE -> TREATED AS NO EXTRA HOLD (NEUTRAL).
549 pub home_dist_extra_ns: u64,
550}
551
552impl OscillatorState {
553 // 0.0 = AT FLOOR (TIGHTENED), 1.0 = AT MAX (RELAXED).
554 // SENTINEL OR DEGENERATE RANGE -> 0.5 (CENTER, NEUTRAL).
555 pub fn position(&self) -> f64 {
556 if self.codel_target_max_ns == 0 || self.codel_target_floor_ns >= self.codel_target_max_ns {
557 return 0.5;
558 }
559 let range = (self.codel_target_max_ns - self.codel_target_floor_ns) as f64;
560 let pos = self
561 .codel_target_ns
562 .saturating_sub(self.codel_target_floor_ns) as f64;
563 (pos / range).clamp(0.0, 1.0)
564 }
565
566 // EFFECTIVE PHI RELEASE POINT: WHERE WARM-STAY / STEP-1 STEAL ACTUALLY LET
567 // A TASK LEAVE HOME (codel_target + NEAREST-PEER HOLD). THE DEFER GATE
568 // COMPARES THIS AGAINST codel_eq TO DECIDE WHETHER THE BPF HAS GENUINELY
569 // RESPONDED, RATHER THAN TRUSTING THE BARE-WINDOW position() ALONE.
570 pub fn effective_release_ns(&self) -> u64 {
571 self.codel_target_ns.saturating_add(self.home_dist_extra_ns)
572 }
573}
574
575pub struct MwuController {
576 // PER-REGIME WEIGHT VECTORS, INDEXED BY `regime as usize`
577 // (Light=0, Mixed=1, Heavy=2). EACH REGIME KEEPS ITS OWN LEARNED
578 // VECTOR INSTEAD OF BEING RESET ON EVERY TRANSITION.
579 weights: [[f64; N_EXPERTS]; 3],
580 // ONE-TICK-AGO SNAPSHOT PER REGIME -- THE BUTTERWORTH DAMPING
581 // TARGET AND THE L1-MOVEMENT REFERENCE.
582 prev_weights: [[f64; N_EXPERTS]; 3],
583 // PER-REGIME RING OF L1 WEIGHT-MOVEMENT SAMPLES (CONVERGENCE).
584 weight_move_hist: [[f64; VAR_HIST]; 3],
585 move_hist_head: [usize; 3],
586 move_hist_filled: [usize; 3],
587 baseline: TuningKnobs,
588 // CACHED BLEND OUTPUT -- RETURNED DIRECTLY WHEN THE ACTIVE VECTOR
589 // DID NOT MOVE THIS TICK (COARSE DIRTY-TRACKING).
590 last_knobs: TuningKnobs,
591 healthy_streak: u32,
592 // WARM-UP GATE COUNTER, RESET PER REGIME CHANGE.
593 warmup_ticks: u32,
594 prev_io_bucket: IoBucket,
595 prev_rescuing: bool,
596 prev_lambda_chaotic: bool,
597 // LAST TICK'S CROSS-DOMAIN SCATTER %. PATHWAY 6 IS RISING-EDGE GATED ON THIS
598 // SO A STABLE HIGH-SCATTER REGIME (E.G. 8C+ THROUGHPUT WHERE IT WINS) IS
599 // NOT PENALIZED -- ONLY A CLIMBING STORM IS.
600 prev_scatter_pct: u64,
601 losses_applied: bool,
602 // TRUE WHEN THE ACTIVE WEIGHT VECTOR MOVED THIS TICK.
603 weights_changed: bool,
604}
605
606impl MwuController {
607 pub fn new(baseline: TuningKnobs) -> Self {
608 Self {
609 weights: [EQUILIBRIUM; 3],
610 prev_weights: [EQUILIBRIUM; 3],
611 weight_move_hist: [[0.0; VAR_HIST]; 3],
612 move_hist_head: [0; 3],
613 move_hist_filled: [0; 3],
614 baseline,
615 last_knobs: baseline,
616 healthy_streak: 0,
617 warmup_ticks: 0,
618 prev_io_bucket: IoBucket::Mid,
619 prev_rescuing: false,
620 prev_lambda_chaotic: false,
621 prev_scatter_pct: 0,
622 losses_applied: false,
623 weights_changed: false,
624 }
625 }
626
627 // RESET ONE REGIME'S WEIGHT VECTOR + THE CROSS-PATHWAY EDGE STATE.
628 // CALLED BY THE MONITOR LOOP ON A REGIME TRANSITION: a regime change
629 // IS A DISTURBANCE; STALE EDGE STATE SHOULD NOT CARRY OVER, AND THE
630 // WARM-UP GATE RE-ARMS.
631 pub fn reset_regime(&mut self, r: Regime) {
632 let ri = r as usize;
633 self.weights[ri] = EQUILIBRIUM;
634 self.prev_weights[ri] = EQUILIBRIUM;
635 self.weight_move_hist[ri] = [0.0; VAR_HIST];
636 self.move_hist_head[ri] = 0;
637 self.move_hist_filled[ri] = 0;
638 self.healthy_streak = 0;
639 self.warmup_ticks = 0;
640 self.prev_io_bucket = IoBucket::Mid;
641 self.prev_rescuing = false;
642 self.prev_lambda_chaotic = false;
643 self.prev_scatter_pct = 0;
644 self.losses_applied = false;
645 self.weights_changed = false;
646 self.last_knobs = self.baseline;
647 }
648
649 pub fn set_baseline(&mut self, baseline: TuningKnobs) {
650 self.baseline = baseline;
651 }
652
653 // WEIGHT-VARIANCE CONVERGENCE DETECTOR. TRUE WHEN THE ACTIVE
654 // REGIME'S WEIGHT VECTOR HAS STOPPED MOVING -- BOTH HALVES OF THE
655 // MOVEMENT RING BELOW CONVERGE_MOVE_EPS AND THEIR RATIO NEAR 1.
656 // RETURNS FALSE UNTIL THE RING HAS FILLED (NO FREEZING ON A FRESH
657 // OR JUST-RESET CONTROLLER).
658 pub fn converged(&self, r: Regime) -> bool {
659 let ri = r as usize;
660 if self.move_hist_filled[ri] < VAR_HIST {
661 return false;
662 }
663 let hist = &self.weight_move_hist[ri];
664 let head = self.move_hist_head[ri];
665 let half = VAR_HIST / 2;
666 let mut recent = 0.0;
667 let mut older = 0.0;
668 for k in 0..half {
669 recent += hist[(head + VAR_HIST - 1 - k) % VAR_HIST];
670 older += hist[(head + k) % VAR_HIST];
671 }
672 let recent_mean = recent / half as f64;
673 let older_mean = older / half as f64;
674 if recent_mean >= CONVERGE_MOVE_EPS || older_mean >= CONVERGE_MOVE_EPS {
675 return false;
676 }
677 // BOTH HALVES BELOW EPS. IF THE OLDER HALF IS ESSENTIALLY ZERO
678 // BOTH ARE ZERO -> CONVERGED. OTHERWISE REQUIRE THE RATIO NEAR 1
679 // (NOT STILL DECAYING).
680 if older_mean < 1e-12 {
681 return true;
682 }
683 ((recent_mean / older_mean) - 1.0).abs() <= CONVERGE_RATIO_DELTA
684 }
685
686 pub fn update(
687 &mut self,
688 sig: &MwuSignals,
689 ceiling: u64,
690 _nr_cpus: u64,
691 tau_ns: u64,
692 osc: &OscillatorState,
693 regime: Regime,
694 ) -> TuningKnobs {
695 let ri = regime as usize;
696 let worst = sig.p99_ns.max(sig.interactive_p99_ns);
697 let above = worst > ceiling;
698 let below_relax = (worst as f64) < (ceiling as f64 * RELAX_CEIL_PCT);
699
700 // OSCILLATOR-AWARE GATING. THE BPF DAMPED OSCILLATOR ALREADY
701 // CONSUMES global_rescue_count AND ADAPTS codel_target_ns ON
702 // EVERY TICK. PATHWAYS THAT TRIGGER ON THE SAME RESCUE SIGNAL
703 // (PATHWAY 2 RESCUE-DELTA, PATHWAY 4 FORK-STORM) SHOULD DEFER
704 // TO IT WHEN THE OSCILLATOR HAS ALREADY MOVED -- OTHERWISE BOTH
705 // CONTROLLERS PUSH IN THE SAME DIRECTION AND OVERSHOOT.
706 //
707 // POSITION 0.0 = TIGHTENED (FLOOR), 1.0 = RELAXED (MAX).
708 // < 0.40 -> BPF HAS RESPONDED HEAVILY; SKIP RESCUE-DRIVEN LOSSES.
709 // > 0.90 -> BPF SAYS QUIET; RESCUE BURSTS ARE STALE NOISE; SKIP.
710 //
711 // position() MEASURES THE BARE codel WINDOW, BUT THE BPF ACTUALLY
712 // RELEASES WARM-STAY / STEP-1 STEAL AT codel_target + home_dist_extra
713 // (THE NEAREST-PEER PHI HOLD). WITH A LARGE HOLD THE BARE POSITION CAN
714 // READ "LOOSE" WHILE THE EFFECTIVE RELEASE IS STILL HARD, AND MWU WOULD
715 // WRONGLY STAY OUT OF GENUINE RESCUE PRESSURE. CONFIRM EACH SIDE WITH
716 // THE EFFECTIVE RELEASE VS THE PHI EQUILIBRIUM (codel_eq, FROM THE
717 // BASELINE): ONLY TREAT THE OSCILLATOR AS TIGHT WHEN THE EFFECTIVE
718 // RELEASE IS BELOW EQUILIBRIUM, AND AS LOOSE WHEN IT IS ABOVE. WITH
719 // home_dist_extra_ns = 0 (READBACK UNAVAILABLE) THIS REDUCES TO THE
720 // BARE-POSITION BEHAVIOR. codel_eq = 0 (UNSEEDED) DISABLES THE ABSOLUTE
721 // QUALIFIER (THE BARE POSITION GOVERNS), NEVER GATING SPURIOUSLY.
722 let osc_pos = osc.position();
723 let eff_release = osc.effective_release_ns();
724 let codel_eq = self.baseline.codel_eq_ns;
725 let osc_already_tight = osc_pos < 0.40 && (codel_eq == 0 || eff_release < codel_eq);
726 let osc_already_loose = osc_pos > 0.90 && (codel_eq == 0 || eff_release > codel_eq);
727 let defer_to_oscillator = osc_already_tight || osc_already_loose;
728
729 let mut losses = [0.0f64; N_EXPERTS];
730 let mut has_loss = false;
731
732 // PATHWAY 1: P99 SPIKE (FIRES IMMEDIATELY)
733 if above {
734 self.healthy_streak = 0;
735 let v = ((worst - ceiling) as f64 / ceiling as f64).min(3.0);
736 losses[EX_BALANCED] += v * 0.5;
737 losses[EX_THROUGHPUT] += v * 1.0;
738 losses[EX_IO_HEAVY] += v * 0.6;
739 losses[EX_FORK_STORM] += v * 0.3;
740 losses[EX_SATURATED] += v * 0.9;
741 has_loss = true;
742 }
743
744 // PATHWAY 2: RESCUE DELTA (0 -> NONZERO TRANSITION)
745 // PATHWAY 2: RESCUE DELTA (0 -> NONZERO TRANSITION)
746 // PENALIZE ALL EXPERTS EQUALLY: THE DAMPED OSCILLATION IN BPF
747 // HANDLES TIGHTENING VIA THE CODEL TARGET. MWU SHOULD HOLD STEADY,
748 // NOT COMPOUND BY ALSO TIGHTENING SLICES VIA LATENCY EXPERT.
749 let rescuing = sig.rescue_count > 0;
750 if rescuing && !self.prev_rescuing && !defer_to_oscillator {
751 let v = (sig.rescue_count as f64 * 1.5).min(3.0);
752 losses[EX_LATENCY] += v * 0.4;
753 losses[EX_THROUGHPUT] += v * 0.6;
754 losses[EX_SATURATED] += v * 0.6;
755 losses[EX_IO_HEAVY] += v * 0.4;
756 losses[EX_BALANCED] += v * 0.2;
757 has_loss = true;
758 }
759 self.prev_rescuing = rescuing;
760
761 // PATHWAY 3: IO DELTA (BUCKET TRANSITION)
762 let cur_io = io_bucket(sig.io_pct);
763 if cur_io != self.prev_io_bucket {
764 match cur_io {
765 IoBucket::High => {
766 let v = ((sig.io_pct as f64 - 60.0) / 40.0).min(1.0);
767 for i in 0..N_EXPERTS {
768 if i != EX_IO_HEAVY {
769 losses[i] += v * 0.8;
770 }
771 }
772 has_loss = true;
773 }
774 IoBucket::Low => {
775 let v = ((15.0 - sig.io_pct as f64) / 15.0).clamp(0.0, 1.0);
776 losses[EX_IO_HEAVY] += v * 1.0;
777 has_loss = true;
778 }
779 IoBucket::Mid => {}
780 }
781 }
782 self.prev_io_bucket = cur_io;
783
784 // PATHWAY 4: FORK STORM (PRESSURE-CONFIRMED, FIRES IMMEDIATELY).
785 // GATE THRESHOLD IS TAU-DERIVED. sig.wakeup_rate IS THE RAW TOTAL
786 // WAKES/SEC; scale_tau_u64(tau, K_FORK_STORM_RATE_Q16) PRODUCES
787 // THE COMPARISON THRESHOLD. AT THE 12C REFERENCE (tau=40MS) THE
788 // THRESHOLD IS ~8000/SEC; AT 4C IT TIGHTENS TO ~1200/SEC. A FORK
789 // STORM ALSO REQUIRES ACTIVE RESCUES (rescue_count > 0) AS GROUND-
790 // TRUTH PRESSURE -- THE PRESSURE PREDICATE IS THE CONFIRMATION;
791 // NO ADDITIONAL STREAK COUNTER.
792 //
793 // FORK_STORM EXPERT (SC_SLICE=0.49, SC_PREEMPT=0.49, SC_BATCH=0.52,
794 // SC_SOJOURN=0.53, SC_BURST=0.49) DOMINATES THE BLEND DURING A REAL
795 // STORM, DRIVING burst_slice_ns / preempt_thresh_ns / sojourn_thresh_ns
796 // / batch_slice_ns DOWN END-TO-END.
797 let fork_thresh = scale_tau_u64(tau_ns, K_FORK_STORM_RATE_Q16).max(FORK_STORM_RATE_FLOOR);
798 let fork_storm = sig.wakeup_rate > fork_thresh && sig.rescue_count > 0;
799 if fork_storm && !defer_to_oscillator {
800 let denom = fork_thresh.max(1) as f64;
801 let v = ((sig.wakeup_rate as f64 / denom) - 1.0).clamp(0.0, 3.0);
802 losses[EX_BALANCED] += v * 0.30;
803 losses[EX_THROUGHPUT] += v * 1.00;
804 losses[EX_IO_HEAVY] += v * 0.50;
805 losses[EX_SATURATED] += v * 0.80;
806 has_loss = true;
807 }
808
809 // PATHWAY 5: CHAOS TRANSITION (RAW-WINDOW, NO EWMA, NO SCHMITT)
810 // FIRES WHEN THE WORKLOAD'S ORDINAL OR AMPLITUDE-DEGREE STRUCTURE
811 // SHIFTS WITHIN THE LAST WINDOW. THE FAILED-PREDICTION INTERPRETATION
812 // IS: WHICHEVER EXPERT IS CURRENTLY DOMINANT WAS PRICED FOR THE
813 // PREVIOUS WINDOW; PENALIZE IT PROPORTIONAL TO THE TRANSITION SIZE
814 // SO THE BLEND RAPIDLY RE-WEIGHTS TOWARD BALANCED / SATURATED.
815 // EX_BALANCED IS EXEMPT (IT IS THE REGIME-AGNOSTIC BASELINE).
816 //
817 // GATE CONDITIONS (EITHER FIRES):
818 // - bp_h_delta > 0.10 (>10% NORMALIZED PERMUTATION-ENTROPY JUMP)
819 // - hvg_lambda CROSSED CHAOTIC_MIN UPWARD
820 let bp_jump = sig.bp_h_delta > 0.10;
821 let lambda_cross =
822 sig.hvg_lambda >= crate::chaos::HVG_LAMBDA_CHAOTIC_MIN && !self.prev_lambda_chaotic;
823 let chaos_transition = bp_jump || lambda_cross;
824 if chaos_transition {
825 // SIZE OF THE LOSS: SCALES WITH WHICHEVER GATE TRIGGERED HARDER.
826 let v_bp = (sig.bp_h_delta / 0.10).clamp(0.0, 3.0);
827 let v_l = if lambda_cross {
828 ((sig.hvg_lambda - crate::chaos::HVG_LAMBDA_CHAOTIC_MIN)
829 / (4.0 - crate::chaos::HVG_LAMBDA_CHAOTIC_MIN))
830 .clamp(0.0, 1.5)
831 } else {
832 0.0
833 };
834 let v = v_bp.max(v_l);
835 // FIND THE DOMINANT EXPERT (HIGHEST WEIGHT) AND PENALIZE IT.
836 // EX_BALANCED IS EXEMPT (THE ANCHOR IS NEVER PENALIZED).
837 let mut dom_idx = EX_BALANCED;
838 let mut dom_w = 0.0f64;
839 for i in 0..N_EXPERTS {
840 if i == EX_BALANCED {
841 continue;
842 }
843 if self.weights[ri][i] > dom_w {
844 dom_w = self.weights[ri][i];
845 dom_idx = i;
846 }
847 }
848 if dom_idx != EX_BALANCED && dom_w > 0.0 {
849 losses[dom_idx] += v * 0.8;
850 has_loss = true;
851 }
852 }
853 self.prev_lambda_chaotic = sig.hvg_lambda >= crate::chaos::HVG_LAMBDA_CHAOTIC_MIN;
854
855 // PATHWAY 6: CROSS-DOMAIN SCATTER (PHI-AWARE, RISING-EDGE GATED).
856 // sig.scatter_pct IS THE PLACEMENT-SIDE CROSS-DOMAIN MIGRATION FRACTION
857 // (XDOM_SEL_* + XDOM_ENQ_T1, EXCLUDING THE PHI-CORRECT STEAL/STEP5
858 // WORK-CONSERVATION PATHS -- COMPUTED IN THE ADAPTIVE LOOP). WHEN IT
859 // CLIMBS PAST THE EEVDF-BASELINE HEADROOM THE BLEND HAS DRIFTED TOWARD
860 // EXPERTS THAT SCATTER WAKEES THE BPF WARM-STAY/PER-CPU LANDING TRIED
861 // TO KEEP HOME. PENALIZE THE EXPERTS THAT FAVOR WIDE SLICES + WEAK/OFF
862 // AFFINITY (THROUGHPUT, SATURATED, FORK_STORM) SO THE BLEND RE-WEIGHTS
863 // TOWARD LATENCY/BALANCED AND AFFINITY STRONG, ALIGNING THE ADAPTIVE
864 // LAYER WITH PHI PLACEMENT INSTEAD OF FIGHTING IT.
865 //
866 // RISING-EDGE: ONLY FIRES WHILE SCATTER IS HIGH AND INCREASING. A
867 // STABLE HIGH-SCATTER REGIME (8C+ THROUGHPUT, WHERE WIDE-SLICE SCATTER
868 // IS A WINNING TRADE) PLATEAUS AND IS NOT PENALIZED -- ONLY A CLIMBING
869 // STORM IS. EX_BALANCED/EX_LATENCY ARE NEVER PENALIZED (THEY ARE THE
870 // DIRECTION WE WANT THE BLEND TO MOVE TOWARD).
871 let scatter_high = sig.scatter_pct > SCATTER_THRESH_PCT;
872 let scatter_rising = scatter_high && sig.scatter_pct > self.prev_scatter_pct;
873 if scatter_rising {
874 let v = ((sig.scatter_pct as f64 - SCATTER_THRESH_PCT as f64)
875 / SCATTER_THRESH_PCT as f64)
876 .clamp(0.0, 3.0);
877 losses[EX_THROUGHPUT] += v * 1.00;
878 losses[EX_SATURATED] += v * 0.80;
879 losses[EX_FORK_STORM] += v * 0.60;
880 has_loss = true;
881 }
882 self.prev_scatter_pct = sig.scatter_pct;
883
884 // WARM-UP GATE. THE PATHWAYS ABOVE HAVE SCORED `losses` AND
885 // UPDATED THE prev_* EDGE STATE. FOR THE FIRST WARMUP_TICKS
886 // CALLS PER REGIME, SKIP THE WEIGHT UPDATE ENTIRELY AND RETURN
887 // BASELINE -- THE FIRST NOISY TICKS AFTER START / REGIME CHANGE
888 // MUST NOT YANK KNOBS.
889 if self.warmup_ticks < WARMUP_TICKS {
890 self.warmup_ticks += 1;
891 self.losses_applied = has_loss;
892 self.weights_changed = false;
893 let mut k = self.baseline;
894 k.topology_tau_ns = 0;
895 k.codel_eq_ns = 0;
896 self.last_knobs = k;
897 return k;
898 }
899
900 // ACTIVE WEIGHT VECTOR -- LOCAL COPY, MUTATED THROUGH THE
901 // LOSS / RELAX / DAMP STAGES THEN COMMITTED BACK.
902 let mut w = self.weights[ri];
903
904 // APPLY LOSSES WITH WEIGHT FLOOR.
905 if has_loss {
906 for i in 0..N_EXPERTS {
907 if losses[i] > 0.0 {
908 w[i] *= (-ETA * losses[i]).exp();
909 }
910 if w[i] < WEIGHT_FLOOR {
911 w[i] = WEIGHT_FLOOR;
912 }
913 }
914 let sum: f64 = w.iter().sum();
915 for x in w.iter_mut() {
916 *x /= sum;
917 }
918 }
919
920 // RELAXATION -- TOWARD THE NO-OP-SKEWED EQUILIBRIUM.
921 if !has_loss && below_relax {
922 self.healthy_streak += 1;
923 if self.healthy_streak >= RELAX_HOLD {
924 for i in 0..N_EXPERTS {
925 w[i] = (1.0 - RELAX_RATE) * w[i] + RELAX_RATE * EQUILIBRIUM[i];
926 }
927 }
928 } else if !has_loss {
929 self.healthy_streak = 0;
930 }
931
932 // DYNAMIC BUTTERWORTH DAMPING + ADAPTIVE STEP. `w` IS NOW THE
933 // RAW POST-LOSS / POST-RELAX TARGET; LOW-PASS BLEND IT TOWARD
934 // THE ONE-TICK-AGO VECTOR. DAMP IS DRIVEN BY SIGNAL TRUST
935 // (RQA-DET + HVG-LAMBDA), NOT CPU COUNT -- SEE compute_damp.
936 // residual = aggregate loss this tick: a big disturbance takes
937 // a smaller corrective step (anti-overshoot governor).
938 let residual: f64 = losses.iter().sum();
939 let step = STEP_BASE / (1.0 + residual);
940 let damp = compute_damp(sig.rqa_det, sig.hvg_lambda);
941 let eff = (damp * step).clamp(0.0, 1.0);
942 let prev = self.prev_weights[ri];
943 for i in 0..N_EXPERTS {
944 w[i] = eff * w[i] + (1.0 - eff) * prev[i];
945 if w[i] < WEIGHT_FLOOR {
946 w[i] = WEIGHT_FLOOR;
947 }
948 }
949 let sum: f64 = w.iter().sum();
950 for x in w.iter_mut() {
951 *x /= sum;
952 }
953
954 // L1 MOVEMENT FROM LAST TICK -- FEEDS THE CONVERGENCE DETECTOR.
955 let mut move_l1 = 0.0;
956 for i in 0..N_EXPERTS {
957 move_l1 += (w[i] - prev[i]).abs();
958 }
959 let h = self.move_hist_head[ri];
960 self.weight_move_hist[ri][h] = move_l1;
961 self.move_hist_head[ri] = (h + 1) % VAR_HIST;
962 if self.move_hist_filled[ri] < VAR_HIST {
963 self.move_hist_filled[ri] += 1;
964 }
965
966 // COMMIT: w_new IS BOTH THE ACTIVE VECTOR AND NEXT TICK'S
967 // prev SNAPSHOT.
968 self.weights[ri] = w;
969 self.prev_weights[ri] = w;
970 self.weights_changed = move_l1 > WEIGHTS_MOVED_EPS;
971 self.losses_applied = has_loss;
972
973 // COARSE DIRTY-TRACKING: IF THE VECTOR DID NOT MOVE, THE BLEND
974 // OUTPUT IS IDENTICAL TO LAST TICK -- RETURN THE CACHE AND SKIP
975 // THE BLEND ENTIRELY.
976 if !self.weights_changed {
977 return self.last_knobs;
978 }
979
980 // BLEND: CONTINUOUS KNOBS VIA SCALE FACTORS, DISCRETE VIA MAJORITY.
981 let b = &self.baseline;
982 let blended_slice = blend_continuous(b.slice_ns, &SC_SLICE, &w);
983 let blended_burst = blend_continuous(b.burst_slice_ns, &SC_BURST, &w);
984 let mut blended_sojourn = blend_continuous(b.sojourn_thresh_ns, &SC_SOJOURN, &w);
985
986 // SOJOURN FLOOR: tick() KICKS PER-CPU DSQs WHOSE OLDEST TASK HAS AGED
987 // PAST sojourn_thresh_ns (BPF, main.bpf.c:2374). THAT EVICTION DEADLINE
988 // MUST NOT SIT ABOVE THE THRESHOLD WARM-STAY / STEP-1 R_eff STEAL
989 // ACTUALLY RELEASE AT, OR THE ADAPTIVE LAYER HOLDS TASKS LONG PAST THE
990 // POINT THE PHI GATES ASSUMED RELIEF HAD ARRIVED (THE 2C/4C LONG-RUN /
991 // LAT P99 TAIL). THE BPF OVERFLOW RESCUE IS NO LONGER "[4ms,10ms]" --
992 // IT WAS REWIRED TO THE PHI EQUILIBRIUM (overflow_sojourn_rescue_ns =
993 // codel_target_equilibrium_ns, main.bpf.c:995), SUB-MS AT LOW CORE.
994 // ANCHOR THE FLOOR TO THAT SAME PHI-DERIVED EQUILIBRIUM SO USERSPACE
995 // EVICTION AND THE BPF PHI RELEASE AGREE. THE +blended_slice TERM IS
996 // THE GENUINE DISPATCH-SERVICE-WINDOW GUARD (KEPT). codel_eq_ns CAN BE
997 // SEEDED 0 BY MwuController::new BEFORE THE TOPOLOGY OVERLAY LANDS --
998 // GUARD AGAINST COLLAPSING THE FLOOR AND REINTRODUCING THE KICK STORM.
999 let floor_base = if b.codel_eq_ns >= 200_000 {
1000 b.codel_eq_ns
1001 } else {
1002 4_000_000
1003 };
1004 let sojourn_floor = floor_base.saturating_add(blended_slice);
1005 if blended_sojourn < sojourn_floor {
1006 blended_sojourn = sojourn_floor;
1007 }
1008
1009 let k = TuningKnobs {
1010 slice_ns: blended_slice,
1011 // FLOOR AT REGIME BASELINE: MWU may loosen preempt (raise it) but
1012 // never tighten below baseline -- sub-ms preempt at 2C thrashed the
1013 // longrun (the ADAPTIVE 2C tail). No-op where MWU stays above it.
1014 preempt_thresh_ns: blend_continuous(b.preempt_thresh_ns, &SC_PREEMPT, &w)
1015 .max(b.preempt_thresh_ns),
1016 batch_slice_ns: blend_continuous(b.batch_slice_ns, &SC_BATCH, &w),
1017 lat_cri_thresh_high: blend_continuous(b.lat_cri_thresh_high, &SC_LCRI_HI, &w),
1018 lat_cri_thresh_low: blend_continuous(b.lat_cri_thresh_low, &SC_LCRI_LO, &w),
1019 affinity_mode: majority_discrete(&DV_AFFINITY, &w),
1020 sojourn_thresh_ns: blended_sojourn,
1021 burst_slice_ns: blended_burst,
1022 // topology_tau_ns AND codel_eq_ns ARE OWNED BY THE TOPOLOGY LAYER;
1023 // MWU DOESN'T TOUCH THEM. THE MONITOR LOOP OVERLAYS THE LIVE BPF
1024 // VALUES BACK ONTO MWU'S OUTPUT BEFORE WRITING -- PASSTHROUGH.
1025 topology_tau_ns: 0,
1026 codel_eq_ns: 0,
1027 };
1028 self.last_knobs = k;
1029 k
1030 }
1031
1032 pub fn had_losses(&self) -> bool {
1033 self.losses_applied
1034 }
1035
1036 pub fn scale(&self, r: Regime) -> f64 {
1037 let w = &self.weights[r as usize];
1038 (0..N_EXPERTS).map(|i| w[i] * SC_SLICE[i]).sum()
1039 }
1040}
1041
1042// QUIESCENCE GATE
1043// DETECTS STEADY STATE CHEAPLY AND LATCHES A "FROZEN" FLAG THAT TELLS
1044// THE MONITOR LOOP TO SKIP THE EXPENSIVE MWU RETUNE + KNOB WRITE. THE
1045// LOOP STILL TICKS AT 1 HZ -- THE CHAOS SENSORS ARE THE EXIT CONDITION
1046// FOR FROZEN MODE -- BUT THE ORCHESTRATOR MACHINERY STOPS.
1047//
1048// STEADY STATE IS THE CONJUNCTION OF THREE SIGNALS, HELD FOR
1049// QUIESCE_ENTER_TICKS CONSECUTIVE TICKS:
1050// - HVG MEAN DEGREE IN THE PERIODIC BAND (lambda <= PERIODIC_MAX);
1051// NOTE THE Engine A/B SHORTHAND "lambda ~= ln(3/2)" REFERS TO THE
1052// IID *CHARACTERISTIC EXPONENT*, NOT A lambda VALUE -- THE GATE
1053// USES THE PERIODIC-BAND THRESHOLD.
1054// - RQA DETERMINISM AT OR ABOVE RQA_DET_STEADY_MIN.
1055// - THE ACTIVE-REGIME MWU WEIGHT VECTOR HAS CONVERGED.
1056// EXIT IS IMMEDIATE WHEN THE SIGNAL LEAVES THE BAND -- THE STREAK
1057// COUNTER PROVIDES ENTRY HYSTERESIS, NO EXIT HYSTERESIS NEEDED.
1058
1059// CONSECUTIVE IN-BAND TICKS BEFORE THE GATE LATCHES (~1/4 OF THE
1060// 16-TICK CHAOS WINDOW).
1061pub const QUIESCE_ENTER_TICKS: u32 = 4;
1062
1063pub struct QuiescenceState {
1064 in_band_streak: u32,
1065 frozen: bool,
1066}
1067
1068impl Default for QuiescenceState {
1069 fn default() -> Self {
1070 Self::new()
1071 }
1072}
1073
1074impl QuiescenceState {
1075 pub const fn new() -> Self {
1076 Self {
1077 in_band_streak: 0,
1078 frozen: false,
1079 }
1080 }
1081
1082 // ADVANCE THE GATE ONE TICK. RETURNS THE LATCHED `frozen` FLAG.
1083 // rqa_det IS None WHEN THE WINDOW IS NOT YET FULL -- THAT NEVER
1084 // COUNTS AS IN-BAND (NEVER FREEZE ON INSUFFICIENT DATA).
1085 pub fn update(&mut self, hvg_lambda: f64, rqa_det: Option<f64>, mwu_converged: bool) -> bool {
1086 let in_band = hvg_lambda <= crate::chaos::HVG_LAMBDA_PERIODIC_MAX
1087 && rqa_det.map_or(false, |d| d >= crate::chaos::RQA_DET_STEADY_MIN)
1088 && mwu_converged;
1089
1090 if in_band {
1091 self.in_band_streak = self.in_band_streak.saturating_add(1);
1092 if self.in_band_streak >= QUIESCE_ENTER_TICKS {
1093 self.frozen = true;
1094 }
1095 } else {
1096 self.in_band_streak = 0;
1097 self.frozen = false;
1098 }
1099 self.frozen
1100 }
1101}
1102
1103// ADAPTIVE-RARITY RETUNE INTERVAL
1104// WHEN THE ORCHESTRATOR IS NOT FROZEN BUT A RETUNE PRODUCES ONLY A
1105// SUB-THRESHOLD KNOB DELTA, STRETCH THE INTERVAL BETWEEN RETUNES x1.5
1106// (UP TO A FORCED CEILING) SO THE LOOP CONVERGES TOWARD QUIESCENCE.
1107// ANY DISTURBANCE SNAPS IT BACK TO THE BASE INTERVAL.
1108
1109pub const RETUNE_INTERVAL_BASE: u32 = 1;
1110pub const RETUNE_INTERVAL_MAX: u32 = 8;
1111
1112pub fn next_retune_interval(cur: u32, sub_threshold: bool, disturbed: bool) -> u32 {
1113 if disturbed {
1114 RETUNE_INTERVAL_BASE
1115 } else if sub_threshold {
1116 // x1.5 STRETCH, BUT ALWAYS GROW BY AT LEAST 1 -- INTEGER x1.5
1117 // OF THE BASE INTERVAL (1) WOULD OTHERWISE STALL AT 1.
1118 let stretched = (cur.saturating_mul(3) / 2).max(cur + 1);
1119 stretched.clamp(RETUNE_INTERVAL_BASE, RETUNE_INTERVAL_MAX)
1120 } else {
1121 cur
1122 }
1123}
1124
1125// COMMIT-ON-CHANGE: TRUE IFF THE TWO KNOB SETS DIFFER ON ANY
1126// MWU-OWNED FIELD. topology_tau_ns / codel_eq_ns ARE EXCLUDED -- THEY
1127// ARE OWNED BY THE TOPOLOGY LAYER AND WRITTEN INDEPENDENTLY VIA
1128// write_topology_fields(); INCLUDING THEM WOULD SPURIOUSLY TRIP THE
1129// DIFF EVERY TICK THE LOOP OVERLAYS THEM.
1130pub fn knobs_differ(a: &TuningKnobs, b: &TuningKnobs) -> bool {
1131 a.slice_ns != b.slice_ns
1132 || a.preempt_thresh_ns != b.preempt_thresh_ns
1133 || a.batch_slice_ns != b.batch_slice_ns
1134 || a.lat_cri_thresh_high != b.lat_cri_thresh_high
1135 || a.lat_cri_thresh_low != b.lat_cri_thresh_low
1136 || a.affinity_mode != b.affinity_mode
1137 || a.sojourn_thresh_ns != b.sojourn_thresh_ns
1138 || a.burst_slice_ns != b.burst_slice_ns
1139}