forked from NM512/r2dreamer
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathtrainer.py
More file actions
192 lines (180 loc) · 8.91 KB
/
Copy pathtrainer.py
File metadata and controls
192 lines (180 loc) · 8.91 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
import torch
import tools
class OnlineTrainer:
def __init__(self, config, replay_buffer, logger, logdir, train_envs, eval_envs):
self.replay_buffer = replay_buffer
self.logger = logger
self.train_envs = train_envs
self.eval_envs = eval_envs
self.steps = int(config.steps)
self.pretrain = int(config.pretrain)
self.eval_every = int(config.eval_every)
self.eval_episode_num = int(config.eval_episode_num)
self.video_pred_log = bool(config.video_pred_log)
self.params_hist_log = bool(config.params_hist_log)
self.batch_length = int(config.batch_length)
batch_steps = int(config.batch_size * config.batch_length)
# train_ratio is based on data steps rather than environment steps.
self._updates_needed = tools.Every(batch_steps / config.train_ratio * config.action_repeat)
self._should_pretrain = tools.Once()
self._should_log = tools.Every(config.update_log_every)
self._should_eval = tools.Every(self.eval_every)
self._action_repeat = config.action_repeat
def eval(self, agent, train_step):
"""Run evaluation episodes.
Environment stepping is executed on CPU to avoid GPU<->CPU synchronizations
in the worker processes. Observations are moved back to GPU asynchronously
(H2D with non_blocking=True) right before policy inference.
"""
print("Evaluating the policy...")
envs = self.eval_envs
agent.eval()
# (B,)
done = torch.ones(envs.env_num, dtype=torch.bool, device=agent.device)
once_done = torch.zeros(envs.env_num, dtype=torch.bool, device=agent.device)
steps = torch.zeros(envs.env_num, dtype=torch.int32, device=agent.device)
returns = torch.zeros(envs.env_num, dtype=torch.float32, device=agent.device)
log_metrics = {}
# cache is only used for video logging / open-loop prediction.
cache = []
agent_state = agent.get_initial_state(envs.env_num)
# (B, A)
act = agent_state["prev_action"].clone()
while not once_done.all():
steps += ~done * ~once_done
# Step environments on CPU.
# (B, A)
act_cpu = act.detach().to("cpu")
# (B,)
done_cpu = done.detach().to("cpu")
trans_cpu, done_cpu = envs.step(act_cpu, done_cpu)
# Move observations back to GPU asynchronously for the agent.
# dict of (B, 1, *)
trans = trans_cpu.to(agent.device, non_blocking=True)
# (B,)
done = done_cpu.to(agent.device)
# Store transition.
# We keep the observation and the action that produced it together.
trans["action"] = act
if len(cache) < self.batch_length:
cache.append(trans.clone())
# (B, A)
act, agent_state = agent.act(trans, agent_state, eval=True)
returns += trans["reward"][:, 0] * ~once_done
for key, value in trans.items():
if key.startswith("log_"):
if key not in log_metrics:
log_metrics[key] = torch.zeros_like(returns)
log_metrics[key] += value[:, 0] * ~once_done
once_done |= done
# dict of (B, T, *)
cache = torch.stack(cache, dim=1) if len(cache) else None
self.logger.scalar("episode/eval_score", returns.mean())
self.logger.scalar("episode/eval_length", steps.to(torch.float32).mean())
for key, value in log_metrics.items():
if key == "log_success":
value = torch.clip(value, max=1.0) # make sure 1.0 for success episode
self.logger.scalar(f"episode/eval_{key[4:]}", value.mean())
if cache is not None and "image" in cache:
self.logger.video("eval_video", tools.to_np(cache["image"][:1]))
if self.video_pred_log and cache is not None:
initial = agent.get_initial_state(1)
self.logger.video(
"eval_open_loop",
tools.to_np(
agent.video_pred(
cache[:1], # give only first batch
(initial["stoch"], initial["deter"]),
)
),
)
self.logger.write(train_step)
agent.train()
def begin(self, agent):
"""Main online training loop.
The loop is designed to overlap CPU environment stepping and GPU model
execution. Environments are stepped on CPU, observations are pinned,
then transferred to GPU with non_blocking=True.
"""
envs = self.train_envs
video_cache = []
step = self.replay_buffer.count() * self._action_repeat
update_count = 0
# (B,)
done = torch.ones(envs.env_num, dtype=torch.bool, device=agent.device)
returns = torch.zeros(envs.env_num, dtype=torch.float32, device=agent.device)
lengths = torch.zeros(envs.env_num, dtype=torch.int32, device=agent.device)
episode_ids = torch.arange(
envs.env_num, dtype=torch.int32, device=agent.device
) # Increment this to prevent sampling across episode boundaries
train_metrics = {}
agent_state = agent.get_initial_state(envs.env_num)
# (B, A)
act = agent_state["prev_action"].clone()
while step < self.steps:
# Evaluation
if self._should_eval(step) and self.eval_episode_num > 0:
self.eval(agent, step)
# Save metrics
if done.any():
for i, d in enumerate(done):
if d and lengths[i] > 0:
if i == 0 and len(video_cache) > 0:
video = torch.stack(video_cache, axis=0)
self.logger.video("train_video", tools.to_np(video[None]))
video_cache = []
self.logger.scalar("episode/score", returns[i])
self.logger.scalar("episode/length", lengths[i])
self.logger.write(step + i) # to show all values on tensorboard
returns[i] = lengths[i] = 0
step += int((~done).sum()) * self._action_repeat # step is based on env side
lengths += ~done
# Step environments on CPU to avoid GPU<->CPU sync in the worker processes.
# (B, A)
act_cpu = act.detach().to("cpu")
# (B,)
done_cpu = done.detach().to("cpu")
trans_cpu, done_cpu = envs.step(act_cpu, done_cpu)
# Move observations back to GPU asynchronously for the agent.
# dict of (B, 1, *)
trans = trans_cpu.to(agent.device, non_blocking=True)
# (B,)
done = done_cpu.to(agent.device)
# Policy inference on GPU.
# "agent_state" is reset by the agent based on the "is_first" flag in trans.
# (B, A)
act, agent_state = agent.act(trans.clone(), agent_state, eval=False)
# Store transition.
# We keep the observation and the action that produced it together.
# Mask actions after an episode has ended.
trans["action"] = act * ~done.unsqueeze(-1)
trans["stoch"] = agent_state["stoch"]
trans["deter"] = agent_state["deter"]
trans["episode"] = episode_ids # Don't lift dim
if "image" in trans:
video_cache.append(trans["image"][0])
self.replay_buffer.add_transition(trans.detach())
returns += trans["reward"][:, 0]
# Update models after enough data has accumulated
if step // (envs.env_num * self._action_repeat) > self.batch_length + 1:
if self._should_pretrain():
update_num = self.pretrain
else:
update_num = self._updates_needed(step)
for _ in range(update_num):
_metrics = agent.update(self.replay_buffer)
train_metrics = _metrics
update_count += update_num
# Log training metrics
if self._should_log(step):
for name, value in train_metrics.items():
value = tools.to_np(value) if isinstance(value, torch.Tensor) else value
self.logger.scalar(f"train/{name}", value)
self.logger.scalar("train/opt/updates", update_count)
if self.video_pred_log:
data, _, initial = self.replay_buffer.sample()
self.logger.video("open_loop", tools.to_np(agent.video_pred(data, initial)))
if self.params_hist_log:
for name, param in agent._named_params.items():
self.logger.histogram(name, tools.to_np(param))
self.logger.write(step, fps=True)