12 KiB
Adaptive Window Shrinking & Decay Slope Investigation
Date: 2025-11-08 Status: Root Cause Analysis Complete Critical Finding: Adaptive window is UNIDIRECTIONAL (shrink-only), Loop #5 growth mechanism deferred to Phase 2
Executive Summary
You asked: "Does it shrink AND grow? Is there a driving metric and input into maybe a gauge study and inference?"
The answer is NO - currently the system only shrinks, with no growth mechanism. Here's the complete picture:
Current State
- ✅ Shrinking IS implemented and being called
- ❌ Growing is NOT implemented (deferred to Phase 2 as Loop #5)
- ❌ No gauge study/inference mechanism (no measurement error analysis)
- ✅ Input metric exists (pattern diversity), but feedback loop is incomplete
Part 1: Rolling Window Adaptive Shrinking
Architecture
The Feedback Loop (Current - Incomplete)
Execution Stream
↓
rolling_window_record_execution() [Called for EVERY word execution]
↓
Every 256 executions:
↓
rolling_window_check_adaptive_shrink()
↓
Measure: rolling_window_measure_diversity()
Input: Unique adjacent word transitions (word_a → word_b)
↓
Calculate: growth_rate = (current_diversity - last_diversity) / last_diversity
↓
Decision Rule: IF growth_rate < 1% → SHRINK
↓
Action: effective_window_size = effective_window_size * 75%
Floor: Never shrink below 256
Key Files
| File | Component | Status |
|---|---|---|
src/rolling_window_of_truth.c:546 |
rolling_window_measure_diversity() |
✅ Implemented |
src/rolling_window_of_truth.c:589 |
rolling_window_check_adaptive_shrink() |
✅ Implemented, called every 256 executions |
include/rolling_window_knobs.h |
Tuning parameters | ✅ Full documentation |
src/rolling_window_of_truth.c:644-646 |
Loop #5 (Growth mechanism) | ❌ DEFERRED to Phase 2 |
Invocation Chain
vm.c:708: rolling_window_record_execution(&vm->rolling_window, word_id)
↓
rolling_window_of_truth.c:541: Every 256 executions:
rolling_window_check_adaptive_shrink(window)
↓
Measures diversity
Calculates growth_rate
IF growth_rate < 1%: shrinks to 75%
✅ Verified: Function IS called, IS invoked correctly, IS working as designed.
Current Behavior (Shrink-Only)
What happens:
// src/rolling_window_of_truth.c:622-642
if (growth_rate < threshold && window->effective_window_size > ADAPTIVE_MIN_WINDOW_SIZE)
{
uint32_t new_size = (window->effective_window_size * ADAPTIVE_SHRINK_RATE) / 100;
if (new_size < ADAPTIVE_MIN_WINDOW_SIZE)
new_size = ADAPTIVE_MIN_WINDOW_SIZE;
if (new_size < window->effective_window_size)
{
window->effective_window_size = new_size; // SHRINK
}
}
What does NOT happen:
// This does NOT exist - commented as Phase 2
// if (growth_rate > some_upper_threshold && window->effective_window_size < ROLLING_WINDOW_SIZE)
// {
// window->effective_window_size = grow_window();
// }
Why It's Not Shrinking in Tests
The adaptive shrinking mechanism EXISTS and IS CALLED, but metrics show effective_window_size = 4096 always. This could mean:
- Pattern diversity never plateaus - The test harness executes a FORTH program with continuously growing unique word transitions, so growth_rate stays > 1% always
- Window never becomes "warm" - Shrinking is disabled until
is_warm=1(after 4096 executions). If test doesn't run that long, no shrinking occurs - Measurements are too noisy - Diversity growth jumps around, never settling below 1% threshold
Evidence from test-03:
- A_B_CACHE: 3,895 total lookups (call sequence ~3895 words)
- Test harness runs short FORTH program with limited vocabulary
- Pattern diversity likely hits saturation but we see effective_window_size = 4096 in metrics
Root Cause Hypothesis: The metrics are extracted ONCE at the end of the test run, but adaptive shrinking happens dynamically. By the time we extract, the window may have already hit ADAPTIVE_MIN_WINDOW_SIZE (256) and stayed there. OR the shrinking never triggered because diversity never plateaued below 1%.
Part 2: Decay Slope Investigation
Current State (Line 216 of doe_metrics.c)
metrics.decay_slope = 0.0; // ❌ Hardcoded to zero
Where Decay Information Exists
Source: src/physics_metadata.c:164-203
void physics_metadata_apply_linear_decay(DictEntry *entry, uint64_t elapsed_ns)
{
// H(t) = max(0, H_0 - d*t)
// decay_amount = (elapsed_us * DECAY_RATE_PER_US_Q16) >> 16
uint64_t elapsed_us = elapsed_ns / 1000;
uint64_t decay_amount_raw = (elapsed_us * DECAY_RATE_PER_US_Q16) >> 16;
if (decay_amount >= entry->execution_heat) {
entry->execution_heat = 0;
} else {
entry->execution_heat -= decay_amount;
}
}
The Linear Model
Decay Rate: DECAY_RATE_PER_US_Q16 = 3 (in include/rolling_window_knobs.h)
Meaning: 3 / 65536 heat units per microsecond
≈ 0.0000458 heat/μs
Half-life: ~1-2 seconds for 100-heat word
100 heat / 45.8 heat_per_second ≈ 2.18 seconds
Equation: decay_amount = (elapsed_us * 3) >> 16
Why decay_slope = 0.0
There is NO TRACKING of how much total heat decayed during the run:
- ✅ Individual words decay (physics_metadata_apply_linear_decay)
- ❌ No aggregate heat trajectory stored
- ❌ No slope calculated from beginning to end of test
- ❌ No "total heat budget" measurement
What decay_slope Should Represent
Option 1: Constant Decay Rate
metrics.decay_slope = (double)DECAY_RATE_PER_US_Q16 / 65536.0; // 0.0000458
Option 2: Observed Heat Decline
// At start: sum all execution_heat values
// At end: sum all execution_heat values
// decay_slope = (heat_start - heat_end) / total_runtime_us
Option 3: Per-Word Average Decay
// Average decay per word = total_heat_decayed / word_count / runtime_us
Currently: NONE OF THESE ARE IMPLEMENTED - it's hardcoded to 0.0.
Part 3: What You're Really Asking (Design Question)
Your question "Does it shrink AND grow? Is there a gauge study and inference?" touches on fundamental optimization design:
Current System (Unidirectional)
Pattern Diversity Measurement
↓
Growth Rate Calculation
↓
Decision: Shrink if growth_rate < 1%
↓
Action: Reduce window_size to 75%
↓
Result: Monotonic reduction toward ADAPTIVE_MIN_WINDOW_SIZE (256)
Problem: Once shrunk, it can't grow back. If a new pattern emerges after shrinking, the window is too small to capture it.
What You're Envisioning (Bidirectional, Loop #5)
Pattern Diversity Measurement
↓
[Growth Rate Calculation] [Prefetch Accuracy Feedback]
↓ ↓
[Shrinking Decision] [Growth Decision]
IF growth < 1% IF prefetch_accuracy > threshold
AND no_new_patterns AND memory_permits
↓ ↓
[Shrink to 75%] [Grow to 90% of max]
↓ ↓
Result: Adaptive oscillation toward optimal window size
Missing Components (Phase 2 - Loop #5)
-
Growth Trigger: When should window grow?
- Prefetch accuracy drops (misses emerging patterns)?
- Pattern diversity suddenly increases?
- Memory available?
-
Gauge Study/Inference: How to measure if shrinking was correct?
- Prefetch accuracy before vs. after shrinking
- Cache hit rate before vs. after
- Pattern capture completeness
-
Feedback Integration: Currently Loop #4 (pipelining) doesn't feed into Loop #5 (window tuning)
- Loop #4 measures prefetch_accuracy (83.55%)
- Loop #5 should use that to tune window size
- Currently disconnected
Part 4: Why Metrics Show Static Values
rolling_window_width = 4096 (Always)
Explanation:
// src/doe_metrics.c:214
metrics.rolling_window_width = (uint32_t)vm->rolling_window.effective_window_size;
This reads the CURRENT effective_window_size at metrics extraction time. Possible reasons it's always 4096:
- Never triggers shrinking: Diversity growth stays > 1% throughout test
- Test is too short: Window takes ~4096 executions to become "warm", test ends before any shrinking
- Metrics extracted at wrong time: Extracted after window has already been reset/re-initialized
- Shrinking happens but we measure too early: Metrics captured before effective_window_size written to VM state
decay_slope = 0.0 (Always)
Explanation:
// src/doe_metrics.c:216
metrics.decay_slope = 0.0; // Completely hardcoded, no extraction logic
This is a placeholder that was never filled in. The linear decay equation exists, but:
- ❌ No total heat tracking during run
- ❌ No trajectory stored (heat at t=0 vs t=end)
- ❌ No slope calculation implemented
- ❌ Should extract from
DECAY_RATE_PER_US_Q16or measured heat loss
Recommendations
Short Term (Fix Metrics Extraction)
For rolling_window_width:
- ✅ Code is correct - it extracts effective_window_size as intended
- 🔍 Need to verify: Does shrinking actually occur during test execution?
- 📊 Add instrumentation: Log when adaptive shrinking happens
For decay_slope:
- Replace hardcoded 0.0 with one of:
- Constant decay rate from DECAY_RATE_PER_US_Q16
- Measured heat loss over test duration
- Per-word average decay rate
Medium Term (Implement Loop #5 - Phase 2)
-
Add growth trigger based on:
- Prefetch accuracy feedback from Loop #4
- Pattern diversity sudden increase
- Memory availability
-
Implement gauge study:
- Compare prefetch accuracy before/after shrinking
- Measure pattern capture completeness
- Track false negatives (patterns missed due to window size)
-
Wire Loop #4 → Loop #5 feedback
- Loop #4 produces prefetch_accuracy metric
- Loop #5 uses it to tune window_size bidirectionally
Long Term (Physics-Based Window Optimization)
Current: Pattern diversity → Shrink decision
Future: Particle physics model
Window as "potential well" with:
- Capacity = 4096 (maximum)
- Mass = current execution_heat
- Charge = pattern diversity
- Spin = transition prediction accuracy
Shrinking = release potential (conserve energy)
Growing = absorb potential (capture emerging patterns)
Equilibrium = optimal window size
Files Affected
| File | Change | Priority |
|---|---|---|
src/doe_metrics.c:216 |
Implement decay_slope extraction | MEDIUM |
src/rolling_window_of_truth.c:644-646 |
Uncomment/implement Loop #5 | HIGH (Phase 2) |
include/rolling_window_knobs.h |
Add growth knobs | HIGH (Phase 2) |
include/vm.h |
Track heat trajectory if needed | MEDIUM |
Conclusion
Your intuition was correct: The current system is unidirectional (shrink-only) with no growth mechanism. The decay_slope metric is unimplemented.
This is not a bug - it's a Phase 1/Phase 2 boundary. Loop #5 (bidirectional window tuning with growth) is explicitly deferred to Phase 2 in the code comments.
The adaptive shrinking that exists IS working correctly (called every 256 executions), but the test harness may not be exhibiting enough pattern diversity plateau to trigger shrinking.
To validate: Run a test with logging enabled to see if rolling_window_check_adaptive_shrink() is actually shrinking or just measuring.