Skip to content

Commit 0055425

Browse files
authored
Merge pull request #75 from CurtisTechSolutions/test/vector-training-throughput
test(training): measure vector throughput across wheel platforms
2 parents 4d78d2b + 282c0c6 commit 0055425

3 files changed

Lines changed: 78 additions & 0 deletions

File tree

‎.github/workflows/python.yml‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,11 @@ jobs:
4141
env:
4242
POSITION_BASE_SHA: ${{ github.event.pull_request.base.sha }}
4343
- run: python -m pytest python/tests -q
44+
- run: python examples/vector_throughput.py --output vector-benchmark.json
45+
- uses: actions/upload-artifact@v4
46+
with:
47+
name: vector-throughput-${{ matrix.os }}
48+
path: vector-benchmark.json
4449
- uses: actions/upload-artifact@v4
4550
with:
4651
name: gamesforai-wheel-${{ matrix.os }}

‎examples/benchmarking.md‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
# Vector environment throughput
2+
3+
## Measuring batch throughput
4+
5+
Run `python examples/vector_throughput.py` for seeded full-game Connect Four batches. It times `VectorEnv.step`, including native transitions, projections, and owned NumPy transfer, with random policy selection and explicit resets outside the timed interval. One sample warms up the process; the JSON report records repeated rates, batch size, workers, platform, and logical CPUs. Wheel CI uploads one report per OS. Hosted-runner measurements are diagnostics; the PRD target still requires verification on the specified eight-core reference machine.

‎examples/vector_throughput.py‎

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
"""Measure native batched turns plus NumPy transfer, excluding model inference."""
2+
import argparse
3+
import json
4+
import os
5+
import platform
6+
import statistics
7+
import time
8+
from pathlib import Path
9+
10+
import numpy as np
11+
12+
from gamesforai import VectorEnv
13+
14+
15+
def benchmark(n, workers, batches, samples, seed):
16+
env = VectorEnv("connect4", n, threads=workers, seed=seed)
17+
rng = np.random.default_rng(seed)
18+
frame = env.frame()
19+
rates, total_resets = [], 0
20+
for sample in range(samples + 1):
21+
elapsed = 0.0
22+
for _ in range(batches):
23+
actions = [int(rng.choice(np.flatnonzero(mask))) for mask in frame["action_mask"]]
24+
started = time.perf_counter_ns()
25+
frame = env.step(actions)
26+
elapsed += (time.perf_counter_ns() - started) / 1e9
27+
indices = np.flatnonzero(frame["terminated"] | frame["truncated"]).tolist()
28+
if indices:
29+
seeds = [int(rng.integers(0, 2**63)) for _ in indices]
30+
frame = env.reset_at(indices, seeds)
31+
total_resets += len(indices)
32+
if sample: # Warm up allocations, workers, and caches before recording.
33+
rates.append(n * batches / elapsed)
34+
return {
35+
"game": "connect4", "batch_size": n, "requested_workers": workers,
36+
"samples": samples, "turns_per_sample": n * batches,
37+
"median_steps_per_second": statistics.median(rates),
38+
"min_steps_per_second": min(rates), "max_steps_per_second": max(rates),
39+
"episode_resets_including_warmup": total_resets, "seed": seed,
40+
}
41+
42+
43+
def main():
44+
parser = argparse.ArgumentParser(description=__doc__)
45+
parser.add_argument("--n", type=int, default=256)
46+
parser.add_argument("--batches", type=int, default=64)
47+
parser.add_argument("--samples", type=int, default=5)
48+
parser.add_argument("--seed", type=int, default=42)
49+
parser.add_argument("--output", default="vector-benchmark.json")
50+
args = parser.parse_args()
51+
if not 1 <= args.n <= 4096 or not 1 <= args.batches <= 10000 or not 1 <= args.samples <= 100:
52+
parser.error("n, batches, or samples outside benchmark limits")
53+
workers = sorted({1, min(8, os.cpu_count() or 1)})
54+
report = {
55+
"format_version": 1, "python": platform.python_version(),
56+
"platform": platform.platform(), "logical_cpus": os.cpu_count(),
57+
"measurement": "VectorEnv.step including Python call, Rust transition, projections, and owned NumPy arrays",
58+
"excluded": ["action sampling/model inference", "explicit reset_at calls", "one warmup sample"],
59+
"games": [benchmark(args.n, count, args.batches, args.samples, args.seed) for count in workers],
60+
"prd_reference": {"steps_per_second": 1_000_000, "cores": 8},
61+
"reference_hardware_verified": False,
62+
}
63+
Path(args.output).write_text(json.dumps(report, indent=2) + "\n", encoding="utf-8")
64+
print(json.dumps(report, indent=2))
65+
66+
67+
if __name__ == "__main__":
68+
main()

0 commit comments

Comments
 (0)