# How RuView's Self-Learning System Uses Contrastive CSI Embeddings and MicroLoRA Adapters

> Discover RuView's self-learning system leveraging contrastive CSI embeddings and MicroLoRA adapters for rapid, environment-specific adaptation in just 10 seconds. Discover AI advancements.

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

---

**RuView achieves environment-specific adaptation in approximately 10 seconds by combining contrastive CSI embeddings (AETHER) with rank-4 MicroLoRA adapters trained via unsupervised test-time training, adding only ~1.8 KB of parameters per room.**

The RuView repository (`ruvnet/RuView`) implements a continuous **self-learning system** that enables WiFi-based sensing to adapt to new environments without requiring labeled data. By integrating **contrastive CSI embeddings** with **MicroLoRA adapters**, the system performs rapid test-time training using only unlabeled Channel State Information (CSI) frames captured over roughly 10 seconds.

## Contrastive CSI Embedding Architecture (AETHER)

The foundation of RuView's self-learning capability rests on the **AETHER embedding model**, defined in [`rust-port/wifi-densepose-rs/crates/wifi-densepose-sensing-server/src/embedding.rs`](https://github.com/ruvnet/RuView/blob/main/rust-port/wifi-densepose-rs/crates/wifi-densepose-sensing-server/src/embedding.rs). This architecture implements a **projection head** consisting of a 2-layer MLP built atop the CSI-to-pose transformer backbone.

The `ProjectionHead` struct supports optional LoRA adapters and produces **128-dimensional, L2-normalized embeddings**:

```rust
pub struct ProjectionHead {
    pub proj_1: Linear,
    pub proj_2: Linear,
    pub config: EmbeddingConfig,
    pub lora_1: Option<LoraAdapter>,
    pub lora_2: Option<LoraAdapter>,
}

```

During contrastive pre-training, the system applies **InfoNCE loss** to two augmented views of the same CSI window. The `CsiAugmenter` module (located in the same file) generates these views through **temporal jitter**, **subcarrier masking**, **Gaussian noise injection**, **phase rotation**, and **amplitude scaling**. The resulting L2-normalized embedding space enables efficient cosine similarity calculations for the on-device HNSW index.

