Add JAX PPO league self-play v1
This commit is contained in:
@@ -0,0 +1,109 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import jax
|
||||
import jax.numpy as jnp
|
||||
|
||||
from lost_cities_jax import reset
|
||||
from lost_cities_jax.league import (
|
||||
LeagueConfig,
|
||||
PoolMember,
|
||||
active_training_pool,
|
||||
load_league_config,
|
||||
pool_sampling_probabilities,
|
||||
)
|
||||
from lost_cities_jax.opponents import policy_by_name
|
||||
from lost_cities_jax.ppo import (
|
||||
JaxPPOConfig,
|
||||
NetworkConfig,
|
||||
OpponentConfig,
|
||||
PPOHyperConfig,
|
||||
RunConfig,
|
||||
create_train_state,
|
||||
make_league_train_iteration,
|
||||
sample_league_assignments,
|
||||
)
|
||||
|
||||
|
||||
def tiny_league_ppo_config(tmp_path) -> JaxPPOConfig:
|
||||
return JaxPPOConfig(
|
||||
run=RunConfig(
|
||||
experiment_name="pytest-jax-ppo-league",
|
||||
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_league_config_loads():
|
||||
cfg = load_league_config("configs/jax_ppo/league-v1.yaml")
|
||||
assert cfg.experiment_name == "jax-ppo-league-v1"
|
||||
assert cfg.cycles == 5
|
||||
assert len(cfg.anchors) == 7
|
||||
assert any(member.name == "heuristic_expert" for member in cfg.anchors)
|
||||
|
||||
|
||||
def test_pfsp_probabilities_cap_stalling_anchors():
|
||||
cfg = LeagueConfig(
|
||||
base_config="configs/jax_ppo/ladder-v2-expert.yaml",
|
||||
warm_start_checkpoint="unused",
|
||||
stalling_anchor_floor=0.02,
|
||||
stalling_anchor_cap=0.05,
|
||||
uniform_mix=0.1,
|
||||
)
|
||||
pool = [
|
||||
PoolMember("discard_only", "static", stalling=True, recent_win_rate=0.01),
|
||||
PoolMember("heuristic_expert", "static", recent_win_rate=0.5),
|
||||
PoolMember("snapshot", "checkpoint", anchor=False, recent_win_rate=0.2),
|
||||
]
|
||||
probs = pool_sampling_probabilities(pool, cfg)
|
||||
assert abs(sum(probs) - 1.0) < 1.0e-6
|
||||
assert 0.02 <= probs[0] <= 0.05
|
||||
|
||||
|
||||
def test_active_training_pool_keeps_anchors_and_hard_nonanchors():
|
||||
cfg = LeagueConfig(
|
||||
base_config="configs/jax_ppo/ladder-v2-expert.yaml",
|
||||
warm_start_checkpoint="unused",
|
||||
max_active_pool_members=4,
|
||||
)
|
||||
pool = [
|
||||
PoolMember("anchor_a", "static", anchor=True),
|
||||
PoolMember("anchor_b", "static", anchor=True),
|
||||
PoolMember("easy", "checkpoint", anchor=False, recent_win_rate=0.9),
|
||||
PoolMember("hard", "checkpoint", anchor=False, recent_win_rate=0.1),
|
||||
PoolMember("medium", "checkpoint", anchor=False, recent_win_rate=0.5),
|
||||
]
|
||||
active = active_training_pool(pool, cfg)
|
||||
assert [member.name for member in active] == ["anchor_a", "anchor_b", "hard", "medium"]
|
||||
|
||||
|
||||
def test_one_jitted_league_train_iteration_shapes(tmp_path):
|
||||
cfg = tiny_league_ppo_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))
|
||||
probs = jnp.asarray([0.5, 0.5], dtype=jnp.float32)
|
||||
assignments = sample_league_assignments(jax.random.PRNGKey(2), 8, probs, 0.5)
|
||||
train_iteration = make_league_train_iteration(
|
||||
cfg,
|
||||
[policy_by_name("discard_only"), policy_by_name("heuristic_expert")],
|
||||
probs,
|
||||
0.5,
|
||||
)
|
||||
|
||||
train_state, env_state, assignments, rng, metrics = train_iteration(
|
||||
train_state,
|
||||
env_state,
|
||||
assignments,
|
||||
jax.random.PRNGKey(3),
|
||||
jnp.asarray(0.0, dtype=jnp.float32),
|
||||
)
|
||||
|
||||
assert env_state.to_move.shape == (8,)
|
||||
assert assignments.learner_seat.shape == (8,)
|
||||
assert rng.shape == (2,)
|
||||
assert "opened_colors_mean" in metrics
|
||||
Reference in New Issue
Block a user