nova-spike-hybrid / scripts /benchmark.py
thefinalboss's picture
Initial release: NOVA + SPIKE + AETHER + HYBRID non-transformer AI stack
3636a69
Raw
History Blame Contribute Delete
15.7 kB
#!/usr/bin/env python3
"""
Benchmark v2 — compare SPIKE, NOVA, AETHER, HYBRID on agentic tasks.
Tasks:
1. Arithmetic (5 questions)
2. Memory recall (teach + query)
3. Tool calling (calculator, python, time)
4. Robustness (paraphrases)
Metrics:
- Latency (ms)
- Accuracy (%)
- Memory (MB)
"""
import sys
import os
import time
import json
import numpy as np
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from spike import SpikeBrain, SpikeConfig
from nova import Nova, NovaConfig
from hybrid import HybridBrain, HybridConfig
from aether import AETHER
def banner(title: str, char="="):
print(f"\n{char * 70}")
print(f" {title}")
print(f"{char * 70}")
def measure(fn, *args, **kwargs):
t0 = time.time()
result = fn(*args, **kwargs)
t1 = time.time()
return result, (t1 - t0) * 1000
def estimate_memory(brain) -> float:
"""Estimates memory footprint in MB."""
if hasattr(brain, "ask"):
# AETHER
s = 0
if hasattr(brain, "sdm") and brain.sdm is not None:
s += brain.sdm.locations.nbytes + brain.sdm.contents.nbytes
if hasattr(brain, "kb_store") and brain.kb_store is not None:
s += brain.kb_store.locations.nbytes + brain.kb_store.contents.nbytes
if hasattr(brain, "encoder") and hasattr(brain.encoder, "item_memory"):
for v in brain.encoder.item_memory.values():
if hasattr(v, "vec"):
s += v.vec.nbytes
if hasattr(brain, "lm") and brain.lm is not None:
if hasattr(brain.lm, "sdm"):
s += brain.lm.sdm.locations.nbytes + brain.lm.sdm.contents.nbytes
if s == 0:
# Fallback — estimate from stats
try:
stats = brain.stats()
if "kb_store" in stats:
n = stats["kb_store"].get("n_locations", 5000)
d = stats["kb_store"].get("dim", 4096)
s = n * d * 2 # int8 locations + int8 contents
except Exception:
pass
return s / (1024 * 1024)
if hasattr(brain, "net"):
# SPIKE
s = brain.net.syn_sens_to_assoc.W.data.nbytes
s += brain.net.syn_sens_to_assoc.W.indptr.nbytes + brain.net.syn_sens_to_assoc.W.indices.nbytes
s += brain.net.syn_assoc_to_assoc.W.data.nbytes
s += brain.net.syn_assoc_to_assoc.W.indptr.nbytes + brain.net.syn_assoc_to_assoc.W.indices.nbytes
s += brain.net.syn_assoc_to_motor.W.data.nbytes
s += brain.net.syn_assoc_to_motor.W.indptr.nbytes + brain.net.syn_assoc_to_motor.W.indices.nbytes
if brain.syn_sens_to_motor is not None:
s += brain.syn_sens_to_motor.W.data.nbytes
s += brain.syn_sens_to_motor.W.indptr.nbytes + brain.syn_sens_to_motor.W.indices.nbytes
s += brain.cfg.n_sensory * 4 * 4
s += brain.cfg.n_associative * 4 * 4
s += brain.cfg.n_motor * 4 * 4
return s / (1024 * 1024)
elif hasattr(brain, "memory"):
# NOVA
s = brain.memory.locations.nbytes + brain.memory.contents.nbytes
s += brain.memory.locations_f32.nbytes
s += brain.resonator.W.data.nbytes
s += brain.resonator.W.indptr.nbytes + brain.resonator.W.indices.nbytes
return s / (1024 * 1024)
elif hasattr(brain, "spike") and hasattr(brain, "nova"):
# HYBRID
return estimate_memory(brain.spike) + estimate_memory(brain.nova)
return 0.0
# ---------------------------------------------------------------- #
# Tasks
# ---------------------------------------------------------------- #
def task_arithmetic(brain, brain_name) -> dict:
"""Task: 5 arithmetic questions."""
if brain_name == "AETHER":
questions = [
("calc 2+2", "4"),
("calc 15 times 3", "45"),
("calc 144 / 12", "12"),
("calc 2 to the power of 10", "1024"),
("calc 10 + 5 * 2", "20"),
]
else:
questions = [
("calcule 2+2", "4"),
("combien font 15 fois 3", "45"),
("que vaut la racine carrée de 144", "12"),
("calcule 2 puissance 10", "1024"),
("que vaut 10 plus 5 fois 2", "20"),
]
correct = 0
times = []
for q, expected in questions:
try:
r, dt = measure(brain.chat if hasattr(brain, "chat") else brain.ask, q)
times.append(dt)
if expected in r:
correct += 1
except Exception as e:
print(f" error: {e}")
times.append(1000.0)
return {
"correct": correct,
"total": len(questions),
"accuracy": correct / len(questions),
"avg_time_ms": float(np.mean(times)),
"max_time_ms": float(max(times)),
}
def task_memory(brain, brain_name) -> dict:
"""Task: teaching + recall."""
if brain_name == "AETHER":
# AETHER: format "X is the capital of Y"
facts = [
("Paris is the capital of France", "paris"),
("Kinshasa is the capital of Congo", "kinshasa"),
("Montreal is located in Canada", "canada"),
("Water is composed of H2O", "h2o"),
("Einstein discovered relativity", "relativity"),
]
queries = [
("What is the capital of France?", "paris"),
("What is the capital of Congo?", "kinshasa"),
("Where is Montreal located?", "canada"),
("What is water composed of?", "h2o"),
("What did Einstein discover?", "relativity"),
]
else:
facts = [
("le chat", "un animal"),
("Paris", "la capitale de la France"),
("Mars", "la quatrième planète"),
("Einstein", "physicien"),
("l'eau", "H2O"),
]
queries = [
(f"que sais-tu sur {k}", v) for k, v in facts
]
# Apprentissage
learn_times = []
for fact, _ in facts:
if brain_name == "AETHER":
_, dt = measure(brain.teach, fact)
else:
if " est " in fact or " is " in fact or " located" in fact or " composed" in fact or " discovered" in fact:
# Pour AETHER on a déjà teach; pour les autres on apprend k=v
k, v = facts[len(learn_times)][1], fact # fallback
_, dt = measure(brain.learn, fact, _)
else:
_, dt = measure(brain.learn, fact, _)
learn_times.append(dt)
# Rappel
correct = 0
recall_times = []
for q, expected in queries:
try:
r, dt = measure(brain.chat if hasattr(brain, "chat") else brain.ask, q)
recall_times.append(dt)
if expected.lower() in r.lower() or any(w in r.lower() for w in expected.split()):
correct += 1
except Exception:
recall_times.append(1000.0)
return {
"correct": correct,
"total": len(facts),
"accuracy": correct / len(facts),
"avg_learn_ms": float(np.mean(learn_times)),
"avg_recall_ms": float(np.mean(recall_times)),
}
def task_tool_calling(brain, brain_name) -> dict:
"""Task: various tool calls."""
if brain_name == "AETHER":
tests = [
("calc 5+5", "calc"),
("time", "time"),
("python eval [1,2,3]", "python"),
]
else:
tests = [
("calcule 5+5", "calculator"),
("python: print(42)", "python"),
("quelle heure est-il", "time"),
]
correct = 0
times = []
for q, expected in tests:
try:
r, dt = measure(brain.chat if hasattr(brain, "chat") else brain.ask, q)
times.append(dt)
if expected in r.lower() or "outil" in r.lower() or "calc" in r.lower():
correct += 1
except Exception:
times.append(1000.0)
return {
"correct": correct,
"total": len(tests),
"accuracy": correct / len(tests),
"avg_time_ms": float(np.mean(times)),
}
def task_robustness(brain, brain_name) -> dict:
"""Task: robustness to phrasing variations."""
if brain_name == "AETHER":
brain.teach("The cat is an animal")
queries = [
"What is a cat?",
"Tell me about the cat",
"cat",
"the cat",
"What does the cat be?",
]
expected = "animal"
else:
brain.learn("le chat", "un animal")
queries = [
"le chat",
"chat",
"le chat dort",
"qui est le chat",
"parle-moi du chat",
]
expected = "animal"
correct = 0
times = []
for q in queries:
try:
r, dt = measure(brain.chat if hasattr(brain, "chat") else brain.ask, q)
times.append(dt)
if expected.lower() in r.lower() or "chat" in r.lower():
correct += 1
except Exception:
times.append(1000.0)
return {
"correct": correct,
"total": len(queries),
"accuracy": correct / len(queries),
"avg_time_ms": float(np.mean(times)),
}
# ---------------------------------------------------------------- #
# Main
# ---------------------------------------------------------------- #
def main():
banner("BENCHMARK v2 — SPIKE vs NOVA vs AETHER vs HYBRID", char="#")
print("""
Compare 4 brains on 4 agentic tasks.
Metrics: accuracy, latency, memory.
""")
# Init
print("Initializing...")
t0 = time.time()
spike = SpikeBrain(SpikeConfig(
n_sensory=300, n_associative=800, n_motor=300, sim_ticks=25,
))
nova = Nova(NovaConfig(D=2000, sdm_locations=5000))
aether = AETHER()
hybrid = HybridBrain(HybridConfig(
spike=SpikeConfig(n_sensory=300, n_associative=800, n_motor=300, sim_ticks=25),
nova=NovaConfig(D=2000, sdm_locations=5000),
))
print(f"Ready in {time.time()-t0:.2f}s\n")
brains = {
"SPIKE": (spike, "SPIKE"),
"NOVA": (nova, "NOVA"),
"AETHER": (aether, "AETHER"),
"HYBRID": (hybrid, "HYBRID"),
}
results = {}
for name, (brain, brain_name) in brains.items():
banner(f"Test {name}")
try:
results[name] = {
"arithmetic": task_arithmetic(brain, brain_name),
"memory": task_memory(brain, brain_name),
"tool_calling": task_tool_calling(brain, brain_name),
"robustness": task_robustness(brain, brain_name),
"memory_mb": estimate_memory(brain),
}
except Exception as e:
print(f" Error: {e}")
results[name] = {"error": str(e)}
# Reset entre les tests
if hasattr(brain, "net"):
brain.net.reset()
elif hasattr(brain, "resonator"):
brain.resonator.reset()
elif hasattr(brain, "spike"):
brain.spike.net.reset()
# Summary table
banner("RESULTS")
print(f"\n{'Metric':<30} {'SPIKE':<14} {'NOVA':<14} {'AETHER':<14} {'HYBRID':<14}")
print("-" * 86)
for task_name in ["arithmetic", "memory", "tool_calling", "robustness"]:
print(f"\n{task_name.upper()}:")
for metric in ["accuracy", "avg_time_ms", "avg_learn_ms", "avg_recall_ms"]:
row = f" {metric:<28}"
for brain_name in ["SPIKE", "NOVA", "AETHER", "HYBRID"]:
val = results[brain_name].get(task_name, {}).get(metric)
if val is None:
row += f" {'—':<13}"
elif "time" in metric or "ms" in metric:
row += f" {val:<13.1f}"
else:
row += f" {val*100:<13.1f}"
print(row + ("%" if "accuracy" in metric else " ms"))
print(f"\nMEMORY (MB):")
row = f" {'RAM':<28}"
for brain_name in ["SPIKE", "NOVA", "AETHER", "HYBRID"]:
row += f" {results[brain_name]['memory_mb']:<13.2f}"
print(row)
# Save JSON
output_path = "/home/z/my-project/download/benchmark_results_v2.json"
with open(output_path, "w") as f:
json.dump(results, f, indent=2, default=str)
print(f"\n✓ Results saved to {output_path}")
# Chart
try:
import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt
fig, axes = plt.subplots(2, 2, figsize=(14, 9), constrained_layout=True)
fig.suptitle("Benchmark v2 — SPIKE vs NOVA vs AETHER vs HYBRID",
fontsize=15, fontweight='bold')
brain_names = ["SPIKE", "NOVA", "AETHER", "HYBRID"]
colors = ["#00d2ff", "#feca57", "#5f27cd", "#ff6b6b"]
# Accuracy per task
tasks = ["arithmetic", "memory", "tool_calling", "robustness"]
ax = axes[0, 0]
x = np.arange(len(tasks))
w = 0.20
for i, name in enumerate(brain_names):
accs = [results[name].get(t, {}).get("accuracy", 0) * 100 for t in tasks]
ax.bar(x + i * w, accs, w, label=name, color=colors[i])
ax.set_xticks(x + 1.5 * w)
ax.set_xticklabels(tasks, rotation=15)
ax.set_ylabel("Accuracy (%)")
ax.set_title("Accuracy per task")
ax.legend()
ax.grid(True, alpha=0.2, axis='y')
ax.set_ylim(0, 110)
# Latence arithmetic
ax = axes[0, 1]
for i, name in enumerate(brain_names):
times = []
for t in tasks:
tm = results[name].get(t, {}).get("avg_time_ms")
if tm is None:
tm = results[name].get(t, {}).get("avg_recall_ms", 0)
times.append(tm if tm else 0)
ax.plot(tasks, times, marker="o", label=name,
color=colors[i], linewidth=2)
ax.set_ylabel("Latency (ms)")
ax.set_title("Mean latency per task (log scale)")
ax.set_yscale("log")
ax.legend()
ax.grid(True, alpha=0.2)
# Memory
ax = axes[1, 0]
mems = [results[name]["memory_mb"] for name in brain_names]
bars = ax.bar(brain_names, mems, color=colors)
ax.set_ylabel("Memory (MB)")
ax.set_title("Memory footprint")
ax.grid(True, alpha=0.2, axis='y')
for bar, mem in zip(bars, mems):
height = bar.get_height()
ax.text(bar.get_x() + bar.get_width()/2., height,
f'{mem:.2f}', ha='center', va='bottom')
# Apprentissage vs rappel
ax = axes[1, 1]
x = np.arange(len(brain_names))
w = 0.35
learn_times = [results[name].get("memory", {}).get("avg_learn_ms", 0) for name in brain_names]
recall_times = [results[name].get("memory", {}).get("avg_recall_ms", 0) for name in brain_names]
ax.bar(x - w/2, learn_times, w, label="Learn", color="#00d2ff")
ax.bar(x + w/2, recall_times, w, label="Recall", color="#feca57")
ax.set_xticks(x)
ax.set_xticklabels(brain_names)
ax.set_ylabel("Time (ms)")
ax.set_title("Learn vs Recall (memory task)")
ax.set_yscale("log")
ax.legend()
ax.grid(True, alpha=0.2, axis='y')
fig_path = "/home/z/my-project/download/benchmark_chart_v2.png"
fig.savefig(fig_path, dpi=100, bbox_inches="tight", facecolor="white")
plt.close(fig)
print(f"✓ Chart saved to {fig_path}")
except Exception as e:
print(f"⚠ Chart not generated: {e}")
banner("END OF BENCHMARK", char="#")
if __name__ == "__main__":
main()