Deep CFR multiprocessing benchmark 보강

This commit is contained in:
2026-05-07 00:11:01 +09:00
parent 5555e781af
commit 8f54415832
5 changed files with 60 additions and 11 deletions
@@ -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:
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(
DeepCFRConfig(
traversals_per_iteration=args.traversals,
max_traversal_depth=args.depth,
seed=args.seed,
save_every_iteration=False,
),
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"),
+14 -1
View File
@@ -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: