mirror of
https://github.com/wassname/ray.git
synced 2026-07-19 11:27:32 +08:00
[RLlib] Add a minimal JAX ModelV2 (FCNet) to RLlib. (#12502)
This commit is contained in:
+44
-12
@@ -8,6 +8,7 @@ from typing import List, Optional, Type, Union
|
||||
from ray.tune.registry import RLLIB_MODEL, RLLIB_PREPROCESSOR, \
|
||||
RLLIB_ACTION_DIST, _global_registry
|
||||
from ray.rllib.models.action_dist import ActionDistribution
|
||||
from ray.rllib.models.jax.jax_action_dist import JAXCategorical
|
||||
from ray.rllib.models.modelv2 import ModelV2
|
||||
from ray.rllib.models.preprocessors import get_preprocessor, Preprocessor
|
||||
from ray.rllib.models.tf.recurrent_net import LSTMWrapper
|
||||
@@ -131,7 +132,7 @@ class ModelCatalog:
|
||||
dist_type (Optional[Union[str, Type[ActionDistribution]]]):
|
||||
Identifier of the action distribution (str) interpreted as a
|
||||
hint or the actual ActionDistribution class to use.
|
||||
framework (str): One of "tf", "tfe", or "torch".
|
||||
framework (str): One of "tf2", "tf", "tfe", "torch", or "jax".
|
||||
kwargs (dict): Optional kwargs to pass on to the Distribution's
|
||||
constructor.
|
||||
|
||||
@@ -179,8 +180,8 @@ class ModelCatalog:
|
||||
else Deterministic
|
||||
# Discrete Space -> Categorical.
|
||||
elif isinstance(action_space, gym.spaces.Discrete):
|
||||
dist_cls = (TorchCategorical
|
||||
if framework == "torch" else Categorical)
|
||||
dist_cls = TorchCategorical if framework == "torch" else \
|
||||
JAXCategorical if framework == "jax" else Categorical
|
||||
# Tuple/Dict Spaces -> MultiAction.
|
||||
elif dist_type in (MultiActionDistribution,
|
||||
TorchMultiActionDistribution) or \
|
||||
@@ -282,7 +283,7 @@ class ModelCatalog:
|
||||
unflatten the tensor into a ragged tensor.
|
||||
action_space (Space): Action space of the target gym env.
|
||||
num_outputs (int): The size of the output vector of the model.
|
||||
framework (str): One of "tf", "tfe", or "torch".
|
||||
framework (str): One of "tf2", "tf", "tfe", "torch", or "jax".
|
||||
name (str): Name (scope) for the model.
|
||||
model_interface (cls): Interface required for the model
|
||||
default_model (cls): Override the default class for the model. This
|
||||
@@ -294,7 +295,6 @@ class ModelCatalog:
|
||||
"""
|
||||
|
||||
if model_config.get("custom_model"):
|
||||
|
||||
# Allow model kwargs to be overridden / augmented by
|
||||
# custom_model_config.
|
||||
customized_model_kwargs = dict(
|
||||
@@ -358,7 +358,7 @@ class ModelCatalog:
|
||||
"model.register_variables() on the variables in "
|
||||
"question?".format(not_registered, instance,
|
||||
registered))
|
||||
else:
|
||||
elif framework == "torch":
|
||||
# PyTorch automatically tracks nn.Modules inside the parent
|
||||
# nn.Module's constructor.
|
||||
# Try calling with kwargs first (custom ModelV2 should
|
||||
@@ -381,8 +381,14 @@ class ModelCatalog:
|
||||
# Other error -> re-raise.
|
||||
else:
|
||||
raise e
|
||||
else:
|
||||
raise NotImplementedError(
|
||||
"`framework` must be 'tf2|tf|tfe|torch', but is "
|
||||
"{}!".format(framework))
|
||||
|
||||
return instance
|
||||
|
||||
# Find a default TFModelV2 and wrap with model_interface.
|
||||
if framework in ["tf", "tfe", "tf2"]:
|
||||
v2_class = None
|
||||
# Try to get a default v2 model.
|
||||
@@ -404,6 +410,8 @@ class ModelCatalog:
|
||||
wrapper = ModelCatalog._wrap_if_needed(v2_class, model_interface)
|
||||
return wrapper(obs_space, action_space, num_outputs, model_config,
|
||||
name, **model_kwargs)
|
||||
|
||||
# Find a default TorchModelV2 and wrap with model_interface.
|
||||
elif framework == "torch":
|
||||
v2_class = \
|
||||
default_model or ModelCatalog._get_v2_model_class(
|
||||
@@ -420,6 +428,18 @@ class ModelCatalog:
|
||||
wrapper = ModelCatalog._wrap_if_needed(v2_class, model_interface)
|
||||
return wrapper(obs_space, action_space, num_outputs, model_config,
|
||||
name, **model_kwargs)
|
||||
|
||||
# Find a default JAXModelV2 and wrap with model_interface.
|
||||
elif framework == "jax":
|
||||
v2_class = \
|
||||
default_model or ModelCatalog._get_v2_model_class(
|
||||
obs_space, model_config, framework=framework)
|
||||
if model_config.get("use_lstm"):
|
||||
raise NotImplementedError("JAXModel's not LSTM-wrappable yet!")
|
||||
# Wrap in the requested interface.
|
||||
wrapper = ModelCatalog._wrap_if_needed(v2_class, model_interface)
|
||||
return wrapper(obs_space, action_space, num_outputs, model_config,
|
||||
name, **model_kwargs)
|
||||
else:
|
||||
raise NotImplementedError(
|
||||
"`framework` must be 'tf2|tf|tfe|torch', but is "
|
||||
@@ -542,16 +562,26 @@ class ModelCatalog:
|
||||
def _get_v2_model_class(input_space: gym.Space,
|
||||
model_config: ModelConfigDict,
|
||||
framework: str = "tf") -> ModelV2:
|
||||
if framework == "torch":
|
||||
from ray.rllib.models.torch.fcnet import (FullyConnectedNetwork as
|
||||
FCNet)
|
||||
from ray.rllib.models.torch.visionnet import (VisionNetwork as
|
||||
VisionNet)
|
||||
else:
|
||||
|
||||
VisionNet = None
|
||||
|
||||
if framework in ["tf2", "tf", "tfe"]:
|
||||
from ray.rllib.models.tf.fcnet import \
|
||||
FullyConnectedNetwork as FCNet
|
||||
from ray.rllib.models.tf.visionnet import \
|
||||
VisionNetwork as VisionNet
|
||||
elif framework == "torch":
|
||||
from ray.rllib.models.torch.fcnet import (FullyConnectedNetwork as
|
||||
FCNet)
|
||||
from ray.rllib.models.torch.visionnet import (VisionNetwork as
|
||||
VisionNet)
|
||||
elif framework == "jax":
|
||||
from ray.rllib.models.jax.fcnet import (FullyConnectedNetwork as
|
||||
FCNet)
|
||||
else:
|
||||
raise ValueError(
|
||||
"framework={} not supported in `ModelCatalog._get_v2_model_"
|
||||
"class`!".format(framework))
|
||||
|
||||
# Discrete/1D obs-spaces.
|
||||
if isinstance(input_space, gym.spaces.Discrete) or \
|
||||
@@ -559,6 +589,8 @@ class ModelCatalog:
|
||||
return FCNet
|
||||
# Default Conv2D net.
|
||||
else:
|
||||
if framework == "jax":
|
||||
raise NotImplementedError("No Conv2D default net for JAX yet!")
|
||||
return VisionNet
|
||||
|
||||
@staticmethod
|
||||
|
||||
@@ -0,0 +1,125 @@
|
||||
import logging
|
||||
import numpy as np
|
||||
import time
|
||||
|
||||
from ray.rllib.models.jax.jax_modelv2 import JAXModelV2
|
||||
from ray.rllib.models.jax.misc import SlimFC
|
||||
from ray.rllib.utils.annotations import override
|
||||
from ray.rllib.utils.framework import try_import_jax
|
||||
|
||||
jax, flax = try_import_jax()
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class FullyConnectedNetwork(JAXModelV2):
|
||||
"""Generic fully connected network."""
|
||||
|
||||
def __init__(self, obs_space, action_space, num_outputs, model_config,
|
||||
name):
|
||||
super().__init__(obs_space, action_space, num_outputs, model_config,
|
||||
name)
|
||||
|
||||
self.key = jax.random.PRNGKey(int(time.time()))
|
||||
|
||||
activation = model_config.get("fcnet_activation")
|
||||
hiddens = model_config.get("fcnet_hiddens", [])
|
||||
no_final_linear = model_config.get("no_final_linear")
|
||||
self.vf_share_layers = model_config.get("vf_share_layers")
|
||||
self.free_log_std = model_config.get("free_log_std")
|
||||
|
||||
# Generate free-floating bias variables for the second half of
|
||||
# the outputs.
|
||||
if self.free_log_std:
|
||||
assert num_outputs % 2 == 0, (
|
||||
"num_outputs must be divisible by two", num_outputs)
|
||||
num_outputs = num_outputs // 2
|
||||
|
||||
self._hidden_layers = []
|
||||
prev_layer_size = int(np.product(obs_space.shape))
|
||||
self._logits = None
|
||||
|
||||
# Create layers 0 to second-last.
|
||||
for size in hiddens[:-1]:
|
||||
self._hidden_layers.append(
|
||||
SlimFC(
|
||||
in_size=prev_layer_size,
|
||||
out_size=size,
|
||||
activation_fn=activation))
|
||||
prev_layer_size = size
|
||||
|
||||
# The last layer is adjusted to be of size num_outputs, but it's a
|
||||
# layer with activation.
|
||||
if no_final_linear and num_outputs:
|
||||
self._hidden_layers.append(
|
||||
SlimFC(
|
||||
in_size=prev_layer_size,
|
||||
out_size=num_outputs,
|
||||
activation_fn=activation))
|
||||
prev_layer_size = num_outputs
|
||||
# Finish the layers with the provided sizes (`hiddens`), plus -
|
||||
# iff num_outputs > 0 - a last linear layer of size num_outputs.
|
||||
else:
|
||||
if len(hiddens) > 0:
|
||||
self._hidden_layers.append(
|
||||
SlimFC(
|
||||
in_size=prev_layer_size,
|
||||
out_size=hiddens[-1],
|
||||
activation_fn=activation))
|
||||
prev_layer_size = hiddens[-1]
|
||||
if num_outputs:
|
||||
self._logits = SlimFC(
|
||||
in_size=prev_layer_size,
|
||||
out_size=num_outputs,
|
||||
activation_fn=None)
|
||||
else:
|
||||
self.num_outputs = (
|
||||
[int(np.product(obs_space.shape))] + hiddens[-1:])[-1]
|
||||
|
||||
# Layer to add the log std vars to the state-dependent means.
|
||||
if self.free_log_std and self._logits:
|
||||
raise ValueError("`free_log_std` not supported for JAX yet!")
|
||||
|
||||
self._value_branch_separate = None
|
||||
if not self.vf_share_layers:
|
||||
# Build a parallel set of hidden layers for the value net.
|
||||
prev_vf_layer_size = int(np.product(obs_space.shape))
|
||||
vf_layers = []
|
||||
for size in hiddens:
|
||||
vf_layers.append(
|
||||
SlimFC(
|
||||
in_size=prev_vf_layer_size,
|
||||
out_size=size,
|
||||
activation_fn=activation,
|
||||
))
|
||||
prev_vf_layer_size = size
|
||||
self._value_branch_separate = vf_layers
|
||||
|
||||
self._value_branch = SlimFC(
|
||||
in_size=prev_layer_size, out_size=1, activation_fn=None)
|
||||
# Holds the current "base" output (before logits layer).
|
||||
self._features = None
|
||||
# Holds the last input, in case value branch is separate.
|
||||
self._last_flat_in = None
|
||||
|
||||
@override(JAXModelV2)
|
||||
def forward(self, input_dict, state, seq_lens):
|
||||
self._last_flat_in = input_dict["obs_flat"]
|
||||
x = self._last_flat_in
|
||||
for layer in self._hidden_layers:
|
||||
x = layer(x)
|
||||
self._features = x
|
||||
logits = self._logits(self._features) if self._logits else \
|
||||
self._features
|
||||
if self.free_log_std:
|
||||
logits = self._append_free_log_std(logits)
|
||||
return logits, state
|
||||
|
||||
@override(JAXModelV2)
|
||||
def value_function(self):
|
||||
assert self._features is not None, "must call forward() first"
|
||||
if self._value_branch_separate:
|
||||
return self._value_branch(
|
||||
self._value_branch_separate(self._last_flat_in)).squeeze(1)
|
||||
else:
|
||||
return self._value_branch(self._features).squeeze(1)
|
||||
@@ -0,0 +1,70 @@
|
||||
import time
|
||||
|
||||
from ray.rllib.models.action_dist import ActionDistribution
|
||||
from ray.rllib.models.modelv2 import ModelV2
|
||||
from ray.rllib.utils.annotations import override
|
||||
from ray.rllib.utils.framework import try_import_jax, try_import_tfp
|
||||
from ray.rllib.utils.typing import TensorType, List
|
||||
|
||||
jax, flax = try_import_jax()
|
||||
tfp = try_import_tfp()
|
||||
|
||||
|
||||
class JAXDistribution(ActionDistribution):
|
||||
"""Wrapper class for JAX distributions."""
|
||||
|
||||
@override(ActionDistribution)
|
||||
def __init__(self, inputs: List[TensorType], model: ModelV2):
|
||||
super().__init__(inputs, model)
|
||||
# Store the last sample here.
|
||||
self.last_sample = None
|
||||
# Use current time as pseudo-random number generator's seed.
|
||||
self.prng_key = jax.random.PRNGKey(seed=int(time.time()))
|
||||
|
||||
@override(ActionDistribution)
|
||||
def logp(self, actions: TensorType) -> TensorType:
|
||||
return self.dist.log_prob(actions)
|
||||
|
||||
@override(ActionDistribution)
|
||||
def entropy(self) -> TensorType:
|
||||
return self.dist.entropy()
|
||||
|
||||
@override(ActionDistribution)
|
||||
def kl(self, other: ActionDistribution) -> TensorType:
|
||||
return self.dist.kl_divergence(other.dist)
|
||||
|
||||
@override(ActionDistribution)
|
||||
def sample(self) -> TensorType:
|
||||
# Update the state of our PRNG.
|
||||
_, self.prng_key = jax.random.split(self.prng_key)
|
||||
self.last_sample = jax.random.categorical(self.prng_key, self.inputs)
|
||||
return self.last_sample
|
||||
|
||||
@override(ActionDistribution)
|
||||
def sampled_action_logp(self) -> TensorType:
|
||||
assert self.last_sample is not None
|
||||
return self.logp(self.last_sample)
|
||||
|
||||
|
||||
class JAXCategorical(JAXDistribution):
|
||||
"""Wrapper class for a JAX Categorical distribution."""
|
||||
|
||||
@override(ActionDistribution)
|
||||
def __init__(self, inputs, model=None, temperature=1.0):
|
||||
if temperature != 1.0:
|
||||
assert temperature > 0.0, \
|
||||
"Categorical `temperature` must be > 0.0!"
|
||||
inputs /= temperature
|
||||
super().__init__(inputs, model)
|
||||
self.dist = tfp.experimental.substrates.jax.distributions.Categorical(
|
||||
logits=self.inputs)
|
||||
|
||||
@override(ActionDistribution)
|
||||
def deterministic_sample(self):
|
||||
self.last_sample = self.inputs.argmax(axis=1)
|
||||
return self.last_sample
|
||||
|
||||
@staticmethod
|
||||
@override(ActionDistribution)
|
||||
def required_model_output_shape(action_space, model_config):
|
||||
return action_space.n
|
||||
@@ -0,0 +1,27 @@
|
||||
import gym
|
||||
|
||||
from ray.rllib.models.modelv2 import ModelV2
|
||||
from ray.rllib.utils.annotations import PublicAPI
|
||||
from ray.rllib.utils.typing import ModelConfigDict
|
||||
|
||||
|
||||
@PublicAPI
|
||||
class JAXModelV2(ModelV2):
|
||||
"""JAX version of ModelV2.
|
||||
|
||||
Note that this class by itself is not a valid model unless you
|
||||
implement forward() in a subclass."""
|
||||
|
||||
def __init__(self, obs_space: gym.spaces.Space,
|
||||
action_space: gym.spaces.Space, num_outputs: int,
|
||||
model_config: ModelConfigDict, name: str):
|
||||
"""Initializes a JAXModelV2 instance."""
|
||||
|
||||
ModelV2.__init__(
|
||||
self,
|
||||
obs_space,
|
||||
action_space,
|
||||
num_outputs,
|
||||
model_config,
|
||||
name,
|
||||
framework="jax")
|
||||
@@ -0,0 +1,65 @@
|
||||
import time
|
||||
from typing import Callable, Optional
|
||||
|
||||
from ray.rllib.utils.framework import get_activation_fn, try_import_jax
|
||||
|
||||
jax, flax = try_import_jax()
|
||||
nn = np = None
|
||||
if flax:
|
||||
import flax.linen as nn
|
||||
import jax.numpy as np
|
||||
|
||||
|
||||
class SlimFC:
|
||||
"""Simple JAX version of a fully connected layer."""
|
||||
|
||||
def __init__(self,
|
||||
in_size,
|
||||
out_size,
|
||||
initializer: Optional[Callable] = None,
|
||||
activation_fn: Optional[str] = None,
|
||||
use_bias: bool = True,
|
||||
prng_key: Optional[jax.random.PRNGKey] = None,
|
||||
name: Optional[str] = None):
|
||||
"""Initializes a SlimFC instance.
|
||||
|
||||
Args:
|
||||
in_size (int): The input size of the input data that will be passed
|
||||
into this layer.
|
||||
out_size (int): The number of nodes in this FC layer.
|
||||
initializer (flax.:
|
||||
activation_fn (str): An activation string specifier, e.g. "relu".
|
||||
use_bias (bool): Whether to add biases to the dot product or not.
|
||||
#bias_init (float):
|
||||
prng_key (Optional[jax.random.PRNGKey]): An optional PRNG key to
|
||||
use for initialization. If None, create a new random one.
|
||||
name (Optional[str]): An optional name for this layer.
|
||||
"""
|
||||
|
||||
# By default, use Glorot unform initializer.
|
||||
if initializer is None:
|
||||
initializer = flax.nn.initializers.xavier_uniform()
|
||||
|
||||
self.prng_key = prng_key or jax.random.PRNGKey(int(time.time()))
|
||||
_, self.prng_key = jax.random.split(self.prng_key)
|
||||
# Create the flax dense layer.
|
||||
self._dense = nn.Dense(
|
||||
out_size,
|
||||
use_bias=use_bias,
|
||||
kernel_init=initializer,
|
||||
name=name,
|
||||
)
|
||||
# Initialize it.
|
||||
dummy_in = jax.random.normal(
|
||||
self.prng_key, (in_size, ), dtype=np.float32)
|
||||
_, self.prng_key = jax.random.split(self.prng_key)
|
||||
self._params = self._dense.init(self.prng_key, dummy_in)
|
||||
|
||||
# Activation function (if any; default=None (linear)).
|
||||
self.activation_fn = get_activation_fn(activation_fn, "jax")
|
||||
|
||||
def __call__(self, x):
|
||||
out = self._dense.apply(self._params, x)
|
||||
if self.activation_fn:
|
||||
out = self.activation_fn(out)
|
||||
return out
|
||||
@@ -5,6 +5,7 @@ from scipy.stats import beta, norm
|
||||
import tree
|
||||
import unittest
|
||||
|
||||
from ray.rllib.models.jax.jax_action_dist import JAXCategorical
|
||||
from ray.rllib.models.tf.tf_action_dist import Beta, Categorical, \
|
||||
DiagGaussian, GumbelSoftmax, MultiActionDistribution, MultiCategorical, \
|
||||
SquashedGaussian
|
||||
@@ -55,7 +56,9 @@ class TestDistributions(unittest.TestCase):
|
||||
dist = distribution_cls(inputs, {}, **(extra_kwargs or {}))
|
||||
for _ in range(100):
|
||||
sample = dist.sample()
|
||||
if fw != "tf":
|
||||
if fw == "jax":
|
||||
sample_check = sample
|
||||
elif fw != "tf":
|
||||
sample_check = sample.numpy()
|
||||
else:
|
||||
sample_check = sess.run(sample)
|
||||
@@ -71,7 +74,9 @@ class TestDistributions(unittest.TestCase):
|
||||
assert bounds[0] in sample_check
|
||||
assert bounds[1] in sample_check
|
||||
logp = dist.logp(sample)
|
||||
if fw != "tf":
|
||||
if fw == "jax":
|
||||
logp_check = logp
|
||||
elif fw != "tf":
|
||||
logp_check = logp.numpy()
|
||||
else:
|
||||
logp_check = sess.run(logp)
|
||||
@@ -88,9 +93,11 @@ class TestDistributions(unittest.TestCase):
|
||||
|
||||
inputs = inputs_space.sample()
|
||||
|
||||
for fw, sess in framework_iterator(session=True):
|
||||
for fw, sess in framework_iterator(
|
||||
session=True, frameworks=("jax", "tf", "tf2", "torch")):
|
||||
# Create the correct distribution object.
|
||||
cls = Categorical if fw != "torch" else TorchCategorical
|
||||
cls = JAXCategorical if fw == "jax" else Categorical if \
|
||||
fw != "torch" else TorchCategorical
|
||||
categorical = cls(inputs, {})
|
||||
|
||||
# Do a stability test using extreme NN outputs to see whether
|
||||
@@ -112,7 +119,7 @@ class TestDistributions(unittest.TestCase):
|
||||
# Batch of size=3 and non-deterministic -> expect roughly the mean.
|
||||
out = categorical.sample()
|
||||
check(
|
||||
tf.reduce_mean(out)
|
||||
np.mean(out) if fw == "jax" else tf.reduce_mean(out)
|
||||
if fw != "torch" else torch.mean(out.float()),
|
||||
1.0,
|
||||
decimals=0)
|
||||
|
||||
@@ -25,7 +25,7 @@ class FullyConnectedNetwork(TorchModelV2, nn.Module):
|
||||
nn.Module.__init__(self)
|
||||
|
||||
activation = model_config.get("fcnet_activation")
|
||||
hiddens = model_config.get("fcnet_hiddens")
|
||||
hiddens = model_config.get("fcnet_hiddens", [])
|
||||
no_final_linear = model_config.get("no_final_linear")
|
||||
self.vf_share_layers = model_config.get("vf_share_layers")
|
||||
self.free_log_std = model_config.get("free_log_std")
|
||||
|
||||
@@ -136,7 +136,7 @@ class SlimFC(nn.Module):
|
||||
"""
|
||||
super(SlimFC, self).__init__()
|
||||
layers = []
|
||||
# Actual Conv2D layer (including correct initialization logic).
|
||||
# Actual nn.Linear layer (including correct initialization logic).
|
||||
linear = nn.Linear(in_size, out_size, bias=use_bias)
|
||||
if initializer:
|
||||
initializer(linear.weight)
|
||||
|
||||
@@ -52,29 +52,32 @@ class TestAttentionNetLearning(unittest.TestCase):
|
||||
})
|
||||
tune.run("PPO", config=config, stop=self.stop, verbose=1)
|
||||
|
||||
# TODO: (sven) causes memory failures/timeouts on Travis.
|
||||
# Re-enable this once we have fast attention in master branch.
|
||||
def test_impala_attention_net_learning(self):
|
||||
ModelCatalog.register_custom_model("attention_net", GTrXLNet)
|
||||
config = dict(
|
||||
self.config, **{
|
||||
"num_workers": 4,
|
||||
"num_gpus": 0,
|
||||
"entropy_coeff": 0.01,
|
||||
"vf_loss_coeff": 0.001,
|
||||
"lr": 0.0008,
|
||||
"model": {
|
||||
"custom_model": "attention_net",
|
||||
"max_seq_len": 65,
|
||||
"custom_model_config": {
|
||||
"num_transformer_units": 1,
|
||||
"attn_dim": 64,
|
||||
"num_heads": 1,
|
||||
"memory_tau": 10,
|
||||
"head_dim": 32,
|
||||
"ff_hidden_dim": 32,
|
||||
},
|
||||
},
|
||||
})
|
||||
tune.run("IMPALA", config=config, stop=self.stop, verbose=1)
|
||||
return
|
||||
# ModelCatalog.register_custom_model("attention_net", GTrXLNet)
|
||||
# config = dict(
|
||||
# self.config, **{
|
||||
# "num_workers": 4,
|
||||
# "num_gpus": 0,
|
||||
# "entropy_coeff": 0.01,
|
||||
# "vf_loss_coeff": 0.001,
|
||||
# "lr": 0.0008,
|
||||
# "model": {
|
||||
# "custom_model": "attention_net",
|
||||
# "max_seq_len": 65,
|
||||
# "custom_model_config": {
|
||||
# "num_transformer_units": 1,
|
||||
# "attn_dim": 64,
|
||||
# "num_heads": 1,
|
||||
# "memory_tau": 10,
|
||||
# "head_dim": 32,
|
||||
# "ff_hidden_dim": 32,
|
||||
# },
|
||||
# },
|
||||
# })
|
||||
# tune.run("IMPALA", config=config, stop=self.stop, verbose=1)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
+30
-15
@@ -9,12 +9,12 @@ from ray.rllib.models.tf.tf_modelv2 import TFModelV2
|
||||
from ray.rllib.models.tf.tf_action_dist import TFActionDistribution
|
||||
from ray.rllib.models.preprocessors import (NoPreprocessor, OneHotPreprocessor,
|
||||
Preprocessor)
|
||||
from ray.rllib.models.tf.fcnet import FullyConnectedNetwork
|
||||
from ray.rllib.models.tf.visionnet import VisionNetwork
|
||||
from ray.rllib.utils.annotations import override
|
||||
from ray.rllib.utils.framework import try_import_tf
|
||||
from ray.rllib.utils.framework import try_import_tf, try_import_torch
|
||||
from ray.rllib.utils.test_utils import framework_iterator
|
||||
|
||||
tf1, tf, tfv = try_import_tf()
|
||||
torch, _ = try_import_torch()
|
||||
|
||||
|
||||
class CustomPreprocessor(Preprocessor):
|
||||
@@ -102,19 +102,34 @@ class TestModelCatalog(unittest.TestCase):
|
||||
def test_default_models(self):
|
||||
ray.init(object_store_memory=1000 * 1024 * 1024)
|
||||
|
||||
p1 = ModelCatalog.get_model_v2(
|
||||
obs_space=Box(0, 1, shape=(3, ), dtype=np.float32),
|
||||
action_space=Discrete(5),
|
||||
num_outputs=5,
|
||||
model_config={})
|
||||
self.assertEqual(type(p1), FullyConnectedNetwork)
|
||||
for fw in framework_iterator(frameworks=("jax", "tf", "tf2", "torch")):
|
||||
obs_space = Box(0, 1, shape=(3, ), dtype=np.float32)
|
||||
p1 = ModelCatalog.get_model_v2(
|
||||
obs_space=obs_space,
|
||||
action_space=Discrete(5),
|
||||
num_outputs=5,
|
||||
model_config={},
|
||||
framework=fw,
|
||||
)
|
||||
self.assertTrue("FullyConnectedNetwork" in type(p1).__name__)
|
||||
# Do a test forward pass.
|
||||
obs = np.array([obs_space.sample()])
|
||||
if fw == "torch":
|
||||
obs = torch.from_numpy(obs)
|
||||
out, state_outs = p1({"obs": obs})
|
||||
self.assertTrue(out.shape == (1, 5))
|
||||
self.assertTrue(state_outs == [])
|
||||
|
||||
p2 = ModelCatalog.get_model_v2(
|
||||
obs_space=Box(0, 1, shape=(84, 84, 3), dtype=np.float32),
|
||||
action_space=Discrete(5),
|
||||
num_outputs=5,
|
||||
model_config={})
|
||||
self.assertEqual(type(p2), VisionNetwork)
|
||||
# No Conv2Ds for JAX yet.
|
||||
if fw != "jax":
|
||||
p2 = ModelCatalog.get_model_v2(
|
||||
obs_space=Box(0, 1, shape=(84, 84, 3), dtype=np.float32),
|
||||
action_space=Discrete(5),
|
||||
num_outputs=5,
|
||||
model_config={},
|
||||
framework=fw,
|
||||
)
|
||||
self.assertTrue("VisionNetwork" in type(p2).__name__)
|
||||
|
||||
def test_custom_model(self):
|
||||
ray.init(object_store_memory=1000 * 1024 * 1024)
|
||||
|
||||
@@ -15,6 +15,33 @@ TensorType = TensorType
|
||||
TensorStructType = TensorStructType
|
||||
|
||||
|
||||
def try_import_jax(error=False):
|
||||
"""Tries importing JAX and returns the module (or None).
|
||||
|
||||
Args:
|
||||
error (bool): Whether to raise an error if JAX cannot be imported.
|
||||
|
||||
Returns:
|
||||
The jax module.
|
||||
|
||||
Raises:
|
||||
ImportError: If error=True and JAX is not installed.
|
||||
"""
|
||||
if "RLLIB_TEST_NO_JAX_IMPORT" in os.environ:
|
||||
logger.warning("Not importing JAX for test purposes.")
|
||||
return None
|
||||
|
||||
try:
|
||||
import jax
|
||||
import flax
|
||||
except ImportError as e:
|
||||
if error:
|
||||
raise e
|
||||
return None, None
|
||||
|
||||
return jax, flax
|
||||
|
||||
|
||||
def try_import_tf(error=False):
|
||||
"""Tries importing tf and returns the module (or None).
|
||||
|
||||
@@ -251,6 +278,16 @@ def get_activation_fn(name: Optional[str] = None, framework: str = "tf"):
|
||||
return nn.ReLU
|
||||
elif name == "tanh":
|
||||
return nn.Tanh
|
||||
elif framework == "jax":
|
||||
if name in ["linear", None]:
|
||||
return None
|
||||
jax = try_import_jax()
|
||||
if name == "swish":
|
||||
return jax.nn.swish
|
||||
if name == "relu":
|
||||
return jax.nn.relu
|
||||
elif name == "tanh":
|
||||
return jax.nn.hard_tanh
|
||||
else:
|
||||
if name in ["linear", None]:
|
||||
return None
|
||||
|
||||
@@ -2,8 +2,10 @@ import gym
|
||||
import logging
|
||||
import numpy as np
|
||||
|
||||
from ray.rllib.utils.framework import try_import_tf, try_import_torch
|
||||
from ray.rllib.utils.framework import try_import_jax, try_import_tf, \
|
||||
try_import_torch
|
||||
|
||||
jax = try_import_jax()
|
||||
tf1, tf, tfv = try_import_tf()
|
||||
if tf1:
|
||||
eager_mode = None
|
||||
@@ -65,7 +67,10 @@ def framework_iterator(config=None,
|
||||
logger.warning(
|
||||
"framework_iterator skipping tf2.x (tf version is < 2.0)!")
|
||||
continue
|
||||
assert fw in ["tf2", "tf", "tfe", "torch", None]
|
||||
elif fw == "jax" and not jax:
|
||||
logger.warning("framework_iterator skipping JAX (not installed)!")
|
||||
continue
|
||||
assert fw in ["tf2", "tf", "tfe", "torch", "jax", None]
|
||||
|
||||
# Do we need a test session?
|
||||
sess = None
|
||||
|
||||
Reference in New Issue
Block a user