EWC++ Lifelong Learning Architecture in RuView for On-Device Adaptation
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. 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:
// 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:
// 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:
// 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:
- Snapshotting current parameters into
theta_star(θ*) - Finalizing the Fisher information diagonal for this task
- Resetting gradient accumulators and stable frame counters
- Setting
has_prior = trueto enable EWC penalties for subsequent learning - Incrementing
task_countand 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 = 4parameters 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:
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_starand 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. 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.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →