Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
23 changes: 23 additions & 0 deletions docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,29 @@ confirm the two sides talk to each other, not to train anything.
and starts stepping episodes.
- With `fpo`, the server prints a metrics table whose `rollout/reward` rises.

## What runs on it

PlugRL keeps the policy, the algorithm and the environment apart, so the
question worth answering is which combinations actually work. Below is every
combination of the two MLP policies and the two algorithms on four tasks, and
pi0.5 on LIBERO. The border and its label say what the experiments found. The
line under each clip is the training return of all three seeds, drawn on one
scale per column, so a flat line really is flat. Hover over a cell, or tap
it, to play it.

<div class="cov" data-src="/media/coverage/coverage.json"></div>

Each clip comes from the final checkpoint of the seed whose last ten
iterations were the median of three. That checkpoint was evaluated for five
episodes, and the clip is the episode with the median return, not the best
one. `dppo-policy · FPO` is missing because it cannot exist: FPO trains flow
policies only. The three pi0.5 clips all start from the same scene, the first
one the released policy solves, and the numbers under them come from
fifty-episode evaluations. pi0.5 falling to zero after one FPO iteration is a
defect of ours we are still tracking down, not a finding about FPO. The
scripts that made all of this are in
[figures/coverage](https://github.com/PlugRL/plugrl-server/tree/main/figures/coverage).

## A real VLA, end to end

<video src="/media/libero-base.mp4" autoplay loop muted playsinline controls
Expand Down
16 changes: 16 additions & 0 deletions docs/index.zh.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,22 @@ dummy 算法的 `learn` 是一个 sleep,不会移动任何权重。它用来
- env client 打印 server 元信息(策略名、动作形状)并开始跑 episode
- 用 `fpo` 时,server 的指标表里 `rollout/reward` 会上升

## 在它上面能跑什么

PlugRL 把策略、算法和环境拆开,所以真正值得回答的问题是:哪些组合真的能跑。下面是
两个 MLP 策略和两个算法在四个任务上的全部组合,外加 pi0.5 在 LIBERO 上的情况。边框
和上面的标签写的是实验得出的结论。每段视频下面那条线是三个种子的训练回报,同一列用同
一个纵轴,所以平的线就是真的没动。鼠标悬停或者点一下格子就能播放。

<div class="cov" data-src="/media/coverage/coverage.json"></div>

每段视频都取自三个种子里最后十轮回报居中的那个种子的最终检查点。这个检查点评估了五
个回合,放出来的是回报居中的那一回合,不是最好的那一回合。表里没有 `dppo-policy · FPO`,
因为这个组合不存在:FPO 只能训练流策略。pi0.5 的三段视频都从同一个场景开始,也就是原
版策略能完成的第一个场景;视频下面的数字来自 50 个回合的评估。pi0.5 一轮 FPO 后掉到
零,是我们平台的问题,还在排查,不是关于 FPO 的结论。生成这些内容的脚本在
[figures/coverage](https://github.com/PlugRL/plugrl-server/tree/main/figures/coverage)。

## 真实 VLA,端到端

<video src="/media/libero-base.mp4" autoplay loop muted playsinline controls
Expand Down
237 changes: 237 additions & 0 deletions docs/javascripts/coverage.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,237 @@
/* The coverage figure on the home page.
*
* Builds every <div class="cov" data-src="..."> from the JSON that
* plugrl-server's figures/coverage/data.py writes. Each cell shows a still;
* its clip loads on first use and plays on hover (mouse) or on tap / Enter.
* With reduced motion requested, hovering plays nothing.
*/
(function () {
"use strict";

const EXPERIMENTS = "https://github.com/PlugRL/plugrl-server/tree/main/experiments/";

const TEXT = {
en: {
status: {
learns: "learns",
rising: "runs · rising",
flat: "runs · no learning",
runs: "runs end to end",
collapses: "runs · collapses",
holds: "runs · intact",
reference: "as released",
},
legend: {
learns: "learned the task",
rising: "runs, return still rising",
flat: "runs, has not learned",
collapses: "runs, one iteration breaks it",
},
play: "▶",
speed: (s) => `${s}× speed`,
clip: (m) =>
`Episode ${m.episode} of ${m.of}: the median return (${m.return}). ` +
`All returns: ${m.returns.join(", ")}.`,
vlaClip: (m) => `This episode ${m.success ? "succeeds" : "fails"}.`,
of: "of",
successes: "successes in 50 episodes",
missing: "clip not recorded",
},
zh: {
status: {
learns: "学会",
rising: "跑通 · 在上升",
flat: "跑通 · 没学会",
runs: "端到端跑通",
collapses: "跑通 · 归零",
holds: "跑通 · 没被毁",
reference: "原版",
},
legend: {
learns: "学会了任务",
rising: "跑通,回报还在上升",
flat: "跑通,还没学会",
collapses: "跑通,一轮训练就把它毁了",
},
play: "▶",
speed: (s) => `${s} 倍速`,
clip: (m) =>
`第 ${m.episode} 个回合(共 ${m.of} 个),回报取中位数(${m.return})。` +
`全部回报:${m.returns.join("、")}。`,
vlaClip: (m) => `视频里这个回合${m.success ? "成功了" : "失败了"}。`,
of: "/",
successes: "50 个回合里的成功次数",
missing: "没有录像",
},
};

function el(tag, attrs, ...kids) {
const node = document.createElement(tag);
for (const [k, v] of Object.entries(attrs || {})) {
if (v === undefined || v === null || v === false) continue;
if (k === "text") node.textContent = v;
else node.setAttribute(k, v === true ? "" : v);
}
for (const kid of kids) if (kid) node.append(kid);
return node;
}

function svg(tag, attrs) {
const node = document.createElementNS("http://www.w3.org/2000/svg", tag);
for (const [k, v] of Object.entries(attrs)) node.setAttribute(k, v);
return node;
}

function fmtSpeed(s) {
return s >= 1 ? s.toFixed(1) : s < 0.1 ? s.toFixed(2) : s.toFixed(1);
}

// Every seed thin, their mean thick, on the task's shared vertical range.
function spark(curves, range) {
const [lo, hi] = range;
const seeds = curves.filter((c) => c.length > 1);
const box = svg("svg", { class: "cov-spark", viewBox: "0 0 100 30", preserveAspectRatio: "none", "aria-hidden": "true" });
if (!seeds.length) return box;
const y = (v) => (28 - ((v - lo) / (hi - lo)) * 26 + 1).toFixed(2);
const line = (values, cls) => {
const n = values.length;
const pts = values.map((v, i) => `${((i / (n - 1)) * 100).toFixed(2)},${y(v)}`).join(" ");
box.append(svg("polyline", { points: pts, class: cls }));
};
seeds.forEach((s) => line(s, "seed"));
const n = Math.min(...seeds.map((s) => s.length));
const mean = Array.from({ length: n }, (_, i) => seeds.reduce((a, s) => a + s[i], 0) / seeds.length);
line(mean, "mean");
return box;
}

const players = [];

function media(id, clip, base, t, describe) {
const box = el("div", { class: "cov-media" });
if (!clip) {
box.append(el("span", { class: "cov-play", text: t.missing }));
return { box, wire: () => {} };
}
const img = el("img", { src: `${base}/${id}.jpg`, alt: "", loading: "lazy", width: 256, height: 256 });
const video = el("video", { muted: true, loop: true, playsinline: true, preload: "none", "aria-hidden": "true" });
video.muted = true;
box.append(img, video, el("span", { class: "cov-play", text: t.play }));
if (Math.abs(clip.speed - 1) > 0.1) {
box.append(el("span", { class: "cov-speed", text: t.speed(fmtSpeed(clip.speed)) }));
}
const wire = (cell) => {
cell.title = describe(clip);
const reduce = window.matchMedia("(prefers-reduced-motion: reduce)");
const play = () => {
players.forEach((p) => p !== stop && p());
if (!video.src) video.src = `${base}/${id}.mp4`;
video.play().then(() => cell.classList.add("is-playing")).catch(() => {});
};
const stop = () => {
video.pause();
cell.classList.remove("is-playing");
};
players.push(stop);
const toggle = () => (cell.classList.contains("is-playing") ? stop() : play());
cell.addEventListener("pointerenter", (e) => e.pointerType === "mouse" && !reduce.matches && play());
cell.addEventListener("pointerleave", (e) => e.pointerType === "mouse" && stop());
cell.addEventListener("click", (e) => !e.target.closest("a") && toggle());
cell.addEventListener("keydown", (e) => {
if (e.key === "Enter" || e.key === " ") {
e.preventDefault();
toggle();
}
});
};
return { box, wire };
}

function links(ids, dirs) {
const p = el("div", { class: "cov-links" });
ids.forEach((e) => p.append(el("a", { href: EXPERIMENTS + dirs[e], text: e })));
return p;
}

function cell(c, data, base, t, zh) {
const node = el("figure", { class: "cov-cell", "data-status": c.status, tabindex: 0 });
const m = media(c.id, c.clip, base, t, t.clip);
m.box.append(el("span", { class: "cov-chip", text: t.status[c.status] }));
node.append(
m.box,
el("figcaption", { class: "cov-body" },
spark(c.curves, data.ranges[c.column]),
el("div", { class: "cov-note", text: zh ? c.note_zh : c.note }),
links(c.experiments, data.experiments)),
);
m.wire(node);
return node;
}

function vlaCell(c, data, base, t, zh) {
const node = el("figure", { class: "cov-cell", "data-status": c.status, tabindex: 0 });
const m = media(c.id, c.clip, base, t, t.vlaClip);
m.box.append(el("span", { class: "cov-chip", text: t.status[c.status] }));
const [k, n] = c.score;
const bar = el("div", { class: "cov-bar", title: t.successes }, el("span", { style: `width:${(100 * k) / n}%` }));
node.append(
m.box,
el("figcaption", { class: "cov-body" },
el("div", { class: "cov-vla-label", text: zh ? c.label_zh : c.label }),
el("div", { class: "cov-score", title: t.successes },
el("strong", { text: String(k) }), el("span", { text: `${t.of} ${n}` })),
bar,
el("div", { class: "cov-note", text: zh ? c.note_zh : c.note }),
links(c.experiments, data.experiments)),
);
m.wire(node);
return node;
}

function build(root, data) {
const zh = (document.documentElement.lang || "").toLowerCase().startsWith("zh");
const t = zh ? TEXT.zh : TEXT.en;
const base = root.dataset.src.replace(/\/[^/]*$/, "");

const grid = el("div", { class: "cov-grid" });
data.columns.forEach((col) => grid.append(el("div", { class: "cov-colhead", text: col.label })));
data.rows.forEach((row) => {
grid.append(el("div", { class: "cov-rowhead", text: row.label }));
data.columns.forEach((col) => {
const c = data.cells.find((x) => x.row === row.id && x.column === col.id);
grid.append(c ? cell(c, data, base, t, zh) : el("div"));
});
});
root.append(el("div", { class: "cov-scroll" }, grid));

const legend = el("p", { class: "cov-legend" });
Object.entries(t.legend).forEach(([s, label]) => legend.append(el("span", { "data-status": s, text: label })));
root.append(legend);

if (data.vla) {
const row = el("div", { class: "cov-vla-grid" });
data.vla.cells.forEach((c) => row.append(vlaCell(c, data, base, t, zh)));
root.append(
el("div", { class: "cov-vla" },
el("p", { class: "cov-vla-title", text: zh ? data.vla.task_zh : data.vla.task }),
el("div", { class: "cov-scroll" }, row)),
);
}
}

function init() {
document.querySelectorAll(".cov[data-src]").forEach((root) => {
if (root.dataset.built) return;
root.dataset.built = "1";
fetch(root.dataset.src)
.then((r) => r.json())
.then((data) => build(root, data))
.catch(() => {
root.textContent = "";
});
});
}

if (document.readyState === "loading") document.addEventListener("DOMContentLoaded", init);
else init();
})();
1 change: 1 addition & 0 deletions docs/media/coverage/coverage.json

Large diffs are not rendered by default.

Binary file added docs/media/coverage/dppo-dppo-cheetah.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/media/coverage/dppo-dppo-cheetah.mp4
Binary file not shown.
Binary file added docs/media/coverage/dppo-dppo-hopper.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/media/coverage/dppo-dppo-hopper.mp4
Binary file not shown.
Binary file added docs/media/coverage/dppo-dppo-square.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/media/coverage/dppo-dppo-square.mp4
Binary file not shown.
Binary file added docs/media/coverage/dppo-dppo-walker.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/media/coverage/dppo-dppo-walker.mp4
Binary file not shown.
Binary file added docs/media/coverage/fpo-dppo-cheetah.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/media/coverage/fpo-dppo-cheetah.mp4
Binary file not shown.
Binary file added docs/media/coverage/fpo-dppo-hopper.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/media/coverage/fpo-dppo-hopper.mp4
Binary file not shown.
Binary file added docs/media/coverage/fpo-dppo-square.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/media/coverage/fpo-dppo-square.mp4
Binary file not shown.
Binary file added docs/media/coverage/fpo-dppo-walker.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/media/coverage/fpo-dppo-walker.mp4
Binary file not shown.
Binary file added docs/media/coverage/fpo-fpo-cheetah.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/media/coverage/fpo-fpo-cheetah.mp4
Binary file not shown.
Binary file added docs/media/coverage/fpo-fpo-hopper.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/media/coverage/fpo-fpo-hopper.mp4
Binary file not shown.
Binary file added docs/media/coverage/fpo-fpo-square.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/media/coverage/fpo-fpo-square.mp4
Binary file not shown.
Binary file added docs/media/coverage/fpo-fpo-walker.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/media/coverage/fpo-fpo-walker.mp4
Binary file not shown.
Binary file added docs/media/coverage/pi0-base.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/media/coverage/pi0-base.mp4
Binary file not shown.
Binary file added docs/media/coverage/pi0-dppo.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/media/coverage/pi0-dppo.mp4
Binary file not shown.
Binary file added docs/media/coverage/pi0-fpo.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/media/coverage/pi0-fpo.mp4
Binary file not shown.
Loading
Loading