Add JAX PPO static-opponent trainer

This commit is contained in:
2026-07-04 22:30:16 +09:00
parent 768f74693d
commit 9e27f42f27
12 changed files with 1682 additions and 0 deletions
+86
View File
@@ -0,0 +1,86 @@
from __future__ import annotations
import jax
import jax.numpy as jnp
import numpy as np
from lost_cities_jax import legal_action_mask, reset
from lost_cities_jax.opponents import (
discard_only_action,
heuristic_balanced_action,
heuristic_cautious_action,
)
from lost_cities_jax.ppo import (
JaxPPOConfig,
NetworkConfig,
OpponentConfig,
PPOHyperConfig,
RunConfig,
create_train_state,
evaluate,
load_config,
make_train_iteration,
policy_by_name,
random_rollout,
train,
)
def tiny_config(tmp_path) -> JaxPPOConfig:
return JaxPPOConfig(
run=RunConfig(
experiment_name="pytest-jax-ppo",
seed=123,
total_updates=1,
checkpoint_every=1,
artifact_root=str(tmp_path),
),
opponent=OpponentConfig(name="discard_only"),
network=NetworkConfig(hidden_size=32, num_layers=1),
ppo=PPOHyperConfig(batch_games=8, rollout_steps=16, epochs=1, minibatches=2),
)
def test_static_opponents_return_legal_actions():
state = reset(jax.random.PRNGKey(0))
mask = np.asarray(legal_action_mask(state), dtype=bool)
for fn in [discard_only_action, heuristic_balanced_action, heuristic_cautious_action]:
action = int(fn(state, jnp.int32(0), jax.random.PRNGKey(1)))
assert mask[action]
def test_random_rollout_smoke(tmp_path):
row = random_rollout(tiny_config(tmp_path))
assert row["env_steps"] == 8 * 16
assert 0.0 <= row["play_action_rate"] <= 1.0
assert row["game_length_mean"] > 0.0
def test_train_checkpoint_and_eval_smoke(tmp_path):
cfg = tiny_config(tmp_path)
run_dir = train(cfg)
assert (run_dir / "latest").exists()
result = evaluate(cfg, run_dir / "latest", games=8, duplicate=True)
assert result["games"] == 16
assert 0.0 <= result["win_rate"] <= 1.0
def test_one_jitted_train_iteration_shapes(tmp_path):
cfg = tiny_config(tmp_path)
train_state = create_train_state(cfg, jax.random.PRNGKey(0))
env_state = jax.jit(jax.vmap(reset))(jax.random.split(jax.random.PRNGKey(1), 8))
train_iteration = make_train_iteration(cfg, policy_by_name("discard_only"))
train_state, env_state, rng, metrics = train_iteration(
train_state, env_state, jax.random.PRNGKey(2), jnp.asarray(1.0)
)
assert env_state.to_move.shape == (8,)
assert "loss" in metrics
assert jnp.isfinite(metrics["loss"])
def test_load_config_file():
cfg = load_config("configs/jax_ppo/smoke.yaml")
assert cfg.opponent.name == "discard_only"
assert cfg.ppo.batch_games == 64