Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
49 commits
Select commit Hold shift + click to select a range
6bc7f0b
PPO, SAC, DDPG passed
lihuoran Apr 12, 2022
b3f5aef
Explore in SAC
lihuoran Apr 12, 2022
5dab711
Test GYM on server
lihuoran Apr 22, 2022
211c06f
Sync server changes
lihuoran Jan 17, 2023
f92f7f1
Merge branch 'v0.3' into rl_benchmark_debug
lihuoran Jan 17, 2023
514250a
pre-commit
lihuoran Jan 17, 2023
fc0c02d
Ready to try on server
lihuoran Jan 17, 2023
9fcdf42
.
lihuoran Jan 17, 2023
01b5a94
.
lihuoran Jan 17, 2023
dd27eed
.
lihuoran Jan 17, 2023
1c8f258
.
lihuoran Jan 17, 2023
1aa1085
.
lihuoran Jan 17, 2023
148af38
Performance OK
lihuoran Jan 18, 2023
99ff7b9
Move to tests
lihuoran Jan 18, 2023
65ba1a1
Remove old versions
lihuoran Jan 18, 2023
f4a85b8
PPO done
lihuoran Jan 18, 2023
2349191
Start to test AC
lihuoran Jan 18, 2023
f6f7dae
Start to test SAC
lihuoran Jan 18, 2023
110fec4
SAC test passed
lihuoran Jan 28, 2023
2a1ccd5
Multiple round in evaluation
lihuoran Jan 28, 2023
c371220
Modify config.yml
lihuoran Jan 28, 2023
a65d902
Add Callbacks
lihuoran Jan 28, 2023
aa484f8
[wip] SAC performance not good
lihuoran Jan 30, 2023
84ec6e6
[wip] still not good
lihuoran Jan 30, 2023
0ceaac4
update for some PR comments; Add a MARKDOWN file (#576)
Jinyu-W Jan 31, 2023
aad41d9
Use FullyConnected to replace mlp
lihuoran Jan 31, 2023
8884231
Update action bound
lihuoran Jan 31, 2023
0a01fb1
Merge branch 'rl_benchmark_debug' into rl_workflow_refine
lihuoran Jan 31, 2023
0bd25ca
???
lihuoran Jan 31, 2023
8781dd6
Change gym env wrapper metrics logci
lihuoran Jan 31, 2023
7b9b698
Change gym env wrapper metrics logci
lihuoran Jan 31, 2023
52b4d1d
refine env_sampler.sample under step mode
lihuoran Feb 1, 2023
a3fea0d
Add DDPG. Performance not good...
lihuoran Feb 1, 2023
23f39d1
Add DDPG. Performance not good...
lihuoran Feb 1, 2023
9da8b90
wip
lihuoran Feb 1, 2023
fb11c31
Sounds like sac works
lihuoran Feb 1, 2023
d7d3282
Refactor file structure
lihuoran Feb 1, 2023
ea26275
Refactor file structure
lihuoran Feb 1, 2023
8881a1c
Refactor file structure
lihuoran Feb 1, 2023
b4db842
Pre-commit
lihuoran Feb 6, 2023
8874a65
Merge branch 'rl_benchmark_debug' into rl_workflow_refine
lihuoran Feb 6, 2023
2a7334b
Merge branch 'v0.3' into rl_workflow_refine
lihuoran Feb 6, 2023
eb7ae9b
Pre commit
lihuoran Feb 6, 2023
627b7d1
Minor refinement of CIM RL
lihuoran Feb 8, 2023
8386312
Jinyu/rl workflow refine (#578)
Jinyu-W Feb 8, 2023
b05c849
Resolve PR comments
lihuoran Feb 9, 2023
ab5e675
Compare PPO with spinning up (#579)
lihuoran Feb 9, 2023
e180f10
SAC Test parameters update (#580)
Jinyu-W Feb 13, 2023
9371949
Episode truncation & early stopping (#581)
lihuoran Feb 17, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 20 additions & 6 deletions examples/cim/rl/env_sampler.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -90,11 +90,25 @@ def post_collect(self, info_list: list, ep: int) -> None:
for info in info_list:
print(f"env summary (episode {ep}): {info['env_metric']}")

# print the average env metric
if len(info_list) > 1:
metric_keys, num_envs = info_list[0]["env_metric"].keys(), len(info_list)
avg_metric = {key: sum(info["env_metric"][key] for info in info_list) / num_envs for key in metric_keys}
print(f"average env summary (episode {ep}): {avg_metric}")
# average env metric
metric_keys, num_envs = info_list[0]["env_metric"].keys(), len(info_list)
avg_metric = {key: sum(info["env_metric"][key] for info in info_list) / num_envs for key in metric_keys}
print(f"average env summary (episode {ep}): {avg_metric}")

self.metrics.update(avg_metric)
self.metrics = {k: v for k, v in self.metrics.items() if not k.startswith("val/")}

def post_evaluate(self, info_list: list, ep: int) -> None:
self.post_collect(info_list, ep)
# print the env metric from each rollout worker
for info in info_list:
print(f"env summary (episode {ep}): {info['env_metric']}")

# average env metric
metric_keys, num_envs = info_list[0]["env_metric"].keys(), len(info_list)
avg_metric = {key: sum(info["env_metric"][key] for info in info_list) / num_envs for key in metric_keys}
print(f"average env summary (episode {ep}): {avg_metric}")

self.metrics.update({"val/" + k: v for k, v in avg_metric.items()})

def monitor_metrics(self) -> float:
return -self.metrics["val/container_shortage"]
2 changes: 1 addition & 1 deletion examples/cim/rl/rl_component_bundle.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -13,7 +13,7 @@

# Environments
learn_env = Env(**env_conf)
test_env = learn_env
test_env = Env(**env_conf)

# Agent, policy, and trainers
num_agents = len(learn_env.agent_idx_list)
Expand Down
2 changes: 1 addition & 1 deletion examples/rl/README.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -7,7 +7,7 @@ This folder contains scenarios that employ reinforcement learning. MARO's RL too
The entrance of a RL workflow is a YAML config file. For readers' convenience, we call this config file `config.yml` in the rest part of this doc. `config.yml` specifies the path of all necessary resources, definitions, and configurations to run the job. MARO provides a comprehensive template of the config file with detailed explanations (`maro/maro/rl/workflows/config/template.yml`). Meanwhile, MARO also provides several simple examples of `config.yml` under the current folder.

There are two ways to start the RL job:
- If you only need to have a quick look and try to start an out-of-box workflow, just run `python .\examples\rl\run_rl_example.py PATH_TO_CONFIG_YAML`. For example, `python .\examples\rl\run_rl_example.py .\examples\rl\cim.yml` will run the complete example RL training workflow of CIM scenario. If you only want to run the evaluation workflow, you could start the job with `--evaluate_only`.
- If you only need to have a quick look and try to start an out-of-box workflow, just run `python .\examples\rl\run.py PATH_TO_CONFIG_YAML`. For example, `python .\examples\rl\run.py .\examples\rl\cim.yml` will run the complete example RL training workflow of CIM scenario. If you only want to run the evaluation workflow, you could start the job with `--evaluate_only`.
- (**Require install MARO from source**) You could also start the job through MARO CLI. Use the command `maro local run [-c] path/to/your/config` to run in containerized (with `-c`) or non-containerized (without `-c`) environments. Similar, you could add `--evaluate_only` if you only need to run the evaluation workflow.

## Create Your Own Scenarios
Expand Down
5 changes: 3 additions & 2 deletions examples/rl/cim.yml
Original file line numberDiff line numberDiff line change
Expand Up@@ -10,11 +10,12 @@

job: cim_rl_workflow
scenario_path: "examples/cim/rl"
log_path: "log/rl_job/cim.txt"
log_path: "log/cim_rl/"
main:
num_episodes: 30 # Number of episodes to run. Each episode is one cycle of roll-out and training.
num_steps: null
eval_schedule: 5
early_stop_patience: 5
logging:
stdout: INFO
file: DEBUG
Expand All@@ -27,7 +28,7 @@ training:
load_path: null
load_episode: null
checkpointing:
path: "checkpoint/rl_job/cim"
path: "log/cim_rl/checkpoints"
interval: 5
logging:
stdout: INFO
Expand Down
4 changes: 2 additions & 2 deletions examples/rl/cim_distributed.yml
Original file line numberDiff line numberDiff line change
Expand Up@@ -10,7 +10,7 @@

job: cim_rl_workflow

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TODO: runtime error

scenario_path: "examples/cim/rl"
log_path: "log/rl_job/cim.txt"
log_path: "log/cim_rl/"
main:
num_episodes: 30 # Number of episodes to run. Each episode is one cycle of roll-out and training.
num_steps: null
Expand All@@ -35,7 +35,7 @@ training:
load_path: null
load_episode: null
checkpointing:
path: "checkpoint/rl_job/cim"
path: "log/cim_rl/checkpoints"
interval: 5
proxy:
host: "127.0.0.1"
Expand Down
4 changes: 2 additions & 2 deletions examples/rl/vm_scheduling.yml
Original file line numberDiff line numberDiff line change
Expand Up@@ -10,7 +10,7 @@

job: vm_scheduling_rl_workflow
scenario_path: "examples/vm_scheduling/rl"
log_path: "log/rl_job/vm_scheduling.txt"
log_path: "log/vm_rl/"
main:
num_episodes: 30 # Number of episodes to run. Each episode is one cycle of roll-out and training.
num_steps: null
Expand All@@ -27,7 +27,7 @@ training:
load_path: null
load_episode: null
checkpointing:
path: "checkpoint/rl_job/vm_scheduling"
path: "log/vm_rl/checkpoints"
interval: 5
logging:
stdout: INFO
Expand Down
8 changes: 7 additions & 1 deletion maro/rl/model/abs_net.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -4,7 +4,7 @@
from __future__ import annotations

from abc import ABCMeta
from typing import Any, Dict
from typing import Any, Dict, Optional

import torch.nn
from torch.optim import Optimizer
Expand All@@ -18,6 +18,8 @@ class AbsNet(torch.nn.Module, metaclass=ABCMeta):
def __init__(self) -> None:
super(AbsNet, self).__init__()

self._device: Optional[torch.device] = None

@property
def optim(self) -> Optimizer:
optim = getattr(self, "_optim", None)
Expand DownExpand Up@@ -119,3 +121,7 @@ def unfreeze_all_parameters(self) -> None:
"""Unfreeze all parameters."""
for p in self.parameters():
p.requires_grad = True

def to_device(self, device: torch.device) -> None:
self._device = device
self.to(device)
4 changes: 4 additions & 0 deletions maro/rl/model/algorithm_nets/ac_based.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -54,3 +54,7 @@ def _get_actions_with_probs_impl(self, states: torch.Tensor, exploring: bool) ->
def _get_states_actions_probs_impl(self, states: torch.Tensor, actions: torch.Tensor) -> torch.Tensor:
# Not used in Actor-Critic or PPO
pass

def _get_random_actions_impl(self, states: torch.Tensor) -> torch.Tensor:
# Not used in Actor-Critic or PPO
pass
4 changes: 4 additions & 0 deletions maro/rl/model/algorithm_nets/ddpg.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -40,3 +40,7 @@ def _get_states_actions_probs_impl(self, states: torch.Tensor, actions: torch.Te
def _get_states_actions_logps_impl(self, states: torch.Tensor, actions: torch.Tensor) -> torch.Tensor:
# Not used in DDPG
pass

def _get_random_actions_impl(self, states: torch.Tensor) -> torch.Tensor:
# Not used in DDPG
pass
14 changes: 14 additions & 0 deletions maro/rl/model/policy_net.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -221,3 +221,17 @@ class ContinuousPolicyNet(PolicyNet, metaclass=ABCMeta):

def __init__(self, state_dim: int, action_dim: int) -> None:
super(ContinuousPolicyNet, self).__init__(state_dim=state_dim, action_dim=action_dim)

def get_random_actions(self, states: torch.Tensor) -> torch.Tensor:
actions = self._get_random_actions_impl(states)

assert self._shape_check(
states=states,
actions=actions,
), f"Actions shape check failed. Expecting: {(states.shape[0], self.action_dim)}, actual: {actions.shape}."

return actions

@abstractmethod
def _get_random_actions_impl(self, states: torch.Tensor) -> torch.Tensor:
raise NotImplementedError
26 changes: 25 additions & 1 deletion maro/rl/policy/abs_policy.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -129,6 +129,8 @@ class RLPolicy(AbsPolicy, metaclass=ABCMeta):
state_dim (int): Dimension of states.
action_dim (int): Dimension of actions.
trainable (bool, default=True): Whether this policy is trainable.
warmup (int, default=0): Number of steps for uniform-random action selection, before running real policy.
Helps exploration.
"""

def __init__(
Expand All@@ -138,13 +140,16 @@ def __init__(
action_dim: int,
is_discrete_action: bool,
trainable: bool = True,
warmup: int = 0,
) -> None:
super(RLPolicy, self).__init__(name=name, trainable=trainable)
self._state_dim = state_dim
self._action_dim = action_dim
self._is_exploring = False

self._device: Optional[torch.device] = None
self._warmup = warmup
self._call_count = 0

self.is_discrete_action = is_discrete_action

Expand DownExpand Up@@ -200,7 +205,12 @@ def apply_gradients(self, grad: dict) -> None:
raise NotImplementedError

def get_actions(self, states: np.ndarray) -> np.ndarray:
actions = self.get_actions_tensor(ndarray_to_tensor(states, device=self._device))
self._call_count += 1

if self._call_count <= self._warmup:
actions = self.get_random_actions_tensor(ndarray_to_tensor(states, device=self._device))
else:
actions = self.get_actions_tensor(ndarray_to_tensor(states, device=self._device))
return actions.detach().cpu().numpy()

def get_actions_tensor(self, states: torch.Tensor) -> torch.Tensor:
Expand All@@ -217,6 +227,16 @@ def get_actions_tensor(self, states: torch.Tensor) -> torch.Tensor:

return actions

def get_random_actions_tensor(self, states: torch.Tensor) -> torch.Tensor:
actions = self._get_random_actions_impl(states)

assert self._shape_check(
states=states,
actions=actions,
), f"Actions shape check failed. Expecting: {(states.shape[0], self.action_dim)}, actual: {actions.shape}."

return actions

def get_actions_with_probs(self, states: torch.Tensor) -> Tuple[torch.Tensor, torch.Tensor]:
assert self._shape_check(
states=states,
Expand DownExpand Up@@ -273,6 +293,10 @@ def get_states_actions_logps(self, states: torch.Tensor, actions: torch.Tensor)
def _get_actions_impl(self, states: torch.Tensor) -> torch.Tensor:
raise NotImplementedError

@abstractmethod
def _get_random_actions_impl(self, states: torch.Tensor) -> torch.Tensor:
raise NotImplementedError

@abstractmethod
def _get_actions_with_probs_impl(self, states: torch.Tensor) -> Tuple[torch.Tensor, torch.Tensor]:
raise NotImplementedError
Expand Down
21 changes: 18 additions & 3 deletions maro/rl/policy/continuous_rl_policy.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -42,6 +42,8 @@ class ContinuousRLPolicy(RLPolicy):
the bound for every dimension. If it is a float, it will be broadcast to all dimensions.
policy_net (ContinuousPolicyNet): The core net of this policy.
trainable (bool, default=True): Whether this policy is trainable.
warmup (int, default=0): Number of steps for uniform-random action selection, before running real policy.
Helps exploration.
"""

def __init__(
Expand All@@ -50,6 +52,7 @@ def __init__(
action_range: Tuple[Union[float, List[float]], Union[float, List[float]]],
policy_net: ContinuousPolicyNet,
trainable: bool = True,
warmup: int = 0,
) -> None:
assert isinstance(policy_net, ContinuousPolicyNet)

Expand All@@ -59,6 +62,7 @@ def __init__(
action_dim=policy_net.action_dim,
trainable=trainable,
is_discrete_action=False,
warmup=warmup,
)

self._lbounds, self._ubounds = _parse_action_range(self.action_dim, action_range)
Expand All@@ -83,6 +87,9 @@ def _post_check(self, states: torch.Tensor, actions: torch.Tensor) -> bool:
def _get_actions_impl(self, states: torch.Tensor) -> torch.Tensor:
return self._policy_net.get_actions(states, self._is_exploring)

def _get_random_actions_impl(self, states: torch.Tensor) -> torch.Tensor:
return self._policy_net.get_random_actions(states)

def _get_actions_with_probs_impl(self, states: torch.Tensor) -> Tuple[torch.Tensor, torch.Tensor]:
return self._policy_net.get_actions_with_probs(states, self._is_exploring)

Expand DownExpand Up@@ -117,14 +124,22 @@ def train(self) -> None:
self._policy_net.train()

def get_state(self) -> dict:
return self._policy_net.get_state()
return {
"net": self._policy_net.get_state(),
"policy": {
"warmup": self._warmup,
"call_count": self._call_count,
},
}

def set_state(self, policy_state: dict) -> None:
self._policy_net.set_state(policy_state)
self._policy_net.set_state(policy_state["net"])
self._warmup = policy_state["policy"]["warmup"]
self._call_count = policy_state["policy"]["call_count"]

def soft_update(self, other_policy: RLPolicy, tau: float) -> None:
assert isinstance(other_policy, ContinuousRLPolicy)
self._policy_net.soft_update(other_policy.policy_net, tau)

def _to_device_impl(self, device: torch.device) -> None:
self._policy_net.to(device)
self._policy_net.to_device(device)
Loading