SmolVM is optimized for low-latency agent workflows, providing sub-second boot times and minimal overhead for command execution.
Benchmark results
Latest lifecycle timings (p50) on a standard Linux host:
| Phase | Time |
|---|
| Create + Start | ~572ms |
| SSH ready | ~2.1s |
| Command execution | ~43ms |
| Stop + Delete | ~751ms |
| Full lifecycle (boot → run → teardown) | ~3.5s |
Benchmarks measured on AMD Ryzen 7 7800X3D (8C/16T), Ubuntu Linux, KVM/Firecracker backend.
Running your own benchmarks
You can benchmark SmolVM on your hardware using the included benchmark script:
python scripts/benchmarks/bench_subprocess.py --vms 10 -v
This will create, start, run commands in, and teardown 10 VMs sequentially, reporting detailed timing metrics.
- MicroVM Creation: SmolVM allocates IP addresses, TAP devices, and network rules in ~572ms
- SSH Ready Time: The VM boots and becomes accessible via SSH in ~2.1s total
- Hardware Virtualization: Uses KVM on Linux and Hypervisor.framework on macOS for near-native performance
- Command Execution: ~43ms average latency for running commands via SSH
- Memory Overhead: Minimal host overhead beyond configured VM memory (default 512MB)
- CPU Efficiency: Hardware virtualization provides near-native CPU performance
- Graceful Shutdown: Firecracker VMs can be stopped in ~751ms
- Resource Cleanup: Network rules, TAP devices, and disk images are cleaned up automatically
- Fast Path for Ephemeral VMs: SIGKILL-based teardown for sandbox VMs that don’t need state preservation
Optimization tips
1. Reuse VMs for multiple commands
Instead of creating a new VM for each command, reuse the same VM:
from smolvm import SmolVM
with SmolVM() as vm:
# The context manager starts the VM automatically
result1 = vm.run("apk add py3-requests")
result2 = vm.run("python3 script.py")
result3 = vm.run("cat output.txt")
This amortizes the ~2.1s boot time across multiple operations.
2. Use appropriate resource allocation
Configure CPU and memory based on your workload:
from smolvm import SmolVM, VMConfig
# Lightweight workload
config = VMConfig(
vcpu_count=1,
mem_size_mib=256,
disk_size_mib=2048
)
# Heavy workload
config = VMConfig(
vcpu_count=4,
mem_size_mib=2048,
disk_size_mib=8192
)
Over-allocating resources can lead to host memory pressure and slower performance.
3. Pre-built custom images
For workloads requiring specific dependencies, build a custom rootfs image with pre-installed packages using ImageBuilder:
from smolvm.build import ImageBuilder
from smolvm import SmolVM, VMConfig
from smolvm.utils import ensure_ssh_key
private_key, public_key = ensure_ssh_key()
builder = ImageBuilder()
kernel, rootfs = builder.build_alpine_ssh_key(
public_key,
rootfs_size_mb=2048,
)
config = VMConfig(
kernel_path=kernel,
rootfs_path=rootfs,
boot_args="console=ttyS0 reboot=k panic=1 init=/init",
)
with SmolVM(config, ssh_key_path=str(private_key)) as vm:
vm.run("apk add python3 py3-requests py3-numpy")
This eliminates the need to install packages at runtime.
4. Shared vs isolated disk mode
Choose the appropriate disk mode for your use case:
Isolated Mode (default): Each VM gets its own copy of the rootfs
- ✅ Complete isolation between VMs
- ✅ No cross-VM contamination
- ❌ Higher disk usage
- ❌ Copy overhead on first boot
Shared Mode: All VMs use the same rootfs image
- ✅ No disk copy overhead
- ✅ Lower disk usage
- ❌ Changes persist across VMs
- ❌ Potential cross-VM contamination
config = VMConfig(disk_mode="shared") # For read-only workloads
5. Backend selection
SmolVM supports multiple backends with different performance characteristics:
- Firecracker (Linux): Fastest boot times, lowest overhead, recommended for production
- QEMU (macOS/Linux): Broader compatibility, slightly higher overhead
from smolvm import SmolVM
# Explicitly choose backend
vm = SmolVM(backend="firecracker") # Linux only
vm = SmolVM(backend="qemu") # macOS/Linux
Check VM status
Use the CLI to list all running VMs and their status:
Or check a specific VM from Python:
from smolvm import SmolVM
vm = SmolVM.from_id("my-vm")
info = vm.info
print(f"VM {vm.vm_id}: {info.status}")
print(f" PID: {info.pid}")
if info.network:
print(f" IP: {info.network.guest_ip}")
vm.close()
Clean up stale VMs
Remove VMs marked as running but whose processes have died:
Scalability considerations
IP address pool
By default, SmolVM allocates IPs from 172.16.0.2 to 172.16.0.254, supporting 253 concurrent VMs.
SSH port pool
Host-side SSH forwarding uses ports 2200-2999, supporting 800 concurrent VMs.
System limits
Check your system’s ulimit for open files and processes:
# Check file descriptor limit
ulimit -n
# Check process limit
ulimit -u
# Increase limits (add to /etc/security/limits.conf)
* soft nofile 65536
* hard nofile 65536
Profiling tips
Measure individual phases
import time
from smolvm import SmolVM
# Create + start phase
start = time.time()
vm = SmolVM()
vm.start()
print(f"Create + Start: {time.time() - start:.3f}s")
# Command execution
start = time.time()
vm.run("echo hello")
print(f"Command: {time.time() - start:.3f}s")
# Teardown
start = time.time()
vm.delete()
vm.close()
print(f"Teardown: {time.time() - start:.3f}s")
Network latency
Measure network roundtrip time:
import time
from smolvm import SmolVM
with SmolVM() as vm:
start = time.time()
result = vm.run("echo pong")
elapsed = (time.time() - start) * 1000
print(f"SSH roundtrip: {elapsed:.1f}ms")