Skip to content

Latest commit

 

History

History
218 lines (177 loc) · 8.87 KB

File metadata and controls

218 lines (177 loc) · 8.87 KB

Below is a toy implementation of a bacterium‑inspired replicating LLM in Python. It demonstrates:

  • Agents (tiny neural networks) that live on devices.
  • Replication (division) after a fixed number of interactions.
  • Mutation during replication (small random changes to weights).
  • Memory sharing via a simulated Distributed Hash Table (DHT).

The code uses PyTorch for the neural network but can be adapted to pure NumPy. It is intentionally simple to illustrate the core ideas.


import torch
import torch.nn as nn
import torch.optim as optim
import random
import math
from collections import defaultdict
from typing import Dict, List, Tuple

# ------------------------------------------------------------
# 1. Agent Definition (tiny neural network)
# ------------------------------------------------------------
class Agent(nn.Module):
    def __init__(self, input_dim=16, hidden_dim=32, output_dim=16):
        super().__init__()
        self.net = nn.Sequential(
            nn.Linear(input_dim, hidden_dim),
            nn.ReLU(),
            nn.Linear(hidden_dim, output_dim)
        )
        # Each agent also has a memory vector (compressed history)
        self.memory = torch.zeros(output_dim)
        self.interaction_count = 0
        self.id = None  # will be set by population

    def forward(self, x):
        # produce an output embedding from input embedding
        return self.net(x)

    def respond(self, query_embedding):
        """Generate a response embedding and update memory."""
        response = self.forward(query_embedding)
        # update memory: simple running average
        self.memory = 0.9 * self.memory + 0.1 * response.detach()
        self.interaction_count += 1
        return response

    def replicate(self, mutation_strength=0.01):
        """Create a child agent with mutated weights."""
        child = Agent(input_dim=16, hidden_dim=32, output_dim=16)
        # copy weights with Gaussian noise
        for param, child_param in zip(self.parameters(), child.parameters()):
            child_param.data = param.data + torch.randn_like(param.data) * mutation_strength
        # copy memory (with a little mutation too)
        child.memory = self.memory + torch.randn_like(self.memory) * mutation_strength
        child.interaction_count = 0
        return child

# ------------------------------------------------------------
# 2. Distributed Hash Table (DHT) – simulated with a global dict
# ------------------------------------------------------------
class DHT:
    def __init__(self):
        self.storage = defaultdict(list)  # key -> list of (value, agent_id, fitness)

    def store(self, key, value, agent_id, fitness=1.0):
        self.storage[key].append((value, agent_id, fitness))

    def retrieve(self, key, top_k=3):
        """Return the best values for a key (by fitness)."""
        if key not in self.storage:
            return []
        items = self.storage[key]
        items.sort(key=lambda x: x[2], reverse=True)  # highest fitness first
        return [v for v, _, _ in items[:top_k]]

# ------------------------------------------------------------
# 3. Population Manager
# ------------------------------------------------------------
class Population:
    def __init__(self, initial_agents=1, replication_threshold=5):
        self.agents = []
        self.dht = DHT()
        self.next_id = 0
        self.replication_threshold = replication_threshold
        for _ in range(initial_agents):
            self._add_agent(Agent())

    def _add_agent(self, agent):
        agent.id = self.next_id
        self.next_id += 1
        self.agents.append(agent)

    def interact(self, query_embedding, user_feedback_score=1.0):
        """Simulate a user interaction: pick a random agent, get response, store memory."""
        agent = random.choice(self.agents)
        response = agent.respond(query_embedding)

        # Store the query-response pair in the DHT, keyed by query embedding hash
        key = tuple(query_embedding.tolist())  # crude hash
        self.dht.store(key, response, agent.id, user_feedback_score)

        # If agent reached replication threshold, divide
        if agent.interaction_count >= self.replication_threshold:
            child = agent.replicate()
            self._add_agent(child)
            # reset parent's counter (or not – here we keep it but it will divide again later)
            agent.interaction_count = 0
            print(f"Agent {agent.id} replicated! New agent {child.id} born.")

    def consensus_answer(self, query_embedding):
        """All agents vote: retrieve memories and combine."""
        key = tuple(query_embedding.tolist())
        memories = self.dht.retrieve(key, top_k=3)
        if not memories:
            # fallback: pick a random agent's response
            return random.choice(self.agents).respond(query_embedding)
        # average the retrieved memories
        avg = torch.stack(memories).mean(dim=0)
        return avg

