PyIsolate RPC Benchmarks
This document describes the benchmarking suite for measuring RPC call overhead in pyisolate.
Overview
The benchmark suite measures the performance overhead of proxied calls compared to local execution, specifically excluding setup costs like virtual environment creation and process startup.
Key Features
- Comprehensive error handling: Gracefully handles CUDA OOM errors and process timeouts
- Detailed results reporting: Shows successful, failed, and skipped tests separately
- Multiple test modes: Support for standard (no share_torch) and shared (share_torch) configurations
- Statistical analysis: Provides mean, standard deviation, min/max times for each benchmark
Benchmark Categories
1. Small Data Benchmarks
What it measures: Overhead for calls with minimal data transfer
- Integer values (4-8 bytes)
- Small strings (~110 characters)
Purpose: Isolates the pure RPC protocol overhead without data serialization costs.
2. Large Data Benchmarks
What it measures: Overhead for calls with significant data transfer
- Large numpy arrays (~8MB)
- Large byte arrays (~50MB)
Purpose: Measures how RPC performance scales with data size and identifies serialization bottlenecks.
3. Torch Tensor Benchmarks
What it measures: Performance with PyTorch tensors across various sizes
- Tiny tensors (10x10, ~400B)
- Small tensors (100x100, ~40KB)
- Medium tensors (512x512, ~1MB)
- Large tensors (1024x1024, ~4MB)
- Image-sized tensors (3x8192x8192, ~800MB for 8K RGB)
- Model-sized tensors (40132x40132, ~6GB for modern LLM/diffusion models)
- CPU and GPU tensors
- With and without
share_torchoptimization
Purpose: Evaluates tensor serialization scaling, tensor-specific optimizations, and GPU memory transfer costs.
4. Complex Call Patterns
What it measures: Advanced RPC scenarios
- Recursive calls through host singletons
- Extension → Host → Extension call chains
- Proxied singleton performance
Purpose: Tests real-world usage patterns and nested call overhead.
Running Benchmarks
Quick Start
# Install dependencies
uv pip install -e ".[bench]"
# Quick benchmark using existing extensions
python benchmarks/simple_benchmark.py
# Include very large tensors (uses significant memory)
python benchmarks/simple_benchmark.py --large-tensors
# Full benchmark suite with statistical analysis
python benchmarks/benchmark.py
# Quick mode (fewer iterations)
python benchmarks/benchmark.py --quick
Command-Line Options
benchmark.py Options
--quick: Run with reduced iterations (2 warmup, 100 benchmark runs instead of 5 warmup, 1000 benchmark runs)--no-torch: Skip PyTorch tensor benchmarks entirely--no-gpu: Skip GPU benchmarks even if CUDA is available--torch-mode {both,standard,shared}: Control which torch configurations to test (default: shared)both: Test both standard and share_torch configurationsstandard: Test only without share_torch optimizationshared: Test only with share_torch optimization enabled
simple_benchmark.py Options
--large-tensors: Include very large tensor benchmarks (6GB+ memory usage)
Example Commands
# Test both standard and shared modes
python benchmarks/benchmark.py --torch-mode both
# Quick test without GPU
python benchmarks/benchmark.py --quick --no-gpu
# Test only standard mode without torch optimizations
python benchmarks/benchmark.py --torch-mode standard
# Run via pytest for more verbose output
pytest tests/test_benchmarks.py -v -s
Understanding Results
Results Format
The benchmark output is organized into three sections:
-
Successful Benchmarks: Tests that completed successfully with performance metrics
- Test name (includes _standard or _shared suffix)
- Mean time in milliseconds
- Standard deviation
- Min/Max times observed
-
Failed Tests: Tests that encountered errors
- Test name
- Error type (e.g., "CUDA OOM/Timeout")
-
Skipped Tests: Tests that couldn't run (e.g., when extension was stopped)
- Test name
- Reason for skipping
-
Summary Statistics: Overall counts of successful/failed/skipped tests
Example Output
============================================================
RPC BENCHMARK RESULTS
============================================================
Successful Benchmarks:
+--------------------------+-------------+----------------+------------+------------+
| Test | Mean (ms) | Std Dev (ms) | Min (ms) | Max (ms) |
+==========================+=============+================+============+============+
| small_int_shared | 0.31 | 0.06 | 0.26 | 0.69 |
+--------------------------+-------------+----------------+------------+------------+
| small_string_shared | 0.34 | 0.07 | 0.26 | 0.86 |
+--------------------------+-------------+----------------+------------+------------+
| medium_tensor_gpu_shared | 1.38 | 0.30 | 1.09 | 2.72 |
+--------------------------+-------------+----------------+------------+------------+
Fastest result: 0.31ms
Failed Tests:
+----------------------+------------------+
| Test | Error |
+======================+==================+
| model_6gb_gpu_shared | CUDA OOM/Timeout |
+----------------------+------------------+
Summary: 15 successful, 1 failed, 0 skipped (Total: 16)
Metrics Reported
- Mean: Average time across all runs
- Std Dev: Standard deviation (consistency measure)
- Min/Max: Range of observed times
- Fastest result: The lowest mean time observed (baseline reference)
Baseline Comparisons
Local execution baselines use the @local_execution decorator to run the same operations without RPC overhead. This isolates the pure network/serialization costs.
Share Torch Configuration
Benchmarks test both configurations for PyTorch tensors:
- Standard Mode (
share_torch=False): Tensors are serialized/deserialized for each call - Shared Mode (
share_torch=True): Tensors are shared via memory mapping (zero-copy)
The performance difference between these modes can be significant for large tensors.
Memory Usage
For large data benchmarks, memory usage is tracked to identify potential leaks or excessive copying.
Expected Performance Characteristics
Typical Overhead Ranges
- Small data: 0.1-1ms overhead (protocol + serialization)
- Large arrays: Proportional to data size (serialization dominant)
- Torch tensors: Varies with
share_torchoptimization - Recursive calls: Multiplicative overhead per hop
Performance Factors
- Data size: Larger payloads take longer to serialize/deserialize
- Data type: Torch tensors have optimized paths vs generic Python objects
- Call depth: Nested calls accumulate overhead
- Memory pressure: Large transfers may trigger garbage collection
Interpreting Results
Good Performance Indicators
- Low standard deviation (consistent performance)
- Reasonable overhead percentages for use case
- Linear scaling with data size
- Memory usage remains stable
Performance Issues
- High standard deviation (inconsistent performance)
- Exponential scaling with data size
- Memory usage growing over time
- Excessive overhead for small data
Troubleshooting
Common Issues
- High variability: System load, garbage collection, or thermal throttling
- Memory growth: Potential leaks or inefficient serialization
- GPU errors: CUDA availability or memory issues
- Import errors: Missing dependencies (torch, numpy, etc.)
- CUDA OOM errors: Large tensors exceeding GPU memory
Solutions
- Run benchmarks on idle system
- Use
--quickmode for faster iteration - Check
nvidia-smifor GPU status - Verify all dependencies installed with
pip install -e .[bench] - Use
--no-gputo skip GPU tests if encountering CUDA OOM - Use
--torch-mode standardto avoid tensor sharing issues
Error Handling
The benchmark suite includes robust error handling:
- Timeout detection: Tests that hang (e.g., due to CUDA OOM during serialization) are detected via 30-second timeout
- Process cleanup: Failed extensions are automatically stopped and cleaned up
- Graceful continuation: Benchmarks continue running even after individual test failures
- Clear error reporting: Failed tests are reported with error reasons in the results table
Benchmark Implementation
Design Principles
- Setup Exclusion: Extension creation and process startup excluded from timing
- Statistical Rigor: Multiple runs with warmup to ensure stable results
- Baseline Comparison: Local execution baselines for overhead calculation
- Real-world Scenarios: Tests reflect actual usage patterns
- Error Resilience: Individual test failures don't stop the entire benchmark suite
Technical Details
- Uses
time.perf_counter()for high-resolution timing - Garbage collection forced before measurements
- Memory usage tracked with
psutil - Statistical analysis with built-in
statisticsmodule - Timeout protection via
asyncio.wait_for()(30 seconds per test) - Process management through
ExtensionManager.stop_extension()
File Structure
benchmarks/benchmark.py: Main benchmark suite with full statistical analysisbenchmarks/simple_benchmark.py: Quick benchmarks for rapid testingtests/test_benchmarks.py: Benchmark runner class and test utilities
Memory Benchmarking
Overview
The memory benchmarking suite (benchmarks/memory_benchmark.py) measures RAM and VRAM usage across host and child processes with varying numbers of extensions and different tensor sharing configurations.
Running Memory Benchmarks
# Run full memory benchmark suite
python benchmarks/memory_benchmark.py
# Test with custom extension counts
python benchmarks/memory_benchmark.py --counts 1,5,10,20,50
# Test up to 100 extensions
python benchmarks/memory_benchmark.py --max-extensions 100
# Only test large tensor sharing
python benchmarks/memory_benchmark.py --large-only
# Only test small tensor scaling
python benchmarks/memory_benchmark.py --small-only
Memory Benchmark Features
- Process Memory Tracking: Uses
psutilto track RAM usage across process trees - GPU Memory Tracking: Uses
nvidia-ml-py3to track VRAM usage per process - Extension Scaling: Tests memory usage with 1-100 extensions
- Tensor Sharing Analysis: Compares memory usage with and without
share_torch - Large Tensor Tests: Tests with 2GB tensors to verify memory sharing efficiency
Memory Benchmark Output
The memory benchmark provides detailed tables showing:
- RAM usage per extension
- Memory overhead for tensor transfers
- VRAM usage for GPU tensors
- Memory savings from
share_torchoptimization
Example output:
MEMORY BENCHMARK SUMMARY
================================================================================
Baseline Memory Usage:
RAM: 150.3 MB
VRAM: 0.0 MB
CPU NO SHARE Results:
+-------------+----------------+-------------------+-------------+---------+
| Extensions | RAM/Ext (MB) | Tensor RAM (MB) | VRAM (MB) | Shared |
+=============+================+===================+=============+=========+
| 1 | 45.2 | 1.1 | 0.0 | No |
+-------------+----------------+-------------------+-------------+---------+
| 5 | 44.8 | 5.3 | 0.0 | No |
+-------------+----------------+-------------------+-------------+---------+
2GB TENSOR SHARING TEST:
+--------------------+--------------------+--------------------------+------------------------+
| Config | Tensor Size (MB) | Distribution RAM (MB) | RAM/Extension (MB) |
+====================+====================+==========================+========================+
| share_torch=False | 2048.0 | 10240.0 | 2048.0 |
+--------------------+--------------------+--------------------------+------------------------+
| share_torch=True | 2048.0 | 512.0 | 102.4 |
+--------------------+--------------------+--------------------------+------------------------+
Memory Sharing Analysis:
Memory saved with share_torch: 9728.0 MB (95.0%)
Key Metrics
- RAM/Extension: Average memory overhead per extension process
- Tensor RAM: Additional RAM used for tensor distribution
- VRAM: GPU memory usage (if CUDA available)
- Memory Sharing: Whether tensors are shared (same memory address) or copied
Contributing
When adding new benchmarks:
- Follow the existing pattern in
benchmarks/benchmark.pyorbenchmarks/memory_benchmark.py - Include error handling for potential failures
- Add appropriate test data sizes
- Document what the benchmark measures
- Update this README with new benchmark descriptions
- Test with various
--torch-modeoptions to ensure compatibility - For memory benchmarks, ensure proper cleanup to avoid memory leaks