Deep CFR 프로파일링 메트릭 추가
This commit is contained in:
@@ -0,0 +1,56 @@
|
|||||||
|
# Deep CFR Profile 2026-05-07
|
||||||
|
|
||||||
|
Run directory:
|
||||||
|
|
||||||
|
`/mnt/2tbhdd/coolrl-lost-cities-runs/2026-05-07_024616_deep_cfr_profile_10iter`
|
||||||
|
|
||||||
|
Command:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
uv run python -m coolrl_lost_cities.games.classic.deep_cfr.cli train \
|
||||||
|
--config configs/deep_cfr/deep_cfr_selfplay_full_depth_slot_playability.yaml \
|
||||||
|
--checkpoint-dir /mnt/2tbhdd/coolrl-lost-cities-runs/2026-05-07_024616_deep_cfr_profile_10iter \
|
||||||
|
--max-iterations 10 \
|
||||||
|
--save-latest-only
|
||||||
|
```
|
||||||
|
|
||||||
|
## Summary
|
||||||
|
|
||||||
|
The run completed 10 iterations. Loss values stayed finite.
|
||||||
|
|
||||||
|
Non-evaluation iterations were iterations 1-4 and 6-9:
|
||||||
|
|
||||||
|
| Metric | Average |
|
||||||
|
| --- | ---: |
|
||||||
|
| `iteration_seconds` | 9.132692 |
|
||||||
|
| `traversal_seconds` | 3.143286 |
|
||||||
|
| `memory_add_seconds` | 0.171213 |
|
||||||
|
| `advantage_train_seconds` | 5.061859 |
|
||||||
|
| `strategy_train_seconds` | 0.911080 |
|
||||||
|
| `evaluation_seconds` | 0.000004 |
|
||||||
|
| `checkpoint_seconds` | 0.015469 |
|
||||||
|
| `batch_tensor_seconds` | 1.613572 |
|
||||||
|
|
||||||
|
Evaluation iterations were iterations 5 and 10:
|
||||||
|
|
||||||
|
| Metric | Average |
|
||||||
|
| --- | ---: |
|
||||||
|
| `iteration_seconds` | 22.676999 |
|
||||||
|
| `traversal_seconds` | 3.233497 |
|
||||||
|
| `memory_add_seconds` | 0.145742 |
|
||||||
|
| `advantage_train_seconds` | 7.502700 |
|
||||||
|
| `strategy_train_seconds` | 0.915639 |
|
||||||
|
| `evaluation_seconds` | 11.004282 |
|
||||||
|
| `checkpoint_seconds` | 0.019737 |
|
||||||
|
| `batch_tensor_seconds` | 1.704181 |
|
||||||
|
|
||||||
|
## Per-Iteration Notes
|
||||||
|
|
||||||
|
`advantage_memory_size` grew from 43,019 at iteration 1 to 204,903 at
|
||||||
|
iteration 10.
|
||||||
|
|
||||||
|
`advantage_player_0_sample_seconds + advantage_player_1_sample_seconds` grew
|
||||||
|
from about 0.521s at iteration 1 to about 8.231s at iteration 10.
|
||||||
|
|
||||||
|
Traversal stayed near 3 seconds per iteration after iteration 1, except for
|
||||||
|
normal run-to-run variance.
|
||||||
@@ -58,6 +58,7 @@ class IterationMetrics:
|
|||||||
traversal_endpoints: int
|
traversal_endpoints: int
|
||||||
traversal_avg_endpoint_depth: float
|
traversal_avg_endpoint_depth: float
|
||||||
traversal_endpoint_depth_buckets: dict[str, int]
|
traversal_endpoint_depth_buckets: dict[str, int]
|
||||||
|
runtime_metrics: dict[str, float | int]
|
||||||
eval_metrics: dict[str, float | int]
|
eval_metrics: dict[str, float | int]
|
||||||
|
|
||||||
def to_dict(self) -> dict[str, float | int]:
|
def to_dict(self) -> dict[str, float | int]:
|
||||||
@@ -80,6 +81,7 @@ class IterationMetrics:
|
|||||||
for key, value in self.traversal_endpoint_depth_buckets.items()
|
for key, value in self.traversal_endpoint_depth_buckets.items()
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
data.update(self.runtime_metrics)
|
||||||
data.update(self.eval_metrics)
|
data.update(self.eval_metrics)
|
||||||
return data
|
return data
|
||||||
|
|
||||||
@@ -167,6 +169,7 @@ class DeepCFRTrainer:
|
|||||||
]
|
]
|
||||||
)
|
)
|
||||||
self.self_play_league_snapshots: list[list[dict]] = []
|
self.self_play_league_snapshots: list[list[dict]] = []
|
||||||
|
self._runtime_metrics: dict[str, float | int] = {}
|
||||||
|
|
||||||
def checkpoint_payload(self, metrics: IterationMetrics | None = None) -> dict:
|
def checkpoint_payload(self, metrics: IterationMetrics | None = None) -> dict:
|
||||||
return {
|
return {
|
||||||
@@ -216,14 +219,27 @@ class DeepCFRTrainer:
|
|||||||
|
|
||||||
def run_iteration(self, iteration: int) -> IterationMetrics:
|
def run_iteration(self, iteration: int) -> IterationMetrics:
|
||||||
self.iteration = iteration
|
self.iteration = iteration
|
||||||
|
self._runtime_metrics = {}
|
||||||
|
traversal_started = time.perf_counter()
|
||||||
if self.config.traversal.resolved_num_workers() > 1:
|
if self.config.traversal.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)
|
||||||
|
self._runtime_metrics["traversal_seconds"] = time.perf_counter() - traversal_started
|
||||||
|
|
||||||
|
advantage_started = time.perf_counter()
|
||||||
advantage_loss = self._train_advantage_networks()
|
advantage_loss = self._train_advantage_networks()
|
||||||
|
self._runtime_metrics["advantage_train_seconds"] = time.perf_counter() - advantage_started
|
||||||
|
|
||||||
|
strategy_started = time.perf_counter()
|
||||||
strategy_loss = self._train_strategy_network()
|
strategy_loss = self._train_strategy_network()
|
||||||
|
self._runtime_metrics["strategy_train_seconds"] = time.perf_counter() - strategy_started
|
||||||
|
|
||||||
|
eval_started = time.perf_counter()
|
||||||
eval_metrics = self._evaluate(iteration)
|
eval_metrics = self._evaluate(iteration)
|
||||||
|
self._runtime_metrics["evaluation_seconds"] = time.perf_counter() - eval_started
|
||||||
|
self._runtime_metrics["advantage_memory_size"] = len(self.advantage_memory)
|
||||||
|
self._runtime_metrics["strategy_memory_size"] = len(self.strategy_memory)
|
||||||
return IterationMetrics(
|
return IterationMetrics(
|
||||||
iteration=iteration,
|
iteration=iteration,
|
||||||
advantage_samples=len(self.advantage_memory),
|
advantage_samples=len(self.advantage_memory),
|
||||||
@@ -239,6 +255,7 @@ class DeepCFRTrainer:
|
|||||||
traversal_endpoints=total_stats.endpoints,
|
traversal_endpoints=total_stats.endpoints,
|
||||||
traversal_avg_endpoint_depth=total_stats.avg_endpoint_depth,
|
traversal_avg_endpoint_depth=total_stats.avg_endpoint_depth,
|
||||||
traversal_endpoint_depth_buckets=dict(total_stats.endpoint_depth_buckets),
|
traversal_endpoint_depth_buckets=dict(total_stats.endpoint_depth_buckets),
|
||||||
|
runtime_metrics=dict(self._runtime_metrics),
|
||||||
eval_metrics=eval_metrics,
|
eval_metrics=eval_metrics,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -295,8 +312,14 @@ class DeepCFRTrainer:
|
|||||||
seed=self.config.run.seed + iteration * 1_000_003 + player,
|
seed=self.config.run.seed + iteration * 1_000_003 + player,
|
||||||
)
|
)
|
||||||
total_stats.accumulate(stats)
|
total_stats.accumulate(stats)
|
||||||
|
memory_add_started = time.perf_counter()
|
||||||
self.advantage_memory.add_many(advantage_samples, self.rng)
|
self.advantage_memory.add_many(advantage_samples, self.rng)
|
||||||
self.strategy_memory.add_many(strategy_samples, self.rng)
|
self.strategy_memory.add_many(strategy_samples, self.rng)
|
||||||
|
self._runtime_metrics["memory_add_seconds"] = (
|
||||||
|
float(self._runtime_metrics.get("memory_add_seconds", 0.0))
|
||||||
|
+ time.perf_counter()
|
||||||
|
- memory_add_started
|
||||||
|
)
|
||||||
completed += len(seeds)
|
completed += len(seeds)
|
||||||
if progress_every > 0 and completed >= progress_every:
|
if progress_every > 0 and completed >= progress_every:
|
||||||
elapsed = time.perf_counter() - progress_started
|
elapsed = time.perf_counter() - progress_started
|
||||||
@@ -339,8 +362,14 @@ class DeepCFRTrainer:
|
|||||||
for completed_batches, future in enumerate(as_completed(futures), start=1):
|
for completed_batches, future in enumerate(as_completed(futures), start=1):
|
||||||
result = future.result()
|
result = future.result()
|
||||||
total_stats.accumulate(result.stats)
|
total_stats.accumulate(result.stats)
|
||||||
|
memory_add_started = time.perf_counter()
|
||||||
self.advantage_memory.add_many(result.advantage_samples, self.rng)
|
self.advantage_memory.add_many(result.advantage_samples, self.rng)
|
||||||
self.strategy_memory.add_many(result.strategy_samples, self.rng)
|
self.strategy_memory.add_many(result.strategy_samples, self.rng)
|
||||||
|
self._runtime_metrics["memory_add_seconds"] = (
|
||||||
|
float(self._runtime_metrics.get("memory_add_seconds", 0.0))
|
||||||
|
+ time.perf_counter()
|
||||||
|
- memory_add_started
|
||||||
|
)
|
||||||
progress_nodes += result.stats.nodes
|
progress_nodes += result.stats.nodes
|
||||||
progress_traversals += result.traversals
|
progress_traversals += result.traversals
|
||||||
if next_progress_at is not None and progress_traversals >= next_progress_at:
|
if next_progress_at is not None and progress_traversals >= next_progress_at:
|
||||||
@@ -434,11 +463,13 @@ class DeepCFRTrainer:
|
|||||||
while iteration <= stop:
|
while iteration <= stop:
|
||||||
started = time.perf_counter()
|
started = time.perf_counter()
|
||||||
item = self.run_iteration(iteration)
|
item = self.run_iteration(iteration)
|
||||||
elapsed = time.perf_counter() - started
|
|
||||||
metrics.append(item)
|
metrics.append(item)
|
||||||
self._append_metrics(item, elapsed)
|
|
||||||
self._maybe_record_self_play_snapshot(iteration)
|
self._maybe_record_self_play_snapshot(iteration)
|
||||||
|
checkpoint_started = time.perf_counter()
|
||||||
self._save_iteration_checkpoints(iteration, item)
|
self._save_iteration_checkpoints(iteration, item)
|
||||||
|
item.runtime_metrics["checkpoint_seconds"] = time.perf_counter() - checkpoint_started
|
||||||
|
elapsed = time.perf_counter() - started
|
||||||
|
self._append_metrics(item, elapsed)
|
||||||
if self._time_limit_reached(run_started):
|
if self._time_limit_reached(run_started):
|
||||||
break
|
break
|
||||||
iteration += 1
|
iteration += 1
|
||||||
@@ -516,7 +547,12 @@ class DeepCFRTrainer:
|
|||||||
def _train_advantage_networks(self) -> float:
|
def _train_advantage_networks(self) -> float:
|
||||||
losses: list[float] = []
|
losses: list[float] = []
|
||||||
for player, network in enumerate(self.advantage_networks):
|
for player, network in enumerate(self.advantage_networks):
|
||||||
|
filter_started = time.perf_counter()
|
||||||
samples = [sample for sample in self.advantage_memory.all() if sample.player == player]
|
samples = [sample for sample in self.advantage_memory.all() if sample.player == player]
|
||||||
|
self._runtime_metrics[f"advantage_player_{player}_filter_seconds"] = (
|
||||||
|
time.perf_counter() - filter_started
|
||||||
|
)
|
||||||
|
self._runtime_metrics[f"advantage_player_{player}_sample_count"] = len(samples)
|
||||||
if not samples:
|
if not samples:
|
||||||
continue
|
continue
|
||||||
losses.append(self._train_advantage(player, network, self.advantage_optimizers[player]))
|
losses.append(self._train_advantage(player, network, self.advantage_optimizers[player]))
|
||||||
@@ -524,6 +560,7 @@ class DeepCFRTrainer:
|
|||||||
|
|
||||||
def _train_strategy_network(self) -> float:
|
def _train_strategy_network(self) -> float:
|
||||||
samples = self.strategy_memory.all()
|
samples = self.strategy_memory.all()
|
||||||
|
self._runtime_metrics["strategy_sample_count"] = len(samples)
|
||||||
if not samples:
|
if not samples:
|
||||||
return 0.0
|
return 0.0
|
||||||
return self._train_strategy(self.strategy_network, self.strategy_optimizer, samples)
|
return self._train_strategy(self.strategy_network, self.strategy_optimizer, samples)
|
||||||
@@ -532,6 +569,7 @@ class DeepCFRTrainer:
|
|||||||
self,
|
self,
|
||||||
batch: list[TrainingSample],
|
batch: list[TrainingSample],
|
||||||
) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor]:
|
) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor]:
|
||||||
|
started = time.perf_counter()
|
||||||
x = torch.as_tensor(
|
x = torch.as_tensor(
|
||||||
np.stack([sample.info_state for sample in batch]),
|
np.stack([sample.info_state for sample in batch]),
|
||||||
dtype=torch.float32,
|
dtype=torch.float32,
|
||||||
@@ -547,6 +585,11 @@ class DeepCFRTrainer:
|
|||||||
dtype=torch.bool,
|
dtype=torch.bool,
|
||||||
device=self.device,
|
device=self.device,
|
||||||
)
|
)
|
||||||
|
self._runtime_metrics["batch_tensor_seconds"] = (
|
||||||
|
float(self._runtime_metrics.get("batch_tensor_seconds", 0.0))
|
||||||
|
+ time.perf_counter()
|
||||||
|
- started
|
||||||
|
)
|
||||||
return x, y, legal
|
return x, y, legal
|
||||||
|
|
||||||
def _train_advantage(
|
def _train_advantage(
|
||||||
@@ -558,13 +601,18 @@ class DeepCFRTrainer:
|
|||||||
last_loss = 0.0
|
last_loss = 0.0
|
||||||
network.train()
|
network.train()
|
||||||
for _step in range(self.config.optimization.resolved_advantage_train_steps()):
|
for _step in range(self.config.optimization.resolved_advantage_train_steps()):
|
||||||
x, y, legal = self._batch_tensors(
|
sample_started = time.perf_counter()
|
||||||
self.advantage_memory.sample(
|
batch = self.advantage_memory.sample(
|
||||||
self.config.optimization.resolved_advantage_batch_size(),
|
self.config.optimization.resolved_advantage_batch_size(),
|
||||||
self.rng,
|
self.rng,
|
||||||
player=player,
|
player=player,
|
||||||
)
|
)
|
||||||
|
self._runtime_metrics[f"advantage_player_{player}_sample_seconds"] = (
|
||||||
|
float(self._runtime_metrics.get(f"advantage_player_{player}_sample_seconds", 0.0))
|
||||||
|
+ time.perf_counter()
|
||||||
|
- sample_started
|
||||||
)
|
)
|
||||||
|
x, y, legal = self._batch_tensors(batch)
|
||||||
pred = network(x)
|
pred = network(x)
|
||||||
diff = (pred - y).masked_fill(~legal, 0.0)
|
diff = (pred - y).masked_fill(~legal, 0.0)
|
||||||
loss = diff.square().sum() / legal.sum().clamp_min(1)
|
loss = diff.square().sum() / legal.sum().clamp_min(1)
|
||||||
@@ -587,11 +635,16 @@ class DeepCFRTrainer:
|
|||||||
last_loss = 0.0
|
last_loss = 0.0
|
||||||
network.train()
|
network.train()
|
||||||
for _step in range(self.config.optimization.resolved_strategy_train_steps()):
|
for _step in range(self.config.optimization.resolved_strategy_train_steps()):
|
||||||
x, y, legal = self._batch_tensors(
|
sample_started = time.perf_counter()
|
||||||
self.strategy_memory.sample(
|
batch = self.strategy_memory.sample(
|
||||||
self.config.optimization.resolved_strategy_batch_size(), self.rng
|
self.config.optimization.resolved_strategy_batch_size(), self.rng
|
||||||
)
|
)
|
||||||
|
self._runtime_metrics["strategy_sample_seconds"] = (
|
||||||
|
float(self._runtime_metrics.get("strategy_sample_seconds", 0.0))
|
||||||
|
+ time.perf_counter()
|
||||||
|
- sample_started
|
||||||
)
|
)
|
||||||
|
x, y, legal = self._batch_tensors(batch)
|
||||||
logits = network(x).masked_fill(~legal, torch.finfo(torch.float32).min)
|
logits = network(x).masked_fill(~legal, torch.finfo(torch.float32).min)
|
||||||
log_probs = nn.functional.log_softmax(logits, dim=-1).masked_fill(~legal, 0.0)
|
log_probs = nn.functional.log_softmax(logits, dim=-1).masked_fill(~legal, 0.0)
|
||||||
loss = -(y * log_probs).sum(dim=-1).mean()
|
loss = -(y * log_probs).sum(dim=-1).mean()
|
||||||
|
|||||||
Reference in New Issue
Block a user