| """ |
| Condensate: Rust vs Python Benchmark |
| |
| Runs the same workloads through both backends and compares: |
| - Build time |
| - Prediction latency |
| - Accuracy |
| - Scoring time |
| |
| Run: python3 test_rust_vs_python.py |
| """ |
|
|
| import numpy as np |
| import time |
| import os |
| import sys |
|
|
| sys.path.insert(0, os.path.dirname(__file__)) |
|
|
| from membrane import Membrane |
| from graph_builder import GraphBuilder |
| from predictor import Predictor |
| from rust_predictor import is_rust_available, RustPredictorWrapper |
|
|
|
|
| def generate_workload(num_layers, num_hot, iterations): |
| """Generate access log from a simulated workload.""" |
| Membrane.clear() |
|
|
| state = {f"layer_{i}": np.random.randn(64, 64).astype(np.float32) |
| for i in range(num_layers)} |
| wrapped = Membrane.wrap(state, "model") |
|
|
| hot_set = set(range(num_hot)) |
| for _ in range(iterations): |
| for i in range(num_layers): |
| if i in hot_set: |
| _ = wrapped[f"layer_{i}"] |
| elif np.random.random() < 0.03: |
| _ = wrapped[f"layer_{i}"] |
| time.sleep(0.001) |
|
|
| return Membrane.get_log() |
|
|
|
|
| def benchmark_python(log): |
| """Benchmark the Python predictor.""" |
| |
| start = time.monotonic() |
| graph = GraphBuilder(causal_window_ns=5_000_000) |
| graph.build(log) |
| build_time = time.monotonic() - start |
|
|
| |
| predictor = Predictor() |
| start = time.monotonic() |
| predictor.learn(graph) |
| learn_time = time.monotonic() - start |
|
|
| |
| start = time.monotonic() |
| result = predictor.score(log) |
| score_time = time.monotonic() - start |
|
|
| |
| start = time.monotonic() |
| for _ in range(1000): |
| predictor.predict("model.layer_0") |
| predict_time = (time.monotonic() - start) / 1000 |
|
|
| return { |
| "build_ms": build_time * 1000, |
| "learn_ms": learn_time * 1000, |
| "score_ms": score_time * 1000, |
| "predict_us": predict_time * 1_000_000, |
| "accuracy": result["accuracy"], |
| "predictions": result["predictions_made"], |
| "hits": result["hits"], |
| } |
|
|
|
|
| def benchmark_rust(log): |
| """Benchmark the Rust predictor.""" |
| if not is_rust_available(): |
| return None |
|
|
| wrapper = RustPredictorWrapper() |
|
|
| |
| start = time.monotonic() |
| wrapper.learn_from_log(log) |
| build_learn_time = time.monotonic() - start |
|
|
| |
| start = time.monotonic() |
| result = wrapper.score(log) |
| score_time = time.monotonic() - start |
|
|
| |
| start = time.monotonic() |
| for _ in range(1000): |
| wrapper.predict("model.layer_0") |
| predict_time = (time.monotonic() - start) / 1000 |
|
|
| return { |
| "build_ms": build_learn_time * 1000, |
| "learn_ms": 0, |
| "score_ms": score_time * 1000, |
| "predict_us": predict_time * 1_000_000, |
| "accuracy": result["accuracy"], |
| "predictions": result["predictions_made"], |
| "hits": result["hits"], |
| } |
|
|
|
|
| def run_comparison(name, num_layers, num_hot, iterations): |
| """Run both backends on the same workload and compare.""" |
| print(f"\n{'='*65}") |
| print(f" {name}") |
| print(f" {num_layers} layers, {num_hot} hot, {iterations} iterations") |
| print(f"{'='*65}") |
|
|
| log = generate_workload(num_layers, num_hot, iterations) |
| print(f" Events generated: {len(log)}") |
|
|
| |
| py = benchmark_python(log) |
|
|
| |
| rs = benchmark_rust(log) |
|
|
| |
| print(f"\n {'Metric':<25} {'Python':>12} {'Rust':>12} {'Speedup':>10}") |
| print(f" {'-'*25} {'-'*12} {'-'*12} {'-'*10}") |
|
|
| if rs: |
| py_build = py['build_ms'] + py['learn_ms'] |
| rs_build = rs['build_ms'] |
| build_speedup = py_build / rs_build if rs_build > 0 else float('inf') |
|
|
| score_speedup = py['score_ms'] / rs['score_ms'] if rs['score_ms'] > 0 else float('inf') |
| predict_speedup = py['predict_us'] / rs['predict_us'] if rs['predict_us'] > 0 else float('inf') |
|
|
| print(f" {'Build + Learn':<25} {py_build:>10.1f}ms {rs_build:>10.1f}ms {build_speedup:>9.1f}x") |
| print(f" {'Score':<25} {py['score_ms']:>10.1f}ms {rs['score_ms']:>10.1f}ms {score_speedup:>9.1f}x") |
| print(f" {'Single predict()':<25} {py['predict_us']:>10.1f}μs {rs['predict_us']:>10.1f}μs {predict_speedup:>9.1f}x") |
| print(f" {'Accuracy':<25} {py['accuracy']:>11.1f}% {rs['accuracy']:>11.1f}%") |
| print(f" {'Predictions':<25} {py['predictions']:>12} {rs['predictions']:>12}") |
| print(f" {'Hits':<25} {py['hits']:>12} {rs['hits']:>12}") |
| else: |
| print(f" Rust backend not available — showing Python only") |
| print(f" {'Build + Learn':<25} {py['build_ms'] + py['learn_ms']:>10.1f}ms") |
| print(f" {'Score':<25} {py['score_ms']:>10.1f}ms") |
| print(f" {'Single predict()':<25} {py['predict_us']:>10.1f}μs") |
| print(f" {'Accuracy':<25} {py['accuracy']:>11.1f}%") |
|
|
| return py, rs |
|
|
|
|
| if __name__ == "__main__": |
| print("=" * 65) |
| print(" CONDENSATE — Rust vs Python Benchmark") |
| print("=" * 65) |
|
|
| if is_rust_available(): |
| print(" Rust backend: AVAILABLE") |
| else: |
| print(" Rust backend: NOT AVAILABLE") |
| print(" Build it: cd rust_core && maturin develop --release") |
| print(" Showing Python-only results for now.") |
|
|
| run_comparison("Small (inference-like)", 16, 4, 50) |
| run_comparison("Medium (mixed workload)", 32, 8, 50) |
| run_comparison("Large (stress test)", 64, 8, 30) |
|
|
| print(f"\n{'='*65}") |
| if is_rust_available(): |
| print(" Rust core is live. Production-ready prediction engine.") |
| else: |
| print(" Build the Rust core to see speedup numbers:") |
| print(" cd rust_core && maturin develop --release") |
| print(f"{'='*65}") |
|
|