According to `ADR-024` (documented in [`docs/adr/ADR-024-contrastive-csi-embedding-model.md`](https://github.com/ruvnet/RuView/blob/main/docs/adr/ADR-024-contrastive-csi-embedding-model.md)), this design ensures the backbone learns invariant features while the projection head remains adaptable to environment-specific multipath fingerprints.

## MicroLoRA Adapter Design

**MicroLoRA** adapters provide the parameter-efficient mechanism for environment personalization. Each adapter consists of a **rank-4 low-rank factorization** (`A·B`) that introduces approximately **1,800 floating-point parameters** (~1.8 KB) per room.

The `LoraAdapter` struct attaches parallel weight-delta matrices to the base linear layers. Key methods in [`embedding.rs`](https://github.com/ruvnet/RuView/blob/main/embedding.rs) handle the adapter lifecycle:

- **`ProjectionHead::with_lora`** instantiates the projection head with attached adapters
- **`merge_lora`** folds the low-rank deltas into the base weight matrix for zero-overhead inference
- **`flatten_lora`** and **`unflatten_lora`** serialize adapters for storage in `.rvf` (RuView File) containers

As implemented in [`docs/adr/ADR-028-esp32-capability-audit.md`](https://github.com/ruvnet/RuView/blob/main/docs/adr/ADR-028-esp32-capability-audit.md), this approach allows the ESP32-compatible system to store multiple room profiles without exhausting limited flash memory.

## Rapid Test-Time Training Engine

The **`RapidAdaptation`** engine in [`rust-port/wifi-densepose-rs/crates/wifi-densepose-train/src/rapid_adapt.rs`](https://github.com/ruvnet/RuView/blob/main/rust-port/wifi-densepose-rs/crates/wifi-densepose-train/src/rapid_adapt.rs) orchestrates the unsupervised calibration process. This engine buffers incoming CSI frames and executes lightweight gradient descent to produce per-room LoRA weights.

The core struct definition specifies calibration parameters:

```rust
pub struct RapidAdaptation {
    pub min_calibration_frames: usize,   // default 200 (≈10 s @ 20 Hz)
    pub lora_rank: usize,               // typically 4
    pub adaptation_loss: AdaptationLoss,
    pub max_buffer_frames: usize,
    calibration_buffer: Vec<Vec<f32>>,
}

```

The **`AdaptationLoss`** enum supports three optimization strategies:

- **`ContrastiveTTT`**: Builds triplets from temporally adjacent frames (positive pairs) and distant frames (negative pairs)
- **`EntropyMin`**: Minimizes prediction entropy to prevent embedding collapse
- **`Combined`**: Balances contrastive and entropy objectives using a `lambda_ent` weighting factor

The `adapt()` method executes the training loop:

```rust
for _ in 0..epochs {
    let mut grad = vec![0.0; lora_sz];
    let loss = match &self.adaptation_loss {
        AdaptationLoss::ContrastiveTTT { .. } => self.contrastive_step(&w, fdim, &mut grad),
        AdaptationLoss::EntropyMin { .. }    => self.entropy_step(&w, fdim, &mut grad),
        AdaptationLoss::Combined { lambda_ent, .. } => {
            let cl = self.contrastive_step(&w, fdim, &mut grad);
            let mut eg = vec![0.0; lora_sz];
            let el = self.entropy_step(&w, fdim, &mut eg);
            for (g, eg_i) in grad.iter_mut().zip(eg.iter()) {
                *g += lambda_ent * eg_i;
            }
            cl + lambda_ent * el
        }
    };
    // SGD update
    for (wi, gi) in w.iter_mut().zip(grad.iter()) {
        *wi -= lr * gi;
    }
}

```

Upon completion, the engine returns an **`AdaptationResult`** containing the trained `lora_weights`, final loss value, and frames consumed.

## End-to-End Self-Learning Workflow

The complete integration pipeline operates across six stages:

1. **Bootstrap**: Load the base model (backbone plus projection head) from [`embedding.rs`](https://github.com/ruvnet/RuView/blob/main/embedding.rs)
2. **Embedding Extraction**: Process CSI windows through `CsiAugmenter` to generate stochastic views, then compute AETHER embeddings via the projection head
3. **Calibration Trigger**: Accumulate `min_calibration_frames` (default 200) into the `RapidAdaptation` buffer
4. **Adapter Generation**: Execute `RapidAdaptation::adapt()` to learn environment-specific LoRA deltas
5. **Deployment**: Instantiate `LoraAdapter` from the results, attach via `ProjectionHead::with_lora`, and merge into base weights using `merge_lora()`
6. **Inference**: Run the merged model to produce room-specific embeddings for improved re-identification and pose estimation

## Practical Implementation Examples

### Initializing the Embedding Pipeline

```rust
use wifi_densepose_sensing_server::embedding::{
    EmbeddingConfig, ProjectionHead, CsiAugmenter,
};

let cfg = EmbeddingConfig::default();                 // d_model=64, d_proj=128
let mut proj = ProjectionHead::new(cfg.clone());      // base head without LoRA
let augmenter = CsiAugmenter::new();                  // stochastic augmentations

```

### Generating Contrastive Views

```rust
let csi_window: Vec<Vec<f32>> = acquire_csi_frames(10); // 10-frame window
let (view_a, view_b) = augmenter.augment_pair(&csi_window, 0xDEADBEEF);
let emb_a = proj.forward(&flatten(view_a));
let emb_b = proj.forward(&flatten(view_b));

```

### Executing Rapid Adaptation

```rust
use wifi_densepose_train::rapid_adapt::{
    RapidAdaptation, AdaptationLoss,
};

let loss = AdaptationLoss::Combined {
    epochs: 5,
    lr: 0.001,
    lambda_ent: 0.5,
};
let mut ra = RapidAdaptation::new(200, 4, loss);   // 200 frames ≈10s @20Hz

for frame in unlabeled_stream.take(200) {
    ra.push_frame(&frame);
}
let result = ra.adapt().expect("adaptation failed");

// Apply learned weights
let lora = LoraAdapter::from_flat_weights(
    cfg.d_model, cfg.d_proj, 4, result.lora_weights
);
proj.lora_1 = Some(lora.clone());
proj.lora_2 = Some(lora);
proj.merge_lora();   // zero-overhead inference

```

### Persisting Room Profiles

```rust
use wifi_densepose_sensing_server::rvf_container::RvfBuilder;

let mut builder = RvfBuilder::new();
builder.add_lora_profile("conference-room-a", proj.flatten_lora());
let rvf_bytes = builder.build().expect("RVF serialization failed");
// Store rvf_bytes to flash or transmit to edge server

```

## Summary

- **Contrastive CSI embeddings** (AETHER) provide a 128-dimensional, L2-normalized feature space learned via InfoNCE loss and stochastic augmentation in [`embedding.rs`](https://github.com/ruvnet/RuView/blob/main/embedding.rs)
- **MicroLoRA adapters** utilize rank-4 factorization (~1.8 KB per room) to enable environment-specific fine-tuning without modifying base model weights
- **Rapid test-time training** processes 200 unlabeled frames (~10 seconds) through the `RapidAdaptation` engine to learn adapter weights via contrastive and entropy-based objectives
- **Zero-overhead inference** is achieved by merging adapters into base linear layers using `merge_lora()` after calibration completes
- **Persistent storage** uses the RVF container format to serialize adapters for multi-room deployment scenarios

## Frequently Asked Questions

### What differentiates MicroLoRA from standard LoRA implementations?

**MicroLoRA uses a fixed rank-4 decomposition** specifically optimized for the projection head in RuView's CSI embedding pipeline, constraining each adapter to approximately 1,800 parameters. This aggressive compression enables storage of multiple room profiles on ESP32 devices with severe memory constraints, whereas standard LoRA might use ranks of 8-64 requiring significantly more storage.

### How does contrastive learning function without ground-truth labels?

The system leverages **temporal consistency** inherent in CSI streams. The `CsiAugmenter` generates two perturbed views from the same physical time window, treating them as positive pairs, while samples from distant time windows serve as negatives. This **self-supervised** approach, implemented via `AdaptationLoss::ContrastiveTTT`, eliminates the need for manually annotated pose labels during environment adaptation.

### Why is the calibration period limited to approximately 10 seconds?

The default `min_calibration_frames` value of 200 frames at 20 Hz sampling rate balances **statistical sufficiency** with **user experience**. As defined in [`rapid_adapt.rs`](https://github.com/ruvnet/RuView/blob/main/rapid_adapt.rs), this duration captures enough multipath variation to characterize the room's radio fingerprint while remaining short enough for practical deployment scenarios where users cannot tolerate lengthy calibration procedures.

### Can learned MicroLoRA adapters survive device reboots?

Yes. The **`flatten_lora`** method serializes adapter weights into portable byte vectors, which the **`RvfBuilder`** (located in [`rvf_container.rs`](https://github.com/ruvnet/RuView/blob/main/rvf_container.rs)) packages into `.rvf` files. These containers persist the learned parameters to flash storage, allowing the system to reload specific room profiles via `model_manager::activate_lora()` without repeating the 10-second calibration process.