refactor: lift apply_per_node
This commit is contained in:
parent
816102d14f
commit
da06fa2f2e
4 changed files with 35 additions and 32 deletions
|
|
@ -26,7 +26,7 @@ from brittle_star_project.MLPs.mlps import (
|
||||||
)
|
)
|
||||||
from brittle_star_project.MLPs.adjancency_builder import build_adjacency
|
from brittle_star_project.MLPs.adjancency_builder import build_adjacency
|
||||||
from brittle_star_project.environment import MorphMode
|
from brittle_star_project.environment import MorphMode
|
||||||
from brittle_star_project.trainers.PPOTrainer import apply_per_node
|
from brittle_star_project.MLPs.routing import apply_per_node
|
||||||
import logging
|
import logging
|
||||||
import re
|
import re
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
@ -219,8 +219,8 @@ def main(dict_cfg: DictConfig) -> None:
|
||||||
eval_fn = build_eval_rollout_fn(
|
eval_fn = build_eval_rollout_fn(
|
||||||
env=env,
|
env=env,
|
||||||
obs_processor=obs_processor,
|
obs_processor=obs_processor,
|
||||||
sensor_apply=lambda p, x: apply_per_node(sensor, p, x),
|
sensor_apply=lambda p, x: apply_per_node(sensor.apply, p, x),
|
||||||
actor_apply=lambda p, x: apply_per_node(actor, p, x),
|
actor_apply=lambda p, x: apply_per_node(actor.apply, p, x),
|
||||||
message_passer_apply=(None if message_passer is None else message_passer.apply),
|
message_passer_apply=(None if message_passer is None else message_passer.apply),
|
||||||
action_low=action_low,
|
action_low=action_low,
|
||||||
action_high=action_high,
|
action_high=action_high,
|
||||||
|
|
|
||||||
22
src/brittle_star_project/MLPs/routing.py
Normal file
22
src/brittle_star_project/MLPs/routing.py
Normal file
|
|
@ -0,0 +1,22 @@
|
||||||
|
"""Shared JAX routing utilities for decentralized multi-agent models."""
|
||||||
|
|
||||||
|
import jax
|
||||||
|
|
||||||
|
|
||||||
|
def apply_per_node(apply_fn, params, x):
|
||||||
|
"""Apply a Flax module independently to each node.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
apply_fn: The module's ``apply`` method (e.g. ``sensor.apply``).
|
||||||
|
params: Per-node parameters with shape ``(num_nodes, ...)``.
|
||||||
|
x: Input tensor with shape ``(batch, num_nodes, features)``.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Output tensor with shape ``(batch, num_nodes, out_features)``.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def apply_single_node(p, x_node):
|
||||||
|
# x_node: (batch, feat) — one node's input across the batch
|
||||||
|
return jax.vmap(lambda xi: apply_fn(p, xi))(x_node)
|
||||||
|
|
||||||
|
return jax.vmap(apply_single_node, in_axes=(0, 1), out_axes=1)(params, x)
|
||||||
|
|
@ -7,6 +7,7 @@ import jax
|
||||||
import jax.numpy as jnp
|
import jax.numpy as jnp
|
||||||
import numpy as np
|
import numpy as np
|
||||||
|
|
||||||
|
from brittle_star_project.MLPs.routing import apply_per_node
|
||||||
from brittle_star_project.evaluation.checkpoint import load_params
|
from brittle_star_project.evaluation.checkpoint import load_params
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -147,22 +148,12 @@ class PolicyAgent:
|
||||||
obs_processor=obs_processor,
|
obs_processor=obs_processor,
|
||||||
)
|
)
|
||||||
|
|
||||||
def _apply_per_node(self, net, params, x):
|
|
||||||
# params: (nodes, ...)
|
|
||||||
# x: (batch, nodes, feat)
|
|
||||||
|
|
||||||
def apply_single_node(p, x_node):
|
|
||||||
# x_node: (batch, feat)
|
|
||||||
return jax.vmap(lambda xi: net.apply(p, xi))(x_node)
|
|
||||||
|
|
||||||
return jax.vmap(apply_single_node, in_axes=(0, 1), out_axes=1)(params, x)
|
|
||||||
|
|
||||||
def act(self, *, observations: dict[str, Any]) -> np.ndarray:
|
def act(self, *, observations: dict[str, Any]) -> np.ndarray:
|
||||||
"""Return deterministic action (actor mean, no exploration noise)."""
|
"""Return deterministic action (actor mean, no exploration noise)."""
|
||||||
batched_obs = jax.tree.map(lambda x: jnp.asarray(x)[None, ...], observations)
|
batched_obs = jax.tree.map(lambda x: jnp.asarray(x)[None, ...], observations)
|
||||||
obs = self._obs_processor(batched_obs)
|
obs = self._obs_processor(batched_obs)
|
||||||
|
|
||||||
hidden = self._apply_per_node(self._sensor, self._params["sensor_params"], obs)
|
hidden = apply_per_node(self._sensor.apply, self._params["sensor_params"], obs)
|
||||||
|
|
||||||
if self._message_passer is not None:
|
if self._message_passer is not None:
|
||||||
mp_params = self._params.get("message_passer_params")
|
mp_params = self._params.get("message_passer_params")
|
||||||
|
|
@ -172,6 +163,6 @@ class PolicyAgent:
|
||||||
)
|
)
|
||||||
hidden = jax.vmap(lambda x: self._message_passer.apply(mp_params, x))(hidden)
|
hidden = jax.vmap(lambda x: self._message_passer.apply(mp_params, x))(hidden)
|
||||||
|
|
||||||
mean, _log_std = self._apply_per_node(self._actor, self._params["actor_params"], hidden)
|
mean, _log_std = apply_per_node(self._actor.apply, self._params["actor_params"], hidden)
|
||||||
|
|
||||||
return np.asarray(mean, dtype=np.float32).ravel()
|
return np.asarray(mean, dtype=np.float32).ravel()
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,7 @@ from brittle_star_project.evaluation.evaluate_mjx import (
|
||||||
build_eval_rollout_fn,
|
build_eval_rollout_fn,
|
||||||
evaluate_checkpoint_mjx,
|
evaluate_checkpoint_mjx,
|
||||||
)
|
)
|
||||||
|
from brittle_star_project.MLPs.routing import apply_per_node
|
||||||
from brittle_star_project.MLPs.mlps import (
|
from brittle_star_project.MLPs.mlps import (
|
||||||
Actor,
|
Actor,
|
||||||
AgentParams,
|
AgentParams,
|
||||||
|
|
@ -71,7 +72,7 @@ def _get_action_and_value_noise(
|
||||||
action_high,
|
action_high,
|
||||||
):
|
):
|
||||||
# (B, n_nodes, feat)
|
# (B, n_nodes, feat)
|
||||||
hidden = apply_per_node(sensor, agent_state.params["sensor_params"], next_obs)
|
hidden = apply_per_node(sensor.apply, agent_state.params["sensor_params"], next_obs)
|
||||||
|
|
||||||
if message_passer is not None:
|
if message_passer is not None:
|
||||||
params = agent_state.params["message_passer_params"]
|
params = agent_state.params["message_passer_params"]
|
||||||
|
|
@ -82,7 +83,7 @@ def _get_action_and_value_noise(
|
||||||
feature_extractor, agent_state.params["feature_extractor_params"], next_obs
|
feature_extractor, agent_state.params["feature_extractor_params"], next_obs
|
||||||
)
|
)
|
||||||
|
|
||||||
mean, log_std = apply_per_node(actor, agent_state.params["actor_params"], hidden)
|
mean, log_std = apply_per_node(actor.apply, agent_state.params["actor_params"], hidden)
|
||||||
log_std = jnp.clip(log_std, -5, 2)
|
log_std = jnp.clip(log_std, -5, 2)
|
||||||
key, subkey = jax.random.split(key)
|
key, subkey = jax.random.split(key)
|
||||||
noise = jax.random.normal(subkey, shape=mean.shape)
|
noise = jax.random.normal(subkey, shape=mean.shape)
|
||||||
|
|
@ -272,17 +273,6 @@ def _step_env_wrapped(
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def apply_per_node(net, params, x):
|
|
||||||
# params: (nodes, ...)
|
|
||||||
# x: (batch, nodes, feat)
|
|
||||||
|
|
||||||
def apply_single_node(p, x_node):
|
|
||||||
# x_node: (batch, feat)
|
|
||||||
return jax.vmap(lambda xi: net.apply(p, xi))(x_node)
|
|
||||||
|
|
||||||
return jax.vmap(apply_single_node, in_axes=(0, 1), out_axes=1)(params, x)
|
|
||||||
|
|
||||||
|
|
||||||
def apply_shared(net, params, x):
|
def apply_shared(net, params, x):
|
||||||
# x: (batch, nodes, feat)
|
# x: (batch, nodes, feat)
|
||||||
# If the critic expects a single vector per environment:
|
# If the critic expects a single vector per environment:
|
||||||
|
|
@ -516,10 +506,10 @@ class PPOTrainer:
|
||||||
)
|
)
|
||||||
|
|
||||||
def apply_sensor(p, x):
|
def apply_sensor(p, x):
|
||||||
return apply_per_node(self.sensor, p, x)
|
return apply_per_node(self.sensor.apply, p, x)
|
||||||
|
|
||||||
def apply_actor(p, x):
|
def apply_actor(p, x):
|
||||||
return apply_per_node(self.actor, p, x)
|
return apply_per_node(self.actor.apply, p, x)
|
||||||
|
|
||||||
def apply_critic(p, x):
|
def apply_critic(p, x):
|
||||||
return apply_shared(self.critic, p, x)
|
return apply_shared(self.critic, p, x)
|
||||||
|
|
@ -903,8 +893,8 @@ class PPOTrainer:
|
||||||
self._eval_fn = build_eval_rollout_fn(
|
self._eval_fn = build_eval_rollout_fn(
|
||||||
env=self.env,
|
env=self.env,
|
||||||
obs_processor=self.obs_processor,
|
obs_processor=self.obs_processor,
|
||||||
sensor_apply=lambda p, x: apply_per_node(self.sensor, p, x),
|
sensor_apply=lambda p, x: apply_per_node(self.sensor.apply, p, x),
|
||||||
actor_apply=lambda p, x: apply_per_node(self.actor, p, x),
|
actor_apply=lambda p, x: apply_per_node(self.actor.apply, p, x),
|
||||||
message_passer_apply=(
|
message_passer_apply=(
|
||||||
None if self.message_passer is None else self.message_passer.apply
|
None if self.message_passer is None else self.message_passer.apply
|
||||||
),
|
),
|
||||||
|
|
|
||||||
Reference in a new issue