349 lines
12 KiB
Markdown
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.
|