# EWC++ Lifelong Learning Architecture in RuView for On-Device Adaptation

> Explore the EWC++ lifelong learning architecture in RuView for on-device adaptation. Discover how RuView uses diagonal Fisher information on ESP32 S3 via WASM3 to prevent catastrophic forgetting during continual learning.

- Repository: [rUv/RuView](https://github.com/ruvnet/RuView)
- Tags: architecture
- Published: 2026-03-08

---

**RuView implements EWC++ (Elastic Weight Consolidation) as a tiny linear classifier running on ESP32 S3 via WASM3, using diagonal Fisher information to prevent catastrophic forgetting during on-device continual learning.**

The RuView project provides a complete on-device continual learning solution through its `wifi-densepose-wasm-edge` crate. This Rust-based module enables **EWC++ lifelong learning architecture in RuView for on-device adaptation** by combining a minimal neural network with automatic task boundary detection, all constrained to run within the tight memory and latency budgets of embedded hardware.

## Core Architecture Components

### Linear Classifier Design

At the heart of the system lies a tiny linear classifier defined in [`rust-port/wifi-densepose-rs/crates/wifi-densepose-wasm-edge/src/lrn_ewc_lifelong.rs`](https://github.com/ruvnet/RuView/blob/main/rust-port/wifi-densepose-rs/crates/wifi-densepose-wasm-edge/src/lrn_ewc_lifelong.rs). The network maps an **8-dimensional CSI feature vector** to **4 zone outputs** using a flat weight matrix of 32 parameters (`N_PARAMS = N_INPUT * N_OUTPUT`).

The design intentionally omits softmax activation, using a direct linear mapping for inference to minimize computational overhead on the ESP32 S3. The weight matrix is stored as a contiguous `[f32; 32]` array, enabling cache-friendly access patterns critical for WASM3 interpreter performance.

### EwcLifelong State Management

The `EwcLifelong` struct maintains all state necessary for continual learning across multiple tasks:

```rust
// From lrn_ewc_lifelong.rs lines 101-126
pub struct EwcLifelong {
    params: [f32; N_PARAMS],        // Current model weights
    fisher: [f32; N_PARAMS],        // Diagonal Fisher information
    theta_star: [f32; N_PARAMS],    // Optimal params from previous task
    grad_accum: [f32; N_PARAMS],    // Gradient accumulator for Fisher estimation
    task_count: u32,                // Number of committed tasks
    stable_frames: u32,             // Consecutive low-loss frames counter
    has_prior: bool,                // Whether EWC penalty should be applied
    last_loss: f32,                 // Most recent MSE loss value
    last_penalty: f32,              // Most recent EWC penalty value
}

```

### Initialization Strategy

The `EwcLifelong::new()` constructor initializes the classifier with **deterministic pseudo-random weights** to break symmetry while ensuring reproducible behavior across device reboots. All Fisher information matrices, gradient accumulators, and the `theta_star` snapshot begin zeroed. The `has_prior` flag starts as `false`, disabling the EWC penalty until the first task boundary is committed.

## The EWC++ Learning Loop

### Forward Pass and Inference

The `forward()` method performs a simple matrix-vector multiplication between the flattened parameter array and the input features. Given the constraints of the ESP32 S3 target, the implementation avoids branching and uses iterative accumulation:

```rust
// From lrn_ewc_lifelong.rs lines 262-276
pub fn forward(&self, features: &[f32; N_INPUT]) -> [f32; N_OUTPUT] {
    let mut output = [0.0f32; N_OUTPUT];
    for i in 0..N_OUTPUT {
        let mut sum = 0.0;
        for j in 0..N_INPUT {
            sum += self.params[i * N_INPUT + j] * features[j];
        }
        output[i] = sum;
    }
    output
}

```

### Loss Computation with EWC Penalty

RuView combines **mean-squared error (MSE)** with the EWC regularization term. The `compute_ewc_penalty()` method calculates the penalty as `(λ/2) * Σ F_i * (θ_i - θ_i*)²`, where `λ` is the regularization strength, `F_i` is the diagonal Fisher information, `θ_i` represents current parameters, and `θ_i*` (`theta_star`) stores the optimal parameters from the previous task.

The total loss used for gradient computation becomes: `Loss = MSE + EWC_Penalty`. This formulation protects previously learned representations by penalizing large deviations from parameters that were important for earlier tasks.

### Fisher Information Estimation

Rather than computing the full Hessian, EWC++ uses the **diagonal Fisher information matrix** approximated via an exponential moving average of squared gradients. The `update_gradients()` method accumulates finite-difference gradients into `grad_accum`, then updates the Fisher diagonal:

```rust
// Conceptual flow from lrn_ewc_lifelong.rs lines 299-324
fn update_gradients(&mut self, features: &[f32; N_INPUT], label: usize) {
    // Compute finite difference gradients for each parameter
    for i in 0..N_PARAMS {
        let grad = self.compute_finite_diff(i, features, label);
        // Exponential moving average: F = alpha * F + (1-alpha) * grad^2
        self.fisher[i] = FISHER_ALPHA * self.fisher[i] + 
                        (1.0 - FISHER_ALPHA) * grad * grad;
    }
}

```

### Parameter Updates

The `gradient_step()` method performs stochastic gradient descent on the combined loss. To respect the ESP32 S3's computational constraints, the implementation updates only `PARAMS_PER_FRAME` (4) parameters per inference cycle, ensuring the entire learning loop completes in under 2 milliseconds.

## Task Boundary Detection and Commit

### Automatic Task Detection

RuView eliminates the need for manual task labels through **unsupervised task boundary detection**. The system monitors `stable_frames`, incrementing the counter when consecutive frames exhibit loss below `STABLE_LOSS_THRESHOLD`. Once `STABLE_FRAMES_THRESHOLD` consecutive stable frames occur, the `process_frame()` method triggers `commit_task()`.

### Commit Logic

The `commit_task()` method finalizes the current task by:
1. Snapshotting current parameters into `theta_star` (θ*)
2. Finalizing the Fisher information diagonal for this task
3. Resetting gradient accumulators and stable frame counters
4. Setting `has_prior = true` to enable EWC penalties for subsequent learning
5. Incrementing `task_count` and emitting event ID 746 (new task learned)

This commit mechanism ensures that when the device encounters a new environment or user pattern, it protects the previously optimized parameters before adapting to the new distribution.

## Resource Constraints and On-Device Optimization

### Memory and Runtime Budgets

The EWC++ module is explicitly designed for the **ESP32 S3** running under the **WASM3** interpreter. Key constraints include:
- **Parameter budget**: Only 32 floating-point weights (8×4 matrix)
- **Update budget**: `PARAMS_PER_FRAME = 4` parameters updated per frame
- **Latency budget**: Sub-2ms inference and learning per frame
- **Memory footprint**: All state fits in static arrays without heap allocation

### Event Telemetry

The system provides lightweight observability through static event emission. Event IDs 745-748 report:
- **745**: Knowledge retained (EWC penalty applied)
- **746**: New task learned (commit occurred)
- **747**: Fisher information updated
- **748**: Forgetting risk detected

These events write to a static mutable array accessible by the host environment for debugging and monitoring lifelong learning progress.

## Code Implementation Example

The following Rust example demonstrates the complete lifecycle of the EWC++ learner:

```rust
use wifi_densepose_wasm_edge::lrn_ewc_lifelong::EwcLifelong;

/// Create a new lifelong learner.
let mut learner = EwcLifelong::new();

/// Example CSI feature vector (8 sub-carrier groups).
let features = [0.42, 0.17, 0.88, 0.05, 0.61, 0.33, 0.77, 0.24];

/// Supervised update – ground-truth zone = 2 (0-3).
let events = learner.process_frame(&features, 2);
println!("Events this frame: {:?}", events);

/// Inference-only call (no learning) – use -1 as label.
let zone = learner.predict(&features);
println!("Predicted zone: {}", zone);

/// After many frames the learner will have committed one or more tasks.
/// Query its state:
println!("Tasks learned: {}", learner.task_count());
println!("Current loss: {:.3}", learner.last_loss());
println!("EWC penalty: {:.3}", learner.last_penalty());

```

## Summary

- **EWC++** in RuView implements elastic weight consolidation for ESP32 S3 devices using a minimal 8×4 linear classifier with 32 parameters.
- The architecture combines **MSE loss** with an **EWC penalty** based on diagonal Fisher information to prevent catastrophic forgetting across sequential tasks.
- **Unsupervised task detection** automatically commits new tasks when inference stabilizes, snapshotting optimal parameters into `theta_star` and enabling regularization.
- Resource constraints enforce **sub-2ms latency** and **4 parameters updated per frame**, fitting within WASM3 interpreter limits on embedded hardware.
- Event telemetry (IDs 745-748) provides lightweight monitoring of knowledge retention, task commits, and forgetting risks without heap allocation.

## Frequently Asked Questions

### How does EWC++ differ from standard Elastic Weight Consolidation?

EWC++ uses a **diagonal approximation of the Fisher information matrix** rather than the full Hessian, reducing storage from O(n²) to O(n) where n=32 parameters. It also introduces **unsupervised task boundary detection** that eliminates manual task labels, automatically committing tasks when consecutive frame losses fall below `STABLE_LOSS_THRESHOLD` for `STABLE_FRAMES_THRESHOLD` frames.

### What hardware constraints does the RuView EWC++ implementation target?

The module specifically targets the **ESP32 S3** microcontroller running under the **WASM3** WebAssembly interpreter. The implementation enforces strict budgets: **32 floating-point parameters** total, **4 parameters updated per frame** (`PARAMS_PER_FRAME`), and **sub-2 millisecond** inference latency per frame. All state uses static arrays to avoid heap allocation on memory-constrained embedded devices.

### How does the system detect and prevent catastrophic forgetting?

The system prevents forgetting through the **EWC penalty** calculated as `(λ/2) * Σ F_i * (θ_i - θ_i*)²`, where `F_i` represents the diagonal Fisher information and `θ_i*` (`theta_star`) stores parameters from previous tasks. When `commit_task()` detects stable performance, it snapshots current parameters into `theta_star` and finalizes the Fisher matrix, penalizing future updates that would alter critical parameters from earlier tasks.

### Where is the EWC++ implementation located in the RuView repository?

The core implementation resides in [`rust-port/wifi-densepose-rs/crates/wifi-densepose-wasm-edge/src/lrn_ewc_lifelong.rs`](https://github.com/ruvnet/RuView/blob/main/rust-port/wifi-densepose-rs/crates/wifi-densepose-wasm-edge/src/lrn_ewc_lifelong.rs). This file contains the `EwcLifelong` struct, the learning loop with `process_frame()`, Fisher estimation via `update_gradients()`, and task commitment logic in `commit_task()`. Unit tests verifying initialization, learning stability, and budget compliance are located in the `tests/` directory within the same crate.