Deep CFR multiprocessing benchmark 보강
This commit is contained in:
@@ -32,3 +32,16 @@ def benchmark_traversal(
|
|||||||
"advantage_samples": metrics.advantage_samples,
|
"advantage_samples": metrics.advantage_samples,
|
||||||
"strategy_samples": metrics.strategy_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 dataclasses import replace
|
||||||
from pathlib import Path
|
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.config import DeepCFRConfig, config_from_dict
|
||||||
from coolrl_lost_cities.games.classic.deep_cfr.evaluate import (
|
from coolrl_lost_cities.games.classic.deep_cfr.evaluate import (
|
||||||
evaluate_strategy_network,
|
evaluate_strategy_network,
|
||||||
@@ -61,13 +64,17 @@ def eval_command(args: argparse.Namespace) -> None:
|
|||||||
|
|
||||||
|
|
||||||
def benchmark_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(
|
result = benchmark_traversal(
|
||||||
DeepCFRConfig(
|
config,
|
||||||
traversals_per_iteration=args.traversals,
|
|
||||||
max_traversal_depth=args.depth,
|
|
||||||
seed=args.seed,
|
|
||||||
save_every_iteration=False,
|
|
||||||
),
|
|
||||||
num_workers=args.workers,
|
num_workers=args.workers,
|
||||||
)
|
)
|
||||||
print(json.dumps(result, indent=2, sort_keys=True))
|
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("--traversals", type=int, default=8)
|
||||||
benchmark.add_argument("--depth", type=int, default=4)
|
benchmark.add_argument("--depth", type=int, default=4)
|
||||||
benchmark.add_argument("--seed", type=int, default=1)
|
benchmark.add_argument("--seed", type=int, default=1)
|
||||||
|
benchmark.add_argument("--compare", action="store_true")
|
||||||
benchmark.set_defaults(func=benchmark_command)
|
benchmark.set_defaults(func=benchmark_command)
|
||||||
|
|
||||||
args = parser.parse_args(argv)
|
args = parser.parse_args(argv)
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ class DeepCFRConfig:
|
|||||||
eval_games: int = 10
|
eval_games: int = 10
|
||||||
eval_opponents: tuple[str, ...] = ("random",)
|
eval_opponents: tuple[str, ...] = ("random",)
|
||||||
eval_max_steps: int = 10_000
|
eval_max_steps: int = 10_000
|
||||||
num_workers: int = 0
|
num_workers: int | str = 0
|
||||||
traversal_worker_chunk_size: int = 4
|
traversal_worker_chunk_size: int = 4
|
||||||
|
|
||||||
def to_dict(self) -> dict[str, Any]:
|
def to_dict(self) -> dict[str, Any]:
|
||||||
@@ -55,9 +55,24 @@ class DeepCFRConfig:
|
|||||||
def checkpoint_path(self) -> Path:
|
def checkpoint_path(self) -> Path:
|
||||||
return Path(self.checkpoint_dir)
|
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:
|
def config_from_dict(data: dict[str, Any]) -> DeepCFRConfig:
|
||||||
values = dict(data)
|
values = dict(data)
|
||||||
if "eval_opponents" in values:
|
if "eval_opponents" in values:
|
||||||
values["eval_opponents"] = tuple(values["eval_opponents"])
|
values["eval_opponents"] = tuple(values["eval_opponents"])
|
||||||
return DeepCFRConfig(**values)
|
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:
|
def run_iteration(self, iteration: int) -> IterationMetrics:
|
||||||
self.iteration = iteration
|
self.iteration = iteration
|
||||||
if self.config.num_workers > 1:
|
if self.config.resolved_num_workers() > 1:
|
||||||
total_stats = self._run_traversals_parallel(iteration)
|
total_stats = self._run_traversals_parallel(iteration)
|
||||||
else:
|
else:
|
||||||
total_stats = self._run_traversals_single_process(iteration)
|
total_stats = self._run_traversals_single_process(iteration)
|
||||||
@@ -206,7 +206,7 @@ class DeepCFRTrainer:
|
|||||||
total_stats = TraversalStats()
|
total_stats = TraversalStats()
|
||||||
if not batches:
|
if not batches:
|
||||||
return total_stats
|
return total_stats
|
||||||
max_workers = min(self.config.num_workers, len(batches))
|
max_workers = self.config.resolved_num_workers(len(batches))
|
||||||
with ProcessPoolExecutor(
|
with ProcessPoolExecutor(
|
||||||
max_workers=max_workers,
|
max_workers=max_workers,
|
||||||
mp_context=mp.get_context("spawn"),
|
mp_context=mp.get_context("spawn"),
|
||||||
|
|||||||
@@ -3,7 +3,10 @@ from __future__ import annotations
|
|||||||
import numpy as np
|
import numpy as np
|
||||||
from coolrl_lost_cities.games.classic.game import GameState, LostCitiesConfig
|
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.config import DeepCFRConfig
|
||||||
from coolrl_lost_cities.games.classic.deep_cfr.memory import ReservoirMemory, TrainingSample
|
from coolrl_lost_cities.games.classic.deep_cfr.memory import ReservoirMemory, TrainingSample
|
||||||
from coolrl_lost_cities.games.classic.deep_cfr.trainer import DeepCFRTrainer
|
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["traversal_nodes"] > 0
|
||||||
assert result["nodes_per_second"] > 0.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:
|
def test_deep_cfr_self_play_league_records_snapshots(tmp_path) -> None:
|
||||||
|
|||||||
Reference in New Issue
Block a user