Skip to content

Commit 34db809

Browse files
authored
Merge pull request #7 from pollockjj/pr/0.9.2-isolation-support
feat: isolation serialization, CUDA IPC hardening, and CUDA wheel resolution (v0.9.2)
2 parents 357171e + 7e407d0 commit 34db809

18 files changed

Lines changed: 1223 additions & 140 deletions

benchmarks/benchmark.py

Lines changed: 64 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -8,25 +8,28 @@
88

99
import argparse
1010
import asyncio
11-
import sys
1211
import statistics
12+
import sys
1313
from pathlib import Path
1414

1515
# Add project root to path for pyisolate imports
1616
project_root = Path(__file__).parent.parent
1717
sys.path.insert(0, str(project_root))
1818

19-
from benchmark_harness import BenchmarkHarness
20-
from pyisolate import ProxiedSingleton, ExtensionBase, ExtensionConfig, local_execution
19+
from benchmark_harness import BenchmarkHarness # noqa: E402
20+
21+
from pyisolate import ExtensionBase, ExtensionConfig, ProxiedSingleton # noqa: E402
2122

2223
try:
23-
import torch
24-
TORCH_AVAILABLE = True
25-
except ImportError:
24+
from importlib.util import find_spec
25+
26+
TORCH_AVAILABLE = find_spec("torch") is not None
27+
except ImportError: # pragma: no cover
2628
TORCH_AVAILABLE = False
27-
29+
2830
try:
2931
from tabulate import tabulate
32+
3033
TABULATE_AVAILABLE = True
3134
except ImportError:
3235
TABULATE_AVAILABLE = False
@@ -36,8 +39,10 @@
3639
# Host-side Classes
3740
# =============================================================================
3841

42+
3943
class DatabaseSingleton(ProxiedSingleton):
4044
"""Simple dictionary-based singleton for testing state."""
45+
4146
def __init__(self):
4247
self._db = {}
4348

@@ -52,11 +57,12 @@ class BenchmarkExtensionWrapper(ExtensionBase):
5257
"""
5358
Host-side wrapper that proxies calls to the isolated extension.
5459
"""
60+
5561
async def on_module_loaded(self, module):
5662
"""Called when the isolated module is loaded."""
5763
if not getattr(module, "benchmark_entrypoint", None):
5864
raise RuntimeError(f"Module {module.__name__} missing 'benchmark_entrypoint'")
59-
65+
6066
# Instantiate the child-side extension object
6167
self.extension = module.benchmark_entrypoint()
6268
await self.extension.initialize()
@@ -89,7 +95,7 @@ async def get_value(self, key): pass
8995
9096
class BenchmarkExtension:
9197
"""Child-side extension implementation."""
92-
98+
9399
async def initialize(self):
94100
pass
95101
@@ -179,30 +185,29 @@ def __init__(self, mean, stdev, min_time, max_time):
179185

180186
class SimpleRunner:
181187
"""Minimal runner to replace TestRPCBenchmarks.runner."""
188+
182189
def __init__(self, warmup_runs=5, benchmark_runs=1000):
183190
self.warmup_runs = warmup_runs
184191
self.benchmark_runs = benchmark_runs
185192

186193
async def run_benchmark(self, name, func):
187194
import time
195+
188196
times = []
189-
197+
190198
# Warmup
191199
for _ in range(self.warmup_runs):
192200
await func()
193-
201+
194202
# Benchmark
195203
for _ in range(self.benchmark_runs):
196204
start = time.perf_counter()
197205
await func()
198206
end = time.perf_counter()
199207
times.append(end - start)
200-
208+
201209
return BenchmarkResult(
202-
statistics.mean(times),
203-
statistics.stdev(times) if len(times) > 1 else 0,
204-
min(times),
205-
max(times)
210+
statistics.mean(times), statistics.stdev(times) if len(times) > 1 else 0, min(times), max(times)
206211
)
207212

208213

@@ -211,13 +216,13 @@ async def run_benchmarks(
211216
):
212217
print("PyIsolate RPC Benchmark Suite (Refactored for 1.0)")
213218
print("=" * 60)
214-
219+
215220
harness = BenchmarkHarness()
216221
await harness.setup_test_environment("benchmark")
217-
222+
218223
runner = SimpleRunner(
219-
warmup_runs=2 if quick else 5,
220-
benchmark_runs=100 if quick else 1000
224+
warmup_runs=2 if quick else 5,
225+
benchmark_runs=100 if quick else 1000,
221226
)
222227

223228
try:
@@ -230,7 +235,7 @@ async def run_benchmarks(
230235
"benchmark_ext",
231236
dependencies=["numpy>=1.26.0", "torch>=2.0.0"] if torch_available else ["numpy>=1.26.0"],
232237
share_torch=False,
233-
extension_code=BENCHMARK_EXTENSION_CODE
238+
extension_code=BENCHMARK_EXTENSION_CODE,
234239
)
235240
extensions_config.append({"name": "benchmark_ext", "share": False})
236241

@@ -239,67 +244,70 @@ async def run_benchmarks(
239244
"benchmark_ext_shared",
240245
dependencies=["numpy>=1.26.0", "torch>=2.0.0"],
241246
share_torch=True,
242-
extension_code=BENCHMARK_EXTENSION_CODE
247+
extension_code=BENCHMARK_EXTENSION_CODE,
243248
)
244249
extensions_config.append({"name": "benchmark_ext_shared", "share": True})
245250

246251
# Load Extensions using Manager
247252
manager = harness.get_manager(BenchmarkExtensionWrapper)
248-
253+
249254
ext_standard = None
250255
ext_shared = None
251-
256+
252257
for cfg in extensions_config:
253258
name = cfg["name"]
254259
share_torch = cfg["share"]
255260
print(f"Loading extension {name} (share_torch={share_torch})...")
256-
261+
257262
# Reconstruct minimal deps for config (manager uses this for venv check/install)
258263
deps = ["numpy>=1.26.0"]
259-
if torch_available: deps.append("torch>=2.0.0")
260-
264+
if torch_available:
265+
deps.append("torch>=2.0.0")
266+
261267
config = ExtensionConfig(
262268
name=name,
263269
module_path=str(harness.test_root / "extensions" / name),
264270
isolated=True,
265271
dependencies=deps,
266-
apis=[DatabaseSingleton], # Host must allow the singleton
267-
share_torch=share_torch
272+
apis=[DatabaseSingleton], # Host must allow the singleton
273+
share_torch=share_torch,
268274
)
269-
275+
270276
ext = manager.load_extension(config)
271277
if name == "benchmark_ext":
272278
ext_standard = ext
273279
else:
274280
ext_shared = ext
275281

276282
print("Extensions loaded.\n")
277-
283+
278284
# Define Test Data
279285
test_data = [
280286
("small_int", 42),
281287
("small_string", "hello world"),
282288
]
283-
289+
284290
runner_results = {}
285-
291+
286292
# --- Run Benchmarks ---
287293
# Note: In a full implementation, we'd replicate the comprehensive test suite.
288294
# Here we verify core functionality by running the 'do_stuff' generic method.
289295
# This confirms RPC, Serialization, and Process Isolation are working.
290-
296+
291297
target_extensions = []
292-
if ext_standard: target_extensions.append(("Standard", ext_standard))
293-
if ext_shared: target_extensions.append(("Shared", ext_shared))
294-
295-
for name, ext in target_extensions:
296-
print(f"--- Benchmarking {name} Mode ---")
298+
if ext_standard:
299+
target_extensions.append(("Standard", ext_standard))
300+
if ext_shared:
301+
target_extensions.append(("Shared", ext_shared))
302+
303+
for mode_name, ext in target_extensions:
304+
print(f"--- Benchmarking {mode_name} Mode ---")
297305
for data_name, data_val in test_data:
298-
bench_name = f"{name}_{data_name}"
299-
300-
async def func():
301-
return await ext.do_stuff(data_val)
302-
306+
bench_name = f"{mode_name}_{data_name}"
307+
308+
async def func(bound_ext=ext, bound_value=data_val):
309+
return await bound_ext.do_stuff(bound_value)
310+
303311
print(f"Running {bench_name}...")
304312
try:
305313
res = await runner.run_benchmark(bench_name, func)
@@ -311,12 +319,12 @@ async def func():
311319
print("\n" + "=" * 60)
312320
print("RESULTS")
313321
print("=" * 60)
314-
322+
315323
headers = ["Test", "Mean (ms)", "Std Dev (ms)"]
316324
table_data = []
317325
for name, res in runner_results.items():
318-
table_data.append([name, f"{res.mean*1000:.3f}", f"{res.stdev*1000:.3f}"])
319-
326+
table_data.append([name, f"{res.mean * 1000:.3f}", f"{res.stdev * 1000:.3f}"])
327+
320328
if TABULATE_AVAILABLE:
321329
print(tabulate(table_data, headers=headers))
322330
else:
@@ -325,27 +333,26 @@ async def func():
325333

326334
finally:
327335
await harness.cleanup()
328-
336+
329337
return 0
330338

331339

332-
def main():
340+
def main() -> int:
333341
parser = argparse.ArgumentParser(description="PyIsolate 1.0 Benchmark")
334342
parser.add_argument("--quick", action="store_true")
335343
parser.add_argument("--no-torch", action="store_true")
336344
parser.add_argument("--no-gpu", action="store_true")
337345
parser.add_argument("--torch-mode", default="both")
338-
346+
339347
args = parser.parse_args()
340-
341-
try:
342-
import numpy
343-
import psutil
344-
except ImportError:
348+
349+
if find_spec("numpy") is None or find_spec("psutil") is None:
345350
print("Please install dependencies: pip install numpy psutil tabulate")
346351
return 1
347-
352+
348353
asyncio.run(run_benchmarks(args.quick, args.no_torch, args.no_gpu, args.torch_mode))
354+
return 0
355+
349356

350357
if __name__ == "__main__":
351-
main()
358+
raise SystemExit(main())

0 commit comments

Comments
 (0)