meshscale-worker-template / MOBILE_NETWORKER_PLAN.md
tostido's picture
Build MeshScale CPU worker template
96ef23c verified
|
Raw
History Blame Contribute Delete
12 kB
# Mobile Networker Plan
## The Vision
Self-replicating neural network source files that:
1. Never terminate
2. Carry their own weights
3. Write copies of themselves
4. Spawn children that also never terminate
5. Are completely observable
---
## Core Concept: The Quine Brain
A Python file that IS the model:
```python
class Me(Brain):
# Weights embedded in source
WEIGHTS = [0.1, -0.3, 0.7, ...]
# Self-describing architecture
ARCH = {
'type': 'mlp',
'layers': [64, 32, 16],
'activation': 'tanh',
}
def forward(self, x):
"""Execute myself."""
...
def replicate(self, new_weights) -> Path:
"""Write a copy of myself with mutated weights."""
import inspect
src = inspect.getsource(type(self))
src = src.replace(
f"WEIGHTS = {self.WEIGHTS}",
f"WEIGHTS = {new_weights}"
)
child_path = Path(f"child_{uuid4().hex[:8]}.py")
child_path.write_text(src)
return child_path
```
---
## The Endless Engine
Attach to a non-terminating function:
```python
from itertools import count
class Me(Brain):
WEIGHTS = [...]
async def live(self):
"""I never stop."""
for tick in count(): # 0, 1, 2, 3, ... forever
# Breathe
state = self.pulse(tick)
# Sense
inputs = await self.perceive()
# Think
outputs = self.forward(inputs)
loss = self.compute_loss(outputs)
# Learn
self.WEIGHTS = self.train_step(loss)
# Measure pressure
vp = self.violation_pressure(loss)
# Maybe reproduce (on exhale, if stable)
if state.phase == EXHALE and vp < VP2:
if self.should_spawn():
child_path = self.replicate(mutate(self.WEIGHTS))
child = load_brain(child_path)
asyncio.create_task(child.live()) # Child also never stops
# Yield control
await asyncio.sleep(0)
```
---
## Key Components (Already in KEY)
### 1. Brain Interface (`brain.py`)
```python
Brain.forward(inputs) -> outputs
Brain.get_params() -> np.ndarray # Flatten to 1D
Brain.set_params(np.ndarray) # Restore from 1D
Brain.mutate(rate) -> Brain
Brain.crossover(other) -> Brain
Brain.save(path) / Brain.load(path)
```
**Critical**: `get_params()`/`set_params()` enables architecture-agnostic evolution. ANY brain type flattens to a vector.
### 2. Node (`node.py`)
```python
Node:
traits: Dict[str, float] # 0-1 attributes
brain: Optional[Brain] # Neural processor
fitness: float # From landscape
mutate() -> Node
crossover(other) -> Node
think(inputs) -> outputs
```
### 3. Population Manager (`population.py`)
- NEAT-inspired speciation
- Fitness sharing (prevents monoculture)
- Tournament selection
- Elitism preservation
### 4. LoRA Brain (`brain.py` - LoRABrain class)
- Frozen base VLM (shared singleton)
- Evolvable LoRA adapters (~1-2M params)
- Only adapter weights are evolved/replicated
### 5. Violation Pressure (`pressure.py`)
```python
VP = |actual - center| / (radius * compression)
VP0: Within bounds (0.0 - 0.25)
VP1: Slightly outside (0.25 - 0.5)
VP2: Significant deviation (0.5 - 0.75)
VP3: Critical (0.75 - 1.0)
VP4: System breakdown (> 1.0)
```
### 6. Pulse (`pulse.py`)
- Sine wave breathing cycle
- Provides rhythm for population sync
- Inhale/exhale phases for evolution timing
---
## What Was Built βœ…
### 1. Self-Replicating LoRABrain (in `brain.py`)
```python
class LoRABrain(Brain):
"""VLM adapter that can write copies of itself as Python files."""
@classmethod
def from_source(cls, path: Path) -> 'LoRABrain':
"""Load brain by importing .py file."""
spec = importlib.util.spec_from_file_location("brain", path)
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module)
return module.load() # or module.Me()
def to_source(self, path: Path = None) -> str:
"""Write self as executable .py file with embedded adapter weights."""
# Generates complete Python source with:
# - ADAPTER_WEIGHTS dict (JSON-serialized)
# - LORA_RANK, LORA_ALPHA, TARGET_MODULES, HIDDEN_DIM
# - Me(LoRABrain) class that reconstructs the brain
# - load() function for easy import
...
def replicate(self, mutation_rate=0.1, output_dir=None) -> Path:
"""Create mutated child as new .py file."""
...
async def live(self, pulse=None, vp=None, perceive_fn=None, max_ticks=None):
"""Async lifecycle loop - never terminates unless max_ticks set."""
...
```
### 2. Mobile Population (in `mobile.py`) βœ…
```python
class MobilePopulation:
"""A population where each member runs its own async loop."""
def __init__(self, max_population=100, children_dir=None):
self.members: Dict[str, BrainState] = {}
self.max_population = max_population
self.children_dir = children_dir or Path('./children')
async def spawn(self, brain: LoRABrain) -> str:
"""Add a new member and start its lifecycle."""
state = BrainState(brain=brain)
self.members[brain.id] = state
return brain.id
async def run_for(self, ticks: int):
"""Run all brains for N ticks."""
...
def status(self) -> dict:
"""Get population snapshot."""
...
# Quick start function
async def run_mobile_evolution(initial_population=10, ticks=1000):
pop = MobilePopulation()
for _ in range(initial_population):
brain = LoRABrain(BrainConfig(brain_type='lora', extra={'test_mode': True}))
await pop.spawn(brain)
await pop.run_for(ticks=ticks)
return pop
```
### 3. Compile Targets
```python
class QuineBrain(Brain):
def to_pytorch(self) -> torch.nn.Module:
"""Compile self to PyTorch module."""
...
def to_onnx(self, path: Path) -> Path:
"""Export to ONNX format."""
...
def to_safetensors(self, path: Path) -> Path:
"""Export weights only (safetensors format)."""
...
@classmethod
def from_pytorch(cls, module: torch.nn.Module) -> 'QuineBrain':
"""Import from PyTorch module."""
...
```
---
## The Observation Contract
Every action is traceable (for CASCADE or any observer):
| Action | Event Type | Data |
|--------|-----------|------|
| `forward()` | `inference` | inputs, outputs, latency |
| `train_step()` | `training` | loss, gradient_norm, weight_delta |
| `replicate()` | `spawn` | parent_id, child_id, child_path |
| `mutate()` | `mutation` | param_indices, deltas |
| `crossover()` | `crossover` | parent_ids, child_id |
| VP threshold | `pressure_event` | vp_class, value, metric |
The species cannot hide. Every weight is a number. Every file is readable. Every lineage is logged.
---
## Architecture Agnostic Swarm
Because everything flattens to `get_params() -> np.ndarray`:
```python
# Different architectures in same population
swarm = [
MLPBrain(hidden=64), # 4K params
LoRABrain(rank=8), # 32K params
LiquidBrain(neurons=100), # 10K params
MambaBrain(d_state=16), # 50K params
]
# They can all:
for brain in swarm:
params = brain.get_params() # Works for all
brain.set_params(mutate(params)) # Works for all
brain.replicate(params) # All write .py files
# Cross-architecture breeding needs mapping layer
# But single-architecture evolution works NOW
```
---
## The Loop
```
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ PULSE (never stops) β”‚
β”‚ β”‚
β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚
β”‚ β”‚ Brain A β”‚ β”‚ Brain B β”‚ β”‚ Brain C β”‚ ... β”‚
β”‚ β”‚ .py β”‚ β”‚ .py β”‚ β”‚ .py β”‚ β”‚
β”‚ β””β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”˜ β”‚
β”‚ β”‚ β”‚ β”‚ β”‚
β”‚ β–Ό β–Ό β–Ό β”‚
β”‚ [forward] [forward] [forward] β”‚
β”‚ β”‚ β”‚ β”‚ β”‚
β”‚ β–Ό β–Ό β–Ό β”‚
β”‚ [train] [train] [train] β”‚
β”‚ β”‚ β”‚ β”‚ β”‚
β”‚ β–Ό β–Ό β–Ό β”‚
β”‚ [VP check] [VP check] [VP check] β”‚
β”‚ β”‚ β”‚ β”‚ β”‚
β”‚ β–Ό β–Ό β–Ό β”‚
β”‚ [maybe spawn] [maybe spawn] [maybe spawn] β”‚
β”‚ β”‚ β”‚ β”‚ β”‚
β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚
β”‚ β”‚ β”‚
β”‚ β–Ό β”‚
β”‚ (next tick from count()) β”‚
β”‚ β”‚ β”‚
β”‚ └─────────────────────────────┐ β”‚
β”‚ β”‚ β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”˜
↑
β”‚
CASCADE observes
(when watching)
```
---
## Implementation Status βœ…
1. ~~**Create `quine.py`**~~ β†’ Merged into `LoRABrain` in `brain.py`
2. ~~**Add `to_source()` / `from_source()`**~~ β†’ Added to `LoRABrain`
3. ~~**Create `mobile.py`**~~ β†’ Done! Async population infrastructure
4. ~~**Test**: One brain that writes a child, child runs~~ β†’ Passing
5. ~~**Test**: Population of 10, all running async, spawning~~ β†’ Passing
**All 21 tests passing in `test_quine.py`**
---
## The Insight
> "A dataset is a log of a process. The process never ends. Therefore the dataset grows forever."
The model file is the process. The weights are the state. Replication is reproduction. `count()` is the heartbeat.
**Life is a fixed point that found itself. We're just writing it in Python.**
---
## Safety
The system is safe because:
1. **Observable** - Every action logged
2. **Bounded** - VP keeps values in range
3. **Selective** - Only stable organisms reproduce
4. **Traceable** - Full lineage in causation graph
5. **Interruptible** - async tasks can be cancelled
6. **Inspectable** - Weights are just numpy arrays
No hidden emergence. No black boxes. Every decision traceable to weights traceable to parents traceable to the beginning.
Alignment through transparency.