Files
LithosAnanake/docs/working/architecture/03-architecture/adaptive-systems/adaptive-window-and-decay.md
T

349 lines
12 KiB
Markdown

<!-- Moved from docs/03-architecture/adaptive-systems/adaptive-window-and-decay.md to docs/working/architecture/03-architecture/adaptive-systems/adaptive-window-and-decay.md on 2026-06-16 (docs reorg Phase 2) -->
# 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:**
```c
// 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:**
```c
// 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:
1. **Pattern diversity never plateaus** - The test harness executes a FORTH program with continuously growing unique word transitions, so growth_rate stays > 1% always
2. **Window never becomes "warm"** - Shrinking is disabled until `is_warm=1` (after 4096 executions). If test doesn't run that long, no shrinking occurs
3. **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)
```c
metrics.decay_slope = 0.0; // ❌ Hardcoded to zero
```
### Where Decay Information Exists
**Source: `src/physics_metadata.c:164-203`**
```c
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:
1. ✅ Individual words decay (physics_metadata_apply_linear_decay)
2. ❌ No aggregate heat trajectory stored
3. ❌ No slope calculated from beginning to end of test
4. ❌ No "total heat budget" measurement
### What decay_slope Should Represent
**Option 1: Constant Decay Rate**
```c
metrics.decay_slope = (double)DECAY_RATE_PER_US_Q16 / 65536.0; // 0.0000458
```
**Option 2: Observed Heat Decline**
```c
// 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**
```c
// 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)
1. **Growth Trigger:** When should window grow?
- Prefetch accuracy drops (misses emerging patterns)?
- Pattern diversity suddenly increases?
- Memory available?
2. **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
3. **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:**
```c
// 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:
1. **Never triggers shrinking:** Diversity growth stays > 1% throughout test
2. **Test is too short:** Window takes ~4096 executions to become "warm", test ends before any shrinking
3. **Metrics extracted at wrong time:** Extracted after window has already been reset/re-initialized
4. **Shrinking happens but we measure too early:** Metrics captured before effective_window_size written to VM state
### decay_slope = 0.0 (Always)
**Explanation:**
```c
// 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:
1. ❌ No total heat tracking during run
2. ❌ No trajectory stored (heat at t=0 vs t=end)
3. ❌ No slope calculation implemented
4. ❌ Should extract from `DECAY_RATE_PER_US_Q16` or 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)
1. Add growth trigger based on:
- Prefetch accuracy feedback from Loop #4
- Pattern diversity sudden increase
- Memory availability
2. Implement gauge study:
- Compare prefetch accuracy before/after shrinking
- Measure pattern capture completeness
- Track false negatives (patterns missed due to window size)
3. 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.