106 lines
2.8 KiB
Python
106 lines
2.8 KiB
Python
|
|
"""
|
|||
|
|
GPU vs CPU benchmark for ferro_ta.gpu (SMA, EMA, RSI).
|
|||
|
|
|
|||
|
|
Requires:
|
|||
|
|
pip install "ferro-ta[gpu]" # or pip install torch
|
|||
|
|
|
|||
|
|
Run:
|
|||
|
|
python benchmarks/bench_gpu.py
|
|||
|
|
|
|||
|
|
The script compares wall-clock time for 1M-element arrays and prints a
|
|||
|
|
summary table. If PyTorch is not installed or no GPU is found, GPU columns are skipped.
|
|||
|
|
"""
|
|||
|
|
|
|||
|
|
from __future__ import annotations
|
|||
|
|
|
|||
|
|
import time
|
|||
|
|
|
|||
|
|
import numpy as np
|
|||
|
|
|
|||
|
|
# Try to import PyTorch
|
|||
|
|
try:
|
|||
|
|
import torch
|
|||
|
|
|
|||
|
|
TORCH_AVAILABLE = True
|
|||
|
|
if torch.cuda.is_available():
|
|||
|
|
DEVICE = "cuda"
|
|||
|
|
elif hasattr(torch.backends, "mps") and torch.backends.mps.is_available():
|
|||
|
|
DEVICE = "mps"
|
|||
|
|
else:
|
|||
|
|
DEVICE = None
|
|||
|
|
except ImportError:
|
|||
|
|
torch = None # type: ignore[assignment]
|
|||
|
|
TORCH_AVAILABLE = False
|
|||
|
|
DEVICE = None
|
|||
|
|
|
|||
|
|
from ferro_ta.gpu import ema, rsi, sma
|
|||
|
|
|
|||
|
|
N = 1_000_000
|
|||
|
|
REPEATS = 10
|
|||
|
|
|
|||
|
|
|
|||
|
|
def _time_fn(fn, *args, **kwargs) -> float:
|
|||
|
|
"""Return minimum wall time (seconds) over REPEATS calls."""
|
|||
|
|
times = []
|
|||
|
|
for _ in range(REPEATS):
|
|||
|
|
t0 = time.perf_counter()
|
|||
|
|
fn(*args, **kwargs)
|
|||
|
|
if DEVICE == "cuda":
|
|||
|
|
torch.cuda.synchronize()
|
|||
|
|
elif DEVICE == "mps":
|
|||
|
|
torch.mps.synchronize()
|
|||
|
|
times.append(time.perf_counter() - t0)
|
|||
|
|
return min(times)
|
|||
|
|
|
|||
|
|
|
|||
|
|
def main() -> None:
|
|||
|
|
rng = np.random.default_rng(42)
|
|||
|
|
close_cpu = rng.uniform(100.0, 200.0, N)
|
|||
|
|
|
|||
|
|
print(f"Array size: {N:,} elements")
|
|||
|
|
print(f"Repeats: {REPEATS}")
|
|||
|
|
print(f"Device: {DEVICE if DEVICE else 'CPU'}")
|
|||
|
|
print()
|
|||
|
|
|
|||
|
|
header = f"{'Indicator':<20} {'CPU (ms)':>10}"
|
|||
|
|
if DEVICE:
|
|||
|
|
header += f" {'GPU (ms)':>10} {'Speedup':>10}"
|
|||
|
|
print(header)
|
|||
|
|
print("-" * len(header))
|
|||
|
|
|
|||
|
|
for name, fn, kwargs in [
|
|||
|
|
("sma(period=30)", sma, {"timeperiod": 30}),
|
|||
|
|
("ema(period=30)", ema, {"timeperiod": 30}),
|
|||
|
|
("rsi(period=14)", rsi, {"timeperiod": 14}),
|
|||
|
|
]:
|
|||
|
|
cpu_time = _time_fn(fn, close_cpu, **kwargs) * 1000 # ms
|
|||
|
|
|
|||
|
|
row = f"{name:<20} {cpu_time:>10.3f}"
|
|||
|
|
if DEVICE:
|
|||
|
|
dtype = torch.float32 if DEVICE == "mps" else torch.float64
|
|||
|
|
close_gpu = torch.tensor(close_cpu, dtype=dtype, device=DEVICE)
|
|||
|
|
# Warm-up
|
|||
|
|
fn(close_gpu, **kwargs)
|
|||
|
|
if DEVICE == "cuda":
|
|||
|
|
torch.cuda.synchronize()
|
|||
|
|
elif DEVICE == "mps":
|
|||
|
|
torch.mps.synchronize()
|
|||
|
|
gpu_time = _time_fn(fn, close_gpu, **kwargs) * 1000 # ms
|
|||
|
|
speedup = cpu_time / gpu_time
|
|||
|
|
row += f" {gpu_time:>10.3f} {speedup:>10.2f}×"
|
|||
|
|
print(row)
|
|||
|
|
|
|||
|
|
if not TORCH_AVAILABLE:
|
|||
|
|
print()
|
|||
|
|
print("PyTorch not available — GPU columns skipped.")
|
|||
|
|
print("Install with: pip install 'ferro_ta[gpu]'")
|
|||
|
|
elif not DEVICE:
|
|||
|
|
print()
|
|||
|
|
print(
|
|||
|
|
"PyTorch found, but no CUDA or MPS device detected — GPU columns skipped."
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
|
|||
|
|
if __name__ == "__main__":
|
|||
|
|
main()
|