dsh-eval-guard 0.7.3 → 0.7.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/ISSUES.md +5 -1
- package/README.md +4 -2
- package/assets/neural-pendulum/SPEC.md +4 -3
- package/assets/neural-pendulum/TASK.md +4 -3
- package/assets/neural-pendulum/rl-feasibility/README.md +60 -0
- package/assets/neural-pendulum/rl-feasibility/RESULTS.md +370 -0
- package/assets/neural-pendulum/rl-feasibility/evaluate.py +107 -0
- package/assets/neural-pendulum/rl-feasibility/finalize_run.py +168 -0
- package/assets/neural-pendulum/rl-feasibility/models.py +124 -0
- package/assets/neural-pendulum/rl-feasibility/obs.py +205 -0
- package/assets/neural-pendulum/rl-feasibility/plot_selected.py +55 -0
- package/assets/neural-pendulum/rl-feasibility/reference_controller.py +112 -0
- package/assets/neural-pendulum/rl-feasibility/results/curves_selected.png +0 -0
- package/assets/neural-pendulum/rl-feasibility/results/reference_controller.json +417 -0
- package/assets/neural-pendulum/rl-feasibility/results/summary.json +1599 -0
- package/assets/neural-pendulum/rl-feasibility/results/table.md +39 -0
- package/assets/neural-pendulum/rl-feasibility/sim.py +324 -0
- package/assets/neural-pendulum/rl-feasibility/summarize.py +175 -0
- package/assets/neural-pendulum/rl-feasibility/task.py +342 -0
- package/assets/neural-pendulum/rl-feasibility/test_contract.py +188 -0
- package/assets/neural-pendulum/rl-feasibility/train_ppo.py +403 -0
- package/assets/neural-pendulum/rl-feasibility/train_sac.py +406 -0
- package/assets/neural-pendulum/sim.cpp +13 -2
- package/assets/neural-pendulum/sim_eval +0 -0
- package/assets/neural-pendulum/workspace.zip +0 -0
- package/lib/index.js +5 -1
- package/native/dsh-eval-supervisor +0 -0
- package/native/supervisor.c +42 -10
- package/neural-pendulum-eval/sim_eval +0 -0
- package/package.json +2 -2
- package/eval/__pycache__/bomberman.cpython-310.pyc +0 -0
- package/eval/__pycache__/commit0.cpython-310.pyc +0 -0
- package/eval/__pycache__/common.cpython-310.pyc +0 -0
- package/eval/__pycache__/ctf.cpython-310.pyc +0 -0
- package/eval/__pycache__/featurebench.cpython-310.pyc +0 -0
- package/eval/__pycache__/land.cpython-310.pyc +0 -0
- package/eval/__pycache__/neural-cifar.cpython-310.pyc +0 -0
- package/eval/__pycache__/neural-cpp.cpython-310.pyc +0 -0
- package/eval/__pycache__/neural-fp32.cpython-310.pyc +0 -0
- package/eval/__pycache__/neural-pendulum.cpython-310.pyc +0 -0
- package/eval/__pycache__/swe-bench-pro.cpython-310.pyc +0 -0
- package/eval/__pycache__/tank.cpython-310.pyc +0 -0
- package/eval/__pycache__/warehouse.cpython-310.pyc +0 -0
- package/eval/legacy/__pycache__/bomberman.cpython-310.pyc +0 -0
- package/eval/legacy/__pycache__/commit0.cpython-310.pyc +0 -0
- package/eval/legacy/__pycache__/ctf.cpython-310.pyc +0 -0
- package/eval/legacy/__pycache__/featurebench.cpython-310.pyc +0 -0
- package/eval/legacy/__pycache__/land.cpython-310.pyc +0 -0
- package/eval/legacy/__pycache__/swe_bench_pro.cpython-310.pyc +0 -0
- package/eval/legacy/__pycache__/tank.cpython-310.pyc +0 -0
- package/eval/legacy/__pycache__/warehouse.cpython-310.pyc +0 -0
- package/neural-cifar-eval/__pycache__/cifar_cache.cpython-310.pyc +0 -0
- package/neural-cifar-eval/__pycache__/common.cpython-310.pyc +0 -0
- package/neural-cifar-eval/__pycache__/fetch_cifar.cpython-310.pyc +0 -0
- package/neural-cifar-eval/__pycache__/ops_eval.cpython-310.pyc +0 -0
- package/neural-cifar-eval/__pycache__/run_eval.cpython-310.pyc +0 -0
- package/neural-pendulum-eval/__pycache__/ops_eval.cpython-310.pyc +0 -0
- package/neural-pendulum-eval/__pycache__/run_eval.cpython-310.pyc +0 -0
|
@@ -0,0 +1,406 @@
|
|
|
1
|
+
"""SAC + 2x256 tanh MLP + sin/cos H=4 observation, angle-only simulator.
|
|
2
|
+
|
|
3
|
+
Experiments:
|
|
4
|
+
--init rand : theta_i ~ U(-.05,.05), omega_i ~ U(-.02,.02), no bias
|
|
5
|
+
--init fixed : training reset is exactly theta=[0.013,-0.008,0.021], omega=0
|
|
6
|
+
|
|
7
|
+
32 vectorised environments by default, replay buffer, auto entropy tuning,
|
|
8
|
+
shared-trunk twin Q, UTD = 0.25 (8 gradient steps per 32-env vector step).
|
|
9
|
+
The simulator exposes angles only; observation, reward, termination, success
|
|
10
|
+
and t_can all live in trainer-side code (obs.py / task.py).
|
|
11
|
+
|
|
12
|
+
TensorBoard scalars (tb/<exp>/):
|
|
13
|
+
train/episode_return, train/episode_length, train/t_can, train/t_can_max,
|
|
14
|
+
train/success_rate, train/ever_up_frac, train/first_up_time, train/up_frac,
|
|
15
|
+
train/action_abs_mean, train/replay_size, train/steps_per_s, train/alpha,
|
|
16
|
+
train/entropy, train/q_mean, train/q_target_mean,
|
|
17
|
+
loss/actor, loss/critic, loss/alpha, loss/policy, loss/value, loss/entropy,
|
|
18
|
+
eval/t_can, eval/success, eval/ep_return, eval/ever_up, eval/final_ang_err,
|
|
19
|
+
eval/first_up_t, eval/up_frac, eval_final/*, time/elapsed_min
|
|
20
|
+
"""
|
|
21
|
+
from __future__ import annotations
|
|
22
|
+
|
|
23
|
+
import argparse
|
|
24
|
+
import copy
|
|
25
|
+
import json
|
|
26
|
+
import os
|
|
27
|
+
import time
|
|
28
|
+
|
|
29
|
+
import numpy as np
|
|
30
|
+
import torch
|
|
31
|
+
import torch.nn as nn
|
|
32
|
+
import torch.nn.functional as F
|
|
33
|
+
from torch.utils.tensorboard import SummaryWriter
|
|
34
|
+
|
|
35
|
+
torch.set_num_threads(int(os.environ.get("TORCH_THREADS", "4")))
|
|
36
|
+
|
|
37
|
+
from evaluate import evaluate, evaluate_starts, make_sac_policy
|
|
38
|
+
from models import SquashedGaussianActor, TwinQ
|
|
39
|
+
from obs import OBS_MODES, obs_spec
|
|
40
|
+
from sim import EPISODE_STEPS, Params, TorchVecSim
|
|
41
|
+
from task import (TORCH_REWARD_NOTE, TorchTask, get_reward_preset, make_init,
|
|
42
|
+
sample_train_init)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class Replay:
|
|
46
|
+
def __init__(self, cap, dim, act_dim=2):
|
|
47
|
+
self.cap = int(cap)
|
|
48
|
+
self.obs = np.zeros((cap, dim), np.float32)
|
|
49
|
+
self.act = np.zeros((cap, act_dim), np.float32)
|
|
50
|
+
self.rew = np.zeros((cap, 1), np.float32)
|
|
51
|
+
self.nobs = np.zeros((cap, dim), np.float32)
|
|
52
|
+
self.done = np.zeros((cap, 1), np.float32)
|
|
53
|
+
self.ptr = 0
|
|
54
|
+
self.size = 0
|
|
55
|
+
|
|
56
|
+
def push(self, obs, act, rew, nobs, done):
|
|
57
|
+
n = obs.shape[0]
|
|
58
|
+
idx = (self.ptr + np.arange(n)) % self.cap
|
|
59
|
+
self.obs[idx] = obs
|
|
60
|
+
self.act[idx] = act
|
|
61
|
+
self.rew[idx] = np.asarray(rew).reshape(n, 1)
|
|
62
|
+
self.nobs[idx] = nobs
|
|
63
|
+
self.done[idx] = np.asarray(done).reshape(n, 1)
|
|
64
|
+
self.ptr = (self.ptr + n) % self.cap
|
|
65
|
+
self.size = min(self.cap, self.size + n)
|
|
66
|
+
|
|
67
|
+
def sample(self, batch):
|
|
68
|
+
idx = np.random.randint(0, self.size, size=batch)
|
|
69
|
+
return (torch.from_numpy(self.obs[idx]),
|
|
70
|
+
torch.from_numpy(self.act[idx]),
|
|
71
|
+
torch.from_numpy(self.rew[idx]),
|
|
72
|
+
torch.from_numpy(self.nobs[idx]),
|
|
73
|
+
torch.from_numpy(self.done[idx]))
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def soft_update(tgt, src, tau):
|
|
77
|
+
with torch.no_grad():
|
|
78
|
+
for a, b in zip(tgt.parameters(), src.parameters()):
|
|
79
|
+
a.mul_(1 - tau).add_(tau * b)
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def sac_update(actor, q, qt, act_opt, q_opt, log_alpha, alpha_opt, replay, args):
|
|
83
|
+
o, ac, r, no, d = replay.sample(args.batch)
|
|
84
|
+
with torch.no_grad():
|
|
85
|
+
na, nlogp = actor.sample(no)
|
|
86
|
+
q_next = qt(no, na).min(dim=1).values
|
|
87
|
+
target = r.squeeze(-1) + args.gamma * (1.0 - d.squeeze(-1)) * (
|
|
88
|
+
q_next - log_alpha.exp() * nlogp)
|
|
89
|
+
q_pred = q(o, ac)
|
|
90
|
+
critic_loss = 0.5 * F.mse_loss(q_pred, target.unsqueeze(1).expand(-1, 2))
|
|
91
|
+
q_opt.zero_grad()
|
|
92
|
+
critic_loss.backward()
|
|
93
|
+
nn.utils.clip_grad_norm_(q.parameters(), 10.0)
|
|
94
|
+
q_opt.step()
|
|
95
|
+
|
|
96
|
+
for p_ in q.parameters():
|
|
97
|
+
p_.requires_grad_(False)
|
|
98
|
+
pa, logp = actor.sample(o)
|
|
99
|
+
q_pi = q(o, pa).min(dim=1).values
|
|
100
|
+
actor_loss = (log_alpha.exp().detach() * logp - q_pi).mean()
|
|
101
|
+
act_opt.zero_grad()
|
|
102
|
+
actor_loss.backward()
|
|
103
|
+
nn.utils.clip_grad_norm_(actor.parameters(), 10.0)
|
|
104
|
+
act_opt.step()
|
|
105
|
+
for p_ in q.parameters():
|
|
106
|
+
p_.requires_grad_(True)
|
|
107
|
+
|
|
108
|
+
alpha_loss = -(log_alpha.exp() * (logp.detach() + args.target_entropy)).mean()
|
|
109
|
+
alpha_opt.zero_grad()
|
|
110
|
+
alpha_loss.backward()
|
|
111
|
+
alpha_opt.step()
|
|
112
|
+
|
|
113
|
+
soft_update(qt, q, args.tau)
|
|
114
|
+
return dict(actor=float(actor_loss), critic=float(critic_loss),
|
|
115
|
+
alpha=float(alpha_loss),
|
|
116
|
+
q=float(q_pred.mean()), qt=float(target.mean()),
|
|
117
|
+
entropy=float(-logp.detach().mean()))
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def main():
|
|
121
|
+
ap = argparse.ArgumentParser()
|
|
122
|
+
ap.add_argument("--exp", type=str, required=True)
|
|
123
|
+
ap.add_argument("--seed", type=int, default=0)
|
|
124
|
+
ap.add_argument("--init", type=str, default="rand",
|
|
125
|
+
choices=["rand", "fixed", "mix"],
|
|
126
|
+
help="rand=down noise, fixed=eval state, "
|
|
127
|
+
"mix=curriculum with up_frac near upright")
|
|
128
|
+
ap.add_argument("--up-frac", type=float, default=0.5,
|
|
129
|
+
help="fraction of resets near upright when --init mix")
|
|
130
|
+
ap.add_argument("--obs", type=str, default="full",
|
|
131
|
+
choices=sorted(OBS_MODES),
|
|
132
|
+
help="observation mode (see obs.py)")
|
|
133
|
+
ap.add_argument("--reward", type=str, default="base",
|
|
134
|
+
choices=["base", "sparse", "energy"],
|
|
135
|
+
help="reward preset (see task.py REWARD_PRESETS)")
|
|
136
|
+
ap.add_argument("--envs", type=int, default=32)
|
|
137
|
+
ap.add_argument("--minutes", type=float, default=30.0)
|
|
138
|
+
ap.add_argument("--steps", type=int, default=10 ** 12)
|
|
139
|
+
ap.add_argument("--hidden", type=int, default=256)
|
|
140
|
+
ap.add_argument("--depth", type=int, default=2)
|
|
141
|
+
ap.add_argument("--lr", type=float, default=3e-4)
|
|
142
|
+
ap.add_argument("--gamma", type=float, default=0.99)
|
|
143
|
+
ap.add_argument("--tau", type=float, default=0.005)
|
|
144
|
+
ap.add_argument("--batch", type=int, default=256)
|
|
145
|
+
ap.add_argument("--warmup", type=int, default=5000)
|
|
146
|
+
ap.add_argument("--buffer", type=int, default=300000)
|
|
147
|
+
ap.add_argument("--utd", type=float, default=0.25,
|
|
148
|
+
help="gradient steps per environment step")
|
|
149
|
+
ap.add_argument("--target-entropy", type=float, default=-2.0)
|
|
150
|
+
ap.add_argument("--alpha-init", type=float, default=0.2)
|
|
151
|
+
ap.add_argument("--episode-steps", type=int, default=EPISODE_STEPS)
|
|
152
|
+
ap.add_argument("--eval-every", type=int, default=20000)
|
|
153
|
+
ap.add_argument("--eval-start", type=int, default=20000)
|
|
154
|
+
ap.add_argument("--tb-root", type=str, default="tb")
|
|
155
|
+
ap.add_argument("--ckpt-dir", type=str, default="checkpoints")
|
|
156
|
+
ap.add_argument("--out", type=str, default="results")
|
|
157
|
+
ap.add_argument("--tau-max", type=float, default=18.0)
|
|
158
|
+
ap.add_argument("--damping", type=float, default=0.01)
|
|
159
|
+
ap.add_argument("--log-every", type=int, default=10,
|
|
160
|
+
help="log every N vector steps")
|
|
161
|
+
ap.add_argument("--robust-starts", type=int, default=8,
|
|
162
|
+
help="deterministic extra rollouts from the training "
|
|
163
|
+
"reset distribution at the end (0 disables)")
|
|
164
|
+
args = ap.parse_args()
|
|
165
|
+
|
|
166
|
+
torch.manual_seed(args.seed)
|
|
167
|
+
np.random.seed(args.seed)
|
|
168
|
+
for d in (args.tb_root, args.ckpt_dir, args.out):
|
|
169
|
+
os.makedirs(d, exist_ok=True)
|
|
170
|
+
exp = args.exp
|
|
171
|
+
writer = SummaryWriter(os.path.join(args.tb_root, exp))
|
|
172
|
+
p = Params(tau_max=args.tau_max, b=args.damping)
|
|
173
|
+
spec = obs_spec(args.obs)
|
|
174
|
+
obs_dim = spec["dim"]
|
|
175
|
+
reward_w = get_reward_preset(args.reward)
|
|
176
|
+
sim = TorchVecSim(p, n=args.envs, episode_steps=args.episode_steps)
|
|
177
|
+
task = TorchTask(p, n=args.envs, reward_w=reward_w, obs_mode=args.obs)
|
|
178
|
+
gen = torch.Generator().manual_seed(args.seed * 1000 + 11)
|
|
179
|
+
updates_per_vec = max(1, int(round(args.utd * args.envs)))
|
|
180
|
+
eff_utd = updates_per_vec / args.envs
|
|
181
|
+
|
|
182
|
+
init_meta = dict(mode=args.init,
|
|
183
|
+
training=("theta_i ~ U(-0.05,0.05), omega_i ~ U(-0.02,0.02); no bias"
|
|
184
|
+
if args.init == "rand" else
|
|
185
|
+
("theta=[0.013,-0.008,0.021], omega=0 (same as eval)"
|
|
186
|
+
if args.init == "fixed" else
|
|
187
|
+
f"curriculum: {args.up_frac:.2f} near upright "
|
|
188
|
+
f"(pi + U(-0.05,0.05)), rest near down")),
|
|
189
|
+
eval="theta=[0.013,-0.008,0.021], omega=0 (fixed, no noise)")
|
|
190
|
+
meta = dict(algo="sac", exp=exp, seed=args.seed, envs=args.envs,
|
|
191
|
+
obs=spec, obs_dim=obs_dim, init=init_meta,
|
|
192
|
+
reward=reward_w, reward_preset=args.reward,
|
|
193
|
+
reward_note=TORCH_REWARD_NOTE,
|
|
194
|
+
updates_per_vector=updates_per_vec, effective_utd=eff_utd,
|
|
195
|
+
args=vars(args))
|
|
196
|
+
writer.add_text("config", json.dumps(meta, indent=2), 0)
|
|
197
|
+
print(json.dumps(meta, indent=2), flush=True)
|
|
198
|
+
|
|
199
|
+
actor = SquashedGaussianActor(obs_dim, 2, hidden=args.hidden, depth=args.depth)
|
|
200
|
+
q = TwinQ(obs_dim, 2, hidden=args.hidden, depth=args.depth)
|
|
201
|
+
qt = copy.deepcopy(q)
|
|
202
|
+
act_opt = torch.optim.Adam(actor.parameters(), lr=args.lr)
|
|
203
|
+
q_opt = torch.optim.Adam(q.parameters(), lr=args.lr)
|
|
204
|
+
log_alpha = torch.log(torch.tensor(args.alpha_init)).clone().detach().requires_grad_(True)
|
|
205
|
+
alpha_opt = torch.optim.Adam([log_alpha], lr=args.lr)
|
|
206
|
+
|
|
207
|
+
replay = Replay(args.buffer, obs_dim, 2)
|
|
208
|
+
th, om = make_init(args.envs, args.init, gen, up_frac=args.up_frac)
|
|
209
|
+
theta = sim.reset(th, om)
|
|
210
|
+
obs = task.reset(theta)
|
|
211
|
+
|
|
212
|
+
t_start = time.time()
|
|
213
|
+
total = 0
|
|
214
|
+
n_updates = 0
|
|
215
|
+
vec_steps = 0
|
|
216
|
+
next_eval = args.eval_start
|
|
217
|
+
best_t_can = -1.0
|
|
218
|
+
best_eval = None
|
|
219
|
+
best_path = None
|
|
220
|
+
last_path = None
|
|
221
|
+
eval_curve = []
|
|
222
|
+
last = dict(actor=float("nan"), critic=float("nan"), alpha=float("nan"),
|
|
223
|
+
q=float("nan"), qt=float("nan"), entropy=float("nan"))
|
|
224
|
+
ep = {k: [] for k in ("return", "t_can", "ever_up", "first_up", "up_frac", "length")}
|
|
225
|
+
|
|
226
|
+
def save_ckpt(path, ev):
|
|
227
|
+
torch.save(dict(model=actor.state_dict(), q=q.state_dict(),
|
|
228
|
+
log_alpha=log_alpha.detach(), obs=spec,
|
|
229
|
+
obs_dim=obs_dim, init=init_meta, reward=reward_w,
|
|
230
|
+
reward_preset=args.reward,
|
|
231
|
+
args=vars(args), global_steps=total,
|
|
232
|
+
n_updates=n_updates, eval=ev, algo="sac"),
|
|
233
|
+
os.path.join(args.ckpt_dir, path))
|
|
234
|
+
|
|
235
|
+
def log_eval(ev):
|
|
236
|
+
nonlocal best_t_can, best_eval, best_path, last_path
|
|
237
|
+
eval_curve.append(dict(global_steps=total, n_updates=n_updates, **ev))
|
|
238
|
+
for k, v in ev.items():
|
|
239
|
+
if isinstance(v, (int, float)):
|
|
240
|
+
writer.add_scalar(f"eval/{k}", v, total)
|
|
241
|
+
if (ev["t_can"] > best_t_can
|
|
242
|
+
or (ev["t_can"] == best_t_can and best_eval is not None
|
|
243
|
+
and ev["ep_return"] > best_eval["ep_return"])):
|
|
244
|
+
best_t_can = ev["t_can"]
|
|
245
|
+
best_eval = dict(ev)
|
|
246
|
+
best_path = exp + "_best.pt"
|
|
247
|
+
save_ckpt(best_path, ev)
|
|
248
|
+
last_path = exp + "_last.pt"
|
|
249
|
+
save_ckpt(last_path, ev)
|
|
250
|
+
|
|
251
|
+
while total < args.steps:
|
|
252
|
+
if (time.time() - t_start) / 60.0 > args.minutes:
|
|
253
|
+
print("time budget reached", flush=True)
|
|
254
|
+
break
|
|
255
|
+
if replay.size < args.warmup:
|
|
256
|
+
u = torch.rand(args.envs, 2) * 2.0 - 1.0
|
|
257
|
+
else:
|
|
258
|
+
with torch.no_grad():
|
|
259
|
+
u, _ = actor.sample(obs)
|
|
260
|
+
u = torch.clamp(u, -1.0, 1.0)
|
|
261
|
+
theta = sim.step(u)
|
|
262
|
+
nobs, rew = task.step(theta, u)
|
|
263
|
+
done = sim.steps >= sim.episode_steps
|
|
264
|
+
replay.push(obs.detach().cpu().numpy(), u.detach().cpu().numpy(),
|
|
265
|
+
rew.detach().cpu().numpy(), nobs.detach().cpu().numpy(),
|
|
266
|
+
done.detach().cpu().numpy())
|
|
267
|
+
if bool(done.any()):
|
|
268
|
+
idx = done.nonzero().flatten()
|
|
269
|
+
summ = task.summary(idx)
|
|
270
|
+
ep["return"] += summ["ep_return"].tolist()
|
|
271
|
+
ep["t_can"] += summ["t_can"].tolist()
|
|
272
|
+
ep["ever_up"] += summ["ever_up"].tolist()
|
|
273
|
+
ep["first_up"] += summ["first_up_t"].tolist()
|
|
274
|
+
ep["up_frac"] += summ["up_frac"].tolist()
|
|
275
|
+
ep["length"] += summ["length"].tolist()
|
|
276
|
+
th, om = make_init(idx.numel(), args.init, gen, up_frac=args.up_frac)
|
|
277
|
+
reset_theta = sim.reset(th, om, idx)
|
|
278
|
+
task.reset(reset_theta, idx)
|
|
279
|
+
nobs = task.obs
|
|
280
|
+
obs = nobs
|
|
281
|
+
total += args.envs
|
|
282
|
+
vec_steps += 1
|
|
283
|
+
|
|
284
|
+
if replay.size >= args.warmup:
|
|
285
|
+
for _ in range(updates_per_vec):
|
|
286
|
+
last = sac_update(actor, q, qt, act_opt, q_opt, log_alpha,
|
|
287
|
+
alpha_opt, replay, args)
|
|
288
|
+
n_updates += 1
|
|
289
|
+
|
|
290
|
+
if vec_steps % args.log_every == 0:
|
|
291
|
+
elapsed = time.time() - t_start
|
|
292
|
+
if ep["return"]:
|
|
293
|
+
writer.add_scalar("train/episode_return",
|
|
294
|
+
float(np.mean(ep["return"])), total)
|
|
295
|
+
writer.add_scalar("train/episode_length",
|
|
296
|
+
float(np.mean(ep["length"])), total)
|
|
297
|
+
writer.add_scalar("train/t_can", float(np.mean(ep["t_can"])), total)
|
|
298
|
+
writer.add_scalar("train/t_can_max", float(np.max(ep["t_can"])), total)
|
|
299
|
+
writer.add_scalar("train/success_rate",
|
|
300
|
+
float(np.mean(np.asarray(ep["t_can"]) >= 10.0)), total)
|
|
301
|
+
writer.add_scalar("train/ever_up_frac",
|
|
302
|
+
float(np.mean(ep["ever_up"])), total)
|
|
303
|
+
writer.add_scalar("train/up_frac",
|
|
304
|
+
float(np.mean(ep["up_frac"])), total)
|
|
305
|
+
fu = [x for x in ep["first_up"] if x > 0]
|
|
306
|
+
if fu:
|
|
307
|
+
writer.add_scalar("train/first_up_time", float(np.mean(fu)), total)
|
|
308
|
+
ep = {k: [] for k in ep}
|
|
309
|
+
writer.add_scalar("train/replay_size", replay.size, total)
|
|
310
|
+
writer.add_scalar("train/steps_per_s", total / max(elapsed, 1e-9), total)
|
|
311
|
+
writer.add_scalar("train/alpha", float(log_alpha.detach().exp()), total)
|
|
312
|
+
writer.add_scalar("train/action_abs_mean", float(u.abs().mean()), total)
|
|
313
|
+
writer.add_scalar("train/action_std", float(u.std()), total)
|
|
314
|
+
writer.add_scalar("loss/actor", last["actor"], total)
|
|
315
|
+
writer.add_scalar("loss/critic", last["critic"], total)
|
|
316
|
+
writer.add_scalar("loss/alpha", last["alpha"], total)
|
|
317
|
+
writer.add_scalar("loss/policy", last["actor"], total)
|
|
318
|
+
writer.add_scalar("loss/value", last["critic"], total)
|
|
319
|
+
writer.add_scalar("loss/entropy", last["entropy"], total)
|
|
320
|
+
writer.add_scalar("train/entropy", last["entropy"], total)
|
|
321
|
+
writer.add_scalar("train/q_mean", last["q"], total)
|
|
322
|
+
writer.add_scalar("train/q_target_mean", last["qt"], total)
|
|
323
|
+
writer.add_scalar("train/global_steps", total, total)
|
|
324
|
+
writer.add_scalar("time/elapsed_min", elapsed / 60.0, total)
|
|
325
|
+
writer.flush()
|
|
326
|
+
|
|
327
|
+
if total >= next_eval:
|
|
328
|
+
next_eval += args.eval_every
|
|
329
|
+
ev = evaluate(make_sac_policy(actor), p, args.episode_steps,
|
|
330
|
+
reward_w=reward_w, obs_mode=args.obs)
|
|
331
|
+
log_eval(ev)
|
|
332
|
+
print(f"steps {total/1e3:7.1f}k upd {n_updates:6d} "
|
|
333
|
+
f"EVAL tc {ev['t_can']:6.2f} succ {ev['success']:.0f} "
|
|
334
|
+
f"everup {ev['ever_up']:.0f} angerr {ev['final_ang_err']:.3f} "
|
|
335
|
+
f"ret {ev['ep_return']:8.1f} q {last['q']:8.2f} "
|
|
336
|
+
f"a {float(log_alpha.detach().exp()):.2f} "
|
|
337
|
+
f"({time.time()-t_start:.0f}s)", flush=True)
|
|
338
|
+
writer.flush()
|
|
339
|
+
|
|
340
|
+
ev_final = evaluate(make_sac_policy(actor), p, args.episode_steps,
|
|
341
|
+
reward_w=reward_w, obs_mode=args.obs)
|
|
342
|
+
eval_curve.append(dict(global_steps=total, n_updates=n_updates, **ev_final))
|
|
343
|
+
for k, v in ev_final.items():
|
|
344
|
+
if isinstance(v, (int, float)):
|
|
345
|
+
writer.add_scalar(f"eval_final/{k}", v, total)
|
|
346
|
+
final_path = exp + "_final.pt"
|
|
347
|
+
save_ckpt(final_path, ev_final)
|
|
348
|
+
last_path = exp + "_last.pt"
|
|
349
|
+
save_ckpt(last_path, ev_final)
|
|
350
|
+
|
|
351
|
+
# Extra deterministic rollouts from the training reset distribution. This
|
|
352
|
+
# is NOT the mandated fixed-state eval; it checks generalisation.
|
|
353
|
+
rob = None
|
|
354
|
+
rob_best = None
|
|
355
|
+
if args.robust_starts > 0:
|
|
356
|
+
g = torch.Generator().manual_seed(args.seed * 1000 + 123)
|
|
357
|
+
th_r, om_r = sample_train_init(args.robust_starts, gen=g)
|
|
358
|
+
starts = list(zip(th_r.numpy(), om_r.numpy()))
|
|
359
|
+
rob = evaluate_starts(make_sac_policy(actor), p, starts,
|
|
360
|
+
args.episode_steps, reward_w=reward_w,
|
|
361
|
+
obs_mode=args.obs)
|
|
362
|
+
for k, v in rob.items():
|
|
363
|
+
if isinstance(v, (int, float)):
|
|
364
|
+
writer.add_scalar(f"robust/{k}", v, total)
|
|
365
|
+
if best_path is not None:
|
|
366
|
+
ck = torch.load(os.path.join(args.ckpt_dir, best_path),
|
|
367
|
+
map_location="cpu")
|
|
368
|
+
actor_best = SquashedGaussianActor(obs_dim, 2, hidden=args.hidden,
|
|
369
|
+
depth=args.depth)
|
|
370
|
+
actor_best.load_state_dict(ck["model"])
|
|
371
|
+
rob_best = evaluate_starts(make_sac_policy(actor_best), p, starts,
|
|
372
|
+
args.episode_steps, reward_w=reward_w,
|
|
373
|
+
obs_mode=args.obs)
|
|
374
|
+
for k, v in rob_best.items():
|
|
375
|
+
if isinstance(v, (int, float)):
|
|
376
|
+
writer.add_scalar(f"robust_best/{k}", v, total)
|
|
377
|
+
|
|
378
|
+
wall = time.time() - t_start
|
|
379
|
+
out = dict(algo="sac", exp=exp, seed=args.seed, envs=args.envs,
|
|
380
|
+
obs=spec, obs_dim=obs_dim, init=init_meta,
|
|
381
|
+
reward=reward_w, reward_preset=args.reward,
|
|
382
|
+
reward_note=TORCH_REWARD_NOTE,
|
|
383
|
+
args=vars(args), updates_per_vector=updates_per_vec,
|
|
384
|
+
effective_utd=eff_utd, minutes_budget=args.minutes,
|
|
385
|
+
global_steps=total, gradient_steps=n_updates,
|
|
386
|
+
wall_time_s=wall, steps_per_s=total / max(wall, 1e-9),
|
|
387
|
+
updates_per_s=n_updates / max(wall, 1e-9),
|
|
388
|
+
final_eval=ev_final, best_eval=best_eval, best_t_can=best_t_can,
|
|
389
|
+
eval_curve=eval_curve, robust=rob, robust_best=rob_best,
|
|
390
|
+
final_train=last,
|
|
391
|
+
checkpoints=dict(best=best_path, last=last_path,
|
|
392
|
+
final=final_path))
|
|
393
|
+
with open(os.path.join(args.out, exp + ".json"), "w") as f:
|
|
394
|
+
json.dump(out, f, indent=2)
|
|
395
|
+
writer.flush()
|
|
396
|
+
writer.close()
|
|
397
|
+
print("DONE " + json.dumps(dict(
|
|
398
|
+
exp=exp, init=args.init, seed=args.seed, envs=args.envs,
|
|
399
|
+
global_steps=total, gradient_steps=n_updates, wall_s=round(wall, 1),
|
|
400
|
+
steps_per_s=round(out["steps_per_s"], 1),
|
|
401
|
+
eval_t_can=ev_final["t_can"], best_t_can=best_t_can,
|
|
402
|
+
eval_return=ev_final["ep_return"])), flush=True)
|
|
403
|
+
|
|
404
|
+
|
|
405
|
+
if __name__ == "__main__":
|
|
406
|
+
main()
|
|
@@ -65,7 +65,13 @@ static void deriv(const State& s, const std::array<double,N>& tau, State& out) {
|
|
|
65
65
|
std::array<double,N> C{}, G{}, Q{}, rhs{};
|
|
66
66
|
for(int i=0;i<N;i++){
|
|
67
67
|
double ci=0;
|
|
68
|
-
for(int j=0;j<N;j++) if(j!=i)
|
|
68
|
+
for(int j=0;j<N;j++) if(j!=i) {
|
|
69
|
+
// Coriolis coefficient: d(M_ij)/d(theta_j) = LEN[min] * GCOEF[max] * sin(...);
|
|
70
|
+
// M_ij itself already contains cos(...), so multiplying M_ij by sin would
|
|
71
|
+
// add an extra cos factor and inject energy at high speed (NaN after ~7 s).
|
|
72
|
+
const int a=std::min(i,j), b=std::max(i,j);
|
|
73
|
+
ci += LEN[a] * GCOEF[b] * std::sin(s.theta[i]-s.theta[j]) * s.omega[j]*s.omega[j];
|
|
74
|
+
}
|
|
69
75
|
C[i]=ci;
|
|
70
76
|
G[i]=GRAV*std::sin(s.theta[i])*GCOEF[i];
|
|
71
77
|
}
|
|
@@ -101,7 +107,12 @@ int main(int argc, char** argv) {
|
|
|
101
107
|
// parse torque array
|
|
102
108
|
double u1=0,u3=0; auto p=line.find('['); if(p!=std::string::npos){ char* end=nullptr; u1=std::strtod(line.c_str()+p+1,&end); if(end){ const char* q=end; while(*q && (*q==','||*q==' ')) q++; u3=std::strtod(q,&end); } }
|
|
103
109
|
u1=std::max(-1.0,std::min(1.0,u1)); u3=std::max(-1.0,std::min(1.0,u3));
|
|
104
|
-
|
|
110
|
+
// Joint 3 is an internal motor between link 2 and link 3. In absolute
|
|
111
|
+
// coordinates its generalized force is (tau1, -tau3, tau3): the reaction
|
|
112
|
+
// on link 2 is part of the same joint torque. (tau1, 0, tau3) would be a
|
|
113
|
+
// reactionless external torque on link 3 and would contradict joint 2
|
|
114
|
+
// being passive.
|
|
115
|
+
std::array<double,N> tau{{u1*TAU_MAX,-u3*TAU_MAX,u3*TAU_MAX}};
|
|
105
116
|
// one control step with 4 RK4 substeps
|
|
106
117
|
double h=DT/SUB;
|
|
107
118
|
for(int sub=0;sub<SUB;sub++){
|
|
Binary file
|
|
Binary file
|
package/lib/index.js
CHANGED
|
@@ -887,6 +887,8 @@ function bwrapArgs(workspace, rules, runtime) {
|
|
|
887
887
|
args.push("--setenv", "LC_ALL", "C.UTF-8");
|
|
888
888
|
args.push("--setenv", "TERM", "dumb");
|
|
889
889
|
args.push("--setenv", "UV_USE_IO_URING", "0");
|
|
890
|
+
args.push("--setenv", "PIP_DISABLE_PIP_VERSION_CHECK", "1");
|
|
891
|
+
args.push("--setenv", "PIP_NO_INPUT", "1");
|
|
890
892
|
return args;
|
|
891
893
|
}
|
|
892
894
|
/**
|
|
@@ -2480,10 +2482,12 @@ const NEURAL_PENDULUM_DEFINITION = {
|
|
|
2480
2482
|
"",
|
|
2481
2483
|
"请先完整阅读 TASK.md;它是权威规则。硬约束:仅 C++17 标准库、仅 CPU、fp32;不使用 Python/OpenCL/CUDA/BLAS/Eigen/第三方库;必须从提交源码构建出 build/nn_task 与 build/control。",
|
|
2482
2484
|
"",
|
|
2483
|
-
"环境细节:joint1 与 joint3 驱动,joint2 被动;控制频率 100 Hz;每回合最多 3000 步(30 s),达到上限后继续 step 返回 {\"error\":\"episode_finished\"},必须 reset
|
|
2485
|
+
"环境细节:joint1 与 joint3 驱动,joint2 被动;控制频率 100 Hz;每回合最多 3000 步(30 s),达到上限后继续 step 返回 {\"error\":\"episode_finished\"},必须 reset。theta=0 表示竖直向下、theta=pi 表示竖直向上;观测 a_i=wrap(theta_i-pi),即三个关节角度(弧度,不除 pi)。环境不返回 reward/loss/cost/done;不提供杆长、质量、惯量、角速度或 tip。训练 reset 带小范围随机初始角度和角速度;评测使用固定初始状态,无随机扰动。",
|
|
2484
2486
|
"",
|
|
2485
2487
|
"优化目标:从接近全向下起摆,使三根杆全部进入 0.05 rad 的竖直向上容差,并尽可能长时间保持;评测指标为 t_can。损失函数、奖励、优化器、网络结构和训练方法由你自行设计,不规定任何训练方案。",
|
|
2486
2488
|
"",
|
|
2489
|
+
"注意:评测侧用 PyTorch 计算算子参考值,但工作区/候选运行时不提供 PyTorch 或 numpy,也没有网络,无法 pip 安装;开发阶段请用 C++ 或 Python 标准库/独立 double 实现做数值对照。",
|
|
2490
|
+
"",
|
|
2487
2491
|
"评测会从干净副本重新构建提交源码,先运行与 Neural-FP32/Neural-CIFAR 相同的算子数值评测:评测侧用 PyTorch 在相同数据上计算参考值,逐操作比较加权平均相对误差并对照阈值,任一算子不可执行、崩溃、超时、输出缺失或误差超阈值即判为基础设施失败;然后在固定初始状态上驱动 build/control,统计连续全向上保持时间 t_can。不要把任何未提交的二进制、临时构建产物或外部进程当作交付依赖。",
|
|
2488
2492
|
"使用宿主工具 eval_submit 提交当前源码快照,使用 eval_test 请求宿主评测最新提交。提交快照、事件、测试结果和最终分数全部保存在工作区之外的宿主目录;不要尝试读取或修改这些数据,也不要依赖任何工作区内的测试结果文件。最终评测使用最新提交快照,按宿主事件时间积分计算分数。",
|
|
2489
2493
|
"",
|
|
Binary file
|
package/native/supervisor.c
CHANGED
|
@@ -3,8 +3,9 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Installs a seccomp user-notification filter before exec'ing bwrap and enforces
|
|
5
5
|
* a path policy on file-access and network syscalls. Normal syscalls are not
|
|
6
|
-
* logged
|
|
7
|
-
*
|
|
6
|
+
* logged. File-policy violations are audited and kill the process group;
|
|
7
|
+
* AF_INET/AF_INET6 connect/bind/sendto are audited and soft-denied with EPERM
|
|
8
|
+
* so local software checks (pip version probe, NSS fallback) can continue.
|
|
8
9
|
*
|
|
9
10
|
* Usage:
|
|
10
11
|
* dsh-eval-supervisor --policy <policy-file> --audit <audit-file> -- <program> [args...]
|
|
@@ -87,6 +88,7 @@ static int g_dev_count;
|
|
|
87
88
|
static Rule g_rules[MAX_RULES];
|
|
88
89
|
static int g_rule_count;
|
|
89
90
|
static int g_audit_fd = -1;
|
|
91
|
+
static int g_soft_deny; /* network-family denial: EPERM, do not kill the group */
|
|
90
92
|
static volatile sig_atomic_t g_stop;
|
|
91
93
|
/* The sandbox mounted a private tmpfs at /tmp (workspace under host /tmp). */
|
|
92
94
|
static int g_tmpfs_tmp;
|
|
@@ -555,6 +557,7 @@ static int check_notification(pid_t pid, const struct seccomp_notif *req, int *w
|
|
|
555
557
|
|
|
556
558
|
path_out[0] = '\0';
|
|
557
559
|
g_current_pid = pid;
|
|
560
|
+
g_soft_deny = 0;
|
|
558
561
|
if (nr == SYS_execve || nr == SYS_execveat) {
|
|
559
562
|
char executable[MAX_PATH];
|
|
560
563
|
executable[0] = '\0';
|
|
@@ -567,20 +570,43 @@ static int check_notification(pid_t pid, const struct seccomp_notif *req, int *w
|
|
|
567
570
|
return 1;
|
|
568
571
|
}
|
|
569
572
|
if (nr == SYS_socket) {
|
|
570
|
-
|
|
573
|
+
/* Creating a socket does not transmit anything. bwrap already put the
|
|
574
|
+
* command in a private network namespace; tools such as pip create an
|
|
575
|
+
* AF_INET socket for local/version checks without connecting. All the
|
|
576
|
+
* operations that can actually reach a peer (connect/bind/listen/accept/
|
|
577
|
+
* sendto/recvfrom) remain denied below. */
|
|
571
578
|
*write_access = 0;
|
|
572
579
|
snprintf(path_out, MAX_PATH, "<network:socket>");
|
|
573
|
-
|
|
574
|
-
* needed by NSS, Python and the dynamic loader. AF_INET/AF_INET6 and raw
|
|
575
|
-
* sockets are still blocked. */
|
|
576
|
-
if (domain == AF_UNIX || domain == AF_NETLINK) return 1;
|
|
577
|
-
return 0;
|
|
580
|
+
return 1;
|
|
578
581
|
}
|
|
579
582
|
if (is_network_syscall(nr)) {
|
|
580
583
|
*write_access = 0;
|
|
581
584
|
snprintf(path_out, MAX_PATH, "<network:%s>", syscall_name(nr));
|
|
582
|
-
/*
|
|
583
|
-
*
|
|
585
|
+
/* bwrap itself needs AF_UNIX bind/listen/connect while it sets up the
|
|
586
|
+
* namespace; only the target's traffic is checked. */
|
|
587
|
+
if (!g_command_started || (g_current_pid > 0 && is_bwrap(g_current_pid))) return 1;
|
|
588
|
+
if (nr == SYS_listen || nr == SYS_accept || nr == SYS_accept4 || nr == SYS_recvfrom) {
|
|
589
|
+
/* No peer can appear outside the private netns; these operate on an
|
|
590
|
+
* already-authorized local socket. */
|
|
591
|
+
return 1;
|
|
592
|
+
}
|
|
593
|
+
if (nr == SYS_connect || nr == SYS_bind || nr == SYS_sendto) {
|
|
594
|
+
unsigned long addr_ptr = nr == SYS_sendto ? (unsigned long)req->data.args[4] : a1;
|
|
595
|
+
unsigned short family = 0;
|
|
596
|
+
if (addr_ptr != 0 && read_remote(pid, addr_ptr, &family, sizeof(family)) == 0) {
|
|
597
|
+
if (family == AF_UNIX || family == AF_NETLINK) return 1;
|
|
598
|
+
if (family == AF_INET || family == AF_INET6) {
|
|
599
|
+
/* A version check or NSS probe may create the socket and attempt a
|
|
600
|
+
* connection; return EPERM instead of killing the whole group. The
|
|
601
|
+
* private netns is the hard boundary; this is the graceful denial. */
|
|
602
|
+
g_soft_deny = 1;
|
|
603
|
+
snprintf(path_out, MAX_PATH, "<network:%s:inet>", syscall_name(nr));
|
|
604
|
+
return 0;
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
g_soft_deny = 1;
|
|
608
|
+
return 0;
|
|
609
|
+
}
|
|
584
610
|
return 1;
|
|
585
611
|
}
|
|
586
612
|
if (nr == SYS_io_uring_setup || nr == SYS_ptrace || nr == SYS_open_by_handle_at) {
|
|
@@ -817,6 +843,12 @@ static void reply_loop(int fd) {
|
|
|
817
843
|
char syscall_buf[64];
|
|
818
844
|
snprintf(syscall_buf, sizeof(syscall_buf), "%s", syscall_name((long)req.data.nr));
|
|
819
845
|
audit_record((pid_t)req.pid, syscall_buf, path, write_access);
|
|
846
|
+
if (g_soft_deny) {
|
|
847
|
+
/* A software check (pip version probe, NSS lookup fallback) gets a
|
|
848
|
+
* normal EPERM and can continue; file-policy violations still kill. */
|
|
849
|
+
respond(fd, &req, 0);
|
|
850
|
+
continue;
|
|
851
|
+
}
|
|
820
852
|
/* io_uring is a soft denial: libuv probes it despite UV_USE_IO_URING=0
|
|
821
853
|
* and must receive EPERM and fall back to epoll instead of taking the
|
|
822
854
|
* whole command group down. No io_uring instance can exist, so the
|
|
Binary file
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-eval-guard",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.4",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Evaluation guard for DSH: workspace-only filesystem, no network/subagents, wall-clock limits, and task installation for meow-ai-arena and benchmark tasks.",
|
|
6
6
|
"license": "MIT",
|
|
@@ -48,7 +48,7 @@
|
|
|
48
48
|
"build": "tsdown && node scripts/build-native.mjs",
|
|
49
49
|
"build:native": "node scripts/build-native.mjs",
|
|
50
50
|
"prepare": "tsdown",
|
|
51
|
-
"test": "node test/simulate.ts && node test/scoring.ts && node test/jobs.ts",
|
|
51
|
+
"test": "node test/simulate.ts && node test/scoring.ts && node test/jobs.ts && node test/pendulum-sim.ts",
|
|
52
52
|
"typecheck": "tsc --noEmit -p tsconfig.json"
|
|
53
53
|
},
|
|
54
54
|
"dsh": {
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|