# ------------------------------------------------------------
# 4. Simulation
# ------------------------------------------------------------
def simulate():
    pop = Population(initial_agents=2, replication_threshold=5)

    # Create a few random query embeddings
    queries = [torch.randn(16) for _ in range(20)]

    for i, q in enumerate(queries):
        # Random user feedback score (1.0 = good, 0.0 = bad)
        feedback = random.uniform(0.5, 1.0)
        pop.interact(q, feedback)
        # Every few steps, test consensus
        if i % 5 == 0:
            ans = pop.consensus_answer(q)
            print(f"Step {i}: Population size = {len(pop.agents)}")

    print(f"Final population size: {len(pop.agents)}")
    # Show memory distribution
    for agent in pop.agents:
        print(f"Agent {agent.id} memory norm: {agent.memory.norm().item():.3f}")

if __name__ == "__main__":
    simulate()

Explanation of the Code

  1. Agent Class

    • Inherits from nn.Module (PyTorch).
    • Contains a tiny MLP with 16‑dimensional input/output and one hidden layer.
    • Each agent has a memory vector (updated after each response) and an interaction_count.
    • respond(): takes a query embedding, computes response, updates memory.
    • replicate(): creates a child agent by copying weights and adding Gaussian noise (mutation). The child starts with zero interaction count.
  2. DHT Class

    • Simulates a distributed hash table using a global dictionary.
    • Keys are query embeddings (converted to tuple), values are response embeddings.
    • Each stored entry also includes the agent ID and a fitness score (from user feedback).
    • retrieve() returns the top‑k responses by fitness.
  3. Population Class

    • Manages all agents.
    • interact(): picks a random agent, gives it a query, stores the query‑response pair in the DHT, and triggers replication if the agent’s interaction count exceeds a threshold.
    • consensus_answer(): for a new query, retrieves the best matching memories from the DHT and averages them.
  4. Simulation

    • Creates an initial population of 2 agents.
    • Generates 20 random query embeddings.
    • For each query, simulates a user interaction with random feedback.
    • Every 5 steps, prints the population size.
    • At the end, prints the final number of agents and their memory norms.

How It Demonstrates Replication & Memory Growth

  • Replication: Each agent divides after handling 5 interactions, doubling the population (in this simulation, the population grows exponentially).
  • Mutation: The child’s weights and memory are slightly different from the parent, allowing specialization over time.
  • Memory Sharing: The DHT allows agents to store and retrieve responses. As the population grows, the DHT accumulates more memories, effectively increasing the system’s total memory capacity.
  • Consensus: When answering a query, the system combines memories from multiple agents, improving robustness.

Running the Code

Save the script as replicating_llm.py and run:

python replicating_llm.py

You will see output like:

Step 0: Population size = 2
Agent 0 replicated! New agent 2 born.
Agent 1 replicated! New agent 3 born.
Step 5: Population size = 4
...
Final population size: 16

Limitations & Future Work

  • The model is tiny (16‑dim embeddings) – in reality, each agent would be a small transformer (e.g., 100k parameters).
  • The DHT is simulated with a global dictionary; a real implementation would use a peer‑to‑peer network.
  • User feedback is random; a real system would learn from actual user ratings.
  • Replication threshold is fixed; in nature, it could depend on resource availability.

This toy code illustrates the mathematical principles of a bacterium‑inspired replicating LLM. The real version (year 50,000) would run on a liar lattice with agents that are 1 µm³ in size and replicate at the speed of light.