Deep CFR multiprocessing benchmark 보강
This commit is contained in:
@@ -32,3 +32,16 @@ def benchmark_traversal(
|
||||
"advantage_samples": metrics.advantage_samples,
|
||||
"strategy_samples": metrics.strategy_samples,
|
||||
}
|
||||
|
||||
|
||||
def benchmark_traversal_modes(
|
||||
config: DeepCFRConfig | None = None,
|
||||
) -> dict[str, dict[str, float | int]]:
|
||||
single = benchmark_traversal(config, num_workers=0)
|
||||
multi = benchmark_traversal(config, num_workers=2)
|
||||
speedup = float(multi["nodes_per_second"]) / max(float(single["nodes_per_second"]), 1.0e-12)
|
||||
return {
|
||||
"single": single,
|
||||
"multi": multi,
|
||||
"summary": {"speedup": speedup},
|
||||
}
|
||||
|
||||
@@ -5,7 +5,10 @@ import json
|
||||
from dataclasses import replace
|
||||
from pathlib import Path
|
||||
|
||||
from coolrl_lost_cities.games.classic.deep_cfr.benchmark import benchmark_traversal
|
||||
from coolrl_lost_cities.games.classic.deep_cfr.benchmark import (
|
||||
benchmark_traversal,
|
||||
benchmark_traversal_modes,
|
||||
)
|
||||
from coolrl_lost_cities.games.classic.deep_cfr.config import DeepCFRConfig, config_from_dict
|
||||
from coolrl_lost_cities.games.classic.deep_cfr.evaluate import (
|
||||
evaluate_strategy_network,
|
||||
@@ -61,13 +64,17 @@ def eval_command(args: argparse.Namespace) -> None:
|
||||
|
||||
|
||||
def benchmark_command(args: argparse.Namespace) -> None:
|
||||
result = benchmark_traversal(
|
||||
DeepCFRConfig(
|
||||
config = DeepCFRConfig(
|
||||
traversals_per_iteration=args.traversals,
|
||||
max_traversal_depth=args.depth,
|
||||
seed=args.seed,
|
||||
save_every_iteration=False,
|
||||
),
|
||||
)
|
||||
if args.compare:
|
||||
print(json.dumps(benchmark_traversal_modes(config), indent=2, sort_keys=True))
|
||||
return
|
||||
result = benchmark_traversal(
|
||||
config,
|
||||
num_workers=args.workers,
|
||||
)
|
||||
print(json.dumps(result, indent=2, sort_keys=True))
|
||||
@@ -104,6 +111,7 @@ def main(argv: list[str] | None = None) -> None:
|
||||
benchmark.add_argument("--traversals", type=int, default=8)
|
||||
benchmark.add_argument("--depth", type=int, default=4)
|
||||
benchmark.add_argument("--seed", type=int, default=1)
|
||||
benchmark.add_argument("--compare", action="store_true")
|
||||
benchmark.set_defaults(func=benchmark_command)
|
||||
|
||||
args = parser.parse_args(argv)
|
||||
|
||||
@@ -45,7 +45,7 @@ class DeepCFRConfig:
|
||||
eval_games: int = 10
|
||||
eval_opponents: tuple[str, ...] = ("random",)
|
||||
eval_max_steps: int = 10_000
|
||||
num_workers: int = 0
|
||||
num_workers: int | str = 0
|
||||
traversal_worker_chunk_size: int = 4
|
||||
|
||||
def to_dict(self) -> dict[str, Any]:
|
||||
@@ -55,9 +55,24 @@ class DeepCFRConfig:
|
||||
def checkpoint_path(self) -> Path:
|
||||
return Path(self.checkpoint_dir)
|
||||
|
||||
def resolved_num_workers(self, batches: int | None = None) -> int:
|
||||
if isinstance(self.num_workers, str):
|
||||
token = self.num_workers.strip().lower()
|
||||
if token == "auto":
|
||||
guess = max(1, (os_cpu_count() or 2) // 2)
|
||||
return min(guess, batches) if batches is not None and batches > 0 else guess
|
||||
return max(0, int(token))
|
||||
return max(0, int(self.num_workers))
|
||||
|
||||
|
||||
def config_from_dict(data: dict[str, Any]) -> DeepCFRConfig:
|
||||
values = dict(data)
|
||||
if "eval_opponents" in values:
|
||||
values["eval_opponents"] = tuple(values["eval_opponents"])
|
||||
return DeepCFRConfig(**values)
|
||||
|
||||
|
||||
def os_cpu_count() -> int | None:
|
||||
import os
|
||||
|
||||
return os.cpu_count()
|
||||
|
||||
@@ -138,7 +138,7 @@ class DeepCFRTrainer:
|
||||
|
||||
def run_iteration(self, iteration: int) -> IterationMetrics:
|
||||
self.iteration = iteration
|
||||
if self.config.num_workers > 1:
|
||||
if self.config.resolved_num_workers() > 1:
|
||||
total_stats = self._run_traversals_parallel(iteration)
|
||||
else:
|
||||
total_stats = self._run_traversals_single_process(iteration)
|
||||
@@ -206,7 +206,7 @@ class DeepCFRTrainer:
|
||||
total_stats = TraversalStats()
|
||||
if not batches:
|
||||
return total_stats
|
||||
max_workers = min(self.config.num_workers, len(batches))
|
||||
max_workers = self.config.resolved_num_workers(len(batches))
|
||||
with ProcessPoolExecutor(
|
||||
max_workers=max_workers,
|
||||
mp_context=mp.get_context("spawn"),
|
||||
|
||||
@@ -3,7 +3,10 @@ from __future__ import annotations
|
||||
import numpy as np
|
||||
from coolrl_lost_cities.games.classic.game import GameState, LostCitiesConfig
|
||||
|
||||
from coolrl_lost_cities.games.classic.deep_cfr.benchmark import benchmark_traversal
|
||||
from coolrl_lost_cities.games.classic.deep_cfr.benchmark import (
|
||||
benchmark_traversal,
|
||||
benchmark_traversal_modes,
|
||||
)
|
||||
from coolrl_lost_cities.games.classic.deep_cfr.config import DeepCFRConfig
|
||||
from coolrl_lost_cities.games.classic.deep_cfr.memory import ReservoirMemory, TrainingSample
|
||||
from coolrl_lost_cities.games.classic.deep_cfr.trainer import DeepCFRTrainer
|
||||
@@ -234,6 +237,16 @@ def test_deep_cfr_traversal_benchmark_smoke() -> None:
|
||||
|
||||
assert result["traversal_nodes"] > 0
|
||||
assert result["nodes_per_second"] > 0.0
|
||||
comparison = benchmark_traversal_modes(
|
||||
DeepCFRConfig(
|
||||
traversals_per_iteration=1,
|
||||
max_traversal_depth=2,
|
||||
hidden_size=16,
|
||||
save_every_iteration=False,
|
||||
seed=48,
|
||||
)
|
||||
)
|
||||
assert comparison["summary"]["speedup"] > 0.0
|
||||
|
||||
|
||||
def test_deep_cfr_self_play_league_records_snapshots(tmp_path) -> None:
|
||||
|
||||
Reference in New Issue
Block a user