Spaces:
Running
Running
Improve DAgger explainer, add conclusion and expand references
Browse files- Rebuild DAgger loop visualization with SVG icons, dark/light theme
support, curved arrows, iteration counter, and dataset growth bar
- Add conclusion sections: bigger picture, open questions, call to action
- Expand references from 4 to 11 papers, organized by category
(models, techniques, architecture components)
- Update DAgger section text in ablations chapter
Made-with: Cursor
- app/public/images/folding/cam_base.jpg +3 -0
- app/public/images/folding/cam_left_wrist.jpg +3 -0
- app/public/images/folding/cam_right_wrist.jpg +3 -0
- app/src/components/Hero.astro +8 -46
- app/src/components/HtmlEmbed.astro +22 -0
- app/src/content/article.mdx +1 -12
- app/src/content/chapters/folding/01-hero.mdx +4 -4
- app/src/content/chapters/folding/02-results.mdx +2 -2
- app/src/content/chapters/folding/03-hardware.mdx +12 -23
- app/src/content/chapters/folding/04-data-collection.mdx +42 -10
- app/src/content/chapters/folding/05-data-diversity.mdx +1 -1
- app/src/content/chapters/folding/06-training.mdx +21 -40
- app/src/content/chapters/folding/08-ablations.mdx +85 -94
- app/src/content/chapters/folding/09-learnings.mdx +8 -12
- app/src/content/chapters/folding/12-references.mdx +14 -1
- app/src/content/embeds/folding/action-representations.html +7 -7
- app/src/content/embeds/folding/dagger-explainer.html +277 -0
- app/src/content/embeds/folding/experiment-results.html +397 -0
- app/src/content/embeds/folding/failure-analysis.html +105 -72
- app/src/content/embeds/folding/l1-time-quality.html +2 -2
- app/src/content/embeds/folding/loss-curves.html +25 -11
- app/src/content/embeds/folding/pi05-architecture.html +3 -3
- app/src/content/embeds/folding/pi05-sarm-architecture.html +370 -236
- app/src/content/embeds/folding/rabc-explainer.html +191 -0
- app/src/content/embeds/folding/rtc-explainer.html +275 -0
- app/src/content/embeds/folding/statistical-analysis.html +16 -16
- app/src/content/embeds/folding/subtask-heatmap.html +220 -79
- app/src/content/embeds/folding/success-rates.html +12 -12
- app/src/content/embeds/folding/total-score.html +8 -8
- compute_cld.py +156 -0
- export_wandb_losses.py +64 -0
- loss_curves.json +3 -0
app/public/images/folding/cam_base.jpg
ADDED
|
Git LFS Details
|
app/public/images/folding/cam_left_wrist.jpg
ADDED
|
Git LFS Details
|
app/public/images/folding/cam_right_wrist.jpg
ADDED
|
Git LFS Details
|
app/src/components/Hero.astro
CHANGED
|
@@ -100,19 +100,8 @@ const pdfFilename = `${slugify(pdfBase)}.pdf`;
|
|
| 100 |
<h1 class="hero-title" set:html={title} />
|
| 101 |
<div class="hero-banner">
|
| 102 |
<div class="hero-video-wrapper">
|
| 103 |
-
<video id="hero-video" src={announcementVideo} autoplay
|
| 104 |
-
<
|
| 105 |
-
<svg class="icon-muted" xmlns="http://www.w3.org/2000/svg" width="28" height="28" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
| 106 |
-
<polygon points="11 5 6 9 2 9 2 15 6 15 11 19 11 5"></polygon>
|
| 107 |
-
<line x1="23" y1="9" x2="17" y2="15"></line>
|
| 108 |
-
<line x1="17" y1="9" x2="23" y2="15"></line>
|
| 109 |
-
</svg>
|
| 110 |
-
<svg class="icon-unmuted" style="display:none;" xmlns="http://www.w3.org/2000/svg" width="28" height="28" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
| 111 |
-
<polygon points="11 5 6 9 2 9 2 15 6 15 11 19 11 5"></polygon>
|
| 112 |
-
<path d="M19.07 4.93a10 10 0 0 1 0 14.14"></path>
|
| 113 |
-
<path d="M15.54 8.46a5 5 0 0 1 0 7.07"></path>
|
| 114 |
-
</svg>
|
| 115 |
-
</button>
|
| 116 |
</div>
|
| 117 |
{description && <p class="hero-desc">{description}</p>}
|
| 118 |
</div>
|
|
@@ -408,20 +397,6 @@ const pdfFilename = `${slugify(pdfBase)}.pdf`;
|
|
| 408 |
</script>
|
| 409 |
)}
|
| 410 |
|
| 411 |
-
<script is:inline>
|
| 412 |
-
(function() {
|
| 413 |
-
const video = document.getElementById('hero-video');
|
| 414 |
-
const btn = document.getElementById('hero-mute-btn');
|
| 415 |
-
if (!video || !btn) return;
|
| 416 |
-
const iconMuted = btn.querySelector('.icon-muted');
|
| 417 |
-
const iconUnmuted = btn.querySelector('.icon-unmuted');
|
| 418 |
-
btn.addEventListener('click', () => {
|
| 419 |
-
video.muted = !video.muted;
|
| 420 |
-
iconMuted.style.display = video.muted ? 'block' : 'none';
|
| 421 |
-
iconUnmuted.style.display = video.muted ? 'none' : 'block';
|
| 422 |
-
});
|
| 423 |
-
})();
|
| 424 |
-
</script>
|
| 425 |
|
| 426 |
<style>
|
| 427 |
/* Hero (full-width) */
|
|
@@ -447,25 +422,12 @@ const pdfFilename = `${slugify(pdfBase)}.pdf`;
|
|
| 447 |
display: inline-block;
|
| 448 |
width: 100%;
|
| 449 |
}
|
| 450 |
-
.
|
| 451 |
-
|
| 452 |
-
|
| 453 |
-
|
| 454 |
-
|
| 455 |
-
|
| 456 |
-
border-radius: 50%;
|
| 457 |
-
width: 48px;
|
| 458 |
-
height: 48px;
|
| 459 |
-
display: flex;
|
| 460 |
-
align-items: center;
|
| 461 |
-
justify-content: center;
|
| 462 |
-
cursor: pointer;
|
| 463 |
-
color: white;
|
| 464 |
-
backdrop-filter: blur(4px);
|
| 465 |
-
transition: background 0.2s;
|
| 466 |
-
}
|
| 467 |
-
.mute-btn:hover {
|
| 468 |
-
background: rgba(0, 0, 0, 0.7);
|
| 469 |
}
|
| 470 |
.hero-desc {
|
| 471 |
color: var(--muted-color);
|
|
|
|
| 100 |
<h1 class="hero-title" set:html={title} />
|
| 101 |
<div class="hero-banner">
|
| 102 |
<div class="hero-video-wrapper">
|
| 103 |
+
<video id="hero-video" src={announcementVideo} autoplay controls muted playsinline style="width:100%;height:auto;border-radius:12px;"></video>
|
| 104 |
+
<p class="video-credit">Video by Simon Automne-Pinto</p>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 105 |
</div>
|
| 106 |
{description && <p class="hero-desc">{description}</p>}
|
| 107 |
</div>
|
|
|
|
| 397 |
</script>
|
| 398 |
)}
|
| 399 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 400 |
|
| 401 |
<style>
|
| 402 |
/* Hero (full-width) */
|
|
|
|
| 422 |
display: inline-block;
|
| 423 |
width: 100%;
|
| 424 |
}
|
| 425 |
+
.video-credit {
|
| 426 |
+
color: var(--muted-color);
|
| 427 |
+
font-size: 12px;
|
| 428 |
+
margin: 6px 0 0;
|
| 429 |
+
text-align: right;
|
| 430 |
+
opacity: 0.7;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 431 |
}
|
| 432 |
.hero-desc {
|
| 433 |
color: var(--muted-color);
|
app/src/components/HtmlEmbed.astro
CHANGED
|
@@ -520,6 +520,13 @@ const htmlWithId =
|
|
| 520 |
}
|
| 521 |
[data-theme="light"] .html-embed__card .insight strong { color: rgba(0,0,0,.85); }
|
| 522 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 523 |
/* Wilson CI note (success-rates) */
|
| 524 |
[data-theme="light"] .html-embed__card .ci-note {
|
| 525 |
background: rgba(0,0,0,.02); border-color: #e2e4e9; color: #6b7280;
|
|
@@ -551,6 +558,21 @@ const htmlWithId =
|
|
| 551 |
[data-theme="light"] .html-embed__card .toggle-btn:hover {
|
| 552 |
border-color: #9ca3af; color: rgba(0,0,0,.85);
|
| 553 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 554 |
[data-theme="light"] .html-embed__card .divider { background: #e2e4e9; }
|
| 555 |
[data-theme="light"] .html-embed__card .ctrl-label { color: #6b7280; }
|
| 556 |
|
|
|
|
| 520 |
}
|
| 521 |
[data-theme="light"] .html-embed__card .insight strong { color: rgba(0,0,0,.85); }
|
| 522 |
|
| 523 |
+
/* Insight boxes (failure-analysis) */
|
| 524 |
+
[data-theme="light"] .html-embed__card .insight-box {
|
| 525 |
+
background: #f7f8fa; border: 1px solid #e2e4e9;
|
| 526 |
+
border-left: 3px solid #f7934f; color: #6b7280;
|
| 527 |
+
}
|
| 528 |
+
[data-theme="light"] .html-embed__card .insight-box strong { color: rgba(0,0,0,.85); }
|
| 529 |
+
|
| 530 |
/* Wilson CI note (success-rates) */
|
| 531 |
[data-theme="light"] .html-embed__card .ci-note {
|
| 532 |
background: rgba(0,0,0,.02); border-color: #e2e4e9; color: #6b7280;
|
|
|
|
| 558 |
[data-theme="light"] .html-embed__card .toggle-btn:hover {
|
| 559 |
border-color: #9ca3af; color: rgba(0,0,0,.85);
|
| 560 |
}
|
| 561 |
+
[data-theme="light"] .html-embed__card .mode-btn {
|
| 562 |
+
background: #fff; border-color: #e2e4e9; color: #6b7280;
|
| 563 |
+
}
|
| 564 |
+
[data-theme="light"] .html-embed__card .mode-btn.active {
|
| 565 |
+
color: rgba(0,0,0,.85); border-color: #c5c8d0; background: #f3f4f6;
|
| 566 |
+
}
|
| 567 |
+
[data-theme="light"] .html-embed__card .mode-btn:hover:not(.active) {
|
| 568 |
+
color: rgba(0,0,0,.85);
|
| 569 |
+
}
|
| 570 |
+
[data-theme="light"] .html-embed__card .filter-toggle {
|
| 571 |
+
background: #fff; border-color: #e2e4e9; color: #6b7280;
|
| 572 |
+
}
|
| 573 |
+
[data-theme="light"] .html-embed__card .filter-toggle:hover {
|
| 574 |
+
border-color: #9ca3af; color: rgba(0,0,0,.85);
|
| 575 |
+
}
|
| 576 |
[data-theme="light"] .html-embed__card .divider { background: #e2e4e9; }
|
| 577 |
[data-theme="light"] .html-embed__card .ctrl-label { color: #6b7280; }
|
| 578 |
|
app/src/content/article.mdx
CHANGED
|
@@ -1,6 +1,6 @@
|
|
| 1 |
---
|
| 2 |
title: "Unfolding Robotics:\nThe Open-Source Recipe for Teaching a Robot to Fold Your Clothes"
|
| 3 |
-
subtitle: "
|
| 4 |
description: "We trained a bimanual robot to fold t-shirts using LeRobot and open-source hardware. We release the model, data, code, and every insight from data collection to deployment."
|
| 5 |
authors:
|
| 6 |
- name: "Pepijn Kooijmans"
|
|
@@ -54,20 +54,11 @@ import Hero from "./chapters/folding/01-hero.mdx";
|
|
| 54 |
import Results from "./chapters/folding/02-results.mdx";
|
| 55 |
import Hardware from "./chapters/folding/03-hardware.mdx";
|
| 56 |
import DataCollection from "./chapters/folding/04-data-collection.mdx";
|
| 57 |
-
import DataDiversity from "./chapters/folding/05-data-diversity.mdx";
|
| 58 |
import Training from "./chapters/folding/06-training.mdx";
|
| 59 |
import Experiments from "./chapters/folding/08-ablations.mdx";
|
| 60 |
import Learnings from "./chapters/folding/09-learnings.mdx";
|
| 61 |
import References from "./chapters/folding/12-references.mdx";
|
| 62 |
|
| 63 |
-
import Introduction from "./chapters/demo/introduction.mdx";
|
| 64 |
-
import BuiltWithThis from "./chapters/demo/built-with-this.mdx";
|
| 65 |
-
import BestPractices from "./chapters/demo/best-pratices.mdx";
|
| 66 |
-
import WritingYourContent from "./chapters/demo/writing-your-content.mdx";
|
| 67 |
-
import GettingStarted from "./chapters/demo/getting-started.mdx";
|
| 68 |
-
import Markdown from "./chapters/demo/markdown.mdx";
|
| 69 |
-
import Components from "./chapters/demo/components.mdx";
|
| 70 |
-
import VibeCodingCharts from "./chapters/demo/vibe-coding-charts.mdx";
|
| 71 |
|
| 72 |
<Hero />
|
| 73 |
|
|
@@ -77,8 +68,6 @@ import VibeCodingCharts from "./chapters/demo/vibe-coding-charts.mdx";
|
|
| 77 |
|
| 78 |
<DataCollection />
|
| 79 |
|
| 80 |
-
<DataDiversity />
|
| 81 |
-
|
| 82 |
<Training />
|
| 83 |
|
| 84 |
<Experiments />
|
|
|
|
| 1 |
---
|
| 2 |
title: "Unfolding Robotics:\nThe Open-Source Recipe for Teaching a Robot to Fold Your Clothes"
|
| 3 |
+
subtitle: ""
|
| 4 |
description: "We trained a bimanual robot to fold t-shirts using LeRobot and open-source hardware. We release the model, data, code, and every insight from data collection to deployment."
|
| 5 |
authors:
|
| 6 |
- name: "Pepijn Kooijmans"
|
|
|
|
| 54 |
import Results from "./chapters/folding/02-results.mdx";
|
| 55 |
import Hardware from "./chapters/folding/03-hardware.mdx";
|
| 56 |
import DataCollection from "./chapters/folding/04-data-collection.mdx";
|
|
|
|
| 57 |
import Training from "./chapters/folding/06-training.mdx";
|
| 58 |
import Experiments from "./chapters/folding/08-ablations.mdx";
|
| 59 |
import Learnings from "./chapters/folding/09-learnings.mdx";
|
| 60 |
import References from "./chapters/folding/12-references.mdx";
|
| 61 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 62 |
|
| 63 |
<Hero />
|
| 64 |
|
|
|
|
| 68 |
|
| 69 |
<DataCollection />
|
| 70 |
|
|
|
|
|
|
|
| 71 |
<Training />
|
| 72 |
|
| 73 |
<Experiments />
|
app/src/content/chapters/folding/01-hero.mdx
CHANGED
|
@@ -6,16 +6,16 @@ import Stack from "../../../components/Stack.astro";
|
|
| 6 |
> We trained an open-source bimanual robot to fold t-shirts autonomously, reaching 90% success rate. The biggest lever was data quality, not the model, not the architecture.
|
| 7 |
|
| 8 |
<Sidenote>
|
| 9 |
-
Read time: ~
|
| 10 |
</Sidenote>
|
| 11 |
|
| 12 |
This post walks through the complete journey: hardware choices, data collection, training recipes, and different experiments that show what actually matters. We cover the mistakes and dead ends alongside the things that worked, because the messy middle is where most of the learning happens.
|
| 13 |
|
| 14 |
Some of what we found: cheap 3D-printed leader arms outperformed the expensive ones for teleoperation. Early data collection was more wasteful than expected. A trained reward model turned out to be essential for separating useful demonstrations from harmful ones. And curating a small, high-quality dataset did more than any algorithmic improvement on the full dataset.
|
| 15 |
|
| 16 |
-
By sharing this we hope to contribute to our bigger vision: **democratize robotics and robot learning**. By open-sourcing every piece tools, data, models, and knowledge we want to enable a community that pushes this technology further. We've tried to avoid just listing what we did in favor of telling the story of how we got here. We hope being this open will help close the gap between closed-lab demos and what the open-source community can achieve.
|
| 17 |
|
| 18 |
-
Everything we built for this project [SARM](https://huggingface.co/docs/lerobot/sarm), [RTC](https://huggingface.co/docs/lerobot/rtc), DAgger, [OpenArm](https://huggingface.co/docs/lerobot/openarm) and [OpenArm Mini](
|
| 19 |
|
| 20 |
_Let's start with the results, does it actually work?_
|
| 21 |
|
|
@@ -26,7 +26,7 @@ _Let's start with the results, does it actually work?_
|
|
| 26 |
<a href="https://huggingface.co/lerobot-data-collection/folding_sarm_reward" className="card" style="padding: 12px 16px; text-align: center; text-decoration: none;"><strong>SARM Reward</strong><br/>HF Hub</a>
|
| 27 |
<a href="https://huggingface.co/datasets/lerobot/high_quality_folding" className="card" style="padding: 12px 16px; text-align: center; text-decoration: none;"><strong>HQ Dataset</strong><br/>HF Hub</a>
|
| 28 |
<a href="https://huggingface.co/datasets/lerobot/full_folding" className="card" style="padding: 12px 16px; text-align: center; text-decoration: none;"><strong>Full Dataset</strong><br/>HF Hub</a>
|
| 29 |
-
<a href="
|
| 30 |
<a href="https://github.com/huggingface/lerobot" className="card" style="padding: 12px 16px; text-align: center; text-decoration: none;"><strong>LeRobot</strong><br/>Code</a>
|
| 31 |
<a href="https://huggingface.co/docs/lerobot/index" className="card" style="padding: 12px 16px; text-align: center; text-decoration: none;"><strong>LeRobot</strong><br/>Documentation</a>
|
| 32 |
</Stack>
|
|
|
|
| 6 |
> We trained an open-source bimanual robot to fold t-shirts autonomously, reaching 90% success rate. The biggest lever was data quality, not the model, not the architecture.
|
| 7 |
|
| 8 |
<Sidenote>
|
| 9 |
+
Read time: ~25 minutes.
|
| 10 |
</Sidenote>
|
| 11 |
|
| 12 |
This post walks through the complete journey: hardware choices, data collection, training recipes, and different experiments that show what actually matters. We cover the mistakes and dead ends alongside the things that worked, because the messy middle is where most of the learning happens.
|
| 13 |
|
| 14 |
Some of what we found: cheap 3D-printed leader arms outperformed the expensive ones for teleoperation. Early data collection was more wasteful than expected. A trained reward model turned out to be essential for separating useful demonstrations from harmful ones. And curating a small, high-quality dataset did more than any algorithmic improvement on the full dataset.
|
| 15 |
|
| 16 |
+
By sharing this we hope to contribute to our bigger vision: **democratize robotics and robot learning**. By open-sourcing every piece (tools, data, models, and knowledge) we want to enable a community that pushes this technology further. We've tried to avoid just listing what we did in favor of telling the story of how we got here. We hope being this open will help close the gap between closed-lab demos and what the open-source community can achieve.
|
| 17 |
|
| 18 |
+
Everything we built for this project ([SARM](https://huggingface.co/docs/lerobot/sarm), [RTC](https://huggingface.co/docs/lerobot/rtc), DAgger, [OpenArm](https://huggingface.co/docs/lerobot/openarm), and [OpenArm Mini](https://github.com/pkooij/open-arms-mini)) is now merged into [LeRobot](https://github.com/huggingface/lerobot) and ready for the community to use.
|
| 19 |
|
| 20 |
_Let's start with the results, does it actually work?_
|
| 21 |
|
|
|
|
| 26 |
<a href="https://huggingface.co/lerobot-data-collection/folding_sarm_reward" className="card" style="padding: 12px 16px; text-align: center; text-decoration: none;"><strong>SARM Reward</strong><br/>HF Hub</a>
|
| 27 |
<a href="https://huggingface.co/datasets/lerobot/high_quality_folding" className="card" style="padding: 12px 16px; text-align: center; text-decoration: none;"><strong>HQ Dataset</strong><br/>HF Hub</a>
|
| 28 |
<a href="https://huggingface.co/datasets/lerobot/full_folding" className="card" style="padding: 12px 16px; text-align: center; text-decoration: none;"><strong>Full Dataset</strong><br/>HF Hub</a>
|
| 29 |
+
<a href="https://github.com/pkooij/open-arms-mini" className="card" style="padding: 12px 16px; text-align: center; text-decoration: none;"><strong>OpenArm Mini</strong><br/>Repo</a>
|
| 30 |
<a href="https://github.com/huggingface/lerobot" className="card" style="padding: 12px 16px; text-align: center; text-decoration: none;"><strong>LeRobot</strong><br/>Code</a>
|
| 31 |
<a href="https://huggingface.co/docs/lerobot/index" className="card" style="padding: 12px 16px; text-align: center; text-decoration: none;"><strong>LeRobot</strong><br/>Documentation</a>
|
| 32 |
</Stack>
|
app/src/content/chapters/folding/02-results.mdx
CHANGED
|
@@ -30,7 +30,7 @@ How well does it actually work? We evaluated our best model (Experiment 2.5) acr
|
|
| 30 |
| **Combined** (Total SR) | **90%** | |
|
| 31 |
|
| 32 |
<Sidenote>
|
| 33 |
-
All evaluations filmed and scored from video. 20 rollouts per experiment (10 per level). Full methodology in the [Evaluation](#evaluation) section.
|
| 34 |
</Sidenote>
|
| 35 |
|
| 36 |
-
These numbers are the result of 11 experiments, each testing a different combination of model, data, and training strategies. The full breakdown is in the [
|
|
|
|
| 30 |
| **Combined** (Total SR) | **90%** | |
|
| 31 |
|
| 32 |
<Sidenote>
|
| 33 |
+
All evaluations filmed and scored from video. 20 rollouts per experiment (10 per level). Full methodology in the [Model and Evaluation Setup](#model-and-evaluation-setup) section.
|
| 34 |
</Sidenote>
|
| 35 |
|
| 36 |
+
These numbers are the result of 11 experiments, each testing a different combination of model, data, and training strategies. The full breakdown is in the [From Data to Deployment](#from-data-to-deployment) section. But let's start from the beginning: the hardware.
|
app/src/content/chapters/folding/03-hardware.mdx
CHANGED
|
@@ -10,35 +10,32 @@ import openArmMini2 from "../../assets/image/openarm-mini2.jpg";
|
|
| 10 |
|
| 11 |
## Hardware
|
| 12 |
|
| 13 |
-
LeRobot
|
| 14 |
|
| 15 |
### The Robot: Bimanual OpenArm
|
| 16 |
|
| 17 |
-
For starters, the robot. We used the **bimanual [OpenArm](https://huggingface.co/docs/lerobot/openarm)**
|
| 18 |
|
| 19 |
-
1. **
|
| 20 |
-
2. **
|
| 21 |
-
3. **Open source, good specs.** Solid payload, good reach, and fully open hardware. We extended the upper arm by **+5 cm** to increase reach since our setup doesn't have a hip or torso to provide additional workspace.
|
| 22 |
|
| 23 |
-
Everything is mounted on **aluminum extrusion profiles**, which let us quickly iterate on the physical arrangement and adjust both teleop and robot height between sessions to increase data diversity.
|
| 24 |
|
| 25 |
<img src={openArmsImg.src} alt="OpenArm bimanual robot setup" style="width:100%; border-radius: 8px;" />
|
| 26 |
|
| 27 |
-
### Custom Grippers
|
| 28 |
-
|
| 29 |
-
We designed **custom grippers with a larger surface area**, giving the robot a broader contact patch to grip, pinch, and slide fabric reliably. We also added a small polymer patch on one side of the gripper to reduce slippage and make the grasping of fabric easier.
|
| 30 |
-
|
| 31 |
### Teleop Arms: OpenArm Mini
|
| 32 |
|
| 33 |
Next, we need a way to control the robot. We started with full-size OpenArm as leader arms for teleoperation. They seemed like the natural choice: same kinematics as the follower arms, one-to-one mapping.
|
| 34 |
|
| 35 |
-
However, we quickly realized we needed a teleoperator with less inertia,
|
| 36 |
1. **Less inertia** for quicker and more deliberate motions that cloth folding demands
|
| 37 |
2. **Arm-length agnostic** and adaptable to any human operator size
|
| 38 |
3. **Incredibly cheap** (~120 EUR per arm) making it easy to scale to multiple stations
|
| 39 |
-
4. **Still
|
| 40 |
|
| 41 |
-
One small detail mattered more than expected: the **wrist strap**. It locks the wrist to the leader arm, providing
|
|
|
|
|
|
|
| 42 |
|
| 43 |
<Note variant="info" emoji="🔗">
|
| 44 |
[OpenArm Mini repo (3D print files, BOM, LeRobot integration)](https://github.com/pkooij/open-arms-mini)
|
|
@@ -54,17 +51,9 @@ One small detail mattered more than expected: the **wrist strap**. It locks the
|
|
| 54 |
|
| 55 |
<br/>
|
| 56 |
|
| 57 |
-
Another feature that made a surprisingly big difference: when both your hands are on the leader arms, you need a hands-free way to **start and stop episodes recording**. USB foot pedals solved this elegantly.
|
| 58 |
-
|
| 59 |
### Cameras
|
| 60 |
|
| 61 |
-
|
| 62 |
-
|
| 63 |
-
| Camera | Position | Notes |
|
| 64 |
-
|:---|:---|:---|
|
| 65 |
-
| **Base camera** | Mounted between/above arms | Wide FOV to capture the full scene |
|
| 66 |
-
| **Left wrist camera** | Mounted on left end-effector | Close-up view for precise manipulation |
|
| 67 |
-
| **Right wrist camera** | Mounted on right end-effector | Close-up view for precise manipulation |
|
| 68 |
|
| 69 |
<Note variant="info" emoji="🔗">
|
| 70 |
Camera links: <a href="https://www.amazon.fr/-/en/Fafeicy-Camera-Module-Million-Conferencing/dp/B08GLSPTXY" target="_blank">Base camera (Fafeicy OV2710)</a> / <a href="https://www.arducam.com/12mp-imx708-usb-uvc-102-wide-angle-fixed-focus-camera-module-3.html" target="_blank">Wrist cameras (Arducam IMX708)</a>
|
|
@@ -77,6 +66,6 @@ Eventually, the robot needs to see what it is doing: for this purpose we used **
|
|
| 77 |
|
| 78 |
### LeRobot Integration
|
| 79 |
|
| 80 |
-
Integrating OpenArm into LeRobot required adding **CAN-bus
|
| 81 |
|
| 82 |
With the hardware in place, the next step was the hardest and most time-consuming part of the entire project: collecting good data. And "good" is much harder to define than it sounds.
|
|
|
|
| 10 |
|
| 11 |
## Hardware
|
| 12 |
|
| 13 |
+
LeRobot handles the software stack, but you still need the physical hardware. Below we cover the base robot, custom grippers, a cheap 3D-printed teleop setup that outperformed the expensive one, and the camera layout.
|
| 14 |
|
| 15 |
### The Robot: Bimanual OpenArm
|
| 16 |
|
| 17 |
+
For starters, the robot. We used the **bimanual [OpenArm](https://huggingface.co/docs/lerobot/openarm)**. These are open-source, human-like robot arms developed by [Enactic](https://openarm.dev) and sold by multiple vendors like [WowRobo](https://shop.wowrobo.com). Two reasons drove this choice:
|
| 18 |
|
| 19 |
+
1. **Smaller teleop gap.** When the robot's kinematics match a human arm, the teleoperator's motions transfer more naturally, meaning less mental remapping and faster learning. The humanoid form factor also aligns with where the ecosystem is heading: more human-form robots means more transferable data.
|
| 20 |
+
2. **Open source, good specs.** Solid payload, good reach, and fully open hardware. We extended the upper arm (the bicep segment) by **+5 cm** to increase reach since our setup doesn't have a hip or torso to provide additional workspace.
|
|
|
|
| 21 |
|
| 22 |
+
Everything is mounted on **aluminum extrusion profiles**, which let us quickly iterate on the physical arrangement and adjust both teleop and robot height between sessions to increase data diversity. We also designed **custom grippers with a larger surface area**, giving the robot a broader contact patch to grip, pinch, and slide fabric reliably.
|
| 23 |
|
| 24 |
<img src={openArmsImg.src} alt="OpenArm bimanual robot setup" style="width:100%; border-radius: 8px;" />
|
| 25 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 26 |
### Teleop Arms: OpenArm Mini
|
| 27 |
|
| 28 |
Next, we need a way to control the robot. We started with full-size OpenArm as leader arms for teleoperation. They seemed like the natural choice: same kinematics as the follower arms, one-to-one mapping.
|
| 29 |
|
| 30 |
+
However, we quickly realized we needed a teleoperator arm with less inertia, that allows for fast and precise manipulation, and more adaptability to different human morphologies. This led us to develop the **OpenArm Mini**: small, Feetech-based, 3D-printed leader arms based on the [SO-101](https://github.com/TheRobotStudio/SO-ARM100) design. These gave us:
|
| 31 |
1. **Less inertia** for quicker and more deliberate motions that cloth folding demands
|
| 32 |
2. **Arm-length agnostic** and adaptable to any human operator size
|
| 33 |
3. **Incredibly cheap** (~120 EUR per arm) making it easy to scale to multiple stations
|
| 34 |
+
4. **Still supports itself**: strong enough to move during human-in-the-loop corrective data collection
|
| 35 |
|
| 36 |
+
One small detail mattered more than expected: the **wrist strap**. It locks the operator's wrist to the leader arm, providing rotational control essential for precise manipulation.
|
| 37 |
+
|
| 38 |
+
Another feature that made a surprisingly big difference: when both your hands are on the leader arms, you need a hands-free way to **start and stop episodes recording**. USB foot pedals solved this elegantly.
|
| 39 |
|
| 40 |
<Note variant="info" emoji="🔗">
|
| 41 |
[OpenArm Mini repo (3D print files, BOM, LeRobot integration)](https://github.com/pkooij/open-arms-mini)
|
|
|
|
| 51 |
|
| 52 |
<br/>
|
| 53 |
|
|
|
|
|
|
|
| 54 |
### Cameras
|
| 55 |
|
| 56 |
+
We use **three cameras**, each serving a purpose. The **base camera** is mounted between the arms and provides a wide-angle overview of the full workspace, the model's primary understanding of the task state. The two **wrist cameras** are mounted directly on the end-effectors. Because they move with the grippers, they provide a natural depth signal and give a close-up view for precise manipulation. They also act as a proxy for touch, being so close to the grippers, they capture contact details: grip quality, slip, that humans normally sense through their fingers. More cameras could help, but every additional image stream requires more compute and time to process; three was the tradeoff we settled on.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 57 |
|
| 58 |
<Note variant="info" emoji="🔗">
|
| 59 |
Camera links: <a href="https://www.amazon.fr/-/en/Fafeicy-Camera-Module-Million-Conferencing/dp/B08GLSPTXY" target="_blank">Base camera (Fafeicy OV2710)</a> / <a href="https://www.arducam.com/12mp-imx708-usb-uvc-102-wide-angle-fixed-focus-camera-module-3.html" target="_blank">Wrist cameras (Arducam IMX708)</a>
|
|
|
|
| 66 |
|
| 67 |
### LeRobot Integration
|
| 68 |
|
| 69 |
+
Integrating OpenArm into LeRobot required adding **CAN-bus** support. CAN-bus is the communication protocol the arm's motors use, think of it as a shared wire where LeRobot sends position commands ("move joint 3 to 45 degrees") and reads back the current joint angles. Everything else: capturing camera images, running the model, converting predictions into those joint positions, happens in Python inside LeRobot. The CAN-bus driver is the thin bridge between software and hardware. This integration can now be found in the [LeRobot repository](https://github.com/huggingface/lerobot). We also created a UI for non-technical robot operators, so the CLI doesn't need to be used to start and stop episodes.
|
| 70 |
|
| 71 |
With the hardware in place, the next step was the hardest and most time-consuming part of the entire project: collecting good data. And "good" is much harder to define than it sounds.
|
app/src/content/chapters/folding/04-data-collection.mdx
CHANGED
|
@@ -1,28 +1,60 @@
|
|
| 1 |
import Note from "../../../components/Note.astro";
|
| 2 |
import Sidenote from "../../../components/Sidenote.astro";
|
| 3 |
import Accordion from "../../../components/Accordion.astro";
|
|
|
|
|
|
|
| 4 |
|
| 5 |
## Data Collection
|
| 6 |
|
| 7 |
Data collection was the longest phase of this project, and arguably the most important. No amount of compute can compensate for bad demonstrations.
|
| 8 |
|
|
|
|
|
|
|
| 9 |
We ran **8 setups** in parallel, optimizing for **maximum diversity**: 25+ different t-shirts, 8 different backgrounds, and varying camera and robot heights between sessions. We structured collection into two task levels: **Level 1** (fold a laid-out shirt) and **Level 2** (spread a messy shirt, fold it, place it aside).
|
| 10 |
|
| 11 |
### Learning to Teleoperate
|
| 12 |
|
| 13 |
-
Here's an honest truth: **early data is worse than the final data**. Teleoperating a bimanual robot is a genuine skill, and it takes practice. The first episodes are slow, not deliberate, and full of failed attempts. Over hours of practice, operators get dramatically better
|
| 14 |
|
| 15 |
-
This
|
| 16 |
|
| 17 |
-
Aligning on a common strategy across operators was equally important. Folding is a very multi-modal task (there are many valid ways to fold a t-shirt) and the model
|
| 18 |
|
| 19 |
### Tips for Good Data Collection
|
| 20 |
|
| 21 |
-
|
| 22 |
-
|
| 23 |
-
|
| 24 |
-
|
| 25 |
-
|
| 26 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 27 |
|
| 28 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
import Note from "../../../components/Note.astro";
|
| 2 |
import Sidenote from "../../../components/Sidenote.astro";
|
| 3 |
import Accordion from "../../../components/Accordion.astro";
|
| 4 |
+
import Wide from "../../../components/Wide.astro";
|
| 5 |
+
import diversityGridImg from "../../assets/image/lerobot-data-collection_level12_rac_2_2026-02-08_1_grid_15x10.jpg";
|
| 6 |
|
| 7 |
## Data Collection
|
| 8 |
|
| 9 |
Data collection was the longest phase of this project, and arguably the most important. No amount of compute can compensate for bad demonstrations.
|
| 10 |
|
| 11 |
+
T-shirt folding involves deformable objects with complex contact dynamics: simulation can't faithfully reproduce how fabric crumples and slides, world models can't yet predict cloth deformation reliably, and RL from scratch on real hardware is too sample-inefficient for a task this long-horizon (though RL *on top of* a pretrained VLA is a promising future direction). That leaves **real-world teleoperation** as the practical path. We chose **leader-follower arms** because they match the robot's kinematics exactly (what the operator does is what the robot does), giving low latency, high precision, and support for [DAgger](https://github.com/huggingface/lerobot/tree/main/examples/hil), where the teleop setup takes over from a running policy to collect corrections.
|
| 12 |
+
|
| 13 |
We ran **8 setups** in parallel, optimizing for **maximum diversity**: 25+ different t-shirts, 8 different backgrounds, and varying camera and robot heights between sessions. We structured collection into two task levels: **Level 1** (fold a laid-out shirt) and **Level 2** (spread a messy shirt, fold it, place it aside).
|
| 14 |
|
| 15 |
### Learning to Teleoperate
|
| 16 |
|
| 17 |
+
Here's an honest truth: **early data collection is worse than the final data**. Teleoperating a bimanual robot is a genuine skill, and it takes practice. The first episodes are slow, not deliberate, and full of failed attempts. Over hours of practice, operators get dramatically better, with smoother motions, faster execution, and more consistent grasps.
|
| 18 |
|
| 19 |
+
This raises an important practical question: **when do we start using the robot operators data?** Every hour of data collection requires a human operator, it's expensive and doesn't scale easily. But bad demonstrations aren't just wasted effort, they might actively hurt the model, which can reproduce hesitations and inconsistent strategies. But defining what makes a "good" demonstration is itself an unsolved problem in robot learning. Duration is a rough proxy, a fast episode can still contain poor grasps, and a slow one can be perfectly executed. We ended up relying on a combination of heuristics and a learned reward model (introduced later) to separate useful data from harmful data.
|
| 20 |
|
| 21 |
+
Aligning on a common strategy across operators was equally important. Folding is a very multi-modal task (there are many valid ways to fold a t-shirt) and with unlimited data per approach the model could probably learn them all. But at our scale, mixing strategies means each one is under-represented. Aligning on a single strategy is a more data-efficient path. Before each recording sprint, we held brief alignment sessions: experimenting with different techniques, sharing our learnings and then converging on the most efficient fold sequence.
|
| 22 |
|
| 23 |
### Tips for Good Data Collection
|
| 24 |
|
| 25 |
+
After weeks of collecting data across operators, these are the guidelines we found most useful:
|
| 26 |
+
|
| 27 |
+
1. **Practice before you record.** Consistent, deliberate demonstrations are more valuable than hesitant or inconsistent ones.
|
| 28 |
+
2. **Quality over speed.** High quality task execution is more valuable than fast, sloppy ones.
|
| 29 |
+
3. **Be consistent within episodes.** The model learns a coherent strategy more easily than movements that vary wildly each time.
|
| 30 |
+
4. **Start small, then extend.** Train a quick model, see what fails, then add diversity. Don't try to collect the perfect dataset on day one.
|
| 31 |
+
5. **Speed after quality.** Once you've dialed in quality and a consistent strategy, optimize for speed. But never sacrifice quality for it.
|
| 32 |
+
|
| 33 |
+
### What we ended up with
|
| 34 |
+
|
| 35 |
+
After multiple weeks of collection across 8 setups we had **5,688 episodes**, the **full dataset**. Not all of them are equally useful: some contain hesitations, inconsistent strategies, or poor fold quality. Later in the project, we built a smaller **high-quality dataset** of 1,200 episodes by selecting the best recordings from the full set and adding new demonstrations collected with a more unified strategy.
|
| 36 |
+
|
| 37 |
+
| Metric | Full dataset | High-quality dataset |
|
| 38 |
+
|:---|:---:|:---:|
|
| 39 |
+
| Total episodes | **5,688** | **1,200** |
|
| 40 |
+
| Total frames | **14.1M** | **3.2M** |
|
| 41 |
+
| Total hours | **~131 h** | **~30 h** |
|
| 42 |
+
| FPS | **30** | **30** |
|
| 43 |
+
| Cameras | **3** (base 480×640, wrists 720×1280) | **3** (same) |
|
| 44 |
+
| Action dims | **16** (7 joints + gripper × 2 arms) | **16** (same) |
|
| 45 |
+
|
| 46 |
+
All data is stored in the [**LeRobotDataset v3.0**](https://huggingface.co/blog/lerobot-datasets-v3) format, which encodes camera streams as video rather than individual images. This makes the dataset **20x–40x more compressed** compared to storing raw frames, making it practical to share and stream datasets of this scale.
|
| 47 |
+
|
| 48 |
+
The grid below shows one frame from each of 100 different episodes. Notice the variation in t-shirt color, background and camera viewpoint.
|
| 49 |
+
|
| 50 |
+
<Wide>
|
| 51 |
+
<img src={diversityGridImg.src} alt="Trajectory diversity grid showing variation in t-shirt color, background, camera viewpoint, and robot height" style="width:100%; border-radius: 8px;" />
|
| 52 |
+
</Wide>
|
| 53 |
+
|
| 54 |
+
Explore the dataset yourself with the [LeRobot Data Visualizer](https://huggingface.co/spaces/lerobot/visualize_dataset):
|
| 55 |
|
| 56 |
+
<Wide>
|
| 57 |
+
<div className="card" style="overflow: hidden; border-radius: 10px; height: 640px;">
|
| 58 |
+
<iframe src="https://lerobot-visualize-dataset.hf.space/?path=%2Flerobot%2Fhigh_quality_folding%2Fepisode_0" width="125%" height="1000" frameBorder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" style="border: none; transform: scale(0.8); transform-origin: top left;"></iframe>
|
| 59 |
+
</div>
|
| 60 |
+
</Wide>
|
app/src/content/chapters/folding/05-data-diversity.mdx
CHANGED
|
@@ -3,7 +3,7 @@ import diversityGridImg from "../../assets/image/lerobot-data-collection_level12
|
|
| 3 |
|
| 4 |
## Data Overview
|
| 5 |
|
| 6 |
-
After weeks of collection we had **5,688 episodes** across 8 setups. Not all of them are equally useful
|
| 7 |
|
| 8 |
### Dataset Statistics
|
| 9 |
|
|
|
|
| 3 |
|
| 4 |
## Data Overview
|
| 5 |
|
| 6 |
+
After weeks of collection we had **5,688 episodes** across 8 setups, the **full dataset**. Not all of them are equally useful: some contain hesitations, inconsistent strategies, or poor fold quality. Later in the project, we built a smaller **high-quality dataset** of 1,200 episodes by selecting the best recordings from the full set and adding new demonstrations collected with a more unified strategy. Here are both side by side:
|
| 7 |
|
| 8 |
### Dataset Statistics
|
| 9 |
|
app/src/content/chapters/folding/06-training.mdx
CHANGED
|
@@ -6,23 +6,24 @@ import HtmlEmbed from "../../../components/HtmlEmbed.astro";
|
|
| 6 |
|
| 7 |
## Model and Evaluation Setup
|
| 8 |
|
| 9 |
-
Before diving into experiments, let's establish the model
|
| 10 |
|
| 11 |
### Model Architecture
|
| 12 |
|
| 13 |
-
|
| 14 |
|
| 15 |
<Wide>
|
| 16 |
<HtmlEmbed
|
| 17 |
id="pi05-sarm-arch"
|
| 18 |
src="folding/pi05-sarm-architecture.html"
|
| 19 |
-
title="π0.5
|
| 20 |
-
desc="
|
|
|
|
| 21 |
frameless
|
| 22 |
/>
|
| 23 |
</Wide>
|
| 24 |
|
| 25 |
-
The model
|
| 26 |
|
| 27 |
<Sidenote>
|
| 28 |
Flow matching is closely related to diffusion models but uses a simpler, more direct interpolation path between noise and data.
|
|
@@ -30,43 +31,23 @@ The model generates actions through **flow matching**, a generative approach tha
|
|
| 30 |
|
| 31 |
#### [Real-Time Chunking (RTC)](https://huggingface.co/docs/lerobot/rtc)
|
| 32 |
|
| 33 |
-
|
| 34 |
|
| 35 |
-
|
| 36 |
-
|
| 37 |
-
|
| 38 |
-
|
| 39 |
-
|
| 40 |
-
|
| 41 |
-
|
| 42 |
-
Note over R: Execute while next chunk generates
|
| 43 |
-
end
|
| 44 |
-
```
|
| 45 |
-
|
| 46 |
-
All experiments use **RTC** with an action queue size of 30 and a maximum action horizon of 20, along with **action interpolation** (upsampling from 30 Hz to 90 Hz) which makes the robot much quieter and smoother.
|
| 47 |
-
|
| 48 |
-
### Models
|
| 49 |
-
|
| 50 |
-
We focused on two VLA architectures for our cloth folding data:
|
| 51 |
-
|
| 52 |
-
- **π0** the base flow-matching VLA, trained with standard imitation learning
|
| 53 |
-
- **[π0.5](https://huggingface.co/docs/lerobot/pi05)** an improved variant with more pretraining and several improvements to the flow matching denoising process
|
| 54 |
|
| 55 |
-
|
| 56 |
|
| 57 |
-
|
| 58 |
|
| 59 |
-
|
| 60 |
-
|:---|:---:|
|
| 61 |
-
| GPUs | **8x H100** |
|
| 62 |
-
| Batch size | **32** (with gradient accumulation), total batch size is 256 |
|
| 63 |
-
| Action chunk size | **30** |
|
| 64 |
-
| Optimizer | AdamW |
|
| 65 |
-
| Learning rate | **1e-4** (with warmup + cosine decay) |
|
| 66 |
|
| 67 |
-
|
| 68 |
-
Multi-GPU training with 8xH100 and gradient accumulation was necessary to fit the large batch sizes needed for stable VLA training.
|
| 69 |
-
</Sidenote>
|
| 70 |
|
| 71 |
---
|
| 72 |
|
|
@@ -82,15 +63,15 @@ For every experiment, we evaluate the model on:
|
|
| 82 |
Each t-shirt fold is attempted **twice consecutively**, giving **10 rollouts per level** and **20 rollouts total per experiment**. Every evaluation is filmed and scored from video afterward, so judgment is decoupled from execution.
|
| 83 |
|
| 84 |
<Note>
|
| 85 |
-
The evaluation protocol t-shirts, attempts count, scoring rubric, and filming setup is identical across every experiment.
|
| 86 |
</Note>
|
| 87 |
|
| 88 |
### Metrics
|
| 89 |
|
| 90 |
We report four complementary metrics:
|
| 91 |
|
| 92 |
-
1. **Success Rate** Binary pass/fail per rollout.
|
| 93 |
-
2. **Score** Partial credit based on subtasks completed. This distinguishes a model that consistently reaches Fold 3 from one that fails at Unfold, even if neither achieves full success.
|
| 94 |
|
| 95 |
<Accordion title="Scoring rubric Level 1 and Level 2">
|
| 96 |
|
|
|
|
| 6 |
|
| 7 |
## Model and Evaluation Setup
|
| 8 |
|
| 9 |
+
Before diving into experiments, let's establish the model we're training and how we measure performance. These stay constant across every iteration.
|
| 10 |
|
| 11 |
### Model Architecture
|
| 12 |
|
| 13 |
+
The dominant approach in robot learning today is to train a **Vision-Language-Action (VLA)** model: a single neural network that takes in camera images and a task description and outputs motor commands. The recipe most labs follow is to **pretrain** a VLA on large, multi-robot, multi-task datasets to learn general visual and manipulation priors, then **fine-tune** it on data from your specific robot and task. Several strong pretrained VLAs exist: [π0](https://huggingface.co/docs/lerobot/pi0), [π0.5](https://huggingface.co/docs/lerobot/pi05), [GR00T](https://developer.nvidia.com/isaac/gr00t), [SmolVLA](https://huggingface.co/docs/lerobot/smolvla), among others. We chose to use **π0 and π0.5** because they showed the strongest performance in our early experiments, likely due to the scale of their pretraining data. We start from the **pretrained checkpoints** and fine-tune on our folding data.
|
| 14 |
|
| 15 |
<Wide>
|
| 16 |
<HtmlEmbed
|
| 17 |
id="pi05-sarm-arch"
|
| 18 |
src="folding/pi05-sarm-architecture.html"
|
| 19 |
+
title="π0.5 Architecture"
|
| 20 |
+
desc="Data flows left to right. Three inputs are fused in the VLM backbone, then the action expert generates motor commands via flow matching. Hover any component for details."
|
| 21 |
+
config={{ initialTab: "pi05", hideTabBar: true }}
|
| 22 |
frameless
|
| 23 |
/>
|
| 24 |
</Wide>
|
| 25 |
|
| 26 |
+
The model takes in three things: **camera images** (3 views: base + 2 wrist), **joint angles** (16D: 7 joints + gripper per arm), and a **language prompt** ("fold the t-shirt"). The images pass through a frozen **SigLIP** vision encoder that converts raw pixels into visual tokens, a compact representation the rest of the model can work with. Those visual tokens, the text, and the joint state all feed into the **Gemma VLM backbone** (2B parameters), which fuses the three modalities through cross-attention. This is where the model builds its understanding of the scene: what the shirt looks like, where the arms are, and what it's supposed to do. The backbone's output then conditions the **Action Expert** (300M parameters), which uses **flow matching** to turn random Gaussian noise into a coherent sequence of 30 actions, which equals one second of joint targets and gripper commands. Flow matching lets the model represent **multi-modal action distributions**: when there are multiple valid ways to grasp a sleeve, it can capture that ambiguity rather than averaging to a meaningless middle ground.
|
| 27 |
|
| 28 |
<Sidenote>
|
| 29 |
Flow matching is closely related to diffusion models but uses a simpler, more direct interpolation path between noise and data.
|
|
|
|
| 31 |
|
| 32 |
#### [Real-Time Chunking (RTC)](https://huggingface.co/docs/lerobot/rtc)
|
| 33 |
|
| 34 |
+
The model outputs 30 actions at once (a "chunk"), but generating that chunk takes ~100..200ms, during which the robot is still moving. Without RTC, the robot would **pause** between chunks while waiting for the next prediction, producing jerky stop-and-go motion. RTC solves this by generating the next chunk **while the current one is still executing**. The key idea: by the time the new chunk is ready, several actions from the old chunk have already been executed and can't be changed, so RTC **freezes** those committed actions and **inpaints** the rest of the new chunk to be consistent with them. This produces smooth, continuous motion with no pauses.
|
| 35 |
|
| 36 |
+
<HtmlEmbed
|
| 37 |
+
id="rtc-explainer"
|
| 38 |
+
src="folding/rtc-explainer.html"
|
| 39 |
+
title="Real-Time Chunking"
|
| 40 |
+
desc="Toggle between synchronous and RTC mode. In synchronous mode, the robot idles during inference. RTC overlaps inference with execution, freezing committed actions (locked) and inpainting the rest. Press play to see the timing."
|
| 41 |
+
frameless
|
| 42 |
+
/>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 43 |
|
| 44 |
+
In practice, RTC sped up our rollouts by at least a factor of 2 compared to synchronous inference.
|
| 45 |
|
| 46 |
+
All experiments use **RTC** with an action queue size of 30 and a maximum action horizon of 20, along with **action interpolation** (upsampling from 30 Hz to 90 Hz) which makes the robot much quieter and smoother.
|
| 47 |
|
| 48 |
+
### Training recipe
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 49 |
|
| 50 |
+
We fine-tune two variants of this architecture: **π0**, the base flow-matching VLA, and **π0.5**, an improved version with more pretraining data and refinements to the denoising process. Both start from pretrained checkpoints. Training runs on **8× H100 GPUs** with a per-GPU batch size of 32 (a total batch size of 256), gradient accumulation, and using **AdamW** with a learning rate of **1e-4** (warmup + cosine decay). The large batch size is important for stable VLA training, and it's what drives the multi-GPU requirement.
|
|
|
|
|
|
|
| 51 |
|
| 52 |
---
|
| 53 |
|
|
|
|
| 63 |
Each t-shirt fold is attempted **twice consecutively**, giving **10 rollouts per level** and **20 rollouts total per experiment**. Every evaluation is filmed and scored from video afterward, so judgment is decoupled from execution.
|
| 64 |
|
| 65 |
<Note>
|
| 66 |
+
The evaluation protocol (t-shirts, attempts count, scoring rubric, and filming setup) is identical across every experiment.
|
| 67 |
</Note>
|
| 68 |
|
| 69 |
### Metrics
|
| 70 |
|
| 71 |
We report four complementary metrics:
|
| 72 |
|
| 73 |
+
1. **Success Rate.** Binary pass/fail per rollout.
|
| 74 |
+
2. **Score.** Partial credit based on subtasks completed. This distinguishes a model that consistently reaches Fold 3 from one that fails at Unfold, even if neither achieves full success.
|
| 75 |
|
| 76 |
<Accordion title="Scoring rubric Level 1 and Level 2">
|
| 77 |
|
app/src/content/chapters/folding/08-ablations.mdx
CHANGED
|
@@ -22,23 +22,11 @@ We started with the simplest possible recipe: train π0 and π0.5 on the full da
|
|
| 22 |
| 1.1 | π0 | All data (5,688 episodes) | 200k | Baseline |
|
| 23 |
| 1.2 | π0.5 | All data (5,688 episodes) | 200k | Baseline |
|
| 24 |
|
| 25 |
-
|
| 26 |
|
| 27 |
-
|
| 28 |
-
<HtmlEmbed
|
| 29 |
-
id="loss-curves"
|
| 30 |
-
src="folding/loss-curves.html"
|
| 31 |
-
title="Training Loss"
|
| 32 |
-
desc="Click legend entries to toggle runs. <strong>Series 1</strong>: trained from pretrained checkpoints on full data (200k steps). <strong>Series 2</strong>: fine-tuned on curated high-quality data (100k steps)."
|
| 33 |
-
frameless
|
| 34 |
-
/>
|
| 35 |
-
</Wide>
|
| 36 |
|
| 37 |
-
|
| 38 |
-
|
| 39 |
-
Watching the rollouts, the problem was clear: the policy had learned the *average* of many different folding strategies in the dataset. Different operators used different grip points, different fold sequences, and different speeds. The model averaged over all of them, producing indecisive, hesitant motions.
|
| 40 |
-
|
| 41 |
-
One early lesson: **undertraining makes the policy shaky**. Make sure your model has converged before drawing conclusions about what works.
|
| 42 |
|
| 43 |
---
|
| 44 |
|
|
@@ -55,7 +43,26 @@ We filtered episodes in two ways:
|
|
| 55 |
|
| 56 |
#### Stage-Aware Reward Modeling (SARM)
|
| 57 |
|
| 58 |
-
To go beyond binary keep/discard filtering, we trained a reward model: **[SARM](https://huggingface.co/docs/lerobot/sarm)** (Stage-Aware Reward Modeling). SARM
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 59 |
|
| 60 |
<Wide>
|
| 61 |
<Stack layout="3-column" gap="small">
|
|
@@ -69,13 +76,13 @@ We annotated every episode in both datasets using SARM. This gave us continuous
|
|
| 69 |
|
| 70 |
---
|
| 71 |
|
| 72 |
-
###
|
| 73 |
|
| 74 |
-
With cleaner data and SARM scores in hand, we
|
| 75 |
|
| 76 |
#### Relative actions
|
| 77 |
|
| 78 |
-
Following the [UMI](https://arxiv.org/abs/2402.10329) approach, we switched from absolute actions to **relative trajectories**: each action in the chunk is an offset from the robot's current state at prediction time, not from the previous action. The state observation remains absolute
|
| 79 |
|
| 80 |
<HtmlEmbed
|
| 81 |
id="action-representations"
|
|
@@ -84,9 +91,9 @@ Following the [UMI](https://arxiv.org/abs/2402.10329) approach, we switched from
|
|
| 84 |
desc="Relative trajectory (blue) references all actions to the current state. Delta (yellow) chains each action to the previous one, accumulating error. Absolute (red) requires a global coordinate frame. Diagram adapted from UMI (Chi et al., 2024)."
|
| 85 |
/>
|
| 86 |
|
| 87 |
-
See the [LeRobot action representations docs](https://huggingface.co/docs/lerobot/action_representations) for a full guide.
|
| 88 |
|
| 89 |
-
The result: experiment 1.3 (π0.5 + relative actions
|
| 90 |
|
| 91 |
<Sidenote>
|
| 92 |
π0.5 is likely pretrained with relative actions, so 1.3 fine-tunes in a regime consistent with pretraining, while 1.2 fine-tunes against it.
|
|
@@ -94,52 +101,63 @@ The result: experiment 1.3 (π0.5 + relative actions + quantile normalization) j
|
|
| 94 |
|
| 95 |
#### RABC (Reward-Advantage-Based Conditioning)
|
| 96 |
|
| 97 |
-
Next, we put SARM's per-timestep scores to work during training
|
| 98 |
|
| 99 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 100 |
|
| 101 |
#### Combining everything
|
| 102 |
|
| 103 |
-
Experiment 1.7 combined
|
| 104 |
|
| 105 |
| Experiment | Key change | Total SR | Level 1 SR | Level 2 SR |
|
| 106 |
|:---:|:---|:---:|:---:|:---:|
|
| 107 |
-
| 1.1 | π0 baseline |
|
| 108 |
| 1.2 | π0.5 baseline | 20% | 40% | 0% |
|
| 109 |
-
| 1.3 | + Relative actions | 35% | 70% | 0% |
|
| 110 |
-
| 1.
|
| 111 |
-
| 1.5 | + RABC (κ=0.0215) | 30% | 60% | 0% |
|
| 112 |
-
| 1.7 | Relative + RABC + QUANTILES | **40%** | **80%** | **0%** |
|
| 113 |
|
| 114 |
-
The trend was encouraging, but one number was impossible to ignore: **every
|
| 115 |
|
| 116 |
#### DAgger: targeted corrections
|
| 117 |
|
| 118 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 119 |
|
| 120 |
-
DAgger
|
| 121 |
|
| 122 |
---
|
| 123 |
|
| 124 |
### The breakthrough: high-quality data
|
| 125 |
|
| 126 |
-
No
|
| 127 |
|
| 128 |
-
We went back to data collection with a clear plan: collect new demonstrations
|
| 129 |
|
| 130 |
-
The result was the **high-quality dataset**: 1,200 episodes combining the best of the original data with new, strategically collected demonstrations.
|
| 131 |
|
| 132 |
-
|
| 133 |
|
| 134 |
| Experiment | Base | Key change | Total SR | Level 1 SR | Level 2 SR |
|
| 135 |
|:---:|:---:|:---|:---:|:---:|:---:|
|
| 136 |
-
| 2.1 | 1.3 | HQ data only |
|
| 137 |
-
| 2.2 | 1.3 | HQ + RABC + Relative | **75%** | **100%** | **50%** |
|
| 138 |
-
| 2.5 | 1.7 | HQ + RABC + Relative | **90%** | **100%** | **80%** |
|
| 139 |
|
| 140 |
-
The jump was dramatic. Experiment 2.5 reached **90% total success rate**
|
| 141 |
|
| 142 |
-
Both 2.2 and 2.5 used the same recipe (HQ + RABC + Relative Actions), but 2.5 fine-tuned from 1.7 (the stronger base with relative actions + RABC already baked in) while 2.2 fine-tuned from 1.3. The difference (75% → 90%) likely reflects this stronger starting point. Data quality was the single biggest lever, and RABC's effect was strongest on **Level 2**
|
| 143 |
|
| 144 |
---
|
| 145 |
|
|
@@ -147,7 +165,8 @@ Both 2.2 and 2.5 used the same recipe (HQ + RABC + Relative Actions), but 2.5 fi
|
|
| 147 |
|
| 148 |
Not every idea improved results:
|
| 149 |
|
| 150 |
-
- **
|
|
|
|
| 151 |
- **Larger chunk size** (experiment 2.4, chunk=45): no improvement over the default chunk size of 30.
|
| 152 |
|
| 153 |
---
|
|
@@ -156,76 +175,48 @@ Not every idea improved results:
|
|
| 156 |
|
| 157 |
Now that the full journey is clear, here is the complete picture across all 11 experiments.
|
| 158 |
|
| 159 |
-
|
| 160 |
-
|
| 161 |
-
|
|
| 162 |
-
|
|
| 163 |
-
| 1.
|
| 164 |
-
| 1.
|
| 165 |
-
| 1.
|
| 166 |
-
| 1.
|
| 167 |
-
|
|
| 168 |
-
|
|
| 169 |
-
| 2.
|
| 170 |
-
| 2.
|
| 171 |
-
| 2.
|
| 172 |
-
| 2.4 | π0.5 | High-quality only | 100k | QUANTILES | Fine-tune from 1.3 · chunk=45 |
|
| 173 |
-
| 2.5 | π0.5 | High-quality only | 100k | QUANTILES | Fine-tune from 1.7 + Reward model (SARM) with RABC κ=0.0265 + Relative Action |
|
| 174 |
-
|
| 175 |
-
</Wide>
|
| 176 |
-
|
| 177 |
-
<HtmlEmbed
|
| 178 |
-
id="success-rates"
|
| 179 |
-
src="folding/success-rates.html"
|
| 180 |
-
title="Success Rates by Experiment"
|
| 181 |
-
desc="Success rates (Total, Level 1, Level 2) across all experiments. Series 1 trains from scratch on full data; Series 2 finetunes the best Series 1 checkpoint on curated high-quality data."
|
| 182 |
-
/>
|
| 183 |
|
| 184 |
<HtmlEmbed
|
| 185 |
-
id="
|
| 186 |
-
src="folding/
|
| 187 |
-
title="
|
| 188 |
-
desc="
|
| 189 |
/>
|
| 190 |
|
| 191 |
-
Total score captures partial progress that binary success rate misses
|
| 192 |
|
| 193 |
-
|
| 194 |
-
id="l1-time-quality"
|
| 195 |
-
src="folding/l1-time-quality.html"
|
| 196 |
-
title="Level 1 Completion Time vs. Fold Quality"
|
| 197 |
-
desc="Each bubble is one experiment. X-axis = completion time (faster is left), Y-axis = fold quality (higher is better), bubble size = total success rate. The best experiments cluster in the top-left corner."
|
| 198 |
-
/>
|
| 199 |
-
|
| 200 |
-
Speed and quality correlate strongly with data quality. Series 2 experiments fold 2–3x faster than Series 1 (40s vs 100s+), and fold quality only breaks past 3.0 with high-quality training data. Faster isn't a separate goal from better — it's a consequence of the policy learning a clear, unambiguous strategy.
|
| 201 |
|
| 202 |
<HtmlEmbed
|
| 203 |
id="subtask-heatmap"
|
| 204 |
src="folding/subtask-heatmap.html"
|
| 205 |
title="Subtask Timing Heatmap"
|
| 206 |
-
desc="
|
| 207 |
/>
|
| 208 |
|
|
|
|
|
|
|
| 209 |
### Where the policies fail
|
| 210 |
|
| 211 |
-
Understanding *how* each experiment fails
|
| 212 |
|
| 213 |
<HtmlEmbed
|
| 214 |
id="failure-analysis"
|
| 215 |
src="folding/failure-analysis.html"
|
| 216 |
title="Failure Analysis"
|
| 217 |
-
desc="Breakdown of failure modes across experiments for Level
|
| 218 |
-
/>
|
| 219 |
-
|
| 220 |
-
### Which differences are real?
|
| 221 |
-
|
| 222 |
-
With 20 rollouts per experiment, not every visible gap is real. We run **[STEP](https://tri-ml.github.io/step/)** (Sequential Testing for Efficient Policy comparison) on all 55 pairs with **Bonferroni correction** (α = 0.10, per-pair α < 0.0018), following [TRI's statistical evaluation framework](https://medium.com/toyotaresearch/statistical-thinking-for-robot-policy-evaluation-from-rigorous-a-b-testing-to-effective-0ae886fbd68d). STEP is a sequential method, meaning we can extend the experiment with additional rollouts (up to n<sub>max</sub> = 50) without risking p-hacking, something batch tests like Barnard's don't allow. The chart below shows the full **Bayesian Beta posterior** over each policy's true success rate. **CLD letters** above each violin indicate which experiments are statistically separable, policies sharing a letter are not significantly different.
|
| 223 |
-
|
| 224 |
-
<HtmlEmbed
|
| 225 |
-
id="statistical-analysis"
|
| 226 |
-
src="folding/statistical-analysis.html"
|
| 227 |
-
title="Statistical Analysis"
|
| 228 |
-
desc="Bayesian posterior distributions over each policy's true success rate, with Compact Letter Display (CLD) groups summarising statistical separability. Toggle between Total, Level 1, and Level 2."
|
| 229 |
/>
|
| 230 |
|
| 231 |
-
The CLD tells a clear story: **Series 2 separates from Series 1**. Experiments 2.5 and 2.2 form group 'a' — statistically distinct from the bulk of Series 1 experiments which cluster in groups 'c' and 'd'. Within Series 1, no experiment separates from another, meaning none of the algorithmic changes (RABC, relative actions, normalization) produced a statistically detectable improvement on their own when trained on the full dataset. The jump only comes from switching to curated high-quality data in Series 2. Toggle to Level 2 and the separation is even starker: every Series 1 experiment sits in group 'b' with 0% success, while 2.5 stands alone in group 'a'.
|
|
|
|
| 22 |
| 1.1 | π0 | All data (5,688 episodes) | 200k | Baseline |
|
| 23 |
| 1.2 | π0.5 | All data (5,688 episodes) | 200k | Baseline |
|
| 24 |
|
| 25 |
+
When we evaluated, the results were mixed. Experiment 1.1 (π0) achieved 40% total success rate (80% Level 1) but 0% Level 2. Experiment 1.2 (π0.5) managed only 20% total (40% Level 1), also with 0% Level 2. The models could sometimes fold a laid-out shirt, but they were slow and produced poor-quality folds. Level 2 (spreading a messy shirt before folding) was completely out of reach.
|
| 26 |
|
| 27 |
+
Watching the rollouts, we suspected that the problem was in the data. Different operators used different grip points and strategies for unspreading the shirt, and the diversity in our dataset was high. The model didn't converge enough and was producing indecisive motions.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 28 |
|
| 29 |
+
An additional early lesson: **undertraining makes the policy shaky**. Make sure your model has converged before drawing conclusions about what works.
|
|
|
|
|
|
|
|
|
|
|
|
|
| 30 |
|
| 31 |
---
|
| 32 |
|
|
|
|
| 43 |
|
| 44 |
#### Stage-Aware Reward Modeling (SARM)
|
| 45 |
|
| 46 |
+
To go beyond binary keep/discard filtering, we trained a reward model: **[SARM](https://huggingface.co/docs/lerobot/sarm)** (Stage-Aware Reward Modeling). The core problem SARM solves: how do you measure "progress" in a long, multi-stage task like t-shirt folding, where demonstrations vary wildly in length and strategy? You can't just use elapsed time, a shirt that's fully flattened might happen at frame 200 in one demo and frame 800 in another. SARM instead learns a semantic notion of progress that generalizes across demonstrations.
|
| 47 |
+
|
| 48 |
+
**How it works.** SARM is a **vision-language** reward model built on a frozen [CLIP](https://openai.com/index/clip/) backbone. It takes **8 RGB frames** (sampled 1 second apart from the base camera), a **task description** ("fold the t-shirt"), and the robot's **joint state**. The language conditioning means the same model could in principle score different tasks; it's not hard-coded to folding.
|
| 49 |
+
|
| 50 |
+
The images and text are encoded by CLIP into embeddings. These flow into a **Stage Transformer**, which classifies which subtask the robot is in (e.g., "grab", "flatten", "fold", "place"), and a **Subtask Transformer**, conditioned on the predicted stage, which estimates fine-grained progress τ within that stage. The final output is a single progress score (`stage + τ`, normalized to [0, 1]). This two-level decomposition makes the reward signal stable across demonstrations of different lengths and styles: a fully flattened shirt always maps to roughly the same progress value, regardless of how long it took to get there. The score is used downstream for **data curation** (ranking episodes by quality) and **RABC training weights** (emphasizing high-progress actions during policy training).
|
| 51 |
+
|
| 52 |
+
<Wide>
|
| 53 |
+
<HtmlEmbed
|
| 54 |
+
id="sarm-architecture"
|
| 55 |
+
src="folding/pi05-sarm-architecture.html"
|
| 56 |
+
title="SARM Architecture"
|
| 57 |
+
desc="Data flows left to right. Inputs are encoded by CLIP, then two transformers predict stage and within-stage progress. The resulting score is used for data curation and training weights. Hover any component for details."
|
| 58 |
+
config={{ initialTab: "sarm", hideTabBar: true }}
|
| 59 |
+
frameless
|
| 60 |
+
/>
|
| 61 |
+
</Wide>
|
| 62 |
+
|
| 63 |
+
**Training data.** SARM supports multi-stage annotations, but we use it in **single-stage mode**: the entire task (Level 2, which includes Level 1) is treated as one stage with progress going linearly from 0 to 1. This means no human subtask annotation is needed, progress labels are derived directly from the frame index within each episode.
|
| 64 |
+
|
| 65 |
+
Once trained, SARM can score *any* trajectory, including noisy demonstrations it was never trained on. It predicts a 0–1 progression signal at every timestep, correctly identifying **mistakes** (drops in value) and **progress** (increases) in real time:
|
| 66 |
|
| 67 |
<Wide>
|
| 68 |
<Stack layout="3-column" gap="small">
|
|
|
|
| 76 |
|
| 77 |
---
|
| 78 |
|
| 79 |
+
### Better training recipes
|
| 80 |
|
| 81 |
+
With cleaner data and SARM scores in hand, we turned to how the model trains on that data.
|
| 82 |
|
| 83 |
#### Relative actions
|
| 84 |
|
| 85 |
+
Following the [UMI](https://arxiv.org/abs/2402.10329) approach, we switched from absolute actions to **relative action trajectories**: each action in the chunk is an offset from the robot's current state at prediction time, not from the previous action. The state observation remains absolute; only the action targets are relative. This avoids error accumulation (unlike true delta).
|
| 86 |
|
| 87 |
<HtmlEmbed
|
| 88 |
id="action-representations"
|
|
|
|
| 91 |
desc="Relative trajectory (blue) references all actions to the current state. Delta (yellow) chains each action to the previous one, accumulating error. Absolute (red) requires a global coordinate frame. Diagram adapted from UMI (Chi et al., 2024)."
|
| 92 |
/>
|
| 93 |
|
| 94 |
+
See the [LeRobot action representations docs](https://huggingface.co/docs/lerobot/main/en/action_representations) for a full guide.
|
| 95 |
|
| 96 |
+
The result: experiment 1.3 (π0.5 + relative actions) jumped to **35% total success rate and 70% Level 1**, up from 1.2's 20% total and 40% Level 1.
|
| 97 |
|
| 98 |
<Sidenote>
|
| 99 |
π0.5 is likely pretrained with relative actions, so 1.3 fine-tunes in a regime consistent with pretraining, while 1.2 fine-tunes against it.
|
|
|
|
| 101 |
|
| 102 |
#### RABC (Reward-Advantage-Based Conditioning)
|
| 103 |
|
| 104 |
+
Next, we put SARM's per-timestep scores to work during training with **[RABC](https://huggingface.co/docs/lerobot/sarm)** (Reward-Aligned Behavior Cloning). Standard behavior cloning treats every action in the dataset equally. RABC instead computes a per-timestep "progress delta" from SARM (the change in predicted task progress over one action chunk) and uses it to weight the training loss. A threshold κ controls how selective the weighting is: actions with progress above κ get full weight, those below are softly down-weighted, and negative-progress actions are clipped to zero weight entirely. The result is that the policy focuses on the best moments in each demonstration without discarding entire episodes.
|
| 105 |
|
| 106 |
+
<HtmlEmbed
|
| 107 |
+
id="rabc-explainer"
|
| 108 |
+
src="folding/rabc-explainer.html"
|
| 109 |
+
title="How RABC works"
|
| 110 |
+
desc="Top: SARM progress curve for a simulated episode. Background color shows where progress advances (green), stalls (yellow), or regresses (red). Bottom: the resulting per-timestep training weight. Drag the κ slider to see how selectivity changes."
|
| 111 |
+
/>
|
| 112 |
|
| 113 |
#### Combining everything
|
| 114 |
|
| 115 |
+
Experiment 1.7 combined **relative actions + RABC**. The result: **40% total success rate, 80% Level 1**, the best of initial training.
|
| 116 |
|
| 117 |
| Experiment | Key change | Total SR | Level 1 SR | Level 2 SR |
|
| 118 |
|:---:|:---|:---:|:---:|:---:|
|
| 119 |
+
| 1.1 | π0 baseline | 40% | 80% | 0% |
|
| 120 |
| 1.2 | π0.5 baseline | 20% | 40% | 0% |
|
| 121 |
+
| 1.3 | π0.5 + Relative actions | 35% (+15) | 70% (+30) | 0% (+0) |
|
| 122 |
+
| 1.7 | π0.5 + Relative + RABC | **40%** (+20) | **80%** (+40) | **0%** (+0) |
|
|
|
|
|
|
|
| 123 |
|
| 124 |
+
The trend was encouraging, but one number was impossible to ignore: **every initial-training experiment achieved exactly 0% Level 2 success**. Algorithmic improvements alone couldn't compensate for the inconsistency in the full dataset.
|
| 125 |
|
| 126 |
#### DAgger: targeted corrections
|
| 127 |
|
| 128 |
+
Throughout our work on Level 1, we developed and integrated **DAgger** (Dataset Aggregation) as a [Human-in-the-Loop (HIL)](https://github.com/huggingface/lerobot/tree/main/examples/hil) data collection method in LeRobot. The idea: the operator watches the policy run, pauses when it fails, teleoperates a recovery, and hands control back. This generates correction data precisely where the model struggles.
|
| 129 |
+
|
| 130 |
+
<HtmlEmbed
|
| 131 |
+
id="dagger-explainer"
|
| 132 |
+
src="folding/dagger-explainer.html"
|
| 133 |
+
title="DAgger Loop"
|
| 134 |
+
desc="The DAgger cycle: the policy runs until it fails, a human operator takes over to demonstrate the correct recovery, and the correction is recorded as new training data. Click to advance manually."
|
| 135 |
+
frameless
|
| 136 |
+
/>
|
| 137 |
|
| 138 |
+
We didn't test DAgger in an isolated experiment, but added the correction data to both the full dataset and the high-quality dataset. In our early experiments, using DAgger to collect targeted corrections proved very beneficial for making the policy more robust to failures, the model learns to recover from states it would otherwise get stuck in. The full [Human-in-the-Loop infrastructure](https://github.com/huggingface/lerobot/tree/main/examples/hil) is now merged into LeRobot for anyone to use.
|
| 139 |
|
| 140 |
---
|
| 141 |
|
| 142 |
### The breakthrough: high-quality data
|
| 143 |
|
| 144 |
+
No initial-training experiment cracked Level 2. We needed a fundamentally better dataset.
|
| 145 |
|
| 146 |
+
We went back to data collection with a clear plan: collect new demonstrations, with a more **unified strategy** across operators. Every episode followed the similar grip points, sequences and timing. We also manually curated the best episodes from the large dataset, selecting only those with consistent technique and clear progress.
|
| 147 |
|
| 148 |
+
The result was the **high-quality dataset**: 1,200 episodes combining the best of the original data with new, strategically collected demonstrations.
|
| 149 |
|
| 150 |
+
We then **fine-tuned** the best initial-training checkpoints on this curated dataset for 100k steps:
|
| 151 |
|
| 152 |
| Experiment | Base | Key change | Total SR | Level 1 SR | Level 2 SR |
|
| 153 |
|:---:|:---:|:---|:---:|:---:|:---:|
|
| 154 |
+
| 2.1 | 1.3 | HQ data only | 40% (+5) | 70% (+0) | 10% (+10) |
|
| 155 |
+
| 2.2 | 1.3 | HQ + RABC + Relative | **75%** (+40) | **100%** (+30) | **50%** (+50) |
|
| 156 |
+
| 2.5 | 1.7 | HQ + RABC + Relative | **90%** (+50) | **100%** (+20) | **80%** (+80) |
|
| 157 |
|
| 158 |
+
The jump was dramatic. Experiment 2.5 reached **90% total success rate**: 100% Level 1, 80% Level 2, up from 40% with initial training. The same architecture, the same training recipe, just better data.
|
| 159 |
|
| 160 |
+
Both 2.2 and 2.5 used the same recipe (HQ + RABC + Relative Actions), but 2.5 fine-tuned from 1.7 (the stronger base with relative actions + RABC already baked in) while 2.2 fine-tuned from 1.3. The difference (75% → 90%) likely reflects this stronger starting point. Data quality was the single biggest lever, and RABC's effect was strongest on **Level 2**, the longer, harder task where emphasizing the best demonstrations mattered most.
|
| 161 |
|
| 162 |
---
|
| 163 |
|
|
|
|
| 165 |
|
| 166 |
Not every idea improved results:
|
| 167 |
|
| 168 |
+
- **RABC on the full dataset** (experiments 1.4, 1.5; without relative actions): we applied SARM's reward weighting directly during initial training on the full dataset, with different selectivity thresholds. Both performed worse than the baseline (1.4: 15% total, 1.5: 0% total). The reward model was only trained on the high-quality dataset, so when applied to the larger dataset it likely down-weighted too much Level 1 data, meaning the model couldn't reliably accomplish the first steps of the task.
|
| 169 |
+
- **Mirroring augmentation** (experiment 2.3): only 5% total success rate. Mirroring the data doubles the number of valid strategies the model sees, which is the opposite of what we needed: convergence on a single consistent approach.
|
| 170 |
- **Larger chunk size** (experiment 2.4, chunk=45): no improvement over the default chunk size of 30.
|
| 171 |
|
| 172 |
---
|
|
|
|
| 175 |
|
| 176 |
Now that the full journey is clear, here is the complete picture across all 11 experiments.
|
| 177 |
|
| 178 |
+
| # | Model | Data | Steps | Key change |
|
| 179 |
+
|:---:|:---:|:---|:---:|:---|
|
| 180 |
+
| 1.1 | π0 | All | 200k | Baseline |
|
| 181 |
+
| 1.2 | π0.5 | All | 200k | Baseline |
|
| 182 |
+
| 1.3 | π0.5 | All | 200k | Relative actions |
|
| 183 |
+
| 1.4 | π0.5 | All | 200k | RABC κ=0.01 |
|
| 184 |
+
| 1.5 | π0.5 | All | 200k | RABC κ=0.0215 |
|
| 185 |
+
| 1.7 | π0.5 | All | 200k | Rel + RABC κ=0.0215 |
|
| 186 |
+
| 2.1 | π0.5 | HQ | 100k | From 1.3 |
|
| 187 |
+
| 2.2 | π0.5 | HQ | 100k | From 1.3 + RABC + Rel |
|
| 188 |
+
| 2.3 | π0.5 | HQ + mirror | 100k | From 1.3 + Rel + mirroring |
|
| 189 |
+
| 2.4 | π0.5 | HQ | 100k | From 1.3 · chunk=45 |
|
| 190 |
+
| 2.5 | π0.5 | HQ | 100k | From 1.7 + RABC + Rel |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 191 |
|
| 192 |
<HtmlEmbed
|
| 193 |
+
id="experiment-results"
|
| 194 |
+
src="folding/experiment-results.html"
|
| 195 |
+
title="Experiment Results"
|
| 196 |
+
desc="Bars show success rate for the selected level; balloons show Bayesian Beta posterior. Letters above each bar are CLD groups from STEP sequential testing (Bonferroni α=0.10/55). Shared letter means not significantly different. Toggle between Total, Level 1, and Level 2 to re-sort and update. Hover for details."
|
| 197 |
/>
|
| 198 |
|
| 199 |
+
Total score captures partial progress that binary success rate misses: even failed rollouts earn credit for completed subtasks. The CLD letters (from **[STEP](https://tri-ml.github.io/step/)** sequential testing with **Bonferroni correction**, α = 0.10, per-pair α < 0.0018, n<sub>max</sub> = 50, following [TRI's statistical evaluation framework](https://medium.com/toyotaresearch/statistical-thinking-for-robot-policy-evaluation-from-rigorous-a-b-testing-to-effective-0ae886fbd68d)) tell a clear story: **fine-tuning separates from initial training**. Experiments 2.5 and 2.2 form group 'a', statistically distinct from the bulk of initial-training experiments which cluster in groups 'c' and 'd'. Within initial training, no experiment separates from another, meaning none of the algorithmic changes (RABC, relative actions, normalization) produced a statistically detectable improvement on their own when trained on the full dataset. The jump only comes from switching to curated high-quality data in fine-tuning. Toggle to Level 2 and the separation is even starker: every initial-training experiment sits in group 'b' with 0% success, while 2.5 stands alone in group 'a'.
|
| 200 |
|
| 201 |
+
Success rate isn't the full picture. A policy that succeeds but takes twice as long or produces sloppy folds isn't really better. The heatmap below breaks down per-subtask timing and fold quality across experiments, revealing which policies are not just more reliable but also faster and more precise:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 202 |
|
| 203 |
<HtmlEmbed
|
| 204 |
id="subtask-heatmap"
|
| 205 |
src="folding/subtask-heatmap.html"
|
| 206 |
title="Subtask Timing Heatmap"
|
| 207 |
+
desc="Each cell shows the experiment's time relative to the cross-experiment average for that subtask (1.0× = average). The two summary columns after the divider show aggregate L1 completion time and fold quality (1–5). Hover for absolute times. Empty cells indicate the subtask was never completed."
|
| 208 |
/>
|
| 209 |
|
| 210 |
+
Speed and quality correlate strongly with data quality: fine-tuned experiments fold 2–3× faster than initial training (40s vs 100s+), and fold quality only breaks past 3.0 with high-quality training data. Faster isn't a separate goal from better: it's a consequence of the policy learning a clear, unambiguous strategy.
|
| 211 |
+
|
| 212 |
### Where the policies fail
|
| 213 |
|
| 214 |
+
Understanding *how* each experiment fails, not just whether it fails, is essential for knowing what to try next. The chart below breaks down every failed rollout by the subtask where the policy got stuck. Level 2 failures are dominated by "Unfold", most initial-training policies can't reliably spread a messy shirt, so they never reach the folding steps. Fine-tuning dramatically reduces unfold failures (2.5 has just 2). Switch to Level 1 (where unfolding is skipped) and the pattern shifts: failures spread across mid-task folds, and the best fine-tuned experiments nearly eliminate them entirely.
|
| 215 |
|
| 216 |
<HtmlEmbed
|
| 217 |
id="failure-analysis"
|
| 218 |
src="folding/failure-analysis.html"
|
| 219 |
title="Failure Analysis"
|
| 220 |
+
desc="Breakdown of failure modes across experiments for Level 1, Level 2. Use the tabs to switch between levels. Each bar shows which subtask the policy failed at, revealing where different experiments struggle."
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 221 |
/>
|
| 222 |
|
|
|
app/src/content/chapters/folding/09-learnings.mdx
CHANGED
|
@@ -8,16 +8,16 @@ Running all these experiments taught us a lot. Some expected, some not. Here's w
|
|
| 8 |
|
| 9 |
Beyond the experiment findings above, several practical insights stood out:
|
| 10 |
|
| 11 |
-
- **Train a reward model.** [SARM](https://huggingface.co/docs/lerobot/sarm) gave us data scoring, advantage conditioning, and curation in one package.
|
| 12 |
- **Invest in recording quality early.** More time upfront on clean and consistent recordings pays off more than extra volume.
|
| 13 |
-
- **Record at higher frequency.** We'd record at 50 fps if we had to do it again. Folding is dynamic and higher record rates capture transitions better.
|
| 14 |
|
| 15 |
### For the community: the order of operations
|
| 16 |
|
| 17 |
If you're training a policy for a new manipulation task with LeRobot, **here's the sequence we'd recommend**:
|
| 18 |
|
| 19 |
1. **Define your task protocol first.** Before collecting a single episode, define exactly how the task should be performed.
|
| 20 |
-
2. **Collect 50
|
| 21 |
3. **Train a reward model.** Use [SARM](https://huggingface.co/docs/lerobot/sarm) to score your episodes and enable RABC during training. This allows the policy to focus on the best demonstrations, which is crucial for longer tasks.
|
| 22 |
4. **Train a baseline and watch it fail.** Film the rollouts. Understanding *how* and *where* it breaks tells you exactly what kind of data to collect next.
|
| 23 |
5. **Enable action interpolation and [RTC](https://huggingface.co/docs/lerobot/rtc).** This smooths transitions and speeds up execution. Action interpolation upsamples the policy's 30 Hz output to your robot's control frequency (e.g. 90 Hz), and RTC overlaps inference with execution. Both features are available at inference time with the corresponding flags:
|
|
@@ -31,7 +31,7 @@ python examples/rtc/eval_with_real_robot.py \
|
|
| 31 |
--interpolation_multiplier=3
|
| 32 |
```
|
| 33 |
|
| 34 |
-
6. **Find the right [action representation](https://huggingface.co/docs/lerobot/action_representations).** LeRobot uses absolute actions by default. Switching to relative trajectory was one of our key improvements, and unlocked consistency with π0.5 pretraining. To enable relative actions for π0/π0.5 using LeRobot, first precompute the relative action statistics for your dataset, then train with the flag enabled:
|
| 35 |
|
| 36 |
```bash
|
| 37 |
# Precompute relative action stats
|
|
@@ -56,14 +56,10 @@ lerobot-train \
|
|
| 56 |
All the innovations from this project [SARM](https://huggingface.co/docs/lerobot/sarm), [RTC](https://huggingface.co/docs/lerobot/rtc), DAgger, [OpenArm](https://huggingface.co/docs/lerobot/openarm), and OpenArm Mini are merged into [LeRobot repository](https://github.com/huggingface/lerobot). You can use our full pipeline as a starting point and swap in your own task.
|
| 57 |
</Note>
|
| 58 |
|
| 59 |
-
###
|
| 60 |
|
| 61 |
-
|
| 62 |
|
| 63 |
-
|
| 64 |
-
- **More robots, teleoperators, VLAs, and reward models.** We're continuing to expand the ecosystem of supported hardware, teleoperation setups, and model architectures in LeRobot.
|
| 65 |
-
- **Reinforcement Learning support.** Extending LeRobot with new reinforcement learning methods and all the infrastructure needed to train policies online, not just from offline demonstrations.
|
| 66 |
-
- **Better data interpretability tools.** Our biggest lever was data quality, but finding *which* demonstrations help and which hurt is still a hard problem. The field needs tools that provide understanding of what makes a trajectory useful before you train on it. If you're exploring data curation or interpretability for robotics, we'd love to hear from you — [let's talk](https://huggingface.co/lerobot).
|
| 67 |
-
- **Democratize robot learning.** Continue to lower the barrier to entry and share every insight, tool, and method with the community.
|
| 68 |
|
| 69 |
-
We
|
|
|
|
| 8 |
|
| 9 |
Beyond the experiment findings above, several practical insights stood out:
|
| 10 |
|
| 11 |
+
- **Train a reward model.** [SARM](https://huggingface.co/docs/lerobot/sarm) gave us data scoring, advantage conditioning, and curation in one package.
|
| 12 |
- **Invest in recording quality early.** More time upfront on clean and consistent recordings pays off more than extra volume.
|
| 13 |
+
- **Record/Teleoperate at higher frequency.** We'd record at 50 fps instead of 30 fps if we had to do it again. Folding is dynamic and higher record rates capture transitions better.
|
| 14 |
|
| 15 |
### For the community: the order of operations
|
| 16 |
|
| 17 |
If you're training a policy for a new manipulation task with LeRobot, **here's the sequence we'd recommend**:
|
| 18 |
|
| 19 |
1. **Define your task protocol first.** Before collecting a single episode, define exactly how the task should be performed.
|
| 20 |
+
2. **Collect 30-50 clean demonstrations per item/background** Quality over volume. Consistent technique, deliberate motions. This is your foundation, everything else builds on it.
|
| 21 |
3. **Train a reward model.** Use [SARM](https://huggingface.co/docs/lerobot/sarm) to score your episodes and enable RABC during training. This allows the policy to focus on the best demonstrations, which is crucial for longer tasks.
|
| 22 |
4. **Train a baseline and watch it fail.** Film the rollouts. Understanding *how* and *where* it breaks tells you exactly what kind of data to collect next.
|
| 23 |
5. **Enable action interpolation and [RTC](https://huggingface.co/docs/lerobot/rtc).** This smooths transitions and speeds up execution. Action interpolation upsamples the policy's 30 Hz output to your robot's control frequency (e.g. 90 Hz), and RTC overlaps inference with execution. Both features are available at inference time with the corresponding flags:
|
|
|
|
| 31 |
--interpolation_multiplier=3
|
| 32 |
```
|
| 33 |
|
| 34 |
+
6. **Find the right [action representation](https://huggingface.co/docs/lerobot/main/en/action_representations).** LeRobot uses absolute actions by default. Switching to relative trajectory was one of our key improvements, and unlocked consistency with π0.5 pretraining. To enable relative actions for π0/π0.5 using LeRobot, first precompute the relative action statistics for your dataset, then train with the flag enabled:
|
| 35 |
|
| 36 |
```bash
|
| 37 |
# Precompute relative action stats
|
|
|
|
| 56 |
All the innovations from this project [SARM](https://huggingface.co/docs/lerobot/sarm), [RTC](https://huggingface.co/docs/lerobot/rtc), DAgger, [OpenArm](https://huggingface.co/docs/lerobot/openarm), and OpenArm Mini are merged into [LeRobot repository](https://github.com/huggingface/lerobot). You can use our full pipeline as a starting point and swap in your own task.
|
| 57 |
</Note>
|
| 58 |
|
| 59 |
+
### The bigger picture
|
| 60 |
|
| 61 |
+
Across 11 experiments we varied models, action representations, reward weighting, and chunk sizes. Algorithmic changes moved success rates by 5–20 percentage points. Finetuning on a curated dataset one-fifth the size moved it by 50. The bottleneck wasn't the model, it was the data. That means you don't need thousands of hours or a fleet of robots to get strong results. You need dozens of hours of clean, consistent demonstrations and the tooling to score and curate them, which now exists in LeRobot.
|
| 62 |
|
| 63 |
+
### Build on this
|
|
|
|
|
|
|
|
|
|
|
|
|
| 64 |
|
| 65 |
+
We open-sourced everything: code, data, models, reward model, and now the full story of what worked and what didn't, so the community can push further. Use our [dataset](https://huggingface.co/datasets/lerobot/high_quality_folding) directly, try new architectures, experiment with different recipes. If you find something that works, we'd be happy to run your models on our physical setups and share the results back.
|
app/src/content/chapters/folding/12-references.mdx
CHANGED
|
@@ -23,11 +23,24 @@ This project would not have been possible without the contributions and support
|
|
| 23 |
|
| 24 |
A special thank you to our robot operators, who spent countless hours patiently demonstrating shirt folds and collecting the high-quality data that made this entire project possible: **Shiyu Liu**, **Alison Magniez**, **Hamza Ben Taieb**, **Victor Gomez**, **Stéphane Combo**, **Domitille Bissery**, **Tiphaine de Cherisey**, **Nathalie Vi** and **Melaku Yemaneberhan**
|
| 25 |
|
|
|
|
|
|
|
| 26 |
### Papers
|
| 27 |
|
| 28 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 29 |
- **RTC** Black, Galliker & Levine (2025). *Real-Time Execution of Action Chunking Flow Policies.* [pi.website/research/real_time_chunking](https://www.pi.website/research/real_time_chunking) · [LeRobot docs](https://huggingface.co/docs/lerobot/rtc)
|
| 30 |
- **SARM** Chen et al. (2025). *Stage-Aware Reward Modeling for Long Horizon Robot Manipulation.* [arxiv.org/abs/2509.25358](https://arxiv.org/abs/2509.25358) · [LeRobot docs](https://huggingface.co/docs/lerobot/sarm)
|
|
|
|
| 31 |
- **DAgger** Ross, Gordon & Bagnell (2011). *A Reduction of Imitation Learning and Structured Prediction to No-Regret Online Learning.* [arxiv.org/abs/1011.0686](https://arxiv.org/abs/1011.0686)
|
|
|
|
|
|
|
|
|
|
|
|
|
| 32 |
|
| 33 |
*PS if you want to use this format for writing your own blog, check out the [Research Article Template](https://huggingface.co/spaces/tfrere/research-article-template).*
|
|
|
|
| 23 |
|
| 24 |
A special thank you to our robot operators, who spent countless hours patiently demonstrating shirt folds and collecting the high-quality data that made this entire project possible: **Shiyu Liu**, **Alison Magniez**, **Hamza Ben Taieb**, **Victor Gomez**, **Stéphane Combo**, **Domitille Bissery**, **Tiphaine de Cherisey**, **Nathalie Vi** and **Melaku Yemaneberhan**
|
| 25 |
|
| 26 |
+
We also want to thank the [Enactic](https://github.com/enactic/openarm) team and [WowRobo](https://wowrobo.com/) for their support throughout this project.
|
| 27 |
+
|
| 28 |
### Papers
|
| 29 |
|
| 30 |
+
#### Models we trained
|
| 31 |
+
|
| 32 |
+
- **π0** Black et al. (2024). *π0: A Vision-Language-Action Flow Model for General Robot Control.* [arxiv.org/abs/2410.24164](https://arxiv.org/abs/2410.24164) · [LeRobot docs](https://huggingface.co/docs/lerobot/pi0)
|
| 33 |
+
- **π0.5** Black et al. (2025). *π0.5: A Vision-Language-Action Model with Open-World Generalization.* [pi.website/blog/pi05](https://www.pi.website/blog/pi05) · [LeRobot docs](https://huggingface.co/docs/lerobot/pi05)
|
| 34 |
+
|
| 35 |
+
#### Techniques we used
|
| 36 |
+
|
| 37 |
- **RTC** Black, Galliker & Levine (2025). *Real-Time Execution of Action Chunking Flow Policies.* [pi.website/research/real_time_chunking](https://www.pi.website/research/real_time_chunking) · [LeRobot docs](https://huggingface.co/docs/lerobot/rtc)
|
| 38 |
- **SARM** Chen et al. (2025). *Stage-Aware Reward Modeling for Long Horizon Robot Manipulation.* [arxiv.org/abs/2509.25358](https://arxiv.org/abs/2509.25358) · [LeRobot docs](https://huggingface.co/docs/lerobot/sarm)
|
| 39 |
+
- **UMI** Chi et al. (2024). *Universal Manipulation Interface: In-The-Wild Robot Teaching Without In-The-Wild Robots.* [arxiv.org/abs/2402.10329](https://arxiv.org/abs/2402.10329)
|
| 40 |
- **DAgger** Ross, Gordon & Bagnell (2011). *A Reduction of Imitation Learning and Structured Prediction to No-Regret Online Learning.* [arxiv.org/abs/1011.0686](https://arxiv.org/abs/1011.0686)
|
| 41 |
+
- **HG-DAgger** Kelly et al. (2019). *HG-DAgger: Interactive Imitation Learning with Human Experts.* [arxiv.org/abs/1810.02890](https://arxiv.org/abs/1810.02890)
|
| 42 |
+
- **RaC** Hu et al. (2025). *Robot Learning for Long-Horizon Tasks by Scaling Recovery and Correction.* [arxiv.org/abs/2509.07953](https://arxiv.org/abs/2509.07953)
|
| 43 |
+
- **STEP** Mandalika et al. (2025). *STEP: A Statistical Testing Framework for Efficient Policy Evaluation.* [tri-ml.github.io/step](https://tri-ml.github.io/step/)
|
| 44 |
+
|
| 45 |
|
| 46 |
*PS if you want to use this format for writing your own blog, check out the [Research Article Template](https://huggingface.co/spaces/tfrere/research-article-template).*
|
app/src/content/embeds/folding/action-representations.html
CHANGED
|
@@ -59,20 +59,20 @@ g.append('path').datum(poses).attr('d', line)
|
|
| 59 |
.attr('stroke', '#444').attr('stroke-dasharray', '5,4').attr('stroke-width', 0.8);
|
| 60 |
});
|
| 61 |
g.append('text').attr('x', x(2)).attr('y', -4).attr('text-anchor', 'middle')
|
| 62 |
-
.attr('fill', SUB).attr('font-size',
|
| 63 |
g.append('text').attr('x', x(6)).attr('y', -4).attr('text-anchor', 'middle')
|
| 64 |
-
.attr('fill', SUB).attr('font-size',
|
| 65 |
|
| 66 |
// Axes
|
| 67 |
g.append('line').attr('x1', x(0)).attr('x2', x(8.3)).attr('y1', h).attr('y2', h).attr('stroke', '#444');
|
| 68 |
g.append('line').attr('x1', x(0)).attr('x2', x(0)).attr('y1', h).attr('y2', y(6.2)).attr('stroke', '#444');
|
| 69 |
for (let t = 0; t <= 8; t++) {
|
| 70 |
g.append('text').attr('x', x(t)).attr('y', h + 14).attr('text-anchor', 'middle')
|
| 71 |
-
.attr('fill', SUB).attr('font-size',
|
| 72 |
}
|
| 73 |
-
g.append('text').attr('x', x(8.4)).attr('y', h + 4).attr('fill', SUB).attr('font-size',
|
| 74 |
g.append('text').attr('x', x(-0.1)).attr('y', y(6.4)).attr('text-anchor', 'end')
|
| 75 |
-
.attr('fill', SUB).attr('font-size',
|
| 76 |
|
| 77 |
// --- DELTA (yellow): straight arrows, offset below trajectory ---
|
| 78 |
const dOff = 14;
|
|
@@ -112,7 +112,7 @@ const bY = h + 22;
|
|
| 112 |
.attr('d', `M${x(t0)},${bY-3} L${x(t0)},${bY} L${x(t1)},${bY} L${x(t1)},${bY-3}`)
|
| 113 |
.attr('fill', 'none').attr('stroke', COL_REL).attr('stroke-width', 1.2).attr('opacity', 0.5);
|
| 114 |
g.append('text').attr('x', (x(t0) + x(t1)) / 2).attr('y', bY + 12)
|
| 115 |
-
.attr('text-anchor', 'middle').attr('fill', COL_REL).attr('font-size',
|
| 116 |
.text('chunk = 4');
|
| 117 |
});
|
| 118 |
|
|
@@ -138,7 +138,7 @@ legItems.forEach(item => {
|
|
| 138 |
}
|
| 139 |
lx += sw + 6;
|
| 140 |
const t = g.append('text').attr('x', lx).attr('y', legY + 6)
|
| 141 |
-
.attr('fill', item.col).attr('font-size',
|
| 142 |
lx += t.node().getComputedTextLength() + 16;
|
| 143 |
});
|
| 144 |
}
|
|
|
|
| 59 |
.attr('stroke', '#444').attr('stroke-dasharray', '5,4').attr('stroke-width', 0.8);
|
| 60 |
});
|
| 61 |
g.append('text').attr('x', x(2)).attr('y', -4).attr('text-anchor', 'middle')
|
| 62 |
+
.attr('fill', SUB).attr('font-size', 11).text('Inference start at t=0');
|
| 63 |
g.append('text').attr('x', x(6)).attr('y', -4).attr('text-anchor', 'middle')
|
| 64 |
+
.attr('fill', SUB).attr('font-size', 11).text('Inference start at t=4');
|
| 65 |
|
| 66 |
// Axes
|
| 67 |
g.append('line').attr('x1', x(0)).attr('x2', x(8.3)).attr('y1', h).attr('y2', h).attr('stroke', '#444');
|
| 68 |
g.append('line').attr('x1', x(0)).attr('x2', x(0)).attr('y1', h).attr('y2', y(6.2)).attr('stroke', '#444');
|
| 69 |
for (let t = 0; t <= 8; t++) {
|
| 70 |
g.append('text').attr('x', x(t)).attr('y', h + 14).attr('text-anchor', 'middle')
|
| 71 |
+
.attr('fill', SUB).attr('font-size', 12).text(t);
|
| 72 |
}
|
| 73 |
+
g.append('text').attr('x', x(8.4)).attr('y', h + 4).attr('fill', SUB).attr('font-size', 12).text('Time');
|
| 74 |
g.append('text').attr('x', x(-0.1)).attr('y', y(6.4)).attr('text-anchor', 'end')
|
| 75 |
+
.attr('fill', SUB).attr('font-size', 12).text('Pose');
|
| 76 |
|
| 77 |
// --- DELTA (yellow): straight arrows, offset below trajectory ---
|
| 78 |
const dOff = 14;
|
|
|
|
| 112 |
.attr('d', `M${x(t0)},${bY-3} L${x(t0)},${bY} L${x(t1)},${bY} L${x(t1)},${bY-3}`)
|
| 113 |
.attr('fill', 'none').attr('stroke', COL_REL).attr('stroke-width', 1.2).attr('opacity', 0.5);
|
| 114 |
g.append('text').attr('x', (x(t0) + x(t1)) / 2).attr('y', bY + 12)
|
| 115 |
+
.attr('text-anchor', 'middle').attr('fill', COL_REL).attr('font-size', 11).attr('opacity', 0.7)
|
| 116 |
.text('chunk = 4');
|
| 117 |
});
|
| 118 |
|
|
|
|
| 138 |
}
|
| 139 |
lx += sw + 6;
|
| 140 |
const t = g.append('text').attr('x', lx).attr('y', legY + 6)
|
| 141 |
+
.attr('fill', item.col).attr('font-size', 12).attr('font-weight', 500).text(item.label);
|
| 142 |
lx += t.node().getComputedTextLength() + 16;
|
| 143 |
});
|
| 144 |
}
|
app/src/content/embeds/folding/dagger-explainer.html
ADDED
|
@@ -0,0 +1,277 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<!DOCTYPE html>
|
| 2 |
+
<html lang="en">
|
| 3 |
+
<head>
|
| 4 |
+
<meta charset="UTF-8"/>
|
| 5 |
+
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
| 6 |
+
<style>
|
| 7 |
+
* { box-sizing: border-box; margin: 0; padding: 0; }
|
| 8 |
+
body { background: transparent; font-family: system-ui, -apple-system, sans-serif; }
|
| 9 |
+
.dagger-wrap { position: relative; width: 100%; }
|
| 10 |
+
.dagger-desc {
|
| 11 |
+
text-align: center; font-size: 14px; font-weight: 500;
|
| 12 |
+
min-height: 36px; padding: 2px 20px; line-height: 1.6;
|
| 13 |
+
transition: color 0.3s;
|
| 14 |
+
}
|
| 15 |
+
</style>
|
| 16 |
+
</head>
|
| 17 |
+
<body>
|
| 18 |
+
<div class="dagger-wrap">
|
| 19 |
+
<svg id="dagger-svg"></svg>
|
| 20 |
+
<div class="dagger-desc" id="dagger-desc"></div>
|
| 21 |
+
</div>
|
| 22 |
+
<script>
|
| 23 |
+
function _initDAgger() {
|
| 24 |
+
|
| 25 |
+
var container = document.getElementById("dagger-svg").parentElement;
|
| 26 |
+
var svg = d3.select("#dagger-svg");
|
| 27 |
+
var descEl = document.getElementById("dagger-desc");
|
| 28 |
+
|
| 29 |
+
function isLight() {
|
| 30 |
+
try {
|
| 31 |
+
var el = document.querySelector("[data-theme]") || document.documentElement;
|
| 32 |
+
return el.getAttribute("data-theme") === "light";
|
| 33 |
+
} catch(e) { return false; }
|
| 34 |
+
}
|
| 35 |
+
|
| 36 |
+
var DARK = {
|
| 37 |
+
text: "#e8eaf0", subtext: "#8b8fa8", dim: "#333", dimArrow: "#3a3d50",
|
| 38 |
+
nodeBg: "#1a1d2e", glowAlpha: 0.22, barBg: "#252540", barFill: "#10b981",
|
| 39 |
+
};
|
| 40 |
+
var LIGHT = {
|
| 41 |
+
text: "#1a1a2e", subtext: "#6b7280", dim: "#d1d5db", dimArrow: "#bfc3cc",
|
| 42 |
+
nodeBg: "#f9fafb", glowAlpha: 0.14, barBg: "#e5e7eb", barFill: "#10b981",
|
| 43 |
+
};
|
| 44 |
+
function th() { return isLight() ? LIGHT : DARK; }
|
| 45 |
+
|
| 46 |
+
var stages = [
|
| 47 |
+
{
|
| 48 |
+
id: "policy", label: "Policy runs",
|
| 49 |
+
dark: "#22d3ee", light: "#0891b2",
|
| 50 |
+
desc: "The learned policy executes autonomously on a new task attempt",
|
| 51 |
+
},
|
| 52 |
+
{
|
| 53 |
+
id: "failure", label: "Failure",
|
| 54 |
+
dark: "#ef4444", light: "#dc2626",
|
| 55 |
+
desc: "Policy drifts into unfamiliar states. Traditional cloning suffers T\u00B2\u03B5 compounding errors.",
|
| 56 |
+
},
|
| 57 |
+
{
|
| 58 |
+
id: "human", label: "Human takeover",
|
| 59 |
+
dark: "#f59e0b", light: "#d97706",
|
| 60 |
+
desc: "Operator pauses the policy and teleoperates the correct recovery",
|
| 61 |
+
},
|
| 62 |
+
{
|
| 63 |
+
id: "record", label: "Record correction",
|
| 64 |
+
dark: "#10b981", light: "#059669",
|
| 65 |
+
desc: "Correction trajectory added to the aggregate dataset: D \u2190 D \u222A D\u1D62",
|
| 66 |
+
},
|
| 67 |
+
{
|
| 68 |
+
id: "retrain", label: "Retrain",
|
| 69 |
+
dark: "#8b5cf6", light: "#7c3aed",
|
| 70 |
+
desc: "Policy retrained on all data. Each iteration closes the distribution gap.",
|
| 71 |
+
},
|
| 72 |
+
];
|
| 73 |
+
|
| 74 |
+
var N = stages.length;
|
| 75 |
+
var activeIdx = 0;
|
| 76 |
+
var iteration = 1;
|
| 77 |
+
var animTimer = null;
|
| 78 |
+
|
| 79 |
+
function col(i) { return isLight() ? stages[i].light : stages[i].dark; }
|
| 80 |
+
|
| 81 |
+
function drawIcon(g, id, cx, cy, size, fill) {
|
| 82 |
+
var s = size;
|
| 83 |
+
if (id === "policy") {
|
| 84 |
+
g.append("polygon")
|
| 85 |
+
.attr("points", (cx-s*0.22)+","+(cy-s*0.42)+" "+(cx+s*0.48)+","+cy+" "+(cx-s*0.22)+","+(cy+s*0.42))
|
| 86 |
+
.attr("fill", fill);
|
| 87 |
+
} else if (id === "failure") {
|
| 88 |
+
g.append("rect")
|
| 89 |
+
.attr("x", cx-1.6).attr("y", cy-s*0.34).attr("width", 3.2).attr("height", s*0.4)
|
| 90 |
+
.attr("rx", 1.6).attr("fill", fill);
|
| 91 |
+
g.append("circle").attr("cx", cx).attr("cy", cy+s*0.26).attr("r", 2).attr("fill", fill);
|
| 92 |
+
} else if (id === "human") {
|
| 93 |
+
g.append("circle").attr("cx", cx).attr("cy", cy-s*0.2).attr("r", s*0.16).attr("fill", fill);
|
| 94 |
+
g.append("path")
|
| 95 |
+
.attr("d", "M"+(cx-s*0.32)+","+(cy+s*0.38)+" Q"+(cx-s*0.32)+","+(cy+s*0.06)+" "+cx+","+(cy+s*0.06)+" Q"+(cx+s*0.32)+","+(cy+s*0.06)+" "+(cx+s*0.32)+","+(cy+s*0.38))
|
| 96 |
+
.attr("fill", fill);
|
| 97 |
+
} else if (id === "record") {
|
| 98 |
+
var lw = 2.6;
|
| 99 |
+
g.append("line").attr("x1", cx-s*0.28).attr("y1", cy).attr("x2", cx+s*0.28).attr("y2", cy)
|
| 100 |
+
.attr("stroke", fill).attr("stroke-width", lw).attr("stroke-linecap", "round");
|
| 101 |
+
g.append("line").attr("x1", cx).attr("y1", cy-s*0.28).attr("x2", cx).attr("y2", cy+s*0.28)
|
| 102 |
+
.attr("stroke", fill).attr("stroke-width", lw).attr("stroke-linecap", "round");
|
| 103 |
+
} else if (id === "retrain") {
|
| 104 |
+
var rr = s * 0.3;
|
| 105 |
+
var sa = -Math.PI * 0.65, ea = Math.PI * 0.8;
|
| 106 |
+
g.append("path")
|
| 107 |
+
.attr("d", "M"+(cx+rr*Math.cos(sa))+","+(cy+rr*Math.sin(sa))+" A"+rr+","+rr+" 0 1,1 "+(cx+rr*Math.cos(ea))+","+(cy+rr*Math.sin(ea)))
|
| 108 |
+
.attr("fill", "none").attr("stroke", fill).attr("stroke-width", 2.4)
|
| 109 |
+
.attr("stroke-linecap", "round");
|
| 110 |
+
var tx = cx + rr * Math.cos(ea), ty = cy + rr * Math.sin(ea);
|
| 111 |
+
var tang = ea + Math.PI / 2;
|
| 112 |
+
var as = 4.5;
|
| 113 |
+
g.append("polygon")
|
| 114 |
+
.attr("points",
|
| 115 |
+
(tx + as * Math.cos(tang - 0.6))+","+(ty + as * Math.sin(tang - 0.6))+" "+
|
| 116 |
+
tx+","+ty+" "+
|
| 117 |
+
(tx + as * Math.cos(tang + 0.6))+","+(ty + as * Math.sin(tang + 0.6)))
|
| 118 |
+
.attr("fill", fill);
|
| 119 |
+
}
|
| 120 |
+
}
|
| 121 |
+
|
| 122 |
+
function render() {
|
| 123 |
+
svg.selectAll("*").remove();
|
| 124 |
+
var c = th();
|
| 125 |
+
|
| 126 |
+
var W = container.clientWidth || 500;
|
| 127 |
+
var padX = 74;
|
| 128 |
+
var R = Math.min((W - padX * 2) * 0.42, 118);
|
| 129 |
+
var nodeR = Math.min(26, R * 0.24);
|
| 130 |
+
var labelH = nodeR + 14 + 8;
|
| 131 |
+
var cx = W / 2, cy = R + labelH + 6;
|
| 132 |
+
var H = cy + R + labelH + 10;
|
| 133 |
+
|
| 134 |
+
svg.attr("width", W).attr("height", H);
|
| 135 |
+
var g = svg.append("g");
|
| 136 |
+
|
| 137 |
+
var defs = svg.append("defs");
|
| 138 |
+
|
| 139 |
+
var angleStart = -Math.PI / 2;
|
| 140 |
+
var positions = stages.map(function(s, i) {
|
| 141 |
+
var a = angleStart + (2 * Math.PI * i) / N;
|
| 142 |
+
return { x: cx + R * Math.cos(a), y: cy + R * Math.sin(a), angle: a };
|
| 143 |
+
});
|
| 144 |
+
|
| 145 |
+
for (var i = 0; i < N; i++) {
|
| 146 |
+
var isAct = i === activeIdx;
|
| 147 |
+
var ac = isAct ? col(i) : c.dimArrow;
|
| 148 |
+
defs.append("marker").attr("id", "da-" + i)
|
| 149 |
+
.attr("viewBox", "0 0 10 8").attr("refX", 9).attr("refY", 4)
|
| 150 |
+
.attr("markerWidth", 6).attr("markerHeight", 5).attr("orient", "auto")
|
| 151 |
+
.append("polygon").attr("points", "0,0 10,4 0,8").attr("fill", ac);
|
| 152 |
+
}
|
| 153 |
+
|
| 154 |
+
for (var i = 0; i < N; i++) {
|
| 155 |
+
var from = positions[i], to = positions[(i + 1) % N];
|
| 156 |
+
var isAct = i === activeIdx;
|
| 157 |
+
var dx = to.x - from.x, dy = to.y - from.y;
|
| 158 |
+
var dist = Math.sqrt(dx * dx + dy * dy);
|
| 159 |
+
var ux = dx / dist, uy = dy / dist;
|
| 160 |
+
var gap = nodeR + 6;
|
| 161 |
+
var x1 = from.x + ux * gap, y1 = from.y + uy * gap;
|
| 162 |
+
var x2 = to.x - ux * (gap + 4), y2 = to.y - uy * (gap + 4);
|
| 163 |
+
|
| 164 |
+
var mx = (x1 + x2) / 2, my = (y1 + y2) / 2;
|
| 165 |
+
var bow = dist * 0.1;
|
| 166 |
+
var pmx = (from.x + to.x) / 2 - cx, pmy = (from.y + to.y) / 2 - cy;
|
| 167 |
+
var pDist = Math.sqrt(pmx * pmx + pmy * pmy) || 1;
|
| 168 |
+
var cpx = mx + (pmx / pDist) * bow, cpy = my + (pmy / pDist) * bow;
|
| 169 |
+
|
| 170 |
+
g.append("path")
|
| 171 |
+
.attr("d", "M"+x1+","+y1+" Q"+cpx+","+cpy+" "+x2+","+y2)
|
| 172 |
+
.attr("fill", "none")
|
| 173 |
+
.attr("stroke", isAct ? col(i) : c.dimArrow)
|
| 174 |
+
.attr("stroke-width", isAct ? 2.2 : 1.4)
|
| 175 |
+
.attr("marker-end", "url(#da-" + i + ")")
|
| 176 |
+
.attr("opacity", isAct ? 1 : 0.35);
|
| 177 |
+
}
|
| 178 |
+
|
| 179 |
+
positions.forEach(function(pos, i) {
|
| 180 |
+
var s = stages[i];
|
| 181 |
+
var isAct = i === activeIdx;
|
| 182 |
+
var sc = col(i);
|
| 183 |
+
|
| 184 |
+
if (isAct) {
|
| 185 |
+
g.append("circle")
|
| 186 |
+
.attr("cx", pos.x).attr("cy", pos.y).attr("r", nodeR + 6)
|
| 187 |
+
.attr("fill", sc).attr("opacity", c.glowAlpha);
|
| 188 |
+
}
|
| 189 |
+
|
| 190 |
+
g.append("circle")
|
| 191 |
+
.attr("cx", pos.x).attr("cy", pos.y).attr("r", nodeR)
|
| 192 |
+
.attr("fill", isAct ? sc : c.nodeBg)
|
| 193 |
+
.attr("stroke", isAct ? sc : c.dim)
|
| 194 |
+
.attr("stroke-width", isAct ? 2 : 1.4)
|
| 195 |
+
.attr("opacity", isAct ? 1 : 0.55);
|
| 196 |
+
|
| 197 |
+
var iconG = g.append("g").attr("opacity", isAct ? 1 : 0.5);
|
| 198 |
+
drawIcon(iconG, s.id, pos.x, pos.y, nodeR * 0.8, isAct ? "#fff" : sc);
|
| 199 |
+
|
| 200 |
+
var a = pos.angle;
|
| 201 |
+
var cosA = Math.cos(a), sinA = Math.sin(a);
|
| 202 |
+
var labelDist = nodeR + 14;
|
| 203 |
+
var lx = pos.x + labelDist * cosA;
|
| 204 |
+
var ly = pos.y + labelDist * sinA;
|
| 205 |
+
var anchor = "middle";
|
| 206 |
+
if (cosA < -0.3) anchor = "end";
|
| 207 |
+
else if (cosA > 0.3) anchor = "start";
|
| 208 |
+
if (sinA < -0.5) ly -= 3;
|
| 209 |
+
if (sinA > 0.5) ly += 5;
|
| 210 |
+
lx = Math.max(4, Math.min(W - 4, lx));
|
| 211 |
+
|
| 212 |
+
g.append("text")
|
| 213 |
+
.attr("x", lx).attr("y", ly)
|
| 214 |
+
.attr("text-anchor", anchor).attr("dominant-baseline", "middle")
|
| 215 |
+
.attr("fill", isAct ? (isLight() ? sc : "#e8eaf0") : c.subtext)
|
| 216 |
+
.attr("font-size", 12).attr("font-weight", isAct ? 700 : 400)
|
| 217 |
+
.attr("font-family", "system-ui, sans-serif")
|
| 218 |
+
.text(s.label);
|
| 219 |
+
});
|
| 220 |
+
|
| 221 |
+
g.append("text")
|
| 222 |
+
.attr("x", cx).attr("y", cy - 8)
|
| 223 |
+
.attr("text-anchor", "middle").attr("dominant-baseline", "middle")
|
| 224 |
+
.attr("font-size", 12).attr("font-weight", 600)
|
| 225 |
+
.attr("fill", c.subtext).attr("font-family", "system-ui, sans-serif")
|
| 226 |
+
.text("Iteration " + iteration);
|
| 227 |
+
|
| 228 |
+
var barW = R * 0.55, barH = 3.5, barY = cy + 5;
|
| 229 |
+
g.append("rect")
|
| 230 |
+
.attr("x", cx - barW / 2).attr("y", barY)
|
| 231 |
+
.attr("width", barW).attr("height", barH)
|
| 232 |
+
.attr("rx", 1.75).attr("fill", c.barBg);
|
| 233 |
+
var fillW = barW * Math.min(1, iteration / 6);
|
| 234 |
+
if (fillW > 0) {
|
| 235 |
+
g.append("rect")
|
| 236 |
+
.attr("x", cx - barW / 2).attr("y", barY)
|
| 237 |
+
.attr("width", fillW).attr("height", barH)
|
| 238 |
+
.attr("rx", 1.75).attr("fill", c.barFill).attr("opacity", 0.7);
|
| 239 |
+
}
|
| 240 |
+
g.append("text")
|
| 241 |
+
.attr("x", cx).attr("y", barY + barH + 10)
|
| 242 |
+
.attr("text-anchor", "middle").attr("dominant-baseline", "middle")
|
| 243 |
+
.attr("font-size", 10).attr("fill", c.subtext).attr("opacity", 0.6)
|
| 244 |
+
.attr("font-family", "system-ui, sans-serif")
|
| 245 |
+
.text("dataset size");
|
| 246 |
+
|
| 247 |
+
descEl.textContent = stages[activeIdx].desc;
|
| 248 |
+
descEl.style.color = col(activeIdx);
|
| 249 |
+
}
|
| 250 |
+
|
| 251 |
+
function step() {
|
| 252 |
+
activeIdx = (activeIdx + 1) % N;
|
| 253 |
+
if (activeIdx === 0) iteration = iteration >= 6 ? 1 : iteration + 1;
|
| 254 |
+
render();
|
| 255 |
+
}
|
| 256 |
+
|
| 257 |
+
render();
|
| 258 |
+
animTimer = setInterval(step, 2200);
|
| 259 |
+
window.addEventListener("resize", render);
|
| 260 |
+
|
| 261 |
+
svg.node().addEventListener("click", function() {
|
| 262 |
+
clearInterval(animTimer);
|
| 263 |
+
step();
|
| 264 |
+
animTimer = setInterval(step, 2200);
|
| 265 |
+
});
|
| 266 |
+
|
| 267 |
+
var obs = new MutationObserver(function() { render(); });
|
| 268 |
+
var themeEl = document.querySelector("[data-theme]") || document.documentElement;
|
| 269 |
+
obs.observe(themeEl, { attributes: true, attributeFilter: ["data-theme"] });
|
| 270 |
+
|
| 271 |
+
}
|
| 272 |
+
|
| 273 |
+
if (typeof d3 !== "undefined") { _initDAgger(); }
|
| 274 |
+
else { var s = document.createElement("script"); s.src="https://cdnjs.cloudflare.com/ajax/libs/d3/7.9.0/d3.min.js"; s.onload=_initDAgger; document.head.appendChild(s); }
|
| 275 |
+
</script>
|
| 276 |
+
</body>
|
| 277 |
+
</html>
|
app/src/content/embeds/folding/experiment-results.html
ADDED
|
@@ -0,0 +1,397 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<!DOCTYPE html>
|
| 2 |
+
<html lang="en">
|
| 3 |
+
<head>
|
| 4 |
+
<meta charset="UTF-8"/>
|
| 5 |
+
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
| 6 |
+
<style>
|
| 7 |
+
:root { --bg: transparent; --text: #e8eaf0; --subtext: #8b8fa8; --grid: #2a2d3a; --border: #2a2d3a; }
|
| 8 |
+
* { box-sizing: border-box; margin: 0; padding: 0; }
|
| 9 |
+
body { background: var(--bg); font-family: system-ui, sans-serif; color: var(--text); }
|
| 10 |
+
|
| 11 |
+
.controls {
|
| 12 |
+
display: flex; align-items: center; justify-content: center;
|
| 13 |
+
gap: 8px; flex-wrap: wrap; margin-bottom: 8px;
|
| 14 |
+
}
|
| 15 |
+
.ctrl-label { font-size: 13px; color: var(--subtext); }
|
| 16 |
+
.toggle-btn {
|
| 17 |
+
background: #1a1d27; border: 1.5px solid #2a2d3a; color: #8b8fa8;
|
| 18 |
+
font-size: 14px; padding: 6px 16px; border-radius: 20px; cursor: pointer;
|
| 19 |
+
transition: all .15s; user-select: none; font-weight: 500;
|
| 20 |
+
}
|
| 21 |
+
.toggle-btn:hover { border-color: #555; color: #e8eaf0; }
|
| 22 |
+
.toggle-btn.active { background: rgba(167,139,250,0.18); border-color: #a78bfa; color: #a78bfa; }
|
| 23 |
+
.filter-toggle {
|
| 24 |
+
font-size: 12px; color: #8b8fa8; cursor: pointer; border: 1px solid #2a2d3a;
|
| 25 |
+
background: #1a1d27; padding: 4px 12px; border-radius: 14px; transition: all .15s;
|
| 26 |
+
user-select: none; display: inline-flex; align-items: center; gap: 4px;
|
| 27 |
+
}
|
| 28 |
+
.filter-toggle:hover { border-color: #555; color: #e8eaf0; }
|
| 29 |
+
.filter-toggle.showing-all { background: rgba(248,147,79,0.12); border-color: #f7934f; color: #f7934f; }
|
| 30 |
+
|
| 31 |
+
.legend {
|
| 32 |
+
display: flex; gap: 16px; justify-content: center; flex-wrap: wrap; margin-bottom: 6px;
|
| 33 |
+
font-size: 13px; color: var(--subtext);
|
| 34 |
+
}
|
| 35 |
+
.legend-item { display: flex; align-items: center; gap: 5px; }
|
| 36 |
+
.legend-swatch { width: 14px; height: 10px; border-radius: 2px; }
|
| 37 |
+
.legend-balloon { width: 10px; height: 18px; border-radius: 50%; background: rgba(167,139,250,0.22); border: 1px solid #a78bfa; }
|
| 38 |
+
|
| 39 |
+
.info-note {
|
| 40 |
+
background: rgba(255,255,255,0.03); border: 1px solid #2a2d3a;
|
| 41 |
+
border-radius: 8px; padding: 8px 14px; margin-bottom: 8px;
|
| 42 |
+
font-size: 12px; color: #8b8fa8; line-height: 1.6;
|
| 43 |
+
display: flex; align-items: flex-start; gap: 10px;
|
| 44 |
+
}
|
| 45 |
+
.info-note-icon { font-size: 13px; flex-shrink: 0; margin-top: 1px; }
|
| 46 |
+
.info-note strong { color: #c8cad8; }
|
| 47 |
+
|
| 48 |
+
.tooltip {
|
| 49 |
+
position: absolute; background: #1a1d27; border: 1px solid var(--border);
|
| 50 |
+
border-radius: 8px; padding: 10px 14px; font-size: 13px; pointer-events: none;
|
| 51 |
+
opacity: 0; transition: opacity .15s; z-index: 10; min-width: 250px;
|
| 52 |
+
box-shadow: 0 4px 16px rgba(0,0,0,.4);
|
| 53 |
+
}
|
| 54 |
+
.tooltip strong { display: block; margin-bottom: 6px; font-size: 14px; color: var(--text); }
|
| 55 |
+
.tooltip-row { display: flex; justify-content: space-between; gap: 12px; margin-top: 3px; font-size: 12px; color: var(--subtext); }
|
| 56 |
+
.tooltip-row span:last-child { color: var(--text); font-weight: 600; }
|
| 57 |
+
.tooltip-ci { font-size: 10px; color: #555; margin-left: 4px; }
|
| 58 |
+
.tooltip-note { margin-top: 7px; padding-top: 7px; border-top: 1px solid #2a2d3a; font-size: 11px; color: #8b8fa8; line-height: 1.5; }
|
| 59 |
+
|
| 60 |
+
.axis text { fill: var(--subtext); font-size: 13px; }
|
| 61 |
+
.axis line, .axis path { stroke: var(--grid); }
|
| 62 |
+
.grid line { stroke: var(--grid); stroke-dasharray: 2,4; opacity: 0.5; }
|
| 63 |
+
.bar { cursor: pointer; transition: opacity .12s; }
|
| 64 |
+
.bar:hover { opacity: 0.85 !important; }
|
| 65 |
+
</style>
|
| 66 |
+
</head>
|
| 67 |
+
<body>
|
| 68 |
+
|
| 69 |
+
<div class="controls">
|
| 70 |
+
<span class="ctrl-label">Success rate overlay:</span>
|
| 71 |
+
<button class="toggle-btn active" id="btn-total" onclick="setLevel('total')">Total</button>
|
| 72 |
+
<button class="toggle-btn" id="btn-l1" onclick="setLevel('l1')">Level 1</button>
|
| 73 |
+
<button class="toggle-btn" id="btn-l2" onclick="setLevel('l2')">Level 2</button>
|
| 74 |
+
<span style="width:12px"></span>
|
| 75 |
+
<button class="filter-toggle" id="btn-filter" onclick="toggleFilter()">Show all 11</button>
|
| 76 |
+
</div>
|
| 77 |
+
|
| 78 |
+
<div class="legend">
|
| 79 |
+
<div class="legend-item"><div class="legend-swatch" style="background:#f7934f"></div>Initial training (1.x)</div>
|
| 80 |
+
<div class="legend-item"><div class="legend-swatch" style="background:#4dc98a"></div>Fine-tuned (2.x)</div>
|
| 81 |
+
<div class="legend-item"><div class="legend-balloon"></div>Success rate posterior</div>
|
| 82 |
+
<div class="legend-item" style="color:#a78bfa;font-size:12px"><strong>a</strong>,<strong>b</strong>… = CLD group</div>
|
| 83 |
+
</div>
|
| 84 |
+
|
| 85 |
+
<div class="info-note">
|
| 86 |
+
<span class="info-note-icon">ℹ</span>
|
| 87 |
+
<span>
|
| 88 |
+
Bars = success rate for selected level. Violin overlays = Bayesian posterior.
|
| 89 |
+
Letters = CLD from <a href="https://tri-ml.github.io/step/" style="color:#818cf8">STEP</a> sequential test (<a href="https://medium.com/toyotaresearch/statistical-thinking-for-robot-policy-evaluation-from-rigorous-a-b-testing-to-effective-0ae886fbd68d" style="color:#818cf8">TRI framework</a>, Bonferroni α=0.10/55); shared letter = no significant difference.
|
| 90 |
+
</span>
|
| 91 |
+
</div>
|
| 92 |
+
|
| 93 |
+
<div style="position:relative">
|
| 94 |
+
<svg id="er-chart" style="overflow:visible"></svg>
|
| 95 |
+
<div class="tooltip" id="er-tooltip"></div>
|
| 96 |
+
</div>
|
| 97 |
+
|
| 98 |
+
<script>
|
| 99 |
+
function _initExperimentResults() {
|
| 100 |
+
|
| 101 |
+
const EXPERIMENTS = {
|
| 102 |
+
"1.1 π0": { note:"π0 base model fine-tuned on full dataset. Default normalization." },
|
| 103 |
+
"1.2 π0.5": { note:"Upgraded to π0.5 with MEAN_STD normalization." },
|
| 104 |
+
"1.3 Relative": { note:"Adds relative actions and switches to QUANTILES normalization." },
|
| 105 |
+
"1.4 RABC low": { note:"RABC with low κ, not very selective. No relative actions." },
|
| 106 |
+
"1.5 RABC high": { note:"RABC with higher κ (mean + ½σ). No relative actions." },
|
| 107 |
+
"1.7 Rel+RABC": { note:"Best of initial training. Base checkpoint for 2.5." },
|
| 108 |
+
"2.1 HQ": { note:"Fine-tunes 1.3 on curated high-quality data. No RABC." },
|
| 109 |
+
"2.2 HQ+RABC+Rel": { note:"Adds RABC on high-quality fine-tune from 1.3." },
|
| 110 |
+
"2.3 HQ+mirror": { note:"Augments HQ data with mirrored trajectories." },
|
| 111 |
+
"2.4 HQ chunk45": { note:"Larger action chunk size (45 vs default 30)." },
|
| 112 |
+
"2.5 HQ+RABC+Rel★": { note:"Top performer. Best overall result." },
|
| 113 |
+
};
|
| 114 |
+
|
| 115 |
+
const N = { total: 20, l1: 10, l2: 10 };
|
| 116 |
+
|
| 117 |
+
const raw = [
|
| 118 |
+
{ label:"1.1 π0", series:"1", score:440, pct:29.3, total:40, l1:80, l2:0 },
|
| 119 |
+
{ label:"1.2 π0.5", series:"1", score:480, pct:32.0, total:20, l1:40, l2:0 },
|
| 120 |
+
{ label:"1.3 Relative", series:"1", score:460, pct:30.7, total:35, l1:70, l2:0 },
|
| 121 |
+
{ label:"1.4 RABC low", series:"1", score:330, pct:22.0, total:15, l1:30, l2:0 },
|
| 122 |
+
{ label:"1.5 RABC high", series:"1", score:170, pct:11.3, total:0, l1:0, l2:0 },
|
| 123 |
+
{ label:"1.7 Rel+RABC", series:"1", score:600, pct:40.0, total:40, l1:80, l2:0 },
|
| 124 |
+
{ label:"2.1 HQ", series:"2", score:620, pct:41.3, total:40, l1:70, l2:10 },
|
| 125 |
+
{ label:"2.2 HQ+RABC+Rel", series:"2", score:1090, pct:72.7, total:75, l1:100, l2:50 },
|
| 126 |
+
{ label:"2.3 HQ+mirror", series:"2", score:310, pct:20.7, total:5, l1:0, l2:10 },
|
| 127 |
+
{ label:"2.4 HQ chunk45", series:"2", score:460, pct:30.7, total:20, l1:40, l2:0 },
|
| 128 |
+
{ label:"2.5 HQ+RABC+Rel★", series:"2", score:1300, pct:86.7, total:90, l1:100, l2:80 },
|
| 129 |
+
];
|
| 130 |
+
|
| 131 |
+
const CLD = {
|
| 132 |
+
total: {"2.5 HQ+RABC+Rel★":"a","2.2 HQ+RABC+Rel":"ab","1.1 π0":"bc","1.7 Rel+RABC":"bc","2.1 HQ":"bc","1.3 Relative":"bc","1.2 π0.5":"cd","2.4 HQ chunk45":"cd","1.4 RABC low":"cd","2.3 HQ+mirror":"cd","1.5 RABC high":"d"},
|
| 133 |
+
l1: {"2.2 HQ+RABC+Rel":"a","2.5 HQ+RABC+Rel★":"a","1.1 π0":"a","1.7 Rel+RABC":"a","1.3 Relative":"a","2.1 HQ":"a","1.2 π0.5":"ab","2.4 HQ chunk45":"ab","1.4 RABC low":"ab","2.3 HQ+mirror":"b","1.5 RABC high":"b"},
|
| 134 |
+
l2: {"2.5 HQ+RABC+Rel★":"a","2.2 HQ+RABC+Rel":"ab","2.1 HQ":"ab","2.3 HQ+mirror":"ab","1.1 π0":"b","1.2 π0.5":"b","1.3 Relative":"b","1.4 RABC low":"b","1.5 RABC high":"b","1.7 Rel+RABC":"b","2.4 HQ chunk45":"b"},
|
| 135 |
+
};
|
| 136 |
+
|
| 137 |
+
// Clopper-Pearson exact 90% CI
|
| 138 |
+
function lgamma(x) {
|
| 139 |
+
if (x < 0.5) return Math.log(Math.PI / Math.sin(Math.PI * x)) - lgamma(1 - x);
|
| 140 |
+
x--;
|
| 141 |
+
let a = 0.99999999999980993;
|
| 142 |
+
const c = [676.5203681218851,-1259.1392167224028,771.32342877765313,-176.61502916214059,
|
| 143 |
+
12.507343278686905,-0.13857109526572012,9.9843695780195716e-6,1.5056327351493116e-7];
|
| 144 |
+
for (let i = 0; i < 8; i++) a += c[i] / (x + i + 1);
|
| 145 |
+
const t = x + 8 - 0.5;
|
| 146 |
+
return 0.5 * Math.log(2 * Math.PI) + (x + 0.5) * Math.log(t) - t + Math.log(a);
|
| 147 |
+
}
|
| 148 |
+
function betaCf(x, a, b) {
|
| 149 |
+
const qab = a + b, qap = a + 1, qam = a - 1;
|
| 150 |
+
let c = 1, d = 1 - qab * x / qap;
|
| 151 |
+
if (Math.abs(d) < 1e-30) d = 1e-30;
|
| 152 |
+
d = 1 / d; let h = d;
|
| 153 |
+
for (let m = 1; m <= 200; m++) {
|
| 154 |
+
const m2 = 2 * m;
|
| 155 |
+
let aa = m * (b - m) * x / ((qam + m2) * (a + m2));
|
| 156 |
+
d = 1 + aa * d; if (Math.abs(d) < 1e-30) d = 1e-30;
|
| 157 |
+
c = 1 + aa / c; if (Math.abs(c) < 1e-30) c = 1e-30;
|
| 158 |
+
d = 1 / d; h *= d * c;
|
| 159 |
+
aa = -(a + m) * (qab + m) * x / ((a + m2) * (qap + m2));
|
| 160 |
+
d = 1 + aa * d; if (Math.abs(d) < 1e-30) d = 1e-30;
|
| 161 |
+
c = 1 + aa / c; if (Math.abs(c) < 1e-30) c = 1e-30;
|
| 162 |
+
d = 1 / d; const del = d * c; h *= del;
|
| 163 |
+
if (Math.abs(del - 1) < 3e-12) break;
|
| 164 |
+
}
|
| 165 |
+
return h;
|
| 166 |
+
}
|
| 167 |
+
function betaI(x, a, b) {
|
| 168 |
+
if (x <= 0) return 0; if (x >= 1) return 1;
|
| 169 |
+
const bt = Math.exp(lgamma(a+b) - lgamma(a) - lgamma(b) + a*Math.log(x) + b*Math.log(1-x));
|
| 170 |
+
return x < (a+1)/(a+b+2) ? bt * betaCf(x,a,b) / a : 1 - bt * betaCf(1-x,b,a) / b;
|
| 171 |
+
}
|
| 172 |
+
function betaInv(p, a, b) {
|
| 173 |
+
let lo = 0, hi = 1;
|
| 174 |
+
for (let i = 0; i < 100; i++) {
|
| 175 |
+
const mid = (lo + hi) / 2;
|
| 176 |
+
if (betaI(mid, a, b) < p) lo = mid; else hi = mid;
|
| 177 |
+
}
|
| 178 |
+
return (lo + hi) / 2;
|
| 179 |
+
}
|
| 180 |
+
function clopperPearson(pct, n, alpha = 0.10) {
|
| 181 |
+
const k = Math.round(pct / 100 * n);
|
| 182 |
+
const lo = k === 0 ? 0 : betaInv(alpha / 2, k, n - k + 1);
|
| 183 |
+
const hi = k === n ? 1 : betaInv(1 - alpha / 2, k + 1, n - k);
|
| 184 |
+
return { lo: lo * 100, hi: hi * 100 };
|
| 185 |
+
}
|
| 186 |
+
|
| 187 |
+
function betaPdf(x, a, b) {
|
| 188 |
+
if (x <= 0 || x >= 1) return 0;
|
| 189 |
+
return Math.exp(lgamma(a+b) - lgamma(a) - lgamma(b) + (a-1)*Math.log(x) + (b-1)*Math.log(1-x));
|
| 190 |
+
}
|
| 191 |
+
|
| 192 |
+
function betaPosteriorCurve(pct, n, steps) {
|
| 193 |
+
const k = Math.round(pct / 100 * n);
|
| 194 |
+
const a = 1 + k, b = 1 + n - k;
|
| 195 |
+
const ci = clopperPearson(pct, n);
|
| 196 |
+
const lo = Math.max(0, ci.lo / 100 - 0.03);
|
| 197 |
+
const hi = Math.min(1, ci.hi / 100 + 0.03);
|
| 198 |
+
const pts = [];
|
| 199 |
+
for (let i = 0; i <= steps; i++) {
|
| 200 |
+
const p = lo + (hi - lo) * i / steps;
|
| 201 |
+
pts.push({ p: p * 100, density: betaPdf(p, a, b) });
|
| 202 |
+
}
|
| 203 |
+
return pts;
|
| 204 |
+
}
|
| 205 |
+
|
| 206 |
+
// Pre-compute CIs and posteriors
|
| 207 |
+
const allData = raw.map(d => {
|
| 208 |
+
const out = {...d};
|
| 209 |
+
["total","l1","l2"].forEach(k => {
|
| 210 |
+
out[k + "_ci"] = clopperPearson(d[k], N[k]);
|
| 211 |
+
out[k + "_post"] = betaPosteriorCurve(d[k], N[k], 80);
|
| 212 |
+
});
|
| 213 |
+
return out;
|
| 214 |
+
});
|
| 215 |
+
|
| 216 |
+
const seriesColor = s => s === "2" ? "#4dc98a" : "#f7934f";
|
| 217 |
+
const srColor = "#a78bfa";
|
| 218 |
+
let activeLevel = "total";
|
| 219 |
+
const HIDDEN_BY_DEFAULT = new Set(["1.4 RABC low","1.5 RABC high","2.3 HQ+mirror","2.4 HQ chunk45"]);
|
| 220 |
+
let showAll = false;
|
| 221 |
+
|
| 222 |
+
window.setLevel = function(lv) {
|
| 223 |
+
activeLevel = lv;
|
| 224 |
+
["total","l1","l2"].forEach(k => {
|
| 225 |
+
document.getElementById("btn-" + k).className = "toggle-btn" + (k === lv ? " active" : "");
|
| 226 |
+
});
|
| 227 |
+
render();
|
| 228 |
+
};
|
| 229 |
+
|
| 230 |
+
window.toggleFilter = function() {
|
| 231 |
+
showAll = !showAll;
|
| 232 |
+
const btn = document.getElementById("btn-filter");
|
| 233 |
+
btn.textContent = showAll ? "Key experiments" : "Show all 11";
|
| 234 |
+
btn.classList.toggle("showing-all", showAll);
|
| 235 |
+
render();
|
| 236 |
+
};
|
| 237 |
+
|
| 238 |
+
const margin = { top: 28, right: 20, bottom: 72, left: 44 };
|
| 239 |
+
const svg = d3.select("#er-chart");
|
| 240 |
+
const container = svg.node().parentElement;
|
| 241 |
+
const tooltip = d3.select("#er-tooltip");
|
| 242 |
+
|
| 243 |
+
function render() {
|
| 244 |
+
svg.selectAll("*").remove();
|
| 245 |
+
const lvKey = activeLevel;
|
| 246 |
+
const data = (showAll ? allData : allData.filter(d => !HIDDEN_BY_DEFAULT.has(d.label)))
|
| 247 |
+
.slice().sort((a, b) => b[lvKey] - a[lvKey] || b.pct - a.pct);
|
| 248 |
+
const W = container.clientWidth;
|
| 249 |
+
const H = Math.max(310, Math.min(420, W * 0.52));
|
| 250 |
+
const w = W - margin.left - margin.right;
|
| 251 |
+
const h = H - margin.top - margin.bottom;
|
| 252 |
+
svg.attr("width", W).attr("height", H);
|
| 253 |
+
const g = svg.append("g").attr("transform", `translate(${margin.left},${margin.top})`);
|
| 254 |
+
|
| 255 |
+
const x = d3.scaleBand().domain(data.map(d => d.label)).range([0, w]).padding(0.22);
|
| 256 |
+
const y = d3.scaleLinear().domain([0, 105]).range([h, 0]);
|
| 257 |
+
|
| 258 |
+
// Grid — only 50% and 100%
|
| 259 |
+
g.append("g").attr("class","grid").selectAll("line").data([50,100]).join("line")
|
| 260 |
+
.attr("x1",0).attr("x2",w).attr("y1",d=>y(d)).attr("y2",d=>y(d));
|
| 261 |
+
|
| 262 |
+
// X axis
|
| 263 |
+
g.append("g").attr("class","axis").attr("transform",`translate(0,${h})`)
|
| 264 |
+
.call(d3.axisBottom(x).tickSize(0))
|
| 265 |
+
.call(gg => {
|
| 266 |
+
gg.select(".domain").remove();
|
| 267 |
+
gg.selectAll("text").attr("transform","rotate(-40)")
|
| 268 |
+
.attr("text-anchor","end").attr("dx","-0.5em").attr("dy","0.3em").attr("font-size",11);
|
| 269 |
+
});
|
| 270 |
+
|
| 271 |
+
// Y axis
|
| 272 |
+
g.append("g").attr("class","axis")
|
| 273 |
+
.call(d3.axisLeft(y).tickValues([0,50,100]).tickFormat(d => d+"%").tickSize(0))
|
| 274 |
+
.call(ax => { ax.select(".domain").remove(); ax.selectAll(".tick line").remove(); });
|
| 275 |
+
|
| 276 |
+
// Series color pip under each label
|
| 277 |
+
data.forEach(d => {
|
| 278 |
+
g.append("rect")
|
| 279 |
+
.attr("x", x(d.label)).attr("width", x.bandwidth())
|
| 280 |
+
.attr("y", h + 52).attr("height", 3).attr("rx", 1.5)
|
| 281 |
+
.attr("fill", seriesColor(d.series)).attr("opacity", 0.6);
|
| 282 |
+
});
|
| 283 |
+
|
| 284 |
+
const bw = x.bandwidth();
|
| 285 |
+
|
| 286 |
+
data.forEach(d => {
|
| 287 |
+
const bx = x(d.label);
|
| 288 |
+
const cx = bx + bw / 2;
|
| 289 |
+
const col = seriesColor(d.series);
|
| 290 |
+
|
| 291 |
+
// SR bar for active level
|
| 292 |
+
const barVal = d[lvKey];
|
| 293 |
+
g.append("rect").attr("class","bar")
|
| 294 |
+
.attr("x", bx).attr("width", bw)
|
| 295 |
+
.attr("y", y(barVal)).attr("height", h - y(barVal))
|
| 296 |
+
.attr("fill", col).attr("rx", 2).attr("opacity", 0.55)
|
| 297 |
+
.on("mousemove", function(event) { showTooltip(event, d); })
|
| 298 |
+
.on("mouseleave", () => tooltip.style("opacity", 0));
|
| 299 |
+
|
| 300 |
+
// Bayesian posterior balloon (mini violin) — subtle ghost
|
| 301 |
+
const sr = d[activeLevel];
|
| 302 |
+
const post = d[activeLevel + "_post"];
|
| 303 |
+
const maxDensity = d3.max(post, p => p.density) || 1;
|
| 304 |
+
const halfW = bw * 0.22;
|
| 305 |
+
|
| 306 |
+
const areaGen = d3.area()
|
| 307 |
+
.y(p => y(p.p))
|
| 308 |
+
.x0(p => cx - (p.density / maxDensity) * halfW)
|
| 309 |
+
.x1(p => cx + (p.density / maxDensity) * halfW)
|
| 310 |
+
.curve(d3.curveBasis);
|
| 311 |
+
|
| 312 |
+
g.append("path")
|
| 313 |
+
.datum(post)
|
| 314 |
+
.attr("d", areaGen)
|
| 315 |
+
.attr("fill", srColor).attr("fill-opacity", 0.12)
|
| 316 |
+
.attr("stroke", srColor).attr("stroke-width", 0.8).attr("stroke-opacity", 0.45)
|
| 317 |
+
.attr("cursor","pointer")
|
| 318 |
+
.on("mousemove", function(event) { showTooltip(event, d); })
|
| 319 |
+
.on("mouseleave", () => tooltip.style("opacity", 0));
|
| 320 |
+
|
| 321 |
+
// Median tick
|
| 322 |
+
g.append("line")
|
| 323 |
+
.attr("x1", cx - halfW * 0.7).attr("x2", cx + halfW * 0.7)
|
| 324 |
+
.attr("y1", y(sr)).attr("y2", y(sr))
|
| 325 |
+
.attr("stroke", srColor).attr("stroke-width", 1.5).attr("stroke-opacity", 0.7);
|
| 326 |
+
|
| 327 |
+
// CLD letter above bar
|
| 328 |
+
const cldLetter = CLD[activeLevel][d.label] || "?";
|
| 329 |
+
const labelY = Math.min(y(barVal), y(sr)) - 7;
|
| 330 |
+
g.append("text")
|
| 331 |
+
.attr("x", cx).attr("y", labelY)
|
| 332 |
+
.attr("text-anchor","middle").attr("fill", srColor)
|
| 333 |
+
.attr("font-size", 12).attr("font-weight","700")
|
| 334 |
+
.text(cldLetter);
|
| 335 |
+
});
|
| 336 |
+
|
| 337 |
+
// Sort annotation
|
| 338 |
+
const lvLabels = { total:"total SR", l1:"Level 1 SR", l2:"Level 2 SR" };
|
| 339 |
+
g.append("text").attr("x", w).attr("y", -6).attr("text-anchor","end")
|
| 340 |
+
.attr("fill","#555").attr("font-size",11)
|
| 341 |
+
.text("sorted: highest → lowest " + lvLabels[lvKey]);
|
| 342 |
+
}
|
| 343 |
+
|
| 344 |
+
function showTooltip(event, d) {
|
| 345 |
+
const lvLabels = { total:"Total", l1:"Level 1", l2:"Level 2" };
|
| 346 |
+
const cldLetter = CLD[activeLevel][d.label] || "?";
|
| 347 |
+
const col = seriesColor(d.series);
|
| 348 |
+
|
| 349 |
+
tooltip.style("opacity", 1).html(`
|
| 350 |
+
<strong>Experiment ${d.label} <small style="color:${col}">(Series ${d.series})</small></strong>
|
| 351 |
+
<div class="tooltip-row"><span>Total Score</span><span>${d.score} / 1500 (${d.pct}%)</span></div>
|
| 352 |
+
<div class="tooltip-row">
|
| 353 |
+
<span>Total SR</span>
|
| 354 |
+
<span style="color:#a78bfa">${d.total}%
|
| 355 |
+
<span class="tooltip-ci">[${d.total_ci.lo.toFixed(0)}–${d.total_ci.hi.toFixed(0)}%]</span>
|
| 356 |
+
</span>
|
| 357 |
+
</div>
|
| 358 |
+
<div class="tooltip-row">
|
| 359 |
+
<span>Level 1 SR</span>
|
| 360 |
+
<span style="color:#4dc98a">${d.l1}%
|
| 361 |
+
<span class="tooltip-ci">[${d.l1_ci.lo.toFixed(0)}–${d.l1_ci.hi.toFixed(0)}%]</span>
|
| 362 |
+
</span>
|
| 363 |
+
</div>
|
| 364 |
+
<div class="tooltip-row">
|
| 365 |
+
<span>Level 2 SR</span>
|
| 366 |
+
<span style="color:#f87171">${d.l2}%
|
| 367 |
+
<span class="tooltip-ci">[${d.l2_ci.lo.toFixed(0)}–${d.l2_ci.hi.toFixed(0)}%]</span>
|
| 368 |
+
</span>
|
| 369 |
+
</div>
|
| 370 |
+
<div style="margin-top:5px;font-size:10px;color:#555">
|
| 371 |
+
CLD group (${lvLabels[activeLevel]}): <strong style="color:#a78bfa">${cldLetter}</strong> · shared letter = not significantly different
|
| 372 |
+
</div>
|
| 373 |
+
${(EXPERIMENTS[d.label]||{}).note ? `<div class="tooltip-note">${EXPERIMENTS[d.label].note}</div>` : ""}
|
| 374 |
+
`);
|
| 375 |
+
|
| 376 |
+
const bx = container.getBoundingClientRect();
|
| 377 |
+
const ex = event.clientX - bx.left, ey = event.clientY - bx.top;
|
| 378 |
+
tooltip.style("left", Math.min(ex + 14, container.clientWidth - 270) + "px")
|
| 379 |
+
.style("top", Math.max(ey - 140, 0) + "px");
|
| 380 |
+
}
|
| 381 |
+
|
| 382 |
+
render();
|
| 383 |
+
window.addEventListener("resize", render);
|
| 384 |
+
|
| 385 |
+
}
|
| 386 |
+
|
| 387 |
+
if (typeof d3 !== "undefined") {
|
| 388 |
+
_initExperimentResults();
|
| 389 |
+
} else {
|
| 390 |
+
var s = document.createElement("script");
|
| 391 |
+
s.src = "https://cdnjs.cloudflare.com/ajax/libs/d3/7.9.0/d3.min.js";
|
| 392 |
+
s.onload = _initExperimentResults;
|
| 393 |
+
document.head.appendChild(s);
|
| 394 |
+
}
|
| 395 |
+
</script>
|
| 396 |
+
</body>
|
| 397 |
+
</html>
|
app/src/content/embeds/folding/failure-analysis.html
CHANGED
|
@@ -4,12 +4,23 @@
|
|
| 4 |
<meta charset="UTF-8"/>
|
| 5 |
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
| 6 |
<style>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 7 |
* { box-sizing: border-box; margin: 0; padding: 0; }
|
| 8 |
|
| 9 |
body {
|
| 10 |
-
background:
|
| 11 |
font-family: system-ui, sans-serif;
|
| 12 |
-
color:
|
| 13 |
padding: 0;
|
| 14 |
min-height: 100vh;
|
| 15 |
}
|
|
@@ -24,7 +35,7 @@
|
|
| 24 |
display: flex;
|
| 25 |
gap: 4px;
|
| 26 |
margin-bottom: 20px;
|
| 27 |
-
border-bottom: 1px solid
|
| 28 |
padding-bottom: 0;
|
| 29 |
}
|
| 30 |
|
|
@@ -33,10 +44,10 @@
|
|
| 33 |
border: 1px solid transparent;
|
| 34 |
border-bottom: none;
|
| 35 |
background: none;
|
| 36 |
-
color:
|
| 37 |
cursor: pointer;
|
| 38 |
font-family: inherit;
|
| 39 |
-
font-size:
|
| 40 |
letter-spacing: 0.05em;
|
| 41 |
text-transform: uppercase;
|
| 42 |
border-radius: 4px 4px 0 0;
|
|
@@ -44,20 +55,20 @@
|
|
| 44 |
position: relative;
|
| 45 |
bottom: -1px;
|
| 46 |
}
|
| 47 |
-
.tab:hover { color:
|
| 48 |
.tab.active {
|
| 49 |
-
color:
|
| 50 |
-
background:
|
| 51 |
-
border-color:
|
| 52 |
-
border-bottom-color:
|
| 53 |
}
|
| 54 |
|
| 55 |
.panel { display: none; }
|
| 56 |
.panel.active { display: block; }
|
| 57 |
|
| 58 |
.chart-wrap {
|
| 59 |
-
background:
|
| 60 |
-
border: 1px solid
|
| 61 |
border-radius: 6px;
|
| 62 |
padding: 20px 20px 12px;
|
| 63 |
}
|
|
@@ -72,10 +83,10 @@
|
|
| 72 |
}
|
| 73 |
|
| 74 |
.chart-title {
|
| 75 |
-
font-size:
|
| 76 |
text-transform: uppercase;
|
| 77 |
letter-spacing: 0.08em;
|
| 78 |
-
color:
|
| 79 |
}
|
| 80 |
|
| 81 |
.mode-toggle {
|
|
@@ -83,20 +94,20 @@
|
|
| 83 |
gap: 0;
|
| 84 |
}
|
| 85 |
.mode-btn {
|
| 86 |
-
padding:
|
| 87 |
-
font-size:
|
| 88 |
font-family: inherit;
|
| 89 |
cursor: pointer;
|
| 90 |
-
border: 1px solid
|
| 91 |
background: none;
|
| 92 |
-
color:
|
| 93 |
transition: all 0.15s;
|
| 94 |
letter-spacing: 0.04em;
|
| 95 |
}
|
| 96 |
.mode-btn:first-child { border-radius: 4px 0 0 4px; }
|
| 97 |
.mode-btn:last-child { border-radius: 0 4px 4px 0; border-left: none; }
|
| 98 |
-
.mode-btn.active { background:
|
| 99 |
-
.mode-btn:hover:not(.active) { color:
|
| 100 |
|
| 101 |
.legend {
|
| 102 |
display: flex;
|
|
@@ -104,14 +115,14 @@
|
|
| 104 |
gap: 10px 18px;
|
| 105 |
margin-top: 14px;
|
| 106 |
padding-top: 12px;
|
| 107 |
-
border-top: 1px solid
|
| 108 |
}
|
| 109 |
.legend-item {
|
| 110 |
display: flex;
|
| 111 |
align-items: center;
|
| 112 |
gap: 6px;
|
| 113 |
-
font-size:
|
| 114 |
-
color:
|
| 115 |
letter-spacing: 0.03em;
|
| 116 |
}
|
| 117 |
.legend-swatch {
|
|
@@ -130,10 +141,10 @@
|
|
| 130 |
display: flex;
|
| 131 |
align-items: center;
|
| 132 |
gap: 6px;
|
| 133 |
-
font-size:
|
| 134 |
letter-spacing: 0.05em;
|
| 135 |
text-transform: uppercase;
|
| 136 |
-
color:
|
| 137 |
}
|
| 138 |
.series-badge span {
|
| 139 |
width: 24px;
|
|
@@ -145,58 +156,54 @@
|
|
| 145 |
svg text { font-family: system-ui, sans-serif; }
|
| 146 |
|
| 147 |
.note {
|
| 148 |
-
font-size:
|
| 149 |
-
color:
|
| 150 |
margin-top: 10px;
|
| 151 |
line-height: 1.6;
|
| 152 |
letter-spacing: 0.02em;
|
| 153 |
}
|
| 154 |
|
| 155 |
-
@media (prefers-color-scheme: dark) {
|
| 156 |
-
.note { color: #c8cad8; }
|
| 157 |
-
}
|
| 158 |
-
|
| 159 |
.insight-box {
|
| 160 |
-
background:
|
| 161 |
-
border: 1px solid
|
| 162 |
-
border-left: 3px solid
|
| 163 |
border-radius: 4px;
|
| 164 |
padding: 10px 14px;
|
| 165 |
margin-bottom: 16px;
|
| 166 |
-
font-size:
|
| 167 |
-
color: #
|
| 168 |
line-height: 1.6;
|
| 169 |
}
|
| 170 |
-
.insight-box strong { color: #
|
| 171 |
-
|
| 172 |
-
|
| 173 |
-
|
| 174 |
-
|
| 175 |
-
border: 1px solid #2a2d3a;
|
| 176 |
-
border-left: 3px solid #f7934f;
|
| 177 |
-
color: #8b8fa8;
|
| 178 |
-
}
|
| 179 |
-
.insight-box strong { color: #e8eaf0; }
|
| 180 |
}
|
|
|
|
|
|
|
| 181 |
</style>
|
| 182 |
</head>
|
| 183 |
<body>
|
| 184 |
<div class="container">
|
| 185 |
|
| 186 |
-
<div class="tab-row">
|
| 187 |
-
<
|
| 188 |
-
|
|
|
|
|
|
|
|
|
|
| 189 |
</div>
|
| 190 |
|
| 191 |
<!-- LEVEL 2 PANEL -->
|
| 192 |
-
<div class="panel
|
| 193 |
<div class="insight-box">
|
| 194 |
-
<strong>
|
| 195 |
-
<strong>
|
| 196 |
</div>
|
| 197 |
<div class="chart-wrap">
|
| 198 |
<div class="chart-header">
|
| 199 |
-
<div class="chart-title">Where does the robot fail?
|
| 200 |
<div class="mode-toggle">
|
| 201 |
<button class="mode-btn active" id="mode-l2-abs" onclick="setMode('l2','abs')">Counts</button>
|
| 202 |
<button class="mode-btn" id="mode-l2-pct" onclick="setMode('l2','pct')">Percentage</button>
|
|
@@ -205,17 +212,17 @@
|
|
| 205 |
<svg id="chart-l2" width="100%" height="320" style="overflow:visible"></svg>
|
| 206 |
<div class="legend" id="legend-l2"></div>
|
| 207 |
</div>
|
| 208 |
-
<p class="note">Each bar = one experiment, showing how its failed Level 2 rollouts distribute across subtasks. Only failed rollouts shown
|
| 209 |
</div>
|
| 210 |
|
| 211 |
<!-- LEVEL 1 PANEL -->
|
| 212 |
-
<div class="panel" id="panel-l1">
|
| 213 |
<div class="insight-box">
|
| 214 |
-
<strong>Level 1 failures</strong> are more distributed since unfolding is given.
|
| 215 |
</div>
|
| 216 |
<div class="chart-wrap">
|
| 217 |
<div class="chart-header">
|
| 218 |
-
<div class="chart-title">Where does the robot fail?
|
| 219 |
<div class="mode-toggle">
|
| 220 |
<button class="mode-btn active" id="mode-l1-abs" onclick="setMode('l1','abs')">Counts</button>
|
| 221 |
<button class="mode-btn" id="mode-l1-pct" onclick="setMode('l1','pct')">Percentage</button>
|
|
@@ -285,6 +292,23 @@ const COLORS = {
|
|
| 285 |
'Rotation': '#818cf8',
|
| 286 |
};
|
| 287 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 288 |
const modes = { l2: 'abs', l1: 'abs' };
|
| 289 |
|
| 290 |
function setMode(level, mode) {
|
|
@@ -296,7 +320,15 @@ function setMode(level, mode) {
|
|
| 296 |
renderTab(level);
|
| 297 |
}
|
| 298 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 299 |
function buildStackedBar(svgId, legendId, data, subtasks, experiments, normalize) {
|
|
|
|
|
|
|
|
|
|
|
|
|
| 300 |
const svgEl = document.getElementById(svgId);
|
| 301 |
const W = svgEl.parentElement.clientWidth - 40;
|
| 302 |
const H = 340;
|
|
@@ -349,7 +381,7 @@ function buildStackedBar(svgId, legendId, data, subtasks, experiments, normalize
|
|
| 349 |
.call(d3.axisLeft(y).tickSize(-innerW).tickFormat('').ticks(5))
|
| 350 |
.call(gg => {
|
| 351 |
gg.select('.domain').remove();
|
| 352 |
-
gg.selectAll('line').attr('stroke',
|
| 353 |
});
|
| 354 |
|
| 355 |
// Stacked bars
|
|
@@ -370,8 +402,8 @@ function buildStackedBar(svgId, legendId, data, subtasks, experiments, normalize
|
|
| 370 |
.attr('x', d => x(d.id) + x.bandwidth() / 2)
|
| 371 |
.attr('y', d => d._total === 0 ? y(0) - 4 : y(d._displayTotal) - 5)
|
| 372 |
.attr('text-anchor', 'middle')
|
| 373 |
-
.attr('fill', d => d._total === 0 ?
|
| 374 |
-
.attr('font-size', '
|
| 375 |
.text(d => {
|
| 376 |
if (d._total === 0) return '✓ 0 failures';
|
| 377 |
return normalize ? `n=${d._total}` : d._total;
|
|
@@ -385,14 +417,14 @@ function buildStackedBar(svgId, legendId, data, subtasks, experiments, normalize
|
|
| 385 |
g.append('line')
|
| 386 |
.attr('x1', xDiv).attr('x2', xDiv)
|
| 387 |
.attr('y1', -22).attr('y2', innerH + 4)
|
| 388 |
-
.attr('stroke',
|
| 389 |
|
| 390 |
g.append('text').attr('x', xDiv - 6).attr('y', -18).attr('text-anchor', 'end')
|
| 391 |
-
.attr('fill', '#f7934f').attr('font-size', '
|
| 392 |
|
| 393 |
if (s2First) {
|
| 394 |
g.append('text').attr('x', xDiv + 6).attr('y', -18).attr('text-anchor', 'start')
|
| 395 |
-
.attr('fill', '#4dc98a').attr('font-size', '
|
| 396 |
}
|
| 397 |
}
|
| 398 |
|
|
@@ -400,21 +432,21 @@ function buildStackedBar(svgId, legendId, data, subtasks, experiments, normalize
|
|
| 400 |
g.append('g')
|
| 401 |
.call(d3.axisLeft(y).ticks(5).tickSize(4).tickFormat(d => normalize ? d + '%' : d))
|
| 402 |
.call(gg => {
|
| 403 |
-
gg.select('.domain').attr('stroke',
|
| 404 |
-
gg.selectAll('text').attr('fill',
|
| 405 |
-
gg.selectAll('line').attr('stroke',
|
| 406 |
});
|
| 407 |
|
| 408 |
g.append('g').attr('transform', `translate(0,${innerH})`)
|
| 409 |
.call(d3.axisBottom(x).tickSize(0))
|
| 410 |
.call(gg => {
|
| 411 |
-
gg.select('.domain').attr('stroke',
|
| 412 |
gg.selectAll('text')
|
| 413 |
.attr('fill', d => {
|
| 414 |
const a = experiments.find(a => a.id === d);
|
| 415 |
return a?.series === 2 ? '#4dc98a' : '#f7934f';
|
| 416 |
})
|
| 417 |
-
.attr('font-size', '
|
| 418 |
.attr('transform', 'rotate(-40)')
|
| 419 |
.attr('text-anchor', 'end')
|
| 420 |
.attr('dx', '-0.5em')
|
|
@@ -424,7 +456,7 @@ function buildStackedBar(svgId, legendId, data, subtasks, experiments, normalize
|
|
| 424 |
// Y axis label
|
| 425 |
svg.append('text').attr('transform', 'rotate(-90)')
|
| 426 |
.attr('x', -(margin.top + innerH / 2)).attr('y', 10).attr('text-anchor', 'middle')
|
| 427 |
-
.attr('fill',
|
| 428 |
.text(normalize ? 'Failure distribution (%)' : 'Failed rollouts (n)');
|
| 429 |
|
| 430 |
// Legend
|
|
@@ -443,8 +475,9 @@ function renderTab(id) {
|
|
| 443 |
if (rendered[id]) return;
|
| 444 |
rendered[id] = true;
|
| 445 |
const normalize = modes[id] === 'pct';
|
| 446 |
-
|
| 447 |
-
if (id === '
|
|
|
|
| 448 |
}
|
| 449 |
|
| 450 |
function showTab(id) {
|
|
@@ -460,7 +493,7 @@ function showTab(id) {
|
|
| 460 |
window.showTab = showTab;
|
| 461 |
window.setMode = setMode;
|
| 462 |
|
| 463 |
-
renderTab('
|
| 464 |
|
| 465 |
}
|
| 466 |
|
|
|
|
| 4 |
<meta charset="UTF-8"/>
|
| 5 |
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
| 6 |
<style>
|
| 7 |
+
:root {
|
| 8 |
+
--bg: transparent;
|
| 9 |
+
--text: #e8eaf0;
|
| 10 |
+
--subtext: #8b8fa8;
|
| 11 |
+
--border: #2a2d3a;
|
| 12 |
+
--chart-bg: #1a1d27;
|
| 13 |
+
--btn-active-bg: #252835;
|
| 14 |
+
--btn-active-text: #e8eaf0;
|
| 15 |
+
--btn-active-border: #4a4d5a;
|
| 16 |
+
}
|
| 17 |
+
|
| 18 |
* { box-sizing: border-box; margin: 0; padding: 0; }
|
| 19 |
|
| 20 |
body {
|
| 21 |
+
background: var(--bg);
|
| 22 |
font-family: system-ui, sans-serif;
|
| 23 |
+
color: var(--text);
|
| 24 |
padding: 0;
|
| 25 |
min-height: 100vh;
|
| 26 |
}
|
|
|
|
| 35 |
display: flex;
|
| 36 |
gap: 4px;
|
| 37 |
margin-bottom: 20px;
|
| 38 |
+
border-bottom: 1px solid var(--border);
|
| 39 |
padding-bottom: 0;
|
| 40 |
}
|
| 41 |
|
|
|
|
| 44 |
border: 1px solid transparent;
|
| 45 |
border-bottom: none;
|
| 46 |
background: none;
|
| 47 |
+
color: var(--subtext);
|
| 48 |
cursor: pointer;
|
| 49 |
font-family: inherit;
|
| 50 |
+
font-size: 13px;
|
| 51 |
letter-spacing: 0.05em;
|
| 52 |
text-transform: uppercase;
|
| 53 |
border-radius: 4px 4px 0 0;
|
|
|
|
| 55 |
position: relative;
|
| 56 |
bottom: -1px;
|
| 57 |
}
|
| 58 |
+
.tab:hover { color: var(--text); background: var(--chart-bg); }
|
| 59 |
.tab.active {
|
| 60 |
+
color: var(--text);
|
| 61 |
+
background: var(--chart-bg);
|
| 62 |
+
border-color: var(--border);
|
| 63 |
+
border-bottom-color: var(--chart-bg);
|
| 64 |
}
|
| 65 |
|
| 66 |
.panel { display: none; }
|
| 67 |
.panel.active { display: block; }
|
| 68 |
|
| 69 |
.chart-wrap {
|
| 70 |
+
background: var(--chart-bg);
|
| 71 |
+
border: 1px solid var(--border);
|
| 72 |
border-radius: 6px;
|
| 73 |
padding: 20px 20px 12px;
|
| 74 |
}
|
|
|
|
| 83 |
}
|
| 84 |
|
| 85 |
.chart-title {
|
| 86 |
+
font-size: 13px;
|
| 87 |
text-transform: uppercase;
|
| 88 |
letter-spacing: 0.08em;
|
| 89 |
+
color: var(--subtext);
|
| 90 |
}
|
| 91 |
|
| 92 |
.mode-toggle {
|
|
|
|
| 94 |
gap: 0;
|
| 95 |
}
|
| 96 |
.mode-btn {
|
| 97 |
+
padding: 5px 14px;
|
| 98 |
+
font-size: 12px;
|
| 99 |
font-family: inherit;
|
| 100 |
cursor: pointer;
|
| 101 |
+
border: 1px solid var(--border);
|
| 102 |
background: none;
|
| 103 |
+
color: var(--subtext);
|
| 104 |
transition: all 0.15s;
|
| 105 |
letter-spacing: 0.04em;
|
| 106 |
}
|
| 107 |
.mode-btn:first-child { border-radius: 4px 0 0 4px; }
|
| 108 |
.mode-btn:last-child { border-radius: 0 4px 4px 0; border-left: none; }
|
| 109 |
+
.mode-btn.active { background: var(--btn-active-bg); color: var(--btn-active-text); border-color: var(--btn-active-border); }
|
| 110 |
+
.mode-btn:hover:not(.active) { color: var(--text); }
|
| 111 |
|
| 112 |
.legend {
|
| 113 |
display: flex;
|
|
|
|
| 115 |
gap: 10px 18px;
|
| 116 |
margin-top: 14px;
|
| 117 |
padding-top: 12px;
|
| 118 |
+
border-top: 1px solid var(--border);
|
| 119 |
}
|
| 120 |
.legend-item {
|
| 121 |
display: flex;
|
| 122 |
align-items: center;
|
| 123 |
gap: 6px;
|
| 124 |
+
font-size: 12px;
|
| 125 |
+
color: var(--subtext);
|
| 126 |
letter-spacing: 0.03em;
|
| 127 |
}
|
| 128 |
.legend-swatch {
|
|
|
|
| 141 |
display: flex;
|
| 142 |
align-items: center;
|
| 143 |
gap: 6px;
|
| 144 |
+
font-size: 12px;
|
| 145 |
letter-spacing: 0.05em;
|
| 146 |
text-transform: uppercase;
|
| 147 |
+
color: var(--subtext);
|
| 148 |
}
|
| 149 |
.series-badge span {
|
| 150 |
width: 24px;
|
|
|
|
| 156 |
svg text { font-family: system-ui, sans-serif; }
|
| 157 |
|
| 158 |
.note {
|
| 159 |
+
font-size: 12px;
|
| 160 |
+
color: var(--subtext);
|
| 161 |
margin-top: 10px;
|
| 162 |
line-height: 1.6;
|
| 163 |
letter-spacing: 0.02em;
|
| 164 |
}
|
| 165 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 166 |
.insight-box {
|
| 167 |
+
background: #12151f;
|
| 168 |
+
border: 1px solid #2a2d3a;
|
| 169 |
+
border-left: 3px solid #f7934f;
|
| 170 |
border-radius: 4px;
|
| 171 |
padding: 10px 14px;
|
| 172 |
margin-bottom: 16px;
|
| 173 |
+
font-size: 12px;
|
| 174 |
+
color: #8b8fa8;
|
| 175 |
line-height: 1.6;
|
| 176 |
}
|
| 177 |
+
.insight-box strong { color: #e8eaf0; }
|
| 178 |
+
.filter-toggle {
|
| 179 |
+
font-size: 12px; color: var(--subtext); cursor: pointer; border: 1px solid var(--border);
|
| 180 |
+
background: none; padding: 4px 12px; border-radius: 14px; transition: all .15s;
|
| 181 |
+
user-select: none; display: inline-flex; align-items: center; gap: 4px; font-family: inherit;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 182 |
}
|
| 183 |
+
.filter-toggle:hover { color: var(--text); }
|
| 184 |
+
.filter-toggle.showing-all { background: rgba(248,147,79,0.12); border-color: #f7934f; color: #f7934f; }
|
| 185 |
</style>
|
| 186 |
</head>
|
| 187 |
<body>
|
| 188 |
<div class="container">
|
| 189 |
|
| 190 |
+
<div class="tab-row" style="justify-content:space-between;align-items:center">
|
| 191 |
+
<div style="display:flex;gap:4px">
|
| 192 |
+
<button class="tab active" onclick="showTab('l1')">Level 1: Failure point</button>
|
| 193 |
+
<button class="tab" onclick="showTab('l2')">Level 2: Failure point</button>
|
| 194 |
+
</div>
|
| 195 |
+
<button class="filter-toggle" id="fa-filter-btn" onclick="window._faToggleFilter()">Show all 11</button>
|
| 196 |
</div>
|
| 197 |
|
| 198 |
<!-- LEVEL 2 PANEL -->
|
| 199 |
+
<div class="panel" id="panel-l2">
|
| 200 |
<div class="insight-box">
|
| 201 |
+
<strong>Initial training (1.x):</strong> nearly all level 2 failures occur at Unfold (the robot never gets past step 1).
|
| 202 |
+
<strong>Fine-tuned (2.x):</strong> Unfold failures collapse (2.5: 0%), but late-stage failures (Fold 3, Rotation) emerge: the model now reliably unfolds but precision degrades at the end.
|
| 203 |
</div>
|
| 204 |
<div class="chart-wrap">
|
| 205 |
<div class="chart-header">
|
| 206 |
+
<div class="chart-title">Where does the robot fail? Level 2 failed rollouts by subtask</div>
|
| 207 |
<div class="mode-toggle">
|
| 208 |
<button class="mode-btn active" id="mode-l2-abs" onclick="setMode('l2','abs')">Counts</button>
|
| 209 |
<button class="mode-btn" id="mode-l2-pct" onclick="setMode('l2','pct')">Percentage</button>
|
|
|
|
| 212 |
<svg id="chart-l2" width="100%" height="320" style="overflow:visible"></svg>
|
| 213 |
<div class="legend" id="legend-l2"></div>
|
| 214 |
</div>
|
| 215 |
+
<p class="note">Each bar = one experiment, showing how its failed Level 2 rollouts distribute across subtasks. Only failed rollouts shown; successful rollouts are excluded. Toggle "Percentage" to compare failure distributions regardless of total failure count.</p>
|
| 216 |
</div>
|
| 217 |
|
| 218 |
<!-- LEVEL 1 PANEL -->
|
| 219 |
+
<div class="panel active" id="panel-l1">
|
| 220 |
<div class="insight-box">
|
| 221 |
+
<strong>Level 1 failures</strong> are more distributed since unfolding is given. Initial-training failures concentrate at Fold 2 and Fold 4 (mid-task precision). Fine-tuning nearly eliminates failures entirely; only 2.3 (mirroring) and 2.4 (chunk=45) regress significantly.
|
| 222 |
</div>
|
| 223 |
<div class="chart-wrap">
|
| 224 |
<div class="chart-header">
|
| 225 |
+
<div class="chart-title">Where does the robot fail? Level 1 failed rollouts by subtask</div>
|
| 226 |
<div class="mode-toggle">
|
| 227 |
<button class="mode-btn active" id="mode-l1-abs" onclick="setMode('l1','abs')">Counts</button>
|
| 228 |
<button class="mode-btn" id="mode-l1-pct" onclick="setMode('l1','pct')">Percentage</button>
|
|
|
|
| 292 |
'Rotation': '#818cf8',
|
| 293 |
};
|
| 294 |
|
| 295 |
+
const HIDDEN_BY_DEFAULT = new Set(['1.4 RABC low','1.5 RABC high','2.3 HQ+mirror','2.4 HQ chunk45']);
|
| 296 |
+
let faShowAll = false;
|
| 297 |
+
|
| 298 |
+
window._faToggleFilter = function() {
|
| 299 |
+
faShowAll = !faShowAll;
|
| 300 |
+
const btn = document.getElementById("fa-filter-btn");
|
| 301 |
+
btn.textContent = faShowAll ? "Key experiments" : "Show all 11";
|
| 302 |
+
btn.classList.toggle("showing-all", faShowAll);
|
| 303 |
+
rendered.l1 = false;
|
| 304 |
+
rendered.l2 = false;
|
| 305 |
+
renderTab(document.querySelector('.panel.active').id.replace('panel-',''));
|
| 306 |
+
};
|
| 307 |
+
|
| 308 |
+
function getVisibleExperiments() {
|
| 309 |
+
return faShowAll ? EXPERIMENTS : EXPERIMENTS.filter(e => !HIDDEN_BY_DEFAULT.has(e.id));
|
| 310 |
+
}
|
| 311 |
+
|
| 312 |
const modes = { l2: 'abs', l1: 'abs' };
|
| 313 |
|
| 314 |
function setMode(level, mode) {
|
|
|
|
| 320 |
renderTab(level);
|
| 321 |
}
|
| 322 |
|
| 323 |
+
function cssVar(name) {
|
| 324 |
+
return getComputedStyle(document.documentElement).getPropertyValue(name).trim();
|
| 325 |
+
}
|
| 326 |
+
|
| 327 |
function buildStackedBar(svgId, legendId, data, subtasks, experiments, normalize) {
|
| 328 |
+
const textColor = cssVar('--text');
|
| 329 |
+
const subtextColor = cssVar('--subtext');
|
| 330 |
+
const borderColor = cssVar('--border');
|
| 331 |
+
|
| 332 |
const svgEl = document.getElementById(svgId);
|
| 333 |
const W = svgEl.parentElement.clientWidth - 40;
|
| 334 |
const H = 340;
|
|
|
|
| 381 |
.call(d3.axisLeft(y).tickSize(-innerW).tickFormat('').ticks(5))
|
| 382 |
.call(gg => {
|
| 383 |
gg.select('.domain').remove();
|
| 384 |
+
gg.selectAll('line').attr('stroke', borderColor).attr('stroke-dasharray', '3,3');
|
| 385 |
});
|
| 386 |
|
| 387 |
// Stacked bars
|
|
|
|
| 402 |
.attr('x', d => x(d.id) + x.bandwidth() / 2)
|
| 403 |
.attr('y', d => d._total === 0 ? y(0) - 4 : y(d._displayTotal) - 5)
|
| 404 |
.attr('text-anchor', 'middle')
|
| 405 |
+
.attr('fill', d => d._total === 0 ? borderColor : subtextColor)
|
| 406 |
+
.attr('font-size', '11')
|
| 407 |
.text(d => {
|
| 408 |
if (d._total === 0) return '✓ 0 failures';
|
| 409 |
return normalize ? `n=${d._total}` : d._total;
|
|
|
|
| 417 |
g.append('line')
|
| 418 |
.attr('x1', xDiv).attr('x2', xDiv)
|
| 419 |
.attr('y1', -22).attr('y2', innerH + 4)
|
| 420 |
+
.attr('stroke', borderColor).attr('stroke-width', 1).attr('stroke-dasharray', '4,3');
|
| 421 |
|
| 422 |
g.append('text').attr('x', xDiv - 6).attr('y', -18).attr('text-anchor', 'end')
|
| 423 |
+
.attr('fill', '#f7934f').attr('font-size', '10').attr('letter-spacing', '0.06em').text('SERIES 1');
|
| 424 |
|
| 425 |
if (s2First) {
|
| 426 |
g.append('text').attr('x', xDiv + 6).attr('y', -18).attr('text-anchor', 'start')
|
| 427 |
+
.attr('fill', '#4dc98a').attr('font-size', '10').attr('letter-spacing', '0.06em').text('SERIES 2');
|
| 428 |
}
|
| 429 |
}
|
| 430 |
|
|
|
|
| 432 |
g.append('g')
|
| 433 |
.call(d3.axisLeft(y).ticks(5).tickSize(4).tickFormat(d => normalize ? d + '%' : d))
|
| 434 |
.call(gg => {
|
| 435 |
+
gg.select('.domain').attr('stroke', borderColor);
|
| 436 |
+
gg.selectAll('text').attr('fill', subtextColor).attr('font-size', '11');
|
| 437 |
+
gg.selectAll('line').attr('stroke', borderColor);
|
| 438 |
});
|
| 439 |
|
| 440 |
g.append('g').attr('transform', `translate(0,${innerH})`)
|
| 441 |
.call(d3.axisBottom(x).tickSize(0))
|
| 442 |
.call(gg => {
|
| 443 |
+
gg.select('.domain').attr('stroke', borderColor);
|
| 444 |
gg.selectAll('text')
|
| 445 |
.attr('fill', d => {
|
| 446 |
const a = experiments.find(a => a.id === d);
|
| 447 |
return a?.series === 2 ? '#4dc98a' : '#f7934f';
|
| 448 |
})
|
| 449 |
+
.attr('font-size', '11')
|
| 450 |
.attr('transform', 'rotate(-40)')
|
| 451 |
.attr('text-anchor', 'end')
|
| 452 |
.attr('dx', '-0.5em')
|
|
|
|
| 456 |
// Y axis label
|
| 457 |
svg.append('text').attr('transform', 'rotate(-90)')
|
| 458 |
.attr('x', -(margin.top + innerH / 2)).attr('y', 10).attr('text-anchor', 'middle')
|
| 459 |
+
.attr('fill', subtextColor).attr('font-size', '11')
|
| 460 |
.text(normalize ? 'Failure distribution (%)' : 'Failed rollouts (n)');
|
| 461 |
|
| 462 |
// Legend
|
|
|
|
| 475 |
if (rendered[id]) return;
|
| 476 |
rendered[id] = true;
|
| 477 |
const normalize = modes[id] === 'pct';
|
| 478 |
+
const visExps = getVisibleExperiments();
|
| 479 |
+
if (id === 'l2') buildStackedBar('chart-l2', 'legend-l2', L2_FAILURES, SUBTASKS_L2, visExps, normalize);
|
| 480 |
+
if (id === 'l1') buildStackedBar('chart-l1', 'legend-l1', L1_FAILURES, SUBTASKS_L1, visExps, normalize);
|
| 481 |
}
|
| 482 |
|
| 483 |
function showTab(id) {
|
|
|
|
| 493 |
window.showTab = showTab;
|
| 494 |
window.setMode = setMode;
|
| 495 |
|
| 496 |
+
renderTab('l1');
|
| 497 |
|
| 498 |
}
|
| 499 |
|
app/src/content/embeds/folding/l1-time-quality.html
CHANGED
|
@@ -33,8 +33,8 @@
|
|
| 33 |
</head>
|
| 34 |
<body>
|
| 35 |
<div class="legend">
|
| 36 |
-
<div class="legend-item"><div class="legend-pip" style="background:#f7934f"></div>
|
| 37 |
-
<div class="legend-item"><div class="legend-pip" style="background:#4f8ef7"></div>
|
| 38 |
<div class="legend-item" style="margin-left:8px; font-size:10px; color:#555">
|
| 39 |
Bubble size = Total SR
|
| 40 |
</div>
|
|
|
|
| 33 |
</head>
|
| 34 |
<body>
|
| 35 |
<div class="legend">
|
| 36 |
+
<div class="legend-item"><div class="legend-pip" style="background:#f7934f"></div>Fine-tuned (2.x)</div>
|
| 37 |
+
<div class="legend-item"><div class="legend-pip" style="background:#4f8ef7"></div>Initial (1.x)</div>
|
| 38 |
<div class="legend-item" style="margin-left:8px; font-size:10px; color:#555">
|
| 39 |
Bubble size = Total SR
|
| 40 |
</div>
|
app/src/content/embeds/folding/loss-curves.html
CHANGED
|
@@ -13,13 +13,13 @@
|
|
| 13 |
* { box-sizing: border-box; margin: 0; padding: 0; }
|
| 14 |
body { background: var(--bg); font-family: system-ui, -apple-system, sans-serif; color: var(--text); }
|
| 15 |
#chart-container { position: relative; width: 100%; }
|
| 16 |
-
.axis text { fill: #8b8fa8; font-size:
|
| 17 |
.axis path, .axis line { stroke: rgba(255,255,255,0.15); }
|
| 18 |
.grid line { stroke: var(--grid); }
|
| 19 |
.grid path { stroke: none; }
|
| 20 |
.legend-wrap {
|
| 21 |
display: flex; flex-wrap: wrap; gap: 6px 14px; justify-content: center;
|
| 22 |
-
padding: 8px 0 0; font-size:
|
| 23 |
}
|
| 24 |
.legend-item {
|
| 25 |
display: flex; align-items: center; gap: 5px; cursor: pointer;
|
|
@@ -33,7 +33,7 @@
|
|
| 33 |
.tooltip-loss {
|
| 34 |
position: absolute; background: #13152aee; border: 1px solid #333; border-radius: 8px;
|
| 35 |
padding: 10px 14px; pointer-events: none; opacity: 0; transition: opacity .15s;
|
| 36 |
-
z-index: 20; font-size:
|
| 37 |
max-width: 280px;
|
| 38 |
}
|
| 39 |
.tooltip-loss strong { color: #e8eaf0; }
|
|
@@ -41,7 +41,7 @@
|
|
| 41 |
.tooltip-loss .tt-swatch { width: 10px; height: 3px; border-radius: 1px; flex-shrink: 0; }
|
| 42 |
.series-toggle { display: flex; gap: 0; justify-content: center; margin-bottom: 6px; }
|
| 43 |
.series-btn {
|
| 44 |
-
padding:
|
| 45 |
background: #1a1d2e; color: #8b8fa8; transition: all .2s;
|
| 46 |
font-family: system-ui, sans-serif; border-bottom: 2px solid transparent;
|
| 47 |
}
|
|
@@ -54,8 +54,8 @@
|
|
| 54 |
<body>
|
| 55 |
<div class="series-toggle">
|
| 56 |
<button class="series-btn active" data-filter="all">All Runs</button>
|
| 57 |
-
<button class="series-btn" data-filter="s1">
|
| 58 |
-
<button class="series-btn" data-filter="s2">
|
| 59 |
</div>
|
| 60 |
<div id="chart-container">
|
| 61 |
<svg id="loss-svg"></svg>
|
|
@@ -68,8 +68,8 @@ const RUNS = [
|
|
| 68 |
{ key: "ablation1_1_2", label: "1.1 π0", series: "s1", color: "#ef5350" },
|
| 69 |
{ key: "ablation1_2_2", label: "1.2 π0.5", series: "s1", color: "#f59e0b" },
|
| 70 |
{ key: "ablation1_3_17_q", label: "1.3 Relative", series: "s1", color: "#10b981" },
|
| 71 |
-
{ key: "ablation1-4", label: "1.4 RABC low",
|
| 72 |
-
{ key: "ablation1-5_9", label: "1.5 RABC high",
|
| 73 |
{ key: "ablation1-7_2", label: "1.7 Rel+RABC", series: "s1", color: "#ec4899" },
|
| 74 |
{ key: "ablation2-1_100k_q", label: "2.1 HQ", series: "s2", color: "#14b8a6" },
|
| 75 |
{ key: "ablation2-2_100k", label: "2.2 HQ+RABC+Rel", series: "s2", color: "#6366f1" },
|
|
@@ -123,9 +123,23 @@ const svgEl = d3.select("#loss-svg");
|
|
| 123 |
const tt = document.getElementById("tt");
|
| 124 |
const legendEl = document.getElementById("legend");
|
| 125 |
|
| 126 |
-
|
|
|
|
|
|
|
|
|
|
| 127 |
const hidden = new Set();
|
| 128 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 129 |
function render() {
|
| 130 |
svgEl.selectAll("*").remove();
|
| 131 |
const W = container.clientWidth || 800;
|
|
@@ -166,9 +180,9 @@ function render() {
|
|
| 166 |
|
| 167 |
// Axis labels
|
| 168 |
g.append("text").attr("x", w / 2).attr("y", h + 32).attr("text-anchor", "middle")
|
| 169 |
-
.attr("fill", "#8b8fa8").attr("font-size",
|
| 170 |
g.append("text").attr("x", -h / 2).attr("y", -40).attr("text-anchor", "middle")
|
| 171 |
-
.attr("transform", "rotate(-90)").attr("fill", "#8b8fa8").attr("font-size",
|
| 172 |
|
| 173 |
// Lines
|
| 174 |
const line = d3.line().x(d => x(d.step)).y(d => y(d.loss)).curve(d3.curveMonotoneX);
|
|
|
|
| 13 |
* { box-sizing: border-box; margin: 0; padding: 0; }
|
| 14 |
body { background: var(--bg); font-family: system-ui, -apple-system, sans-serif; color: var(--text); }
|
| 15 |
#chart-container { position: relative; width: 100%; }
|
| 16 |
+
.axis text { fill: #8b8fa8; font-size: 13px; }
|
| 17 |
.axis path, .axis line { stroke: rgba(255,255,255,0.15); }
|
| 18 |
.grid line { stroke: var(--grid); }
|
| 19 |
.grid path { stroke: none; }
|
| 20 |
.legend-wrap {
|
| 21 |
display: flex; flex-wrap: wrap; gap: 6px 14px; justify-content: center;
|
| 22 |
+
padding: 8px 0 0; font-size: 13px;
|
| 23 |
}
|
| 24 |
.legend-item {
|
| 25 |
display: flex; align-items: center; gap: 5px; cursor: pointer;
|
|
|
|
| 33 |
.tooltip-loss {
|
| 34 |
position: absolute; background: #13152aee; border: 1px solid #333; border-radius: 8px;
|
| 35 |
padding: 10px 14px; pointer-events: none; opacity: 0; transition: opacity .15s;
|
| 36 |
+
z-index: 20; font-size: 13px; line-height: 1.6; box-shadow: 0 6px 24px rgba(0,0,0,.5);
|
| 37 |
max-width: 280px;
|
| 38 |
}
|
| 39 |
.tooltip-loss strong { color: #e8eaf0; }
|
|
|
|
| 41 |
.tooltip-loss .tt-swatch { width: 10px; height: 3px; border-radius: 1px; flex-shrink: 0; }
|
| 42 |
.series-toggle { display: flex; gap: 0; justify-content: center; margin-bottom: 6px; }
|
| 43 |
.series-btn {
|
| 44 |
+
padding: 7px 20px; font-size: 14px; font-weight: 600; cursor: pointer; border: none;
|
| 45 |
background: #1a1d2e; color: #8b8fa8; transition: all .2s;
|
| 46 |
font-family: system-ui, sans-serif; border-bottom: 2px solid transparent;
|
| 47 |
}
|
|
|
|
| 54 |
<body>
|
| 55 |
<div class="series-toggle">
|
| 56 |
<button class="series-btn active" data-filter="all">All Runs</button>
|
| 57 |
+
<button class="series-btn" data-filter="s1">Initial (1.x)</button>
|
| 58 |
+
<button class="series-btn" data-filter="s2">Fine-tuned (2.x)</button>
|
| 59 |
</div>
|
| 60 |
<div id="chart-container">
|
| 61 |
<svg id="loss-svg"></svg>
|
|
|
|
| 68 |
{ key: "ablation1_1_2", label: "1.1 π0", series: "s1", color: "#ef5350" },
|
| 69 |
{ key: "ablation1_2_2", label: "1.2 π0.5", series: "s1", color: "#f59e0b" },
|
| 70 |
{ key: "ablation1_3_17_q", label: "1.3 Relative", series: "s1", color: "#10b981" },
|
| 71 |
+
{ key: "ablation1-4", label: "1.4 RABC low", series: "s1", color: "#3b82f6" },
|
| 72 |
+
{ key: "ablation1-5_9", label: "1.5 RABC high", series: "s1", color: "#8b5cf6" },
|
| 73 |
{ key: "ablation1-7_2", label: "1.7 Rel+RABC", series: "s1", color: "#ec4899" },
|
| 74 |
{ key: "ablation2-1_100k_q", label: "2.1 HQ", series: "s2", color: "#14b8a6" },
|
| 75 |
{ key: "ablation2-2_100k", label: "2.2 HQ+RABC+Rel", series: "s2", color: "#6366f1" },
|
|
|
|
| 123 |
const tt = document.getElementById("tt");
|
| 124 |
const legendEl = document.getElementById("legend");
|
| 125 |
|
| 126 |
+
const configEl = container.closest("[data-config]");
|
| 127 |
+
const chartConfig = configEl ? JSON.parse(configEl.dataset.config || "{}") : {};
|
| 128 |
+
|
| 129 |
+
let activeFilter = chartConfig.initialFilter || "all";
|
| 130 |
const hidden = new Set();
|
| 131 |
|
| 132 |
+
if (chartConfig.initialRuns) {
|
| 133 |
+
RUNS.forEach(r => { if (!chartConfig.initialRuns.includes(r.key)) hidden.add(r.key); });
|
| 134 |
+
}
|
| 135 |
+
if (chartConfig.hideToggle) {
|
| 136 |
+
const toggle = container.closest("body")?.querySelector(".series-toggle");
|
| 137 |
+
if (toggle) toggle.style.display = "none";
|
| 138 |
+
}
|
| 139 |
+
document.querySelectorAll(".series-btn").forEach(b => {
|
| 140 |
+
b.classList.toggle("active", b.dataset.filter === activeFilter);
|
| 141 |
+
});
|
| 142 |
+
|
| 143 |
function render() {
|
| 144 |
svgEl.selectAll("*").remove();
|
| 145 |
const W = container.clientWidth || 800;
|
|
|
|
| 180 |
|
| 181 |
// Axis labels
|
| 182 |
g.append("text").attr("x", w / 2).attr("y", h + 32).attr("text-anchor", "middle")
|
| 183 |
+
.attr("fill", "#8b8fa8").attr("font-size", 13).text("Training Steps");
|
| 184 |
g.append("text").attr("x", -h / 2).attr("y", -40).attr("text-anchor", "middle")
|
| 185 |
+
.attr("transform", "rotate(-90)").attr("fill", "#8b8fa8").attr("font-size", 13).text("Loss");
|
| 186 |
|
| 187 |
// Lines
|
| 188 |
const line = d3.line().x(d => x(d.step)).y(d => y(d.loss)).curve(d3.curveMonotoneX);
|
app/src/content/embeds/folding/pi05-architecture.html
CHANGED
|
@@ -82,8 +82,8 @@
|
|
| 82 |
// Responsive scaling
|
| 83 |
const scale = Math.min(1, W / 800);
|
| 84 |
const fontSize = Math.round(12 * scale);
|
| 85 |
-
const smallFont = Math.round(
|
| 86 |
-
const titleFont = Math.round(
|
| 87 |
|
| 88 |
// ---- SECTION POSITIONS (top to bottom) ----
|
| 89 |
const inputY = pad;
|
|
@@ -230,7 +230,7 @@
|
|
| 230 |
drawArrow(g, W / 2, vlaY + vlaH, W / 2, actionY);
|
| 231 |
|
| 232 |
// ========== ACTION EXPERT (FLOW MATCHING) ==========
|
| 233 |
-
drawSection(g, pad, actionY, inputW, actionH, actionBg, actionBorder, 'ACTION EXPERT
|
| 234 |
|
| 235 |
// Noise -> vector field -> actions visualization
|
| 236 |
const flowY = actionY + 28 * scale;
|
|
|
|
| 82 |
// Responsive scaling
|
| 83 |
const scale = Math.min(1, W / 800);
|
| 84 |
const fontSize = Math.round(12 * scale);
|
| 85 |
+
const smallFont = Math.round(12 * scale);
|
| 86 |
+
const titleFont = Math.round(15 * scale);
|
| 87 |
|
| 88 |
// ---- SECTION POSITIONS (top to bottom) ----
|
| 89 |
const inputY = pad;
|
|
|
|
| 230 |
drawArrow(g, W / 2, vlaY + vlaH, W / 2, actionY);
|
| 231 |
|
| 232 |
// ========== ACTION EXPERT (FLOW MATCHING) ==========
|
| 233 |
+
drawSection(g, pad, actionY, inputW, actionH, actionBg, actionBorder, 'ACTION EXPERT: FLOW MATCHING (300M)', 'center');
|
| 234 |
|
| 235 |
// Noise -> vector field -> actions visualization
|
| 236 |
const flowY = actionY + 28 * scale;
|
app/src/content/embeds/folding/pi05-sarm-architecture.html
CHANGED
|
@@ -11,14 +11,14 @@
|
|
| 11 |
.tooltip-arch {
|
| 12 |
position: absolute; background: #13152a; border: 1px solid #333; border-radius: 10px;
|
| 13 |
padding: 14px 18px; pointer-events: none; opacity: 0; transition: opacity .2s;
|
| 14 |
-
z-index: 20; max-width: 320px; box-shadow: 0 8px 32px rgba(0,0,0,.6); font-size:
|
| 15 |
}
|
| 16 |
-
.tooltip-arch strong { display: block; margin-bottom: 6px; font-size:
|
| 17 |
-
.tooltip-arch .tt-detail { color: #8b8fa8; font-size:
|
| 18 |
-
.tooltip-arch code { background: #252540; padding: 1px 5px; border-radius: 3px; font-size:
|
| 19 |
.tab-bar { display: flex; gap: 0; justify-content: center; margin-bottom: 0; }
|
| 20 |
.tab-btn {
|
| 21 |
-
padding:
|
| 22 |
background: #1a1d2e; color: #8b8fa8; transition: all .2s; border-bottom: 2px solid transparent;
|
| 23 |
font-family: system-ui, sans-serif;
|
| 24 |
}
|
|
@@ -34,16 +34,32 @@
|
|
| 34 |
<button class="tab-btn" data-tab="pi05">π0.5 Detail</button>
|
| 35 |
<button class="tab-btn" data-tab="sarm">SARM Detail</button>
|
| 36 |
</div>
|
| 37 |
-
<div
|
| 38 |
-
<svg
|
| 39 |
-
<div class="tooltip-arch"
|
| 40 |
</div>
|
| 41 |
<script>
|
| 42 |
function _initArchViz() {
|
| 43 |
-
|
| 44 |
-
|
| 45 |
-
|
| 46 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 47 |
|
| 48 |
function isLight() {
|
| 49 |
try {
|
|
@@ -75,6 +91,7 @@ const DARK = {
|
|
| 75 |
scoreBox: "#1a1a2e", scoreText: "#e8eaf0",
|
| 76 |
ttBg: "#13152a", ttBorder: "#333", ttText: "#e8eaf0", ttSub: "#8b8fa8", ttCode: "#252540", ttCodeText: "#a78bfa",
|
| 77 |
tabBg: "#1a1d2e", tabText: "#8b8fa8", tabActiveBg: "#252540", tabActiveText: "#e8eaf0",
|
|
|
|
| 78 |
};
|
| 79 |
|
| 80 |
const LIGHT = {
|
|
@@ -99,13 +116,14 @@ const LIGHT = {
|
|
| 99 |
scoreBox: "#f9fafb", scoreText: "#1a1a2e",
|
| 100 |
ttBg: "#fff", ttBorder: "#e2e4e9", ttText: "#1a1a2e", ttSub: "#6b7280", ttCode: "#f3f4f6", ttCodeText: "#6d28d9",
|
| 101 |
tabBg: "#f3f4f6", tabText: "#6b7280", tabActiveBg: "#fff", tabActiveText: "#1a1a2e",
|
|
|
|
| 102 |
};
|
| 103 |
|
| 104 |
function T() { return isLight() ? LIGHT : DARK; }
|
| 105 |
|
| 106 |
function applyTabTheme() {
|
| 107 |
var t = T();
|
| 108 |
-
|
| 109 |
if (btn.classList.contains("active")) {
|
| 110 |
btn.style.background = t.tabActiveBg;
|
| 111 |
btn.style.color = t.tabActiveText;
|
|
@@ -114,7 +132,7 @@ function applyTabTheme() {
|
|
| 114 |
btn.style.color = t.tabText;
|
| 115 |
}
|
| 116 |
});
|
| 117 |
-
var tip =
|
| 118 |
if (tip) {
|
| 119 |
tip.style.background = t.ttBg;
|
| 120 |
tip.style.borderColor = t.ttBorder;
|
|
@@ -128,9 +146,9 @@ const tooltips = {
|
|
| 128 |
siglip: { title: "SigLIP Vision Encoder (400M)", detail: "Converts camera images to patch embeddings. Frozen during fine-tuning for stability. Outputs visual tokens for the VLM backbone." },
|
| 129 |
gemma_vlm: { title: "Gemma VLM Backbone (2B)", detail: "Pre-trained language model that processes visual tokens, language tokens, and proprioceptive state. Shares attention across all modalities." },
|
| 130 |
subtask: { title: "Subtask Prediction", detail: "Auto-regressive text decoding produces a high-level subtask like <code>\"pick up left sleeve\"</code>. Acts as chain-of-thought for the action expert." },
|
| 131 |
-
action_expert: { title: "Action Expert (300M)", detail: "Smaller Gemma model dedicated to flow matching. Denoises Gaussian noise into continuous 16D actions (30 steps
|
| 132 |
flow: { title: "Flow Matching", detail: "Generative approach that denoises Gaussian noise into continuous actions. Enables multi-modal action distributions for complex manipulation." },
|
| 133 |
-
actions: { title: "Continuous Actions", detail: "30-step action chunk
|
| 134 |
rtc: { title: "Real-Time Chunking (RTC)", detail: "Generates the next chunk while executing the current one. Freezes actions guaranteed to execute and inpaints the rest for smooth asynchronous execution." },
|
| 135 |
stage_tfm: { title: "Stage Transformer", detail: "Classifies the current frame into a stage/subtask. Uses causal attention over <code>[visual, language, state]</code> tokens. Outputs stage logits for sparse or dense schemes." },
|
| 136 |
subtask_tfm: { title: "Subtask Transformer", detail: "Predicts within-stage progress τ ∈ [0,1] conditioned on the stage prior (one-hot). Teacher forcing: 75% ground-truth, 25% predicted stage." },
|
|
@@ -159,6 +177,7 @@ function showTT(event, key) {
|
|
| 159 |
tt.style("left", Math.min(ex + 14, bx.width - ttW - 10) + "px").style("top", Math.max(ey - 80, 8) + "px");
|
| 160 |
}
|
| 161 |
function hideTT() { tt.style("opacity", 0); }
|
|
|
|
| 162 |
|
| 163 |
function box(g, x, y, w, h, fill, stroke, rx) {
|
| 164 |
return g.append("rect").attr("x",x).attr("y",y).attr("width",w).attr("height",h)
|
|
@@ -201,6 +220,15 @@ function hoverBox(g, x, y, w, h, key) {
|
|
| 201 |
.on("mousemove", e => showTT(e, key)).on("mouseleave", hideTT);
|
| 202 |
}
|
| 203 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 204 |
function renderSystem() {
|
| 205 |
var t = T();
|
| 206 |
const W = container.clientWidth || 800;
|
|
@@ -211,13 +239,13 @@ function renderSystem() {
|
|
| 211 |
const obsY = 14;
|
| 212 |
const obsH = 62*s;
|
| 213 |
box(g, pad, obsY, W-pad*2, obsH, t.obsBox, t.obsBorder);
|
| 214 |
-
label(g, W/2, obsY+16, "OBSERVATION",
|
| 215 |
const items = ["📷 Base Cam","📷 L-Wrist","📷 R-Wrist","🦾 Joints (16D)","💬 Language"];
|
| 216 |
const iw = (W-pad*2-20) / items.length;
|
| 217 |
items.forEach((txt,i) => {
|
| 218 |
const ix = pad+10+i*iw;
|
| 219 |
box(g, ix, obsY+26, iw-6, 26*s, t.obsInner, t.obsInnerBorder, 5);
|
| 220 |
-
label(g, ix+(iw-6)/2, obsY+26+16*s, txt,
|
| 221 |
});
|
| 222 |
hoverBox(g, pad, obsY, W-pad*2, obsH, "obs");
|
| 223 |
|
|
@@ -226,43 +254,43 @@ function renderSystem() {
|
|
| 226 |
const piW = W*0.58;
|
| 227 |
const piX = pad;
|
| 228 |
box(g, piX, piY, piW, piH, t.piBox, t.piBorder);
|
| 229 |
-
label(g, piX+piW/2, piY+18, "π0.5 VISION-LANGUAGE-ACTION MODEL",
|
| 230 |
|
| 231 |
const sigY = piY+32;
|
| 232 |
const sigH = 36*s;
|
| 233 |
box(g, piX+12, sigY, piW*0.35, sigH, t.sigBox, t.sigBorder, 6);
|
| 234 |
-
label(g, piX+12+piW*0.175, sigY+14*s, "SigLIP (400M)",
|
| 235 |
-
label(g, piX+12+piW*0.175, sigY+26*s, "Vision Encoder",
|
| 236 |
hoverBox(g, piX+12, sigY, piW*0.35, sigH, "siglip");
|
| 237 |
|
| 238 |
const gemY = sigY;
|
| 239 |
const gemX = piX+12+piW*0.38;
|
| 240 |
const gemW = piW*0.58;
|
| 241 |
box(g, gemX, gemY, gemW, sigH, t.gemBox, t.gemBorder, 6);
|
| 242 |
-
label(g, gemX+gemW/2, gemY+14*s, "Gemma VLM (2B)",
|
| 243 |
-
label(g, gemX+gemW/2, gemY+26*s, "Language + Vision Backbone",
|
| 244 |
hoverBox(g, gemX, gemY, gemW, sigH, "gemma_vlm");
|
| 245 |
arrow(g, piX+12+piW*0.35, sigY+sigH/2, gemX, sigY+sigH/2, t.sigBorder);
|
| 246 |
|
| 247 |
const subY = sigY+sigH+14*s;
|
| 248 |
const subH = 30*s;
|
| 249 |
box(g, piX+12, subY, piW*0.42, subH, t.subBox, t.subBorder, 6);
|
| 250 |
-
label(g, piX+12+piW*0.21, subY+12*s, "⬆ Subtask: \"pick up left sleeve\"",
|
| 251 |
-
label(g, piX+12+piW*0.21, subY+24*s, "Auto-regressive text decoding",
|
| 252 |
hoverBox(g, piX+12, subY, piW*0.42, subH, "subtask");
|
| 253 |
arrow(g, gemX+gemW/2, gemY+sigH, piX+12+piW*0.21, subY, t.sigBorder, true);
|
| 254 |
|
| 255 |
const aeY = subY+subH+12*s;
|
| 256 |
const aeH = 44*s;
|
| 257 |
box(g, piX+12, aeY, piW-24, aeH, t.aeBox, t.aeBorder, 6);
|
| 258 |
-
label(g, piX+piW/2, aeY+14*s, "ACTION EXPERT (300M)
|
| 259 |
|
| 260 |
const nX = piX+24;
|
| 261 |
const nW = 56*s;
|
| 262 |
const nY2 = aeY+24*s;
|
| 263 |
const nH2 = 16*s;
|
| 264 |
box(g, nX, nY2, nW, nH2, t.noiseBox, t.noiseBorder, 4);
|
| 265 |
-
label(g, nX+nW/2, nY2+11*s, "Noise",
|
| 266 |
hoverBox(g, nX, nY2, nW, nH2, "noise");
|
| 267 |
|
| 268 |
const wvX = nX+nW+10;
|
|
@@ -275,11 +303,11 @@ function renderSystem() {
|
|
| 275 |
|
| 276 |
const oaX = piX+piW-24-68*s;
|
| 277 |
box(g, oaX, nY2, 62*s, nH2, t.actionBox, t.actionBorder, 4);
|
| 278 |
-
label(g, oaX+31*s, nY2+11*s, "30 × 16D",
|
| 279 |
hoverBox(g, piX+12, aeY, piW-24, aeH, "action_expert");
|
| 280 |
|
| 281 |
arrow(g, gemX+gemW*0.3, gemY+sigH, gemX+gemW*0.3, aeY, t.sigBorder, false);
|
| 282 |
-
label(g, gemX+gemW*0.3+4, (gemY+sigH+aeY)/2, "VLM prefix",
|
| 283 |
|
| 284 |
arrow(g, piX+12+piW*0.42, subY+subH/2, piX+piW-12, subY+subH/2, t.subBorder, true);
|
| 285 |
curvedArrow(g, piX+piW-12, subY+subH/2, piX+piW-8, aeY-4, piX+piW-24, aeY, t.subBorder, true);
|
|
@@ -287,37 +315,37 @@ function renderSystem() {
|
|
| 287 |
const rtcX = piX+piW-80*s;
|
| 288 |
const rtcY2 = aeY+aeH+8*s;
|
| 289 |
box(g, rtcX, rtcY2, 70*s, 22*s, t.rtcBox, t.rtcBorder, 5);
|
| 290 |
-
label(g, rtcX+35*s, rtcY2+14*s, "🔄 RTC re-plan",
|
| 291 |
hoverBox(g, rtcX, rtcY2, 70*s, 22*s, "rtc");
|
| 292 |
|
| 293 |
const sX = piX+piW+16;
|
| 294 |
const sW = W-sX-pad;
|
| 295 |
const sH = piH;
|
| 296 |
box(g, sX, piY, sW, sH, t.sarmBox, t.sarmBorder);
|
| 297 |
-
label(g, sX+sW/2, piY+18, "SARM REWARD MODEL",
|
| 298 |
-
label(g, sX+sW/2, piY+34, "(training only)",
|
| 299 |
|
| 300 |
const descY = piY+54;
|
| 301 |
-
label(g, sX+sW/2, descY, "Scores every episode with a",
|
| 302 |
-
label(g, sX+sW/2, descY+16*s, "per-timestep quality signal [0, 1]",
|
| 303 |
|
| 304 |
const rwY = descY+32*s;
|
| 305 |
const rwH = 34*s;
|
| 306 |
box(g, sX+10, rwY, sW-20, rwH, t.rwBox, t.rwBorder, 6);
|
| 307 |
-
label(g, sX+sW/2, rwY+14*s, "Per-timestep Reward",
|
| 308 |
-
label(g, sX+sW/2, rwY+27*s, "0 = start · · · 1 = task complete",
|
| 309 |
hoverBox(g, sX+10, rwY, sW-20, rwH, "sarm_reward");
|
| 310 |
|
| 311 |
const wsY = rwY+rwH+14*s;
|
| 312 |
const wsH = 34*s;
|
| 313 |
box(g, sX+10, wsY, sW-20, wsH, t.wsBox, t.wsBorder, 6);
|
| 314 |
-
label(g, sX+sW/2, wsY+14*s, "Weighted Sampling",
|
| 315 |
-
label(g, sX+sW/2, wsY+27*s, "High-reward episodes contribute more",
|
| 316 |
hoverBox(g, sX+10, wsY, sW-20, wsH, "rabc");
|
| 317 |
arrow(g, sX+sW/2, rwY+rwH, sX+sW/2, wsY, t.rwBorder);
|
| 318 |
|
| 319 |
curvedArrow(g, sX+10, wsY+wsH/2, sX-8, wsY+wsH/2+30*s, piX+piW/2, piY+piH+8, t.wsBorder, true);
|
| 320 |
-
label(g, (sX+piX+piW/2)/2, piY+piH+22*s, "weighted sampling (training only)",
|
| 321 |
hoverBox(g, sX, piY, sW, sH, "sarm_box");
|
| 322 |
|
| 323 |
arrow(g, W*0.3, obsY+obsH, piX+piW/2, piY, t.obsBorder);
|
|
@@ -326,8 +354,8 @@ function renderSystem() {
|
|
| 326 |
const outY = piY+piH+16;
|
| 327 |
const outH = 42*s;
|
| 328 |
box(g, pad, outY, piW, outH, t.outBox, t.outBorder);
|
| 329 |
-
label(g, piX+piW/2, outY+14*s, "ROBOT ACTIONS",
|
| 330 |
-
label(g, piX+piW/2, outY+30*s, "16D joint targets
|
| 331 |
arrow(g, piX+piW/2, piY+piH, piX+piW/2, outY, t.outBorder);
|
| 332 |
hoverBox(g, pad, outY, piW, outH, "actions");
|
| 333 |
|
|
@@ -339,96 +367,184 @@ function renderPi05() {
|
|
| 339 |
var t = T();
|
| 340 |
const W = container.clientWidth || 800;
|
| 341 |
const g = svg.append("g");
|
| 342 |
-
const s = Math.min(1.1, W /
|
| 343 |
-
const pad =
|
| 344 |
-
const
|
| 345 |
-
|
| 346 |
-
const
|
| 347 |
-
const
|
| 348 |
-
|
| 349 |
-
|
| 350 |
-
const
|
| 351 |
-
const
|
| 352 |
-
|
| 353 |
-
|
| 354 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 355 |
});
|
| 356 |
-
hoverBox(g,
|
| 357 |
-
|
| 358 |
-
|
| 359 |
-
|
| 360 |
-
|
| 361 |
-
|
| 362 |
-
|
| 363 |
-
|
| 364 |
-
|
| 365 |
-
|
| 366 |
-
|
| 367 |
-
|
| 368 |
-
|
| 369 |
-
|
| 370 |
-
label(g,
|
| 371 |
-
|
| 372 |
-
|
| 373 |
-
|
| 374 |
-
|
| 375 |
-
|
| 376 |
-
|
| 377 |
-
|
| 378 |
-
|
| 379 |
-
|
| 380 |
-
|
| 381 |
-
|
| 382 |
-
|
| 383 |
-
|
| 384 |
-
|
| 385 |
-
|
| 386 |
-
|
| 387 |
-
|
| 388 |
-
|
| 389 |
-
|
| 390 |
-
|
| 391 |
-
|
| 392 |
-
|
| 393 |
-
|
| 394 |
-
|
| 395 |
-
|
| 396 |
-
const
|
| 397 |
-
|
| 398 |
-
|
| 399 |
-
label(g,
|
| 400 |
-
|
| 401 |
-
|
| 402 |
-
|
| 403 |
-
|
| 404 |
-
|
| 405 |
-
|
| 406 |
-
|
| 407 |
-
|
| 408 |
-
|
| 409 |
-
|
| 410 |
-
|
| 411 |
-
|
| 412 |
-
|
| 413 |
-
|
| 414 |
-
label(g,
|
| 415 |
-
|
| 416 |
-
|
| 417 |
-
|
| 418 |
-
|
| 419 |
-
|
| 420 |
-
|
| 421 |
-
|
| 422 |
-
|
| 423 |
-
|
| 424 |
-
|
| 425 |
-
|
| 426 |
-
|
| 427 |
-
|
| 428 |
-
|
| 429 |
-
|
| 430 |
-
|
| 431 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 432 |
svg.attr("width",W).attr("height",H).attr("viewBox",`0 0 ${W} ${H}`);
|
| 433 |
}
|
| 434 |
|
|
@@ -436,105 +552,121 @@ function renderSARM() {
|
|
| 436 |
var t = T();
|
| 437 |
const W = container.clientWidth || 800;
|
| 438 |
const g = svg.append("g");
|
| 439 |
-
const s = Math.min(1.1, W /
|
| 440 |
-
const pad =
|
| 441 |
-
const
|
| 442 |
-
|
| 443 |
-
const
|
| 444 |
-
const
|
| 445 |
-
|
| 446 |
-
|
| 447 |
-
|
| 448 |
-
|
| 449 |
-
|
| 450 |
-
|
| 451 |
-
|
| 452 |
-
const
|
| 453 |
-
|
| 454 |
-
|
| 455 |
-
|
| 456 |
-
|
| 457 |
-
|
| 458 |
-
|
| 459 |
-
|
| 460 |
-
const
|
| 461 |
-
|
| 462 |
-
|
| 463 |
-
|
| 464 |
-
|
| 465 |
-
|
| 466 |
-
|
| 467 |
-
|
| 468 |
-
|
| 469 |
-
|
| 470 |
-
|
| 471 |
-
|
| 472 |
-
|
| 473 |
-
|
| 474 |
-
|
| 475 |
-
box(g,
|
| 476 |
-
label(g,
|
| 477 |
-
|
| 478 |
-
|
| 479 |
-
|
| 480 |
-
|
| 481 |
-
|
| 482 |
-
|
| 483 |
-
|
| 484 |
-
|
| 485 |
-
|
| 486 |
-
|
| 487 |
-
|
| 488 |
-
|
| 489 |
-
|
| 490 |
-
|
| 491 |
-
|
| 492 |
-
|
| 493 |
-
|
| 494 |
-
|
| 495 |
-
|
| 496 |
-
|
| 497 |
-
|
| 498 |
-
|
| 499 |
-
|
| 500 |
-
|
| 501 |
-
|
| 502 |
-
|
| 503 |
-
|
| 504 |
-
|
| 505 |
-
|
| 506 |
-
|
| 507 |
-
|
| 508 |
-
|
| 509 |
-
|
| 510 |
-
|
| 511 |
-
|
| 512 |
-
|
| 513 |
-
|
| 514 |
-
label(g,
|
| 515 |
-
label(g,
|
| 516 |
-
|
| 517 |
-
|
| 518 |
-
|
| 519 |
-
|
| 520 |
-
|
| 521 |
-
|
| 522 |
-
|
| 523 |
-
|
| 524 |
-
|
| 525 |
-
|
| 526 |
-
|
| 527 |
-
|
| 528 |
-
|
| 529 |
-
|
| 530 |
-
|
| 531 |
-
|
| 532 |
-
|
| 533 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 534 |
svg.attr("width",W).attr("height",H).attr("viewBox",`0 0 ${W} ${H}`);
|
| 535 |
}
|
| 536 |
|
| 537 |
function render() {
|
|
|
|
| 538 |
svg.selectAll("*").remove();
|
| 539 |
applyTabTheme();
|
| 540 |
if (currentTab === "system") renderSystem();
|
|
@@ -542,14 +674,16 @@ function render() {
|
|
| 542 |
else if (currentTab === "sarm") renderSARM();
|
| 543 |
}
|
| 544 |
|
| 545 |
-
|
| 546 |
-
|
| 547 |
-
|
| 548 |
-
|
| 549 |
-
|
| 550 |
-
|
|
|
|
|
|
|
| 551 |
});
|
| 552 |
-
}
|
| 553 |
|
| 554 |
render();
|
| 555 |
window.addEventListener("resize", render);
|
|
@@ -563,10 +697,10 @@ obs.observe(themeEl, { attributes: true, attributeFilter: ["data-theme"] });
|
|
| 563 |
if (typeof d3 !== "undefined") {
|
| 564 |
_initArchViz();
|
| 565 |
} else {
|
| 566 |
-
var
|
| 567 |
-
|
| 568 |
-
|
| 569 |
-
document.head.appendChild(
|
| 570 |
}
|
| 571 |
</script>
|
| 572 |
</body>
|
|
|
|
| 11 |
.tooltip-arch {
|
| 12 |
position: absolute; background: #13152a; border: 1px solid #333; border-radius: 10px;
|
| 13 |
padding: 14px 18px; pointer-events: none; opacity: 0; transition: opacity .2s;
|
| 14 |
+
z-index: 20; max-width: 320px; box-shadow: 0 8px 32px rgba(0,0,0,.6); font-size: 14px; line-height: 1.5;
|
| 15 |
}
|
| 16 |
+
.tooltip-arch strong { display: block; margin-bottom: 6px; font-size: 15px; color: #e8eaf0; }
|
| 17 |
+
.tooltip-arch .tt-detail { color: #8b8fa8; font-size: 13px; }
|
| 18 |
+
.tooltip-arch code { background: #252540; padding: 1px 5px; border-radius: 3px; font-size: 12px; color: #a78bfa; }
|
| 19 |
.tab-bar { display: flex; gap: 0; justify-content: center; margin-bottom: 0; }
|
| 20 |
.tab-btn {
|
| 21 |
+
padding: 9px 22px; font-size: 15px; font-weight: 600; cursor: pointer; border: none;
|
| 22 |
background: #1a1d2e; color: #8b8fa8; transition: all .2s; border-bottom: 2px solid transparent;
|
| 23 |
font-family: system-ui, sans-serif;
|
| 24 |
}
|
|
|
|
| 34 |
<button class="tab-btn" data-tab="pi05">π0.5 Detail</button>
|
| 35 |
<button class="tab-btn" data-tab="sarm">SARM Detail</button>
|
| 36 |
</div>
|
| 37 |
+
<div class="arch-container">
|
| 38 |
+
<svg class="arch-svg"></svg>
|
| 39 |
+
<div class="tooltip-arch"></div>
|
| 40 |
</div>
|
| 41 |
<script>
|
| 42 |
function _initArchViz() {
|
| 43 |
+
var root = document.querySelector(".arch-container:not([data-init])");
|
| 44 |
+
if (!root) return;
|
| 45 |
+
root.setAttribute("data-init", "1");
|
| 46 |
+
var tabBar = root.previousElementSibling;
|
| 47 |
+
if (!tabBar || !tabBar.classList.contains("tab-bar")) tabBar = null;
|
| 48 |
+
|
| 49 |
+
const svg = d3.select(root.querySelector(".arch-svg"));
|
| 50 |
+
const container = root;
|
| 51 |
+
const tt = d3.select(root.querySelector(".tooltip-arch"));
|
| 52 |
+
const configEl = container.closest("[data-config]");
|
| 53 |
+
const chartConfig = configEl ? JSON.parse(configEl.dataset.config || "{}") : {};
|
| 54 |
+
let currentTab = chartConfig.initialTab || "system";
|
| 55 |
+
if (chartConfig.hideTabBar && tabBar) {
|
| 56 |
+
tabBar.style.display = "none";
|
| 57 |
+
}
|
| 58 |
+
if (tabBar) {
|
| 59 |
+
tabBar.querySelectorAll(".tab-btn").forEach(b => {
|
| 60 |
+
b.classList.toggle("active", b.dataset.tab === currentTab);
|
| 61 |
+
});
|
| 62 |
+
}
|
| 63 |
|
| 64 |
function isLight() {
|
| 65 |
try {
|
|
|
|
| 91 |
scoreBox: "#1a1a2e", scoreText: "#e8eaf0",
|
| 92 |
ttBg: "#13152a", ttBorder: "#333", ttText: "#e8eaf0", ttSub: "#8b8fa8", ttCode: "#252540", ttCodeText: "#a78bfa",
|
| 93 |
tabBg: "#1a1d2e", tabText: "#8b8fa8", tabActiveBg: "#252540", tabActiveText: "#e8eaf0",
|
| 94 |
+
bg: "#0f1117",
|
| 95 |
};
|
| 96 |
|
| 97 |
const LIGHT = {
|
|
|
|
| 116 |
scoreBox: "#f9fafb", scoreText: "#1a1a2e",
|
| 117 |
ttBg: "#fff", ttBorder: "#e2e4e9", ttText: "#1a1a2e", ttSub: "#6b7280", ttCode: "#f3f4f6", ttCodeText: "#6d28d9",
|
| 118 |
tabBg: "#f3f4f6", tabText: "#6b7280", tabActiveBg: "#fff", tabActiveText: "#1a1a2e",
|
| 119 |
+
bg: "#ffffff",
|
| 120 |
};
|
| 121 |
|
| 122 |
function T() { return isLight() ? LIGHT : DARK; }
|
| 123 |
|
| 124 |
function applyTabTheme() {
|
| 125 |
var t = T();
|
| 126 |
+
if (tabBar) tabBar.querySelectorAll(".tab-btn").forEach(function(btn) {
|
| 127 |
if (btn.classList.contains("active")) {
|
| 128 |
btn.style.background = t.tabActiveBg;
|
| 129 |
btn.style.color = t.tabActiveText;
|
|
|
|
| 132 |
btn.style.color = t.tabText;
|
| 133 |
}
|
| 134 |
});
|
| 135 |
+
var tip = root.querySelector(".tooltip-arch");
|
| 136 |
if (tip) {
|
| 137 |
tip.style.background = t.ttBg;
|
| 138 |
tip.style.borderColor = t.ttBorder;
|
|
|
|
| 146 |
siglip: { title: "SigLIP Vision Encoder (400M)", detail: "Converts camera images to patch embeddings. Frozen during fine-tuning for stability. Outputs visual tokens for the VLM backbone." },
|
| 147 |
gemma_vlm: { title: "Gemma VLM Backbone (2B)", detail: "Pre-trained language model that processes visual tokens, language tokens, and proprioceptive state. Shares attention across all modalities." },
|
| 148 |
subtask: { title: "Subtask Prediction", detail: "Auto-regressive text decoding produces a high-level subtask like <code>\"pick up left sleeve\"</code>. Acts as chain-of-thought for the action expert." },
|
| 149 |
+
action_expert: { title: "Action Expert (300M)", detail: "Smaller Gemma model dedicated to flow matching. Denoises Gaussian noise into continuous 16D actions (30 steps) over <code>10 denoising steps</code>. Conditioned on VLM backbone output." },
|
| 150 |
flow: { title: "Flow Matching", detail: "Generative approach that denoises Gaussian noise into continuous actions. Enables multi-modal action distributions for complex manipulation." },
|
| 151 |
+
actions: { title: "Continuous Actions", detail: "30-step action chunk: target joint angles + gripper commands for 2 arms (16D). Re-planned every <code>execution_horizon</code> steps via RTC." },
|
| 152 |
rtc: { title: "Real-Time Chunking (RTC)", detail: "Generates the next chunk while executing the current one. Freezes actions guaranteed to execute and inpaints the rest for smooth asynchronous execution." },
|
| 153 |
stage_tfm: { title: "Stage Transformer", detail: "Classifies the current frame into a stage/subtask. Uses causal attention over <code>[visual, language, state]</code> tokens. Outputs stage logits for sparse or dense schemes." },
|
| 154 |
subtask_tfm: { title: "Subtask Transformer", detail: "Predicts within-stage progress τ ∈ [0,1] conditioned on the stage prior (one-hot). Teacher forcing: 75% ground-truth, 25% predicted stage." },
|
|
|
|
| 177 |
tt.style("left", Math.min(ex + 14, bx.width - ttW - 10) + "px").style("top", Math.max(ey - 80, 8) + "px");
|
| 178 |
}
|
| 179 |
function hideTT() { tt.style("opacity", 0); }
|
| 180 |
+
var _pi05AnimId = null;
|
| 181 |
|
| 182 |
function box(g, x, y, w, h, fill, stroke, rx) {
|
| 183 |
return g.append("rect").attr("x",x).attr("y",y).attr("width",w).attr("height",h)
|
|
|
|
| 220 |
.on("mousemove", e => showTT(e, key)).on("mouseleave", hideTT);
|
| 221 |
}
|
| 222 |
|
| 223 |
+
function arrowLabel(g, x, y, text, size, color, anchor) {
|
| 224 |
+
return g.append("text").attr("x",x).attr("y",y).attr("text-anchor",anchor||"middle")
|
| 225 |
+
.attr("font-size",size).attr("fill",color||T().subtext)
|
| 226 |
+
.attr("font-weight",400).attr("font-family","system-ui, sans-serif")
|
| 227 |
+
.attr("paint-order","stroke fill")
|
| 228 |
+
.attr("stroke", T().bg).attr("stroke-width", 4).attr("stroke-linejoin","round")
|
| 229 |
+
.text(text);
|
| 230 |
+
}
|
| 231 |
+
|
| 232 |
function renderSystem() {
|
| 233 |
var t = T();
|
| 234 |
const W = container.clientWidth || 800;
|
|
|
|
| 239 |
const obsY = 14;
|
| 240 |
const obsH = 62*s;
|
| 241 |
box(g, pad, obsY, W-pad*2, obsH, t.obsBox, t.obsBorder);
|
| 242 |
+
label(g, W/2, obsY+16, "OBSERVATION", 13*s, t.obsLabel, "middle", 700);
|
| 243 |
const items = ["📷 Base Cam","📷 L-Wrist","📷 R-Wrist","🦾 Joints (16D)","💬 Language"];
|
| 244 |
const iw = (W-pad*2-20) / items.length;
|
| 245 |
items.forEach((txt,i) => {
|
| 246 |
const ix = pad+10+i*iw;
|
| 247 |
box(g, ix, obsY+26, iw-6, 26*s, t.obsInner, t.obsInnerBorder, 5);
|
| 248 |
+
label(g, ix+(iw-6)/2, obsY+26+16*s, txt, 11*s, t.obsInnerText);
|
| 249 |
});
|
| 250 |
hoverBox(g, pad, obsY, W-pad*2, obsH, "obs");
|
| 251 |
|
|
|
|
| 254 |
const piW = W*0.58;
|
| 255 |
const piX = pad;
|
| 256 |
box(g, piX, piY, piW, piH, t.piBox, t.piBorder);
|
| 257 |
+
label(g, piX+piW/2, piY+18, "π0.5 VISION-LANGUAGE-ACTION MODEL", 14*s, t.piLabel, "middle", 700);
|
| 258 |
|
| 259 |
const sigY = piY+32;
|
| 260 |
const sigH = 36*s;
|
| 261 |
box(g, piX+12, sigY, piW*0.35, sigH, t.sigBox, t.sigBorder, 6);
|
| 262 |
+
label(g, piX+12+piW*0.175, sigY+14*s, "SigLIP (400M)", 12*s, t.sigLabel, "middle", 600);
|
| 263 |
+
label(g, piX+12+piW*0.175, sigY+26*s, "Vision Encoder", 10*s, t.sigSub);
|
| 264 |
hoverBox(g, piX+12, sigY, piW*0.35, sigH, "siglip");
|
| 265 |
|
| 266 |
const gemY = sigY;
|
| 267 |
const gemX = piX+12+piW*0.38;
|
| 268 |
const gemW = piW*0.58;
|
| 269 |
box(g, gemX, gemY, gemW, sigH, t.gemBox, t.gemBorder, 6);
|
| 270 |
+
label(g, gemX+gemW/2, gemY+14*s, "Gemma VLM (2B)", 12*s, t.gemLabel, "middle", 600);
|
| 271 |
+
label(g, gemX+gemW/2, gemY+26*s, "Language + Vision Backbone", 10*s, t.gemSub);
|
| 272 |
hoverBox(g, gemX, gemY, gemW, sigH, "gemma_vlm");
|
| 273 |
arrow(g, piX+12+piW*0.35, sigY+sigH/2, gemX, sigY+sigH/2, t.sigBorder);
|
| 274 |
|
| 275 |
const subY = sigY+sigH+14*s;
|
| 276 |
const subH = 30*s;
|
| 277 |
box(g, piX+12, subY, piW*0.42, subH, t.subBox, t.subBorder, 6);
|
| 278 |
+
label(g, piX+12+piW*0.21, subY+12*s, "⬆ Subtask: \"pick up left sleeve\"", 11*s, t.subLabel, "middle", 600);
|
| 279 |
+
label(g, piX+12+piW*0.21, subY+24*s, "Auto-regressive text decoding", 9.5*s, t.subSub);
|
| 280 |
hoverBox(g, piX+12, subY, piW*0.42, subH, "subtask");
|
| 281 |
arrow(g, gemX+gemW/2, gemY+sigH, piX+12+piW*0.21, subY, t.sigBorder, true);
|
| 282 |
|
| 283 |
const aeY = subY+subH+12*s;
|
| 284 |
const aeH = 44*s;
|
| 285 |
box(g, piX+12, aeY, piW-24, aeH, t.aeBox, t.aeBorder, 6);
|
| 286 |
+
label(g, piX+piW/2, aeY+14*s, "ACTION EXPERT (300M): Flow Matching", 12.5*s, t.aeLabel, "middle", 700);
|
| 287 |
|
| 288 |
const nX = piX+24;
|
| 289 |
const nW = 56*s;
|
| 290 |
const nY2 = aeY+24*s;
|
| 291 |
const nH2 = 16*s;
|
| 292 |
box(g, nX, nY2, nW, nH2, t.noiseBox, t.noiseBorder, 4);
|
| 293 |
+
label(g, nX+nW/2, nY2+11*s, "Noise", 9.5*s, t.noiseLabel);
|
| 294 |
hoverBox(g, nX, nY2, nW, nH2, "noise");
|
| 295 |
|
| 296 |
const wvX = nX+nW+10;
|
|
|
|
| 303 |
|
| 304 |
const oaX = piX+piW-24-68*s;
|
| 305 |
box(g, oaX, nY2, 62*s, nH2, t.actionBox, t.actionBorder, 4);
|
| 306 |
+
label(g, oaX+31*s, nY2+11*s, "30 × 16D", 9.5*s, t.actionLabel);
|
| 307 |
hoverBox(g, piX+12, aeY, piW-24, aeH, "action_expert");
|
| 308 |
|
| 309 |
arrow(g, gemX+gemW*0.3, gemY+sigH, gemX+gemW*0.3, aeY, t.sigBorder, false);
|
| 310 |
+
label(g, gemX+gemW*0.3+4, (gemY+sigH+aeY)/2, "VLM prefix", 9*s, t.sigBorder, "start");
|
| 311 |
|
| 312 |
arrow(g, piX+12+piW*0.42, subY+subH/2, piX+piW-12, subY+subH/2, t.subBorder, true);
|
| 313 |
curvedArrow(g, piX+piW-12, subY+subH/2, piX+piW-8, aeY-4, piX+piW-24, aeY, t.subBorder, true);
|
|
|
|
| 315 |
const rtcX = piX+piW-80*s;
|
| 316 |
const rtcY2 = aeY+aeH+8*s;
|
| 317 |
box(g, rtcX, rtcY2, 70*s, 22*s, t.rtcBox, t.rtcBorder, 5);
|
| 318 |
+
label(g, rtcX+35*s, rtcY2+14*s, "🔄 RTC re-plan", 10*s, t.rtcLabel);
|
| 319 |
hoverBox(g, rtcX, rtcY2, 70*s, 22*s, "rtc");
|
| 320 |
|
| 321 |
const sX = piX+piW+16;
|
| 322 |
const sW = W-sX-pad;
|
| 323 |
const sH = piH;
|
| 324 |
box(g, sX, piY, sW, sH, t.sarmBox, t.sarmBorder);
|
| 325 |
+
label(g, sX+sW/2, piY+18, "SARM REWARD MODEL", 13*s, t.sarmLabel, "middle", 700);
|
| 326 |
+
label(g, sX+sW/2, piY+34, "(training only)", 10.5*s, t.sarmSub, "middle", 600);
|
| 327 |
|
| 328 |
const descY = piY+54;
|
| 329 |
+
label(g, sX+sW/2, descY, "Scores every episode with a", 11*s, t.subtext);
|
| 330 |
+
label(g, sX+sW/2, descY+16*s, "per-timestep quality signal [0, 1]", 11*s, t.subtext);
|
| 331 |
|
| 332 |
const rwY = descY+32*s;
|
| 333 |
const rwH = 34*s;
|
| 334 |
box(g, sX+10, rwY, sW-20, rwH, t.rwBox, t.rwBorder, 6);
|
| 335 |
+
label(g, sX+sW/2, rwY+14*s, "Per-timestep Reward", 12*s, t.rwLabel, "middle", 700);
|
| 336 |
+
label(g, sX+sW/2, rwY+27*s, "0 = start · · · 1 = task complete", 9.5*s, t.rwSub);
|
| 337 |
hoverBox(g, sX+10, rwY, sW-20, rwH, "sarm_reward");
|
| 338 |
|
| 339 |
const wsY = rwY+rwH+14*s;
|
| 340 |
const wsH = 34*s;
|
| 341 |
box(g, sX+10, wsY, sW-20, wsH, t.wsBox, t.wsBorder, 6);
|
| 342 |
+
label(g, sX+sW/2, wsY+14*s, "Weighted Sampling", 12*s, t.wsLabel, "middle", 700);
|
| 343 |
+
label(g, sX+sW/2, wsY+27*s, "High-reward episodes contribute more", 9.5*s, t.wsSub);
|
| 344 |
hoverBox(g, sX+10, wsY, sW-20, wsH, "rabc");
|
| 345 |
arrow(g, sX+sW/2, rwY+rwH, sX+sW/2, wsY, t.rwBorder);
|
| 346 |
|
| 347 |
curvedArrow(g, sX+10, wsY+wsH/2, sX-8, wsY+wsH/2+30*s, piX+piW/2, piY+piH+8, t.wsBorder, true);
|
| 348 |
+
label(g, (sX+piX+piW/2)/2, piY+piH+22*s, "weighted sampling (training only)", 9.5*s, t.wsBorder);
|
| 349 |
hoverBox(g, sX, piY, sW, sH, "sarm_box");
|
| 350 |
|
| 351 |
arrow(g, W*0.3, obsY+obsH, piX+piW/2, piY, t.obsBorder);
|
|
|
|
| 354 |
const outY = piY+piH+16;
|
| 355 |
const outH = 42*s;
|
| 356 |
box(g, pad, outY, piW, outH, t.outBox, t.outBorder);
|
| 357 |
+
label(g, piX+piW/2, outY+14*s, "ROBOT ACTIONS", 13*s, t.outLabel, "middle", 700);
|
| 358 |
+
label(g, piX+piW/2, outY+30*s, "16D joint targets", 10*s, t.outSub);
|
| 359 |
arrow(g, piX+piW/2, piY+piH, piX+piW/2, outY, t.outBorder);
|
| 360 |
hoverBox(g, pad, outY, piW, outH, "actions");
|
| 361 |
|
|
|
|
| 367 |
var t = T();
|
| 368 |
const W = container.clientWidth || 800;
|
| 369 |
const g = svg.append("g");
|
| 370 |
+
const s = Math.min(1.1, W / 740);
|
| 371 |
+
const pad = 16;
|
| 372 |
+
const rowH = 44*s;
|
| 373 |
+
const gap = 18*s;
|
| 374 |
+
const colInputW = W*0.155;
|
| 375 |
+
const colEncW = W*0.105;
|
| 376 |
+
const colVlmW = W*0.185;
|
| 377 |
+
const colAeW = W*0.22;
|
| 378 |
+
const colOutW = W*0.11;
|
| 379 |
+
const arrowGap = 14*s;
|
| 380 |
+
const fs = 12*s;
|
| 381 |
+
const fsSmall = 10*s;
|
| 382 |
+
const fsBold = 13*s;
|
| 383 |
+
|
| 384 |
+
const x0 = pad;
|
| 385 |
+
const x1 = x0+colInputW+arrowGap*2;
|
| 386 |
+
const x2 = x1+colEncW+arrowGap*2;
|
| 387 |
+
const x3 = x2+colVlmW+arrowGap*2;
|
| 388 |
+
const x4 = x3+colAeW+arrowGap*2;
|
| 389 |
+
|
| 390 |
+
const camBoxH = rowH*1.8;
|
| 391 |
+
const imgY = pad;
|
| 392 |
+
const stateY = imgY+camBoxH+gap;
|
| 393 |
+
const langY = stateY+rowH+gap;
|
| 394 |
+
|
| 395 |
+
// Camera images with real thumbnails
|
| 396 |
+
box(g, x0, imgY, colInputW, camBoxH, t.obsBox, t.obsBorder, 6);
|
| 397 |
+
label(g, x0+colInputW/2, imgY+11*s, "Camera Images", fsBold, t.obsLabel, "middle", 600);
|
| 398 |
+
var thumbGap = 3*s;
|
| 399 |
+
var thumbW = (colInputW - 14*s - thumbGap*2) / 3;
|
| 400 |
+
var thumbH = thumbW * 0.7;
|
| 401 |
+
var thumbY = imgY+20*s;
|
| 402 |
+
var cams = [{f:"cam_base.jpg",l:"base"},{f:"cam_left_wrist.jpg",l:"L wrist"},{f:"cam_right_wrist.jpg",l:"R wrist"}];
|
| 403 |
+
cams.forEach(function(cam,i) {
|
| 404 |
+
var tx = x0+7*s + i*(thumbW+thumbGap);
|
| 405 |
+
var cid = "tc_"+Math.random().toString(36).slice(2,8);
|
| 406 |
+
g.append("defs").append("clipPath").attr("id",cid)
|
| 407 |
+
.append("rect").attr("x",tx).attr("y",thumbY).attr("width",thumbW).attr("height",thumbH).attr("rx",3);
|
| 408 |
+
g.append("image").attr("href","/images/folding/"+cam.f)
|
| 409 |
+
.attr("x",tx).attr("y",thumbY).attr("width",thumbW).attr("height",thumbH)
|
| 410 |
+
.attr("preserveAspectRatio","xMidYMid slice").attr("clip-path","url(#"+cid+")");
|
| 411 |
+
g.append("rect").attr("x",tx).attr("y",thumbY).attr("width",thumbW).attr("height",thumbH)
|
| 412 |
+
.attr("rx",3).attr("fill","none").attr("stroke",t.obsInnerBorder).attr("stroke-width",1);
|
| 413 |
+
label(g, tx+thumbW/2, thumbY+thumbH+8*s, cam.l, 8*s, t.obsInnerText);
|
| 414 |
});
|
| 415 |
+
hoverBox(g, x0, imgY, colInputW, camBoxH, "obs");
|
| 416 |
+
|
| 417 |
+
box(g, x0, stateY, colInputW, rowH, t.obsBox, t.obsBorder, 6);
|
| 418 |
+
label(g, x0+colInputW/2, stateY+13*s, "Joint Angles", fsBold, t.obsLabel, "middle", 600);
|
| 419 |
+
label(g, x0+colInputW/2, stateY+25*s, "16D (7 joints+grip ×2)", fsSmall, t.obsInnerText);
|
| 420 |
+
hoverBox(g, x0, stateY, colInputW, rowH, "obs");
|
| 421 |
+
|
| 422 |
+
box(g, x0, langY, colInputW, rowH, t.obsBox, t.obsBorder, 6);
|
| 423 |
+
label(g, x0+colInputW/2, langY+13*s, "Language Prompt", fsBold, t.obsLabel, "middle", 600);
|
| 424 |
+
label(g, x0+colInputW/2, langY+25*s, "\"fold the t-shirt\"", fsSmall, t.obsInnerText);
|
| 425 |
+
hoverBox(g, x0, langY, colInputW, rowH, "obs");
|
| 426 |
+
|
| 427 |
+
// SigLIP encoder (aligned with camera box)
|
| 428 |
+
box(g, x1, imgY, colEncW, camBoxH, t.sigBox, t.sigBorder, 6);
|
| 429 |
+
label(g, x1+colEncW/2, imgY+camBoxH/2-5*s, "SigLIP", fsBold, t.sigLabel, "middle", 600);
|
| 430 |
+
label(g, x1+colEncW/2, imgY+camBoxH/2+7*s, "400M · frozen", fsSmall, t.sigSub);
|
| 431 |
+
hoverBox(g, x1, imgY, colEncW, camBoxH, "siglip");
|
| 432 |
+
|
| 433 |
+
arrow(g, x0+colInputW, imgY+camBoxH/2, x1, imgY+camBoxH/2, t.obsBorder);
|
| 434 |
+
arrowLabel(g, (x0+colInputW+x1)/2, imgY+camBoxH/2-8*s, "pixels", fsSmall, t.subtext);
|
| 435 |
+
|
| 436 |
+
// VLM backbone
|
| 437 |
+
const vlmH = langY+rowH-imgY;
|
| 438 |
+
box(g, x2, imgY, colVlmW, vlmH, t.gemBox, t.gemBorder, 6);
|
| 439 |
+
label(g, x2+colVlmW/2, imgY+14*s, "Gemma VLM", fsBold, t.gemLabel, "middle", 700);
|
| 440 |
+
label(g, x2+colVlmW/2, imgY+26*s, "2B params", fsSmall, t.gemSub);
|
| 441 |
+
label(g, x2+colVlmW/2, imgY+vlmH/2+2*s, "Cross-attention over", fsSmall, t.subtext);
|
| 442 |
+
label(g, x2+colVlmW/2, imgY+vlmH/2+14*s, "all three modalities", fsSmall, t.subtext);
|
| 443 |
+
hoverBox(g, x2, imgY, colVlmW, vlmH, "gemma_vlm");
|
| 444 |
+
|
| 445 |
+
arrow(g, x1+colEncW, imgY+camBoxH/2, x2, imgY+camBoxH/2, t.sigBorder);
|
| 446 |
+
arrowLabel(g, (x1+colEncW+x2)/2, imgY+camBoxH/2-8*s, "visual tokens", fsSmall, t.subtext);
|
| 447 |
+
|
| 448 |
+
arrow(g, x0+colInputW, stateY+rowH/2, x2, stateY+rowH/2, t.obsBorder);
|
| 449 |
+
arrowLabel(g, (x0+colInputW+x2)/2, stateY+rowH/2-8*s, "state vector", fsSmall, t.subtext);
|
| 450 |
+
|
| 451 |
+
arrow(g, x0+colInputW, langY+rowH/2, x2, langY+rowH/2, t.obsBorder);
|
| 452 |
+
arrowLabel(g, (x0+colInputW+x2)/2, langY+rowH/2-8*s, "text tokens", fsSmall, t.subtext);
|
| 453 |
+
|
| 454 |
+
// Action Expert with flow matching animation
|
| 455 |
+
const aeH2 = vlmH;
|
| 456 |
+
box(g, x3, imgY, colAeW, aeH2, t.aeBox, t.aeBorder, 6);
|
| 457 |
+
label(g, x3+colAeW/2, imgY+14*s, "Action Expert", fsBold, t.aeLabel, "middle", 700);
|
| 458 |
+
label(g, x3+colAeW/2, imgY+26*s, "300M · flow matching", fsSmall, t.flowSub);
|
| 459 |
+
|
| 460 |
+
arrow(g, x2+colVlmW, imgY+vlmH/2, x3, imgY+aeH2/2, t.gemBorder);
|
| 461 |
+
arrowLabel(g, (x2+colVlmW+x3)/2, imgY+vlmH/2-8*s, "VLM features", fsSmall, t.subtext);
|
| 462 |
+
|
| 463 |
+
// Flow matching animation area
|
| 464 |
+
var aP = 6*s;
|
| 465 |
+
var aTop = imgY+36*s, aBot = imgY+aeH2-aP-12*s;
|
| 466 |
+
var aLeft = x3+aP, aRight = x3+colAeW-aP;
|
| 467 |
+
var aW = aRight-aLeft, aH = aBot-aTop, aCY = aTop+aH/2;
|
| 468 |
+
|
| 469 |
+
g.append("rect").attr("x",aLeft).attr("y",aTop).attr("width",aW).attr("height",aH)
|
| 470 |
+
.attr("rx",4).attr("fill",isLight()?"rgba(0,0,0,0.03)":"rgba(255,255,255,0.03)");
|
| 471 |
+
|
| 472 |
+
label(g, aLeft+8*s, aBot+10*s, "N(0, I)", 8*s, t.noiseLabel, "start");
|
| 473 |
+
label(g, aRight-8*s, aBot+10*s, "actions", 8*s, t.actionLabel, "end");
|
| 474 |
+
var stepText = g.append("text").attr("x",aLeft+aW/2).attr("y",aBot+10*s)
|
| 475 |
+
.attr("text-anchor","middle").attr("font-size",8*s)
|
| 476 |
+
.attr("fill",t.subtext).attr("font-family","system-ui, sans-serif").text("step 0/10");
|
| 477 |
+
|
| 478 |
+
// Seeded random for deterministic noise positions
|
| 479 |
+
var _seed = 42;
|
| 480 |
+
function sRand() { _seed=(_seed*16807)%2147483647; return(_seed-1)/2147483646; }
|
| 481 |
+
function gRand(m,sd) { var u1=sRand(),u2=sRand(); return m+sd*Math.sqrt(-2*Math.log(Math.max(u1,.001)))*Math.cos(6.2832*u2); }
|
| 482 |
+
|
| 483 |
+
var NP = 24;
|
| 484 |
+
var pts = [];
|
| 485 |
+
for (var i=0;i<NP;i++) {
|
| 486 |
+
var frac = i/(NP-1);
|
| 487 |
+
var tgtX = aLeft+8*s + frac*(aW-16*s);
|
| 488 |
+
var tgtY = aCY + Math.sin(frac*Math.PI*2.5+0.5)*aH*0.3 * (0.7+0.3*Math.cos(frac*Math.PI));
|
| 489 |
+
var nX = gRand(aLeft+aW*0.35, aW*0.18);
|
| 490 |
+
var nY = gRand(aCY, aH*0.25);
|
| 491 |
+
nX = Math.max(aLeft+3, Math.min(aRight-3, nX));
|
| 492 |
+
nY = Math.max(aTop+3, Math.min(aBot-3, nY));
|
| 493 |
+
tgtY = Math.max(aTop+3, Math.min(aBot-3, tgtY));
|
| 494 |
+
pts.push({nX:nX, nY:nY, tX:tgtX, tY:tgtY});
|
| 495 |
+
}
|
| 496 |
+
|
| 497 |
+
var dots = g.selectAll(".fdot").data(pts).enter()
|
| 498 |
+
.append("circle").attr("r",2.5*s).attr("class","fdot");
|
| 499 |
+
|
| 500 |
+
// Trajectory line (appears as dots organize)
|
| 501 |
+
var trajPath = g.append("path").attr("fill","none")
|
| 502 |
+
.attr("stroke",t.actionBorder).attr("stroke-width",1.2*s).attr("opacity",0);
|
| 503 |
+
var trajData = pts.map(function(p){return[p.tX,p.tY]});
|
| 504 |
+
var lineGen = d3.line().curve(d3.curveCatmullRom.alpha(0.5));
|
| 505 |
+
trajPath.attr("d", lineGen(trajData));
|
| 506 |
+
|
| 507 |
+
var noiseC = t.noiseBorder, actionC = t.actionBorder;
|
| 508 |
+
var numSteps = 10;
|
| 509 |
+
var stepDur = 280, pauseEnd = 1000, pauseStart = 600;
|
| 510 |
+
var cycle = pauseStart + numSteps*stepDur + pauseEnd;
|
| 511 |
+
var t0 = null;
|
| 512 |
+
|
| 513 |
+
function animLoop(ts) {
|
| 514 |
+
if(!t0) t0=ts;
|
| 515 |
+
var el = (ts-t0)%cycle;
|
| 516 |
+
var step, tau;
|
| 517 |
+
if (el < pauseStart) { step = 0; tau = 0; }
|
| 518 |
+
else if (el < pauseStart + numSteps*stepDur) {
|
| 519 |
+
step = Math.min(numSteps, Math.floor((el-pauseStart)/stepDur) + 1);
|
| 520 |
+
tau = step / numSteps;
|
| 521 |
+
} else { step = numSteps; tau = 1; }
|
| 522 |
+
|
| 523 |
+
dots.attr("cx",function(d){return d.nX+(d.tX-d.nX)*tau})
|
| 524 |
+
.attr("cy",function(d){return d.nY+(d.tY-d.nY)*tau})
|
| 525 |
+
.attr("fill", d3.interpolateRgb(noiseC, actionC)(tau))
|
| 526 |
+
.attr("opacity", 0.85);
|
| 527 |
+
trajPath.attr("opacity", tau*0.45);
|
| 528 |
+
stepText.text("step "+step+"/10");
|
| 529 |
+
|
| 530 |
+
_pi05AnimId = requestAnimationFrame(animLoop);
|
| 531 |
+
}
|
| 532 |
+
_pi05AnimId = requestAnimationFrame(animLoop);
|
| 533 |
+
|
| 534 |
+
hoverBox(g, x3, imgY, colAeW, aeH2, "action_expert");
|
| 535 |
+
|
| 536 |
+
// Output
|
| 537 |
+
box(g, x4, imgY, colOutW, aeH2, t.actionBox, t.actionBorder, 6);
|
| 538 |
+
label(g, x4+colOutW/2, imgY+aeH2/2-12*s, "30 Actions", fsBold, t.actionLabel, "middle", 700);
|
| 539 |
+
label(g, x4+colOutW/2, imgY+aeH2/2, "16D", fsSmall, t.actionBorder);
|
| 540 |
+
label(g, x4+colOutW/2, imgY+aeH2/2+14*s, "joint angles +", fsSmall, t.subtext);
|
| 541 |
+
label(g, x4+colOutW/2, imgY+aeH2/2+26*s, "gripper cmds", fsSmall, t.subtext);
|
| 542 |
+
hoverBox(g, x4, imgY, colOutW, aeH2, "actions");
|
| 543 |
+
|
| 544 |
+
arrow(g, x3+colAeW, imgY+aeH2/2, x4, imgY+aeH2/2, t.actionBorder);
|
| 545 |
+
arrowLabel(g, (x3+colAeW+x4)/2, imgY+aeH2/2-8*s, "actions", fsSmall, t.subtext);
|
| 546 |
+
|
| 547 |
+
const H = langY + rowH + pad;
|
| 548 |
svg.attr("width",W).attr("height",H).attr("viewBox",`0 0 ${W} ${H}`);
|
| 549 |
}
|
| 550 |
|
|
|
|
| 552 |
var t = T();
|
| 553 |
const W = container.clientWidth || 800;
|
| 554 |
const g = svg.append("g");
|
| 555 |
+
const s = Math.min(1.1, W / 740);
|
| 556 |
+
const pad = 16;
|
| 557 |
+
const rowH = 42*s;
|
| 558 |
+
const gap = 16*s;
|
| 559 |
+
const arrowGap = 14*s;
|
| 560 |
+
const fs = 12*s;
|
| 561 |
+
const fsSmall = 10*s;
|
| 562 |
+
const fsBold = 13*s;
|
| 563 |
+
|
| 564 |
+
const colInW = W*0.14;
|
| 565 |
+
const colEncW = W*0.11;
|
| 566 |
+
const colStageW = W*0.175;
|
| 567 |
+
const colSubW = W*0.175;
|
| 568 |
+
const colOutW = W*0.14;
|
| 569 |
+
|
| 570 |
+
const x0 = pad;
|
| 571 |
+
const x1 = x0+colInW+arrowGap*2;
|
| 572 |
+
const x2 = x1+colEncW+arrowGap*2;
|
| 573 |
+
const x3 = x2+colStageW+arrowGap*2;
|
| 574 |
+
const x4 = x3+colSubW+arrowGap*2;
|
| 575 |
+
|
| 576 |
+
const row1Y = pad;
|
| 577 |
+
const row2Y = row1Y+rowH+gap;
|
| 578 |
+
const row3Y = row2Y+rowH+gap;
|
| 579 |
+
|
| 580 |
+
// inputs
|
| 581 |
+
box(g, x0, row1Y, colInW, rowH, t.obsBox, t.obsBorder, 6);
|
| 582 |
+
label(g, x0+colInW/2, row1Y+12*s, "8 RGB Frames", fsBold, t.obsLabel, "middle", 600);
|
| 583 |
+
label(g, x0+colInW/2, row1Y+24*s, "sampled 1s apart", fsSmall, t.obsInnerText);
|
| 584 |
+
hoverBox(g, x0, row1Y, colInW, rowH, "embed_video");
|
| 585 |
+
|
| 586 |
+
box(g, x0, row2Y, colInW, rowH, t.obsBox, t.obsBorder, 6);
|
| 587 |
+
label(g, x0+colInW/2, row2Y+12*s, "Task Description", fsBold, t.obsLabel, "middle", 600);
|
| 588 |
+
label(g, x0+colInW/2, row2Y+24*s, "\"fold the t-shirt\"", fsSmall, t.obsInnerText);
|
| 589 |
+
hoverBox(g, x0, row2Y, colInW, rowH, "embed_text");
|
| 590 |
+
|
| 591 |
+
box(g, x0, row3Y, colInW, rowH, t.obsBox, t.obsBorder, 6);
|
| 592 |
+
label(g, x0+colInW/2, row3Y+12*s, "Joint State", fsBold, t.obsLabel, "middle", 600);
|
| 593 |
+
label(g, x0+colInW/2, row3Y+24*s, "16D positions", fsSmall, t.obsInnerText);
|
| 594 |
+
hoverBox(g, x0, row3Y, colInW, rowH, "embed_state");
|
| 595 |
+
|
| 596 |
+
// CLIP encoder
|
| 597 |
+
const clipH = row2Y+rowH-row1Y;
|
| 598 |
+
box(g, x1, row1Y, colEncW, clipH, t.sigBox, t.sigBorder, 6);
|
| 599 |
+
label(g, x1+colEncW/2, row1Y+clipH/2-5*s, "CLIP", fsBold, t.sigLabel, "middle", 700);
|
| 600 |
+
label(g, x1+colEncW/2, row1Y+clipH/2+7*s, "frozen", fsSmall, t.sigSub);
|
| 601 |
+
hoverBox(g, x1, row1Y, colEncW, clipH, "clip");
|
| 602 |
+
|
| 603 |
+
arrow(g, x0+colInW, row1Y+rowH/2, x1, row1Y+rowH/2, t.obsBorder);
|
| 604 |
+
arrowLabel(g, (x0+colInW+x1)/2, row1Y+rowH/2-8*s, "pixels", fsSmall, t.subtext);
|
| 605 |
+
|
| 606 |
+
arrow(g, x0+colInW, row2Y+rowH/2, x1, row2Y+rowH/2, t.obsBorder);
|
| 607 |
+
arrowLabel(g, (x0+colInW+x1)/2, row2Y+rowH/2-8*s, "text", fsSmall, t.subtext);
|
| 608 |
+
|
| 609 |
+
// Stage Transformer
|
| 610 |
+
const stH2 = row3Y+rowH-row1Y;
|
| 611 |
+
box(g, x2, row1Y, colStageW, stH2, t.sarmBox, t.stageBorder, 6);
|
| 612 |
+
label(g, x2+colStageW/2, row1Y+14*s, "Stage", fsBold, t.stageText, "middle", 700);
|
| 613 |
+
label(g, x2+colStageW/2, row1Y+26*s, "Transformer", fsBold, t.stageText, "middle", 700);
|
| 614 |
+
label(g, x2+colStageW/2, row1Y+stH2/2+6*s, "Classifies which", fsSmall, t.subtext);
|
| 615 |
+
label(g, x2+colStageW/2, row1Y+stH2/2+18*s, "subtask the robot", fsSmall, t.subtext);
|
| 616 |
+
label(g, x2+colStageW/2, row1Y+stH2/2+30*s, "is currently in", fsSmall, t.subtext);
|
| 617 |
+
hoverBox(g, x2, row1Y, colStageW, stH2, "stage_tfm");
|
| 618 |
+
|
| 619 |
+
arrow(g, x1+colEncW, row1Y+clipH/2, x2, row1Y+clipH/2, t.sigBorder);
|
| 620 |
+
arrowLabel(g, (x1+colEncW+x2)/2, row1Y+clipH/2-8*s, "embeddings", fsSmall, t.subtext);
|
| 621 |
+
|
| 622 |
+
arrow(g, x0+colInW, row3Y+rowH/2, x2, row3Y+rowH/2, t.obsBorder);
|
| 623 |
+
arrowLabel(g, (x0+colInW+x2)/2, row3Y+rowH/2-8*s, "state vector", fsSmall, t.subtext);
|
| 624 |
+
|
| 625 |
+
// Subtask Transformer
|
| 626 |
+
box(g, x3, row1Y, colSubW, stH2, t.sarmBox, t.stageBorder, 6);
|
| 627 |
+
label(g, x3+colSubW/2, row1Y+14*s, "Subtask", fsBold, t.stageText, "middle", 700);
|
| 628 |
+
label(g, x3+colSubW/2, row1Y+26*s, "Transformer", fsBold, t.stageText, "middle", 700);
|
| 629 |
+
label(g, x3+colSubW/2, row1Y+stH2/2+6*s, "Predicts progress", fsSmall, t.subtext);
|
| 630 |
+
label(g, x3+colSubW/2, row1Y+stH2/2+18*s, "within the stage", fsSmall, t.subtext);
|
| 631 |
+
label(g, x3+colSubW/2, row1Y+stH2/2+30*s, "τ ∈ [0, 1]", fsSmall, t.subtext);
|
| 632 |
+
hoverBox(g, x3, row1Y, colSubW, stH2, "subtask_tfm");
|
| 633 |
+
|
| 634 |
+
arrow(g, x2+colStageW, row1Y+stH2*0.3, x3, row1Y+stH2*0.3, t.stageBorder);
|
| 635 |
+
arrowLabel(g, (x2+colStageW+x3)/2, row1Y+stH2*0.3-8*s, "stage (one-hot)", fsSmall, t.subtext);
|
| 636 |
+
|
| 637 |
+
// feed inputs into subtask too
|
| 638 |
+
curvedArrow(g, x1+colEncW/2, row1Y+clipH, x1+colEncW/2, row3Y+rowH+16*s, x3+colSubW*0.3, row1Y+stH2, t.obsBorder, true);
|
| 639 |
+
arrowLabel(g, x1+colEncW/2-2*s, row3Y+rowH+10*s, "same inputs", fsSmall, t.subtext, "end");
|
| 640 |
+
|
| 641 |
+
// output
|
| 642 |
+
box(g, x4, row1Y, colOutW, stH2, t.rwBox, t.rwBorder, 6);
|
| 643 |
+
label(g, x4+colOutW/2, row1Y+stH2/2-20*s, "Progress", fsBold, t.rwLabel, "middle", 700);
|
| 644 |
+
label(g, x4+colOutW/2, row1Y+stH2/2-6*s, "Score", fsBold, t.rwLabel, "middle", 700);
|
| 645 |
+
label(g, x4+colOutW/2, row1Y+stH2/2+10*s, "stage + τ", fs, t.rwSub);
|
| 646 |
+
label(g, x4+colOutW/2, row1Y+stH2/2+24*s, "normalized", fs, t.rwSub);
|
| 647 |
+
label(g, x4+colOutW/2, row1Y+stH2/2+36*s, "to [0, 1]", fs, t.rwSub);
|
| 648 |
+
hoverBox(g, x4, row1Y, colOutW, stH2, "sarm_reward");
|
| 649 |
+
|
| 650 |
+
arrow(g, x3+colSubW, row1Y+stH2*0.5, x4, row1Y+stH2*0.5, t.stageBorder);
|
| 651 |
+
arrowLabel(g, (x3+colSubW+x4)/2, row1Y+stH2*0.5-8*s, "τ value", fsSmall, t.subtext);
|
| 652 |
+
|
| 653 |
+
// uses row below
|
| 654 |
+
const usesY = row3Y+rowH+gap+8*s;
|
| 655 |
+
const useW = Math.min((W-pad*2)*0.5, 240*s);
|
| 656 |
+
const ux = W/2-useW/2;
|
| 657 |
+
box(g, ux, usesY, useW, 34*s, t.scoreBox, t.wsBorder, 6);
|
| 658 |
+
label(g, ux+useW/2, usesY+13*s, "RABC Training Weights", fsBold, t.scoreText, "middle", 600);
|
| 659 |
+
label(g, ux+useW/2, usesY+26*s, "Weight actions by progress delta", fsSmall, t.wsBorder);
|
| 660 |
+
hoverBox(g, ux, usesY, useW, 34*s, "rabc");
|
| 661 |
+
arrow(g, x4+colOutW/2, row1Y+stH2, ux+useW/2, usesY, t.rwBorder);
|
| 662 |
+
arrowLabel(g, (x4+colOutW/2+ux+useW/2)/2+12*s, row1Y+stH2+10*s, "used for ↓", fsSmall, t.subtext);
|
| 663 |
+
|
| 664 |
+
const H = usesY + 34*s + pad;
|
| 665 |
svg.attr("width",W).attr("height",H).attr("viewBox",`0 0 ${W} ${H}`);
|
| 666 |
}
|
| 667 |
|
| 668 |
function render() {
|
| 669 |
+
if (_pi05AnimId) { cancelAnimationFrame(_pi05AnimId); _pi05AnimId = null; }
|
| 670 |
svg.selectAll("*").remove();
|
| 671 |
applyTabTheme();
|
| 672 |
if (currentTab === "system") renderSystem();
|
|
|
|
| 674 |
else if (currentTab === "sarm") renderSARM();
|
| 675 |
}
|
| 676 |
|
| 677 |
+
if (tabBar) {
|
| 678 |
+
tabBar.querySelectorAll(".tab-btn").forEach(btn => {
|
| 679 |
+
btn.addEventListener("click", () => {
|
| 680 |
+
tabBar.querySelectorAll(".tab-btn").forEach(b => b.classList.remove("active"));
|
| 681 |
+
btn.classList.add("active");
|
| 682 |
+
currentTab = btn.dataset.tab;
|
| 683 |
+
render();
|
| 684 |
+
});
|
| 685 |
});
|
| 686 |
+
}
|
| 687 |
|
| 688 |
render();
|
| 689 |
window.addEventListener("resize", render);
|
|
|
|
| 697 |
if (typeof d3 !== "undefined") {
|
| 698 |
_initArchViz();
|
| 699 |
} else {
|
| 700 |
+
var _s = document.createElement("script");
|
| 701 |
+
_s.src = "https://cdnjs.cloudflare.com/ajax/libs/d3/7.9.0/d3.min.js";
|
| 702 |
+
_s.onload = _initArchViz;
|
| 703 |
+
document.head.appendChild(_s);
|
| 704 |
}
|
| 705 |
</script>
|
| 706 |
</body>
|
app/src/content/embeds/folding/rabc-explainer.html
ADDED
|
@@ -0,0 +1,191 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<!DOCTYPE html>
|
| 2 |
+
<html lang="en">
|
| 3 |
+
<head>
|
| 4 |
+
<meta charset="UTF-8"/>
|
| 5 |
+
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
| 6 |
+
<style>
|
| 7 |
+
:root { --bg: transparent; --text: #e8eaf0; --subtext: #8b8fa8; --grid: rgba(255,255,255,0.08); }
|
| 8 |
+
* { box-sizing: border-box; margin: 0; padding: 0; }
|
| 9 |
+
body { background: var(--bg); font-family: system-ui, sans-serif; color: var(--text); }
|
| 10 |
+
.controls {
|
| 11 |
+
display: flex; align-items: center; gap: 14px; justify-content: center;
|
| 12 |
+
margin: 0 0 4px; font-size: 14px; flex-wrap: wrap;
|
| 13 |
+
}
|
| 14 |
+
.controls label { color: var(--subtext); font-weight: 600; }
|
| 15 |
+
.controls input[type=range] {
|
| 16 |
+
width: 140px; accent-color: #6366f1; cursor: pointer;
|
| 17 |
+
}
|
| 18 |
+
.kval { color: #e8eaf0; font-weight: 700; min-width: 48px; }
|
| 19 |
+
.axis text { fill: #8b8fa8; font-size: 12px; }
|
| 20 |
+
.axis path, .axis line { stroke: rgba(255,255,255,0.15); }
|
| 21 |
+
.grid line { stroke: var(--grid); }
|
| 22 |
+
.grid path { stroke: none; }
|
| 23 |
+
.legend-row {
|
| 24 |
+
display: flex; gap: 16px; justify-content: center; font-size: 13px;
|
| 25 |
+
color: var(--subtext); margin-top: 6px; flex-wrap: wrap;
|
| 26 |
+
}
|
| 27 |
+
.legend-row span { display: flex; align-items: center; gap: 5px; }
|
| 28 |
+
.legend-swatch { width: 12px; height: 4px; border-radius: 2px; flex-shrink: 0; }
|
| 29 |
+
</style>
|
| 30 |
+
</head>
|
| 31 |
+
<body>
|
| 32 |
+
<div class="controls">
|
| 33 |
+
<label>κ threshold:</label>
|
| 34 |
+
<input type="range" id="kappa-slider" min="0" max="0.05" step="0.001" value="0.015"/>
|
| 35 |
+
<span class="kval" id="kappa-val">0.015</span>
|
| 36 |
+
</div>
|
| 37 |
+
<svg id="rabc-svg"></svg>
|
| 38 |
+
<div class="legend-row">
|
| 39 |
+
<span><span class="legend-swatch" style="background:#22d3ee"></span>SARM progress</span>
|
| 40 |
+
<span><span class="legend-swatch" style="background:#10b981"></span>Full weight (δ > κ)</span>
|
| 41 |
+
<span><span class="legend-swatch" style="background:#eab308"></span>Soft weight (0 ≤ δ ≤ κ)</span>
|
| 42 |
+
<span><span class="legend-swatch" style="background:#ef4444"></span>Zero weight (δ < 0)</span>
|
| 43 |
+
</div>
|
| 44 |
+
<script>
|
| 45 |
+
function _initRABC() {
|
| 46 |
+
|
| 47 |
+
// Simulated SARM progress for a realistic folding episode
|
| 48 |
+
const N = 120;
|
| 49 |
+
const progressRaw = [];
|
| 50 |
+
const seed = [
|
| 51 |
+
0,0.01,0.02,0.03,0.04,0.06,0.08,0.10,0.12,0.14,
|
| 52 |
+
0.16,0.18,0.20,0.22,0.24,0.26,0.28,0.28,0.27,0.26,
|
| 53 |
+
0.26,0.27,0.28,0.30,0.32,0.34,0.36,0.38,0.40,0.42,
|
| 54 |
+
0.44,0.45,0.45,0.45,0.44,0.44,0.45,0.46,0.48,0.50,
|
| 55 |
+
0.52,0.54,0.56,0.58,0.60,0.62,0.64,0.66,0.68,0.70,
|
| 56 |
+
0.72,0.73,0.74,0.74,0.73,0.72,0.71,0.70,0.70,0.71,
|
| 57 |
+
0.72,0.74,0.76,0.78,0.80,0.81,0.82,0.83,0.84,0.85,
|
| 58 |
+
0.85,0.85,0.86,0.86,0.87,0.88,0.89,0.90,0.91,0.92,
|
| 59 |
+
0.93,0.93,0.93,0.93,0.94,0.94,0.95,0.95,0.96,0.96,
|
| 60 |
+
0.96,0.97,0.97,0.97,0.98,0.98,0.98,0.98,0.99,0.99,
|
| 61 |
+
0.99,0.99,0.99,1.0,1.0,1.0,1.0,1.0,1.0,1.0,
|
| 62 |
+
1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0
|
| 63 |
+
];
|
| 64 |
+
for (let i = 0; i < N; i++) progressRaw.push(seed[i]);
|
| 65 |
+
|
| 66 |
+
const CHUNK = 5;
|
| 67 |
+
|
| 68 |
+
function computeDeltas(progress) {
|
| 69 |
+
const deltas = [];
|
| 70 |
+
for (let i = 0; i < progress.length; i++) {
|
| 71 |
+
const j = Math.min(i + CHUNK, progress.length - 1);
|
| 72 |
+
deltas.push(progress[j] - progress[i]);
|
| 73 |
+
}
|
| 74 |
+
return deltas;
|
| 75 |
+
}
|
| 76 |
+
|
| 77 |
+
function computeWeights(deltas, kappa) {
|
| 78 |
+
const posDeltas = deltas.filter(d => d >= 0);
|
| 79 |
+
let mu = posDeltas.length ? posDeltas.reduce((a,b)=>a+b,0)/posDeltas.length : 0;
|
| 80 |
+
mu = Math.max(mu, 0);
|
| 81 |
+
const variance = posDeltas.length > 1
|
| 82 |
+
? posDeltas.reduce((s,d)=>s+(d-mu)*(d-mu),0)/(posDeltas.length-1) : 0.0001;
|
| 83 |
+
const sigma = Math.sqrt(variance);
|
| 84 |
+
|
| 85 |
+
return deltas.map(d => {
|
| 86 |
+
if (d > kappa) return 1.0;
|
| 87 |
+
if (d < 0) return 0.0;
|
| 88 |
+
const lo = mu - 2*sigma, hi = mu + 2*sigma;
|
| 89 |
+
const range = hi - lo;
|
| 90 |
+
if (range < 0.0001) return d >= 0 ? 1 : 0;
|
| 91 |
+
return Math.max(0, Math.min(1, (d - lo) / range));
|
| 92 |
+
});
|
| 93 |
+
}
|
| 94 |
+
|
| 95 |
+
const container = document.getElementById("rabc-svg").parentElement;
|
| 96 |
+
const svg = d3.select("#rabc-svg");
|
| 97 |
+
|
| 98 |
+
function render() {
|
| 99 |
+
svg.selectAll("*").remove();
|
| 100 |
+
const kappa = parseFloat(document.getElementById("kappa-slider").value);
|
| 101 |
+
document.getElementById("kappa-val").textContent = kappa.toFixed(3);
|
| 102 |
+
|
| 103 |
+
const deltas = computeDeltas(progressRaw);
|
| 104 |
+
const weights = computeWeights(deltas, kappa);
|
| 105 |
+
|
| 106 |
+
const W = container.clientWidth || 700;
|
| 107 |
+
const margin = { top: 12, right: 16, bottom: 56, left: 46 };
|
| 108 |
+
const totalH = Math.max(260, Math.min(360, W * 0.42));
|
| 109 |
+
const progressH = totalH * 0.55;
|
| 110 |
+
const gap = 24;
|
| 111 |
+
const weightH = totalH - progressH - gap;
|
| 112 |
+
const H = totalH + margin.top + margin.bottom;
|
| 113 |
+
const w = W - margin.left - margin.right;
|
| 114 |
+
|
| 115 |
+
svg.attr("width", W).attr("height", H);
|
| 116 |
+
const g = svg.append("g").attr("transform", `translate(${margin.left},${margin.top})`);
|
| 117 |
+
|
| 118 |
+
const x = d3.scaleLinear().domain([0, N-1]).range([0, w]);
|
| 119 |
+
const yP = d3.scaleLinear().domain([0, 1]).range([progressH, 0]);
|
| 120 |
+
const yW = d3.scaleLinear().domain([0, 1]).range([weightH, 0]);
|
| 121 |
+
const wG = g.append("g").attr("transform", `translate(0,${progressH + gap})`);
|
| 122 |
+
|
| 123 |
+
// Progress grid + axis
|
| 124 |
+
g.append("g").attr("class","grid")
|
| 125 |
+
.call(d3.axisLeft(yP).ticks(4).tickSize(-w).tickFormat(""));
|
| 126 |
+
g.append("g").attr("class","axis")
|
| 127 |
+
.call(d3.axisLeft(yP).ticks(4).tickFormat(d3.format(".1f")));
|
| 128 |
+
g.append("text").attr("x",-progressH/2).attr("y",-36).attr("transform","rotate(-90)")
|
| 129 |
+
.attr("text-anchor","middle").attr("fill","#8b8fa8").attr("font-size",12).text("SARM progress");
|
| 130 |
+
|
| 131 |
+
// Weight grid + axis
|
| 132 |
+
wG.append("g").attr("class","grid")
|
| 133 |
+
.call(d3.axisLeft(yW).ticks(3).tickSize(-w).tickFormat(""));
|
| 134 |
+
wG.append("g").attr("class","axis")
|
| 135 |
+
.call(d3.axisLeft(yW).ticks(3).tickFormat(d3.format(".1f")));
|
| 136 |
+
wG.append("text").attr("x",-weightH/2).attr("y",-36).attr("transform","rotate(-90)")
|
| 137 |
+
.attr("text-anchor","middle").attr("fill","#8b8fa8").attr("font-size",12).text("Training weight");
|
| 138 |
+
|
| 139 |
+
// Shared x axis
|
| 140 |
+
wG.append("g").attr("class","axis").attr("transform",`translate(0,${weightH})`)
|
| 141 |
+
.call(d3.axisBottom(x).ticks(6).tickFormat(d => d + ""));
|
| 142 |
+
wG.append("text").attr("x",w/2).attr("y",weightH+30).attr("text-anchor","middle")
|
| 143 |
+
.attr("fill","#8b8fa8").attr("font-size",12).text("Timestep");
|
| 144 |
+
|
| 145 |
+
// Color weight bars
|
| 146 |
+
const barW = Math.max(1, w / N - 0.5);
|
| 147 |
+
weights.forEach((wt, i) => {
|
| 148 |
+
const delta = deltas[i];
|
| 149 |
+
let col;
|
| 150 |
+
if (delta < 0) col = "#ef4444";
|
| 151 |
+
else if (delta > kappa) col = "#10b981";
|
| 152 |
+
else col = "#eab308";
|
| 153 |
+
|
| 154 |
+
wG.append("rect")
|
| 155 |
+
.attr("x", x(i) - barW/2).attr("y", yW(wt))
|
| 156 |
+
.attr("width", barW).attr("height", Math.max(0, weightH - yW(wt)))
|
| 157 |
+
.attr("fill", col).attr("opacity", 0.8);
|
| 158 |
+
});
|
| 159 |
+
|
| 160 |
+
// Progress curve background shading
|
| 161 |
+
for (let i = 0; i < N - 1; i++) {
|
| 162 |
+
const delta = deltas[i];
|
| 163 |
+
let col;
|
| 164 |
+
if (delta < 0) col = "#ef4444";
|
| 165 |
+
else if (delta > kappa) col = "#10b981";
|
| 166 |
+
else col = "#eab308";
|
| 167 |
+
|
| 168 |
+
g.append("rect")
|
| 169 |
+
.attr("x", x(i)).attr("y", 0)
|
| 170 |
+
.attr("width", Math.max(1, x(i+1) - x(i))).attr("height", progressH)
|
| 171 |
+
.attr("fill", col).attr("opacity", 0.07);
|
| 172 |
+
}
|
| 173 |
+
|
| 174 |
+
// Progress line
|
| 175 |
+
const line = d3.line().x((d,i) => x(i)).y(d => yP(d)).curve(d3.curveMonotoneX);
|
| 176 |
+
g.append("path").datum(progressRaw)
|
| 177 |
+
.attr("fill","none").attr("stroke","#22d3ee").attr("stroke-width",2.5).attr("opacity",0.9)
|
| 178 |
+
.attr("d", line);
|
| 179 |
+
|
| 180 |
+
}
|
| 181 |
+
|
| 182 |
+
render();
|
| 183 |
+
document.getElementById("kappa-slider").addEventListener("input", render);
|
| 184 |
+
window.addEventListener("resize", render);
|
| 185 |
+
}
|
| 186 |
+
|
| 187 |
+
if (typeof d3 !== "undefined") { _initRABC(); }
|
| 188 |
+
else { var s = document.createElement("script"); s.src="https://cdnjs.cloudflare.com/ajax/libs/d3/7.9.0/d3.min.js"; s.onload=_initRABC; document.head.appendChild(s); }
|
| 189 |
+
</script>
|
| 190 |
+
</body>
|
| 191 |
+
</html>
|
app/src/content/embeds/folding/rtc-explainer.html
ADDED
|
@@ -0,0 +1,275 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<!DOCTYPE html>
|
| 2 |
+
<html lang="en">
|
| 3 |
+
<head>
|
| 4 |
+
<meta charset="UTF-8"/>
|
| 5 |
+
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
| 6 |
+
<style>
|
| 7 |
+
:root { --bg: transparent; --text: #e8eaf0; --subtext: #8b8fa8; }
|
| 8 |
+
* { box-sizing: border-box; margin: 0; padding: 0; }
|
| 9 |
+
body { background: var(--bg); font-family: system-ui, sans-serif; color: var(--text); }
|
| 10 |
+
.controls {
|
| 11 |
+
display: flex; align-items: center; gap: 10px; justify-content: center;
|
| 12 |
+
margin: 0 0 4px; font-size: 14px; flex-wrap: wrap;
|
| 13 |
+
}
|
| 14 |
+
.mode-toggle {
|
| 15 |
+
display: flex; border-radius: 6px; overflow: hidden; border: 1px solid #333;
|
| 16 |
+
}
|
| 17 |
+
.mode-btn {
|
| 18 |
+
padding: 6px 16px; font-size: 13px; font-weight: 600; cursor: pointer;
|
| 19 |
+
border: none; background: #1a1d2e; color: #666;
|
| 20 |
+
transition: all .2s; font-family: inherit;
|
| 21 |
+
}
|
| 22 |
+
.mode-btn.active { background: #252540; color: #e8eaf0; }
|
| 23 |
+
.mode-btn:hover:not(.active) { background: #1f2238; color: #999; }
|
| 24 |
+
.play-btn {
|
| 25 |
+
width: 26px; height: 26px; border-radius: 50%; border: 1px solid #444;
|
| 26 |
+
background: #1a1d2e; color: #e8eaf0; cursor: pointer; font-size: 11px;
|
| 27 |
+
display: flex; align-items: center; justify-content: center; font-family: inherit;
|
| 28 |
+
transition: background .2s;
|
| 29 |
+
}
|
| 30 |
+
.play-btn:hover { background: #252540; }
|
| 31 |
+
.time-display { font-weight: 700; font-size: 14px; min-width: 70px; text-align: right; }
|
| 32 |
+
.legend-row {
|
| 33 |
+
display: flex; gap: 14px; justify-content: center; font-size: 12px;
|
| 34 |
+
color: #8b8fa8; margin-top: 4px; flex-wrap: wrap;
|
| 35 |
+
}
|
| 36 |
+
.legend-row span { display: flex; align-items: center; gap: 4px; }
|
| 37 |
+
.legend-swatch { width: 12px; height: 8px; border-radius: 2px; flex-shrink: 0; }
|
| 38 |
+
</style>
|
| 39 |
+
</head>
|
| 40 |
+
<body>
|
| 41 |
+
<div class="controls">
|
| 42 |
+
<div class="mode-toggle">
|
| 43 |
+
<button class="mode-btn active" data-mode="sync">Synchronous</button>
|
| 44 |
+
<button class="mode-btn" data-mode="rtc">RTC</button>
|
| 45 |
+
</div>
|
| 46 |
+
<button class="play-btn" id="play-btn">▶</button>
|
| 47 |
+
<input type="range" id="speed-slider" min="0.5" max="3" step="0.5" value="1" style="width:60px;accent-color:#6366f1;cursor:pointer;" title="Playback speed"/>
|
| 48 |
+
<span class="time-display" id="time-display"></span>
|
| 49 |
+
</div>
|
| 50 |
+
<svg id="rtc-svg"></svg>
|
| 51 |
+
<div class="legend-row" id="legend-row">
|
| 52 |
+
<span><span class="legend-swatch" style="background:#7c7fff"></span>Inference</span>
|
| 53 |
+
<span><span class="legend-swatch" style="background:#22d3ee"></span>Executing</span>
|
| 54 |
+
<span id="legend-idle"><span class="legend-swatch" style="background:transparent;border:1px dashed #555"></span>Idle</span>
|
| 55 |
+
</div>
|
| 56 |
+
<script>
|
| 57 |
+
function _initRTC() {
|
| 58 |
+
|
| 59 |
+
const CHUNK = 10, INF = 3, N_CHUNKS = 4;
|
| 60 |
+
|
| 61 |
+
function buildSync() {
|
| 62 |
+
const inf = [], exec = [], idle = [];
|
| 63 |
+
let t = 0;
|
| 64 |
+
for (let i = 0; i < N_CHUNKS; i++) {
|
| 65 |
+
inf.push({ s: t, e: t + INF, i: i });
|
| 66 |
+
idle.push({ s: t, e: t + INF });
|
| 67 |
+
t += INF;
|
| 68 |
+
exec.push({ s: t, e: t + CHUNK, i: i, lock: 0 });
|
| 69 |
+
t += CHUNK;
|
| 70 |
+
}
|
| 71 |
+
return { inf, exec, idle, T: t };
|
| 72 |
+
}
|
| 73 |
+
|
| 74 |
+
function buildRTC() {
|
| 75 |
+
const inf = [], exec = [], idle = [];
|
| 76 |
+
inf.push({ s: 0, e: INF, i: 0 });
|
| 77 |
+
idle.push({ s: 0, e: INF });
|
| 78 |
+
let t = INF;
|
| 79 |
+
for (let i = 0; i < N_CHUNKS; i++) {
|
| 80 |
+
exec.push({ s: t, e: t + CHUNK, i: i, lock: i > 0 ? INF : 0 });
|
| 81 |
+
if (i + 1 < N_CHUNKS) inf.push({ s: t, e: t + INF, i: i + 1 });
|
| 82 |
+
t += CHUNK - (i + 1 < N_CHUNKS ? INF : 0);
|
| 83 |
+
}
|
| 84 |
+
return { inf, exec, idle, T: exec[exec.length - 1].e };
|
| 85 |
+
}
|
| 86 |
+
|
| 87 |
+
const SYNC = buildSync(), RTC_ = buildRTC();
|
| 88 |
+
let mode = "sync", playing = false, playT = 0, speed = 1, animId = null, lastFrame = null;
|
| 89 |
+
|
| 90 |
+
const container = document.getElementById("rtc-svg").parentElement;
|
| 91 |
+
const svg = d3.select("#rtc-svg");
|
| 92 |
+
const playBtn = document.getElementById("play-btn");
|
| 93 |
+
const speedSlider = document.getElementById("speed-slider");
|
| 94 |
+
const timeDisp = document.getElementById("time-display");
|
| 95 |
+
const legendIdle = document.getElementById("legend-idle");
|
| 96 |
+
const legendRow = document.getElementById("legend-row");
|
| 97 |
+
|
| 98 |
+
const infPalette = ["#7c7fff", "#9590ff", "#7c7fff", "#9590ff"];
|
| 99 |
+
const execPalette = ["#22d3ee", "#14b8a6", "#06b6d4", "#0ea5e9"];
|
| 100 |
+
const lockColor = "#0c5e6f";
|
| 101 |
+
|
| 102 |
+
function data() { return mode === "sync" ? SYNC : RTC_; }
|
| 103 |
+
|
| 104 |
+
function render() {
|
| 105 |
+
svg.selectAll("*").remove();
|
| 106 |
+
const d = data();
|
| 107 |
+
const W = container.clientWidth || 600;
|
| 108 |
+
const ml = 72, mr = 12, mt = 6, mb = 24;
|
| 109 |
+
const laneH = 32, gap = 20;
|
| 110 |
+
const w = W - ml - mr;
|
| 111 |
+
const H = mt + laneH * 2 + gap + mb;
|
| 112 |
+
|
| 113 |
+
svg.attr("width", W).attr("height", H).attr("viewBox", `0 0 ${W} ${H}`);
|
| 114 |
+
const g = svg.append("g").attr("transform", `translate(${ml},${mt})`);
|
| 115 |
+
const maxT = Math.max(SYNC.T, RTC_.T);
|
| 116 |
+
const x = d3.scaleLinear().domain([0, maxT]).range([0, w]);
|
| 117 |
+
const iy = 0, ey = laneH + gap;
|
| 118 |
+
|
| 119 |
+
g.append("rect").attr("x", 0).attr("y", iy).attr("width", w).attr("height", laneH)
|
| 120 |
+
.attr("fill", "#ffffff").attr("opacity", 0.02).attr("rx", 4);
|
| 121 |
+
g.append("rect").attr("x", 0).attr("y", ey).attr("width", w).attr("height", laneH)
|
| 122 |
+
.attr("fill", "#ffffff").attr("opacity", 0.02).attr("rx", 4);
|
| 123 |
+
|
| 124 |
+
g.append("text").attr("x", -10).attr("y", iy + laneH / 2)
|
| 125 |
+
.attr("text-anchor", "end").attr("dominant-baseline", "middle")
|
| 126 |
+
.attr("fill", "#8b8fa8").attr("font-size", 12).attr("font-weight", 500).text("Predict");
|
| 127 |
+
g.append("text").attr("x", -10).attr("y", ey + laneH / 2)
|
| 128 |
+
.attr("text-anchor", "end").attr("dominant-baseline", "middle")
|
| 129 |
+
.attr("fill", "#8b8fa8").attr("font-size", 12).attr("font-weight", 500).text("Execute");
|
| 130 |
+
|
| 131 |
+
d.idle.forEach(id => {
|
| 132 |
+
const bx = x(id.s), bw = x(id.e) - x(id.s);
|
| 133 |
+
g.append("rect").attr("x", bx + 1).attr("y", ey + 3).attr("width", Math.max(0, bw - 2)).attr("height", laneH - 6)
|
| 134 |
+
.attr("fill", "none").attr("stroke", "#444").attr("stroke-width", 1)
|
| 135 |
+
.attr("stroke-dasharray", "4,3").attr("rx", 3);
|
| 136 |
+
});
|
| 137 |
+
|
| 138 |
+
d.inf.forEach(b => {
|
| 139 |
+
const bx = x(b.s), bw = x(b.e) - x(b.s);
|
| 140 |
+
const col = infPalette[b.i % infPalette.length];
|
| 141 |
+
g.append("rect").attr("x", bx).attr("y", iy + 3).attr("width", bw).attr("height", laneH - 6)
|
| 142 |
+
.attr("fill", col).attr("opacity", 0.9).attr("rx", 4);
|
| 143 |
+
|
| 144 |
+
const connX = bx + bw / 2;
|
| 145 |
+
const execBlock = d.exec.find(e => e.i === b.i);
|
| 146 |
+
if (execBlock) {
|
| 147 |
+
const targetX = x(execBlock.s) + (x(execBlock.e) - x(execBlock.s)) / 2;
|
| 148 |
+
g.append("line")
|
| 149 |
+
.attr("x1", connX).attr("y1", iy + laneH - 2)
|
| 150 |
+
.attr("x2", targetX).attr("y2", ey + 2)
|
| 151 |
+
.attr("stroke", col).attr("stroke-width", 1)
|
| 152 |
+
.attr("stroke-dasharray", "2,3").attr("opacity", 0.3);
|
| 153 |
+
}
|
| 154 |
+
});
|
| 155 |
+
|
| 156 |
+
d.exec.forEach(b => {
|
| 157 |
+
const bx = x(b.s), bw = x(b.e) - x(b.s);
|
| 158 |
+
const col = execPalette[b.i % execPalette.length];
|
| 159 |
+
|
| 160 |
+
if (b.lock > 0) {
|
| 161 |
+
const lw = x(b.s + b.lock) - bx;
|
| 162 |
+
g.append("rect").attr("x", bx).attr("y", ey + 3).attr("width", bw).attr("height", laneH - 6)
|
| 163 |
+
.attr("fill", col).attr("opacity", 0.85).attr("rx", 4);
|
| 164 |
+
g.append("rect").attr("x", bx).attr("y", ey + 3).attr("width", lw).attr("height", laneH - 6)
|
| 165 |
+
.attr("fill", lockColor).attr("opacity", 0.95).attr("rx", 4);
|
| 166 |
+
for (let sx = bx + 3; sx < bx + lw - 1; sx += 5) {
|
| 167 |
+
g.append("line").attr("x1", sx).attr("y1", ey + 3)
|
| 168 |
+
.attr("x2", sx + (laneH - 6) * 0.4).attr("y2", ey + laneH - 3)
|
| 169 |
+
.attr("stroke", "#1a8a9e").attr("stroke-width", 0.8).attr("opacity", 0.5);
|
| 170 |
+
}
|
| 171 |
+
g.append("line").attr("x1", bx + lw).attr("y1", ey + 5)
|
| 172 |
+
.attr("x2", bx + lw).attr("y2", ey + laneH - 5)
|
| 173 |
+
.attr("stroke", "#fff").attr("stroke-width", 0.5).attr("opacity", 0.3);
|
| 174 |
+
} else {
|
| 175 |
+
g.append("rect").attr("x", bx).attr("y", ey + 3).attr("width", bw).attr("height", laneH - 6)
|
| 176 |
+
.attr("fill", col).attr("opacity", 0.85).attr("rx", 4);
|
| 177 |
+
}
|
| 178 |
+
|
| 179 |
+
if (bw > 30) {
|
| 180 |
+
g.append("text").attr("x", bx + bw / 2).attr("y", ey + laneH / 2)
|
| 181 |
+
.attr("text-anchor", "middle").attr("dominant-baseline", "middle")
|
| 182 |
+
.attr("fill", "#fff").attr("font-size", 12).attr("font-weight", 600)
|
| 183 |
+
.attr("opacity", 0.9).text("C" + (b.i + 1));
|
| 184 |
+
}
|
| 185 |
+
});
|
| 186 |
+
|
| 187 |
+
const ticks = [];
|
| 188 |
+
const step = maxT > 30 ? 10 : 5;
|
| 189 |
+
for (let t = 0; t <= maxT; t += step) ticks.push(t);
|
| 190 |
+
ticks.forEach(t => {
|
| 191 |
+
const tx = x(t);
|
| 192 |
+
g.append("line").attr("x1", tx).attr("y1", ey + laneH + 2).attr("x2", tx).attr("y2", ey + laneH + 5)
|
| 193 |
+
.attr("stroke", "#555").attr("stroke-width", 0.5);
|
| 194 |
+
g.append("text").attr("x", tx).attr("y", ey + laneH + 14)
|
| 195 |
+
.attr("text-anchor", "middle").attr("fill", "#666").attr("font-size", 11)
|
| 196 |
+
.text(Math.round(t * 33) + "ms");
|
| 197 |
+
});
|
| 198 |
+
|
| 199 |
+
const playhead = g.append("line")
|
| 200 |
+
.attr("x1", x(playT)).attr("x2", x(playT))
|
| 201 |
+
.attr("y1", -2).attr("y2", ey + laneH + 2)
|
| 202 |
+
.attr("stroke", "#f87171").attr("stroke-width", 1.5).attr("opacity", playing || playT > 0 ? 0.9 : 0);
|
| 203 |
+
g.append("circle").attr("cx", x(playT)).attr("cy", -2).attr("r", 3)
|
| 204 |
+
.attr("fill", "#f87171").attr("opacity", playing || playT > 0 ? 0.9 : 0).attr("class", "ph-dot");
|
| 205 |
+
|
| 206 |
+
const totalMs = Math.round(d.T * 33);
|
| 207 |
+
const syncMs = Math.round(SYNC.T * 33);
|
| 208 |
+
if (mode === "rtc") {
|
| 209 |
+
const pct = Math.round((syncMs - totalMs) / syncMs * 100);
|
| 210 |
+
timeDisp.innerHTML = `<span style="color:#10b981">${totalMs}ms</span> <span style="color:#666">(${pct}% faster)</span>`;
|
| 211 |
+
} else {
|
| 212 |
+
timeDisp.innerHTML = `<span style="color:#666">${totalMs}ms</span>`;
|
| 213 |
+
}
|
| 214 |
+
|
| 215 |
+
if (mode === "rtc") {
|
| 216 |
+
legendIdle.style.display = "none";
|
| 217 |
+
if (!document.getElementById("legend-locked")) {
|
| 218 |
+
const sp = document.createElement("span");
|
| 219 |
+
sp.id = "legend-locked";
|
| 220 |
+
sp.innerHTML = `<span class="legend-swatch" style="background:${lockColor}"></span>Committed`;
|
| 221 |
+
legendRow.appendChild(sp);
|
| 222 |
+
}
|
| 223 |
+
} else {
|
| 224 |
+
legendIdle.style.display = "";
|
| 225 |
+
const lk = document.getElementById("legend-locked");
|
| 226 |
+
if (lk) lk.remove();
|
| 227 |
+
}
|
| 228 |
+
|
| 229 |
+
window._ph = { line: playhead, dot: g.select(".ph-dot"), x };
|
| 230 |
+
}
|
| 231 |
+
|
| 232 |
+
function animate(ts) {
|
| 233 |
+
if (!playing) return;
|
| 234 |
+
if (!lastFrame) lastFrame = ts;
|
| 235 |
+
const dt = (ts - lastFrame) / 1000;
|
| 236 |
+
lastFrame = ts;
|
| 237 |
+
playT += dt * speed * 8;
|
| 238 |
+
if (playT > data().T) { playT = 0; lastFrame = null; }
|
| 239 |
+
if (window._ph) {
|
| 240 |
+
const px = window._ph.x(playT);
|
| 241 |
+
window._ph.line.attr("x1", px).attr("x2", px).attr("opacity", 0.9);
|
| 242 |
+
window._ph.dot.attr("cx", px).attr("opacity", 0.9);
|
| 243 |
+
}
|
| 244 |
+
animId = requestAnimationFrame(animate);
|
| 245 |
+
}
|
| 246 |
+
|
| 247 |
+
playBtn.addEventListener("click", () => {
|
| 248 |
+
playing = !playing;
|
| 249 |
+
playBtn.textContent = playing ? "⏸" : "▶";
|
| 250 |
+
if (playing) { lastFrame = null; animId = requestAnimationFrame(animate); }
|
| 251 |
+
else cancelAnimationFrame(animId);
|
| 252 |
+
});
|
| 253 |
+
speedSlider.addEventListener("input", () => {
|
| 254 |
+
speed = parseFloat(speedSlider.value);
|
| 255 |
+
});
|
| 256 |
+
|
| 257 |
+
document.querySelectorAll(".mode-btn").forEach(btn => {
|
| 258 |
+
btn.addEventListener("click", () => {
|
| 259 |
+
document.querySelectorAll(".mode-btn").forEach(b => b.classList.remove("active"));
|
| 260 |
+
btn.classList.add("active");
|
| 261 |
+
mode = btn.dataset.mode;
|
| 262 |
+
playT = 0;
|
| 263 |
+
render();
|
| 264 |
+
});
|
| 265 |
+
});
|
| 266 |
+
|
| 267 |
+
render();
|
| 268 |
+
window.addEventListener("resize", render);
|
| 269 |
+
}
|
| 270 |
+
|
| 271 |
+
if (typeof d3 !== "undefined") { _initRTC(); }
|
| 272 |
+
else { var s = document.createElement("script"); s.src="https://cdnjs.cloudflare.com/ajax/libs/d3/7.9.0/d3.min.js"; s.onload=_initRTC; document.head.appendChild(s); }
|
| 273 |
+
</script>
|
| 274 |
+
</body>
|
| 275 |
+
</html>
|
app/src/content/embeds/folding/statistical-analysis.html
CHANGED
|
@@ -9,26 +9,26 @@ body{background:transparent;font-family:system-ui,sans-serif;color:#e8eaf0}
|
|
| 9 |
.wrap{max-width:980px;margin:0 auto;padding:20px 20px 36px}
|
| 10 |
|
| 11 |
.card{background:#1a1d27;border:1px solid #2a2d3a;border-radius:6px;overflow:visible;margin-bottom:12px}
|
| 12 |
-
.card-head{padding:9px 14px;border-bottom:1px solid #2a2d3a;font-size:
|
| 13 |
.chart-area{padding:16px 16px 10px}
|
| 14 |
svg text{font-family:system-ui,sans-serif}
|
| 15 |
|
| 16 |
-
.insight{background:#12151f;border:1px solid #2a2d3a;border-left:3px solid #f7934f;border-radius:4px;padding:9px 13px;margin-bottom:12px;font-size:
|
| 17 |
.insight strong{color:#e8eaf0}
|
| 18 |
.insight.blue{border-left-color:#60a5fa}
|
| 19 |
.insight.green{border-left-color:#4dc98a}
|
| 20 |
.insight.purple{border-left-color:#818cf8}
|
| 21 |
|
| 22 |
.legend{display:flex;gap:12px;flex-wrap:wrap;padding:8px 14px;border-top:1px solid #2a2d3a}
|
| 23 |
-
.li{display:flex;align-items:center;gap:6px;font-size:
|
| 24 |
.lsw{width:14px;height:3px;border-radius:1px}
|
| 25 |
|
| 26 |
-
.ctrl-btn{padding:
|
| 27 |
.ctrl-btn:hover,.ctrl-btn.active{color:#e8eaf0;border-color:#4a4d5a;background:#252835}
|
| 28 |
|
| 29 |
-
.note{font-size:
|
| 30 |
@media(prefers-color-scheme:dark){.note{color:#c8cad8}}
|
| 31 |
-
.tooltip{position:fixed;background:#252835;border:1px solid #3a3d4a;border-radius:4px;padding:8px 11px;font-size:
|
| 32 |
</style>
|
| 33 |
</head>
|
| 34 |
<body>
|
|
@@ -38,7 +38,7 @@ svg text{font-family:system-ui,sans-serif}
|
|
| 38 |
</div>
|
| 39 |
<div class="card">
|
| 40 |
<div class="card-head">
|
| 41 |
-
<span>Posterior success rate distributions
|
| 42 |
<div style="display:flex;gap:6px">
|
| 43 |
<button class="ctrl-btn active" id="v-total" onclick="setVLevel('total')">Total</button>
|
| 44 |
<button class="ctrl-btn" id="v-l1" onclick="setVLevel('L1')">Level 1</button>
|
|
@@ -47,8 +47,8 @@ svg text{font-family:system-ui,sans-serif}
|
|
| 47 |
</div>
|
| 48 |
<div class="chart-area" style="overflow:visible"><svg id="svg-violin" width="100%" height="500" style="overflow:visible"></svg></div>
|
| 49 |
<div class="legend">
|
| 50 |
-
<div class="li"><div class="lsw" style="background:#f7934f"></div>
|
| 51 |
-
<div class="li"><div class="lsw" style="background:#4dc98a"></div>
|
| 52 |
<div class="li" style="font-size:10px;color:#8b8fa8">Bold letter = CLD group (STEP, Bonferroni α=0.10/55, n<sub>max</sub>=50)</div>
|
| 53 |
</div>
|
| 54 |
</div>
|
|
@@ -78,7 +78,7 @@ const DATA = {
|
|
| 78 |
// CLD assignments (STEP sequential test, two-sided, Bonferroni α=0.10/55, n_max=50)
|
| 79 |
const CLD = {
|
| 80 |
total: {'2.5 HQ+RABC+Rel★':'a','2.2 HQ+RABC+Rel':'ab','1.1 π0':'bc','1.7 Rel+RABC':'bc','2.1 HQ':'bc','1.3 Relative':'bc','1.2 π0.5':'cd','2.4 HQ chunk45':'cd','1.4 RABC low':'cd','2.3 HQ+mirror':'cd','1.5 RABC high':'d'},
|
| 81 |
-
L1: {'2.2 HQ+RABC+Rel':'a','2.5 HQ+RABC+Rel★':'a','1.1 π0':'a','1.7 Rel+RABC':'a','1.3 Relative':'a','2.1 HQ':'a','1.2 π0.5':'ab','2.4 HQ chunk45':'ab','1.4 RABC low':'ab','
|
| 82 |
L2: {'2.5 HQ+RABC+Rel★':'a','2.2 HQ+RABC+Rel':'ab','2.1 HQ':'ab','2.3 HQ+mirror':'ab','1.1 π0':'b','1.2 π0.5':'b','1.3 Relative':'b','1.4 RABC low':'b','1.5 RABC high':'b','1.7 Rel+RABC':'b','2.4 HQ chunk45':'b'},
|
| 83 |
};
|
| 84 |
|
|
@@ -154,7 +154,7 @@ function drawViolin(){
|
|
| 154 |
g.append('line').attr('x1',0).attr('x2',iW).attr('y1',y(0.5)).attr('y2',y(0.5))
|
| 155 |
.attr('stroke','#3a3d4a').attr('stroke-dasharray','4,3').attr('opacity',.6);
|
| 156 |
g.append('text').attr('x',iW-2).attr('y',y(0.5)-4).attr('text-anchor','end')
|
| 157 |
-
.attr('fill','#3a3d4a').attr('font-size',
|
| 158 |
|
| 159 |
const tooltip=document.getElementById('tooltip');
|
| 160 |
|
|
@@ -199,11 +199,11 @@ function drawViolin(){
|
|
| 199 |
// CLD letter
|
| 200 |
const cldLetter=(CLD[vLevel]||CLD.total)[expId]||'?';
|
| 201 |
g.append('text').attr('x',cx).attr('y',-8).attr('text-anchor','middle')
|
| 202 |
-
.attr('fill',c).attr('font-size',
|
| 203 |
|
| 204 |
// SR fraction label
|
| 205 |
g.append('text').attr('x',cx).attr('y',-22).attr('text-anchor','middle')
|
| 206 |
-
.attr('fill',SUB).attr('font-size',
|
| 207 |
|
| 208 |
// Hover
|
| 209 |
const hitbox=g.append('rect').attr('x',bx).attr('y',0).attr('width',bw).attr('height',iH)
|
|
@@ -221,11 +221,11 @@ function drawViolin(){
|
|
| 221 |
// Axes
|
| 222 |
g.append('g').attr('transform',`translate(0,${iH})`)
|
| 223 |
.call(d3.axisBottom(x).tickSize(0))
|
| 224 |
-
.call(gg=>{gg.select('.domain').attr('stroke',BORDER);gg.selectAll('text').attr('fill',d=>seriesColor(DATA[d].series)).attr('font-size',
|
| 225 |
g.append('g').call(d3.axisLeft(y).ticks(5).tickFormat(d=>Math.round(d*100)+'%').tickSize(3))
|
| 226 |
-
.call(gg=>{gg.select('.domain').attr('stroke',BORDER);gg.selectAll('text').attr('fill',SUB).attr('font-size',
|
| 227 |
g.append('text').attr('transform','rotate(-90)').attr('x',-iH/2).attr('y',-30).attr('text-anchor','middle')
|
| 228 |
-
.attr('fill',SUB).attr('font-size',
|
| 229 |
}
|
| 230 |
|
| 231 |
window.setVLevel = setVLevel;
|
|
|
|
| 9 |
.wrap{max-width:980px;margin:0 auto;padding:20px 20px 36px}
|
| 10 |
|
| 11 |
.card{background:#1a1d27;border:1px solid #2a2d3a;border-radius:6px;overflow:visible;margin-bottom:12px}
|
| 12 |
+
.card-head{padding:9px 14px;border-bottom:1px solid #2a2d3a;font-size:12px;text-transform:uppercase;letter-spacing:.07em;color:#8b8fa8;display:flex;justify-content:space-between;align-items:center;flex-wrap:wrap;gap:8px}
|
| 13 |
.chart-area{padding:16px 16px 10px}
|
| 14 |
svg text{font-family:system-ui,sans-serif}
|
| 15 |
|
| 16 |
+
.insight{background:#12151f;border:1px solid #2a2d3a;border-left:3px solid #f7934f;border-radius:4px;padding:9px 13px;margin-bottom:12px;font-size:12px;color:#8b8fa8;line-height:1.7}
|
| 17 |
.insight strong{color:#e8eaf0}
|
| 18 |
.insight.blue{border-left-color:#60a5fa}
|
| 19 |
.insight.green{border-left-color:#4dc98a}
|
| 20 |
.insight.purple{border-left-color:#818cf8}
|
| 21 |
|
| 22 |
.legend{display:flex;gap:12px;flex-wrap:wrap;padding:8px 14px;border-top:1px solid #2a2d3a}
|
| 23 |
+
.li{display:flex;align-items:center;gap:6px;font-size:12px;color:#8b8fa8}
|
| 24 |
.lsw{width:14px;height:3px;border-radius:1px}
|
| 25 |
|
| 26 |
+
.ctrl-btn{padding:5px 12px;background:#1a1d27;border:1px solid #2a2d3a;border-radius:4px;color:#8b8fa8;font-family:inherit;font-size:11px;cursor:pointer;letter-spacing:.04em;transition:all .15s}
|
| 27 |
.ctrl-btn:hover,.ctrl-btn.active{color:#e8eaf0;border-color:#4a4d5a;background:#252835}
|
| 28 |
|
| 29 |
+
.note{font-size:12px;color:#555e7a;margin-top:10px;line-height:1.6}
|
| 30 |
@media(prefers-color-scheme:dark){.note{color:#c8cad8}}
|
| 31 |
+
.tooltip{position:fixed;background:#252835;border:1px solid #3a3d4a;border-radius:4px;padding:8px 11px;font-size:11px;color:#e8eaf0;pointer-events:none;opacity:0;transition:opacity .1s;z-index:100;line-height:1.7;max-width:240px}
|
| 32 |
</style>
|
| 33 |
</head>
|
| 34 |
<body>
|
|
|
|
| 38 |
</div>
|
| 39 |
<div class="card">
|
| 40 |
<div class="card-head">
|
| 41 |
+
<span>Posterior success rate distributions: Beta(1+k, 1+n-k)</span>
|
| 42 |
<div style="display:flex;gap:6px">
|
| 43 |
<button class="ctrl-btn active" id="v-total" onclick="setVLevel('total')">Total</button>
|
| 44 |
<button class="ctrl-btn" id="v-l1" onclick="setVLevel('L1')">Level 1</button>
|
|
|
|
| 47 |
</div>
|
| 48 |
<div class="chart-area" style="overflow:visible"><svg id="svg-violin" width="100%" height="500" style="overflow:visible"></svg></div>
|
| 49 |
<div class="legend">
|
| 50 |
+
<div class="li"><div class="lsw" style="background:#f7934f"></div>Initial (1.x)</div>
|
| 51 |
+
<div class="li"><div class="lsw" style="background:#4dc98a"></div>Fine-tuned (2.x)</div>
|
| 52 |
<div class="li" style="font-size:10px;color:#8b8fa8">Bold letter = CLD group (STEP, Bonferroni α=0.10/55, n<sub>max</sub>=50)</div>
|
| 53 |
</div>
|
| 54 |
</div>
|
|
|
|
| 78 |
// CLD assignments (STEP sequential test, two-sided, Bonferroni α=0.10/55, n_max=50)
|
| 79 |
const CLD = {
|
| 80 |
total: {'2.5 HQ+RABC+Rel★':'a','2.2 HQ+RABC+Rel':'ab','1.1 π0':'bc','1.7 Rel+RABC':'bc','2.1 HQ':'bc','1.3 Relative':'bc','1.2 π0.5':'cd','2.4 HQ chunk45':'cd','1.4 RABC low':'cd','2.3 HQ+mirror':'cd','1.5 RABC high':'d'},
|
| 81 |
+
L1: {'2.2 HQ+RABC+Rel':'a','2.5 HQ+RABC+Rel★':'a','1.1 π0':'a','1.7 Rel+RABC':'a','1.3 Relative':'a','2.1 HQ':'a','1.2 π0.5':'ab','2.4 HQ chunk45':'ab','1.4 RABC low':'ab','2.3 HQ+mirror':'b','1.5 RABC high':'b'},
|
| 82 |
L2: {'2.5 HQ+RABC+Rel★':'a','2.2 HQ+RABC+Rel':'ab','2.1 HQ':'ab','2.3 HQ+mirror':'ab','1.1 π0':'b','1.2 π0.5':'b','1.3 Relative':'b','1.4 RABC low':'b','1.5 RABC high':'b','1.7 Rel+RABC':'b','2.4 HQ chunk45':'b'},
|
| 83 |
};
|
| 84 |
|
|
|
|
| 154 |
g.append('line').attr('x1',0).attr('x2',iW).attr('y1',y(0.5)).attr('y2',y(0.5))
|
| 155 |
.attr('stroke','#3a3d4a').attr('stroke-dasharray','4,3').attr('opacity',.6);
|
| 156 |
g.append('text').attr('x',iW-2).attr('y',y(0.5)-4).attr('text-anchor','end')
|
| 157 |
+
.attr('fill','#3a3d4a').attr('font-size',10).text('50%');
|
| 158 |
|
| 159 |
const tooltip=document.getElementById('tooltip');
|
| 160 |
|
|
|
|
| 199 |
// CLD letter
|
| 200 |
const cldLetter=(CLD[vLevel]||CLD.total)[expId]||'?';
|
| 201 |
g.append('text').attr('x',cx).attr('y',-8).attr('text-anchor','middle')
|
| 202 |
+
.attr('fill',c).attr('font-size',13).attr('font-weight','bold').text(cldLetter);
|
| 203 |
|
| 204 |
// SR fraction label
|
| 205 |
g.append('text').attr('x',cx).attr('y',-22).attr('text-anchor','middle')
|
| 206 |
+
.attr('fill',SUB).attr('font-size',11).text(`${k}/${n}`);
|
| 207 |
|
| 208 |
// Hover
|
| 209 |
const hitbox=g.append('rect').attr('x',bx).attr('y',0).attr('width',bw).attr('height',iH)
|
|
|
|
| 221 |
// Axes
|
| 222 |
g.append('g').attr('transform',`translate(0,${iH})`)
|
| 223 |
.call(d3.axisBottom(x).tickSize(0))
|
| 224 |
+
.call(gg=>{gg.select('.domain').attr('stroke',BORDER);gg.selectAll('text').attr('fill',d=>seriesColor(DATA[d].series)).attr('font-size',11).attr('transform','rotate(-40)').attr('text-anchor','end').attr('dx','-0.5em').attr('dy','0.3em')});
|
| 225 |
g.append('g').call(d3.axisLeft(y).ticks(5).tickFormat(d=>Math.round(d*100)+'%').tickSize(3))
|
| 226 |
+
.call(gg=>{gg.select('.domain').attr('stroke',BORDER);gg.selectAll('text').attr('fill',SUB).attr('font-size',11);gg.selectAll('line').attr('stroke',BORDER)});
|
| 227 |
g.append('text').attr('transform','rotate(-90)').attr('x',-iH/2).attr('y',-30).attr('text-anchor','middle')
|
| 228 |
+
.attr('fill',SUB).attr('font-size',11).text('Posterior success rate');
|
| 229 |
}
|
| 230 |
|
| 231 |
window.setVLevel = setVLevel;
|
app/src/content/embeds/folding/subtask-heatmap.html
CHANGED
|
@@ -16,86 +16,186 @@
|
|
| 16 |
.tooltip strong { display: block; margin-bottom: 5px; }
|
| 17 |
.tooltip-row { display: flex; justify-content: space-between; gap: 12px; margin-top: 3px; font-size: 12px; color: var(--subtext); }
|
| 18 |
.tooltip-row span:last-child { color: var(--text); font-weight: 600; }
|
| 19 |
-
.legend-bar { display: flex; align-items: center; gap: 8px; margin-top: 10px; font-size:
|
| 20 |
.legend-gradient { height: 10px; width: 180px; border-radius: 5px; flex-shrink: 0; }
|
| 21 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 22 |
</style>
|
| 23 |
</head>
|
| 24 |
<body>
|
|
|
|
|
|
|
|
|
|
| 25 |
<div style="position:relative">
|
| 26 |
<svg id="hm-chart"></svg>
|
| 27 |
<div class="tooltip" id="hm-tooltip"></div>
|
| 28 |
</div>
|
| 29 |
-
<div class="legend-bar">
|
| 30 |
-
<span>Fast (0s)</span>
|
| 31 |
<canvas id="lgd" class="legend-gradient" width="180" height="10"></canvas>
|
| 32 |
-
<span>Slow (≥120s)</span>
|
| 33 |
</div>
|
| 34 |
<script>
|
| 35 |
function _initSubtaskHeatmap() {
|
| 36 |
const rawData = [
|
| 37 |
-
{label:"1.1 π0",series:"1",total_sr:40, times:[null, 19.2, 42.22, 14.33, 19.88, 27.25]},
|
| 38 |
-
{label:"1.2 π0.5",series:"1",total_sr:20, times:[50, 39.27, 41.5, 12.3, 13.75, 10.75]},
|
| 39 |
-
{label:"1.3 Relative",series:"1",total_sr:35, times:[null, 19.5, 44.2, 14.8, 30.33, 22.14]},
|
| 40 |
-
{label:"1.4 RABC low",series:"1",total_sr:15, times:[null, 20.8, 36.62, 10.0, 18.8, 12.67]},
|
| 41 |
-
{label:"1.5 RABC high",series:"1",total_sr:0, times:[240, 21.4, 100.0, null, null, null ]},
|
| 42 |
-
{label:"1.7 Rel+RABC",series:"1",total_sr:40, times:[157.5,19.33, 32.64, 8.9, 11.0, 23.38]},
|
| 43 |
-
{label:"2.1 HQ",series:"2",total_sr:40, times:[77.5, 11.08, 21.09, 5.45, 5.5, 11.5 ]},
|
| 44 |
-
{label:"2.2 HQ+RABC+Rel",series:"2",total_sr:75, times:[34.33,6.25, 12.31, 3.75, 5.31, 8.93 ]},
|
| 45 |
-
{label:"2.3 HQ+mirror",series:"2",total_sr:5, times:[49, 14.0, 23.71, 17.5, 11.0, 4.0 ]},
|
| 46 |
-
{label:"2.4 HQ chunk45",series:"2",total_sr:20, times:[120, 10.09, 41.18, 7.89, 7.33, 10.0 ]},
|
| 47 |
-
{label:"2.5 HQ+RABC+Rel★",series:"2",total_sr:90, times:[62.25,8.28, 12.0, 5.28, 5.22, 6.83 ]},
|
| 48 |
];
|
| 49 |
|
| 50 |
-
|
| 51 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 52 |
const subtasks = ["Unfold","Fold 1","Fold 2","Fold 3","Fold 4","Rotation"];
|
| 53 |
const seriesColor = s => s === "2" ? "#f7934f" : "#4f8ef7";
|
| 54 |
|
| 55 |
-
|
| 56 |
-
|
| 57 |
-
|
| 58 |
-
|
| 59 |
-
|
| 60 |
-
|
| 61 |
-
|
| 62 |
-
|
| 63 |
-
|
| 64 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 65 |
}
|
| 66 |
-
|
| 67 |
-
});
|
|
|
|
|
|
|
|
|
|
|
|
|
| 68 |
|
| 69 |
-
//
|
| 70 |
-
const
|
| 71 |
-
const
|
| 72 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 73 |
|
| 74 |
const margin = {top:12, right:16, bottom:36, left:120};
|
| 75 |
const svg = d3.select("#hm-chart");
|
| 76 |
const container = svg.node().parentElement;
|
| 77 |
const tooltip = d3.select("#hm-tooltip");
|
| 78 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 79 |
function render() {
|
| 80 |
svg.selectAll("*").remove();
|
|
|
|
| 81 |
const W = container.clientWidth;
|
| 82 |
-
const
|
|
|
|
| 83 |
const cellH = Math.max(34, Math.min(44, cellW * 0.7));
|
| 84 |
const H = data.length * cellH + margin.top + margin.bottom;
|
| 85 |
svg.attr("width",W).attr("height",H);
|
| 86 |
const g = svg.append("g").attr("transform",`translate(${margin.left},${margin.top})`);
|
| 87 |
const gridW = cellW * subtasks.length;
|
| 88 |
const gridH = cellH * data.length;
|
|
|
|
| 89 |
|
| 90 |
-
//
|
| 91 |
g.selectAll(".col-lbl").data(subtasks).join("text")
|
| 92 |
.attr("x",(_,i)=>i*cellW+cellW/2).attr("y",gridH+22)
|
| 93 |
-
.attr("text-anchor","middle").attr("fill","#8b8fa8").attr("font-size",
|
| 94 |
.text(d=>d);
|
| 95 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 96 |
// Row labels + series stripe
|
| 97 |
data.forEach((d, ri) => {
|
| 98 |
-
// Series colour stripe on the left
|
| 99 |
g.append("rect")
|
| 100 |
.attr("x",-margin.left+2).attr("y",ri*cellH+2)
|
| 101 |
.attr("width",4).attr("height",cellH-4).attr("rx",2)
|
|
@@ -103,76 +203,117 @@ function render() {
|
|
| 103 |
|
| 104 |
g.append("text")
|
| 105 |
.attr("x",-8).attr("y",ri*cellH+cellH/2)
|
| 106 |
-
.attr("text-anchor","end").attr("fill","#e8eaf0").attr("font-size",
|
| 107 |
.text(d.label);
|
| 108 |
|
| 109 |
g.append("text")
|
| 110 |
-
.attr("x",-8).attr("y",ri*cellH+cellH/2+
|
| 111 |
-
.attr("text-anchor","end").attr("fill","#8b8fa8").attr("font-size",
|
| 112 |
.text(d.total_sr+"% SR");
|
| 113 |
});
|
| 114 |
|
| 115 |
-
//
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 116 |
data.forEach((d, ri) => {
|
| 117 |
d.times.forEach((val, ci) => {
|
| 118 |
-
const cellG = g.append("g").attr("transform",`translate(${ci*cellW},${ri*cellH})`);
|
| 119 |
if (val === null) {
|
| 120 |
-
|
| 121 |
-
.attr("fill","#1a1d27").attr("stroke","#2a2d3a");
|
| 122 |
-
cellG.append("text").attr("x",cellW/2-1.5).attr("y",cellH/2+3)
|
| 123 |
-
.attr("text-anchor","middle").attr("fill","#3a3d4a").attr("font-size",10).text("—");
|
| 124 |
} else {
|
| 125 |
-
const
|
| 126 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 127 |
.attr("fill",col).style("cursor","pointer")
|
| 128 |
.on("mousemove",function(event){
|
| 129 |
-
tooltip.style("opacity",1).html(
|
| 130 |
-
<strong>Experiment ${d.label} — ${subtasks[ci]}</strong>
|
| 131 |
-
<div class="tooltip-row"><span>Avg time</span><span>${val.toFixed(1)}s</span></div>
|
| 132 |
-
<div class="tooltip-row"><span>Total SR</span><span>${d.total_sr}%</span></div>
|
| 133 |
-
<div class="tooltip-row"><span>Series</span><span>${d.series}</span></div>
|
| 134 |
-
`);
|
| 135 |
const bx=container.getBoundingClientRect();
|
| 136 |
const ex=event.clientX-bx.left, ey=event.clientY-bx.top;
|
| 137 |
-
tooltip.style("left",Math.min(ex+12,W-
|
| 138 |
})
|
| 139 |
.on("mouseleave",()=>tooltip.style("opacity",0));
|
| 140 |
-
|
| 141 |
-
|
| 142 |
-
cellG.append("text")
|
| 143 |
.attr("x",cellW/2-1.5).attr("y",cellH/2+4)
|
| 144 |
.attr("text-anchor","middle")
|
| 145 |
.attr("fill", lum > 0.52 ? "#1a1d27" : "#e8eaf0")
|
| 146 |
-
.attr("font-size",
|
| 147 |
-
.text(
|
| 148 |
}
|
| 149 |
});
|
| 150 |
});
|
| 151 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 152 |
// Sorted annotation
|
| 153 |
-
|
| 154 |
-
|
| 155 |
-
.
|
|
|
|
| 156 |
}
|
| 157 |
|
| 158 |
render();
|
| 159 |
window.addEventListener("resize", render);
|
| 160 |
|
| 161 |
-
const EXPERIMENTS = {
|
| 162 |
-
"1.1 π0": { desc:"π0 · all data · 200k steps · MEAN_STD", note:"Base pi0 policy trained from scratch on the full dataset." },
|
| 163 |
-
"1.2 π0.5": { desc:"π0.5 · all data · 200k steps · MEAN_STD", note:"Upgraded to pi0.5 architecture, same data and steps." },
|
| 164 |
-
"1.3 Relative": { desc:"π0.5 · all data · 200k steps · Relative Actions · QUANTILES", note:"Adds Relative Actions on top of 1.2 — actions expressed relative to current state." },
|
| 165 |
-
"1.4 RABC low": { desc:"π0.5 · all data · 200k steps · RABC κ=0.01", note:"Selective Action Reward Model with low κ (≈ mean threshold, not very selective)." },
|
| 166 |
-
"1.5 RABC high": { desc:"π0.5 · all data · 200k steps · RABC κ=0.0215", note:"SARM with κ = mean + ½ std — more selective filtering than 1.4." },
|
| 167 |
-
"1.7 Rel+RABC": { desc:"π0.5 · all data · 200k steps · Relative Actions + RABC κ=0.0215 · QUANTILES", note:"Best of Series 1. Base checkpoint for 2.5." },
|
| 168 |
-
"2.1 HQ": { desc:"π0.5 · HQ data · 100k steps · fine-tune from 1.3", note:"Fine-tunes 1.3 on curated high-quality data only." },
|
| 169 |
-
"2.2 HQ+RABC+Rel": { desc:"π0.5 · HQ data · 100k steps · fine-tune from 1.3 + RABC κ=0.0265 + Relative Actions", note:"Adds RABC on high-quality fine-tune from 1.3." },
|
| 170 |
-
"2.3 HQ+mirror": { desc:"π0.5 · HQ + mirrored · 100k steps · fine-tune from 1.3 + Relative Actions + mirroring", note:"Augments the high-quality dataset with mirrored trajectories." },
|
| 171 |
-
"2.4 HQ chunk45": { desc:"π0.5 · HQ data · 100k steps · fine-tune from 1.3 · chunk=45", note:"Explores chunked action prediction (chunk=50, RTC size=50, execution horizon=35)." },
|
| 172 |
-
"2.5 HQ+RABC+Rel★": { desc:"π0.5 · HQ data · 100k steps · fine-tune from 1.7 + RABC κ=0.0265 + Relative Actions (best)", note:"Top performer. Best overall result." },
|
| 173 |
-
};
|
| 174 |
-
|
| 175 |
-
|
| 176 |
}
|
| 177 |
|
| 178 |
if (typeof d3 !== "undefined") {
|
|
|
|
| 16 |
.tooltip strong { display: block; margin-bottom: 5px; }
|
| 17 |
.tooltip-row { display: flex; justify-content: space-between; gap: 12px; margin-top: 3px; font-size: 12px; color: var(--subtext); }
|
| 18 |
.tooltip-row span:last-child { color: var(--text); font-weight: 600; }
|
| 19 |
+
.legend-bar { display: flex; align-items: center; gap: 8px; margin-top: 10px; font-size: 13px; color: var(--subtext); justify-content: center; }
|
| 20 |
.legend-gradient { height: 10px; width: 180px; border-radius: 5px; flex-shrink: 0; }
|
| 21 |
+
.hm-toggle { display: flex; gap: 0; justify-content: center; margin-bottom: 6px; }
|
| 22 |
+
.hm-btn {
|
| 23 |
+
padding: 7px 20px; font-size: 14px; font-weight: 600; cursor: pointer; border: none;
|
| 24 |
+
background: #1a1d2e; color: #8b8fa8; transition: all .2s;
|
| 25 |
+
font-family: system-ui, sans-serif; border-bottom: 2px solid transparent;
|
| 26 |
+
}
|
| 27 |
+
.hm-btn:first-child { border-radius: 6px 0 0 0; }
|
| 28 |
+
.hm-btn:last-child { border-radius: 0 6px 0 0; }
|
| 29 |
+
.hm-btn.active { background: #252540; color: #e8eaf0; border-bottom-color: #6366f1; }
|
| 30 |
+
.hm-btn:hover:not(.active) { background: #1f2238; color: #bbb; }
|
| 31 |
+
.filter-toggle {
|
| 32 |
+
font-size: 12px; color: #8b8fa8; cursor: pointer; border: 1px solid #2a2d3a;
|
| 33 |
+
background: #1a1d27; padding: 4px 12px; border-radius: 14px; transition: all .15s;
|
| 34 |
+
user-select: none; display: inline-flex; align-items: center; gap: 4px;
|
| 35 |
+
}
|
| 36 |
+
.filter-toggle:hover { border-color: #555; color: #e8eaf0; }
|
| 37 |
+
.filter-toggle.showing-all { background: rgba(248,147,79,0.12); border-color: #f7934f; color: #f7934f; }
|
| 38 |
+
.hm-controls { display: flex; justify-content: center; gap: 8px; margin-bottom: 6px; align-items: center; }
|
| 39 |
</style>
|
| 40 |
</head>
|
| 41 |
<body>
|
| 42 |
+
<div class="hm-controls">
|
| 43 |
+
<button class="filter-toggle" id="hm-filter-btn" onclick="window._hmToggleFilter()">Show all 11</button>
|
| 44 |
+
</div>
|
| 45 |
<div style="position:relative">
|
| 46 |
<svg id="hm-chart"></svg>
|
| 47 |
<div class="tooltip" id="hm-tooltip"></div>
|
| 48 |
</div>
|
| 49 |
+
<div class="legend-bar" id="lgd-bar">
|
| 50 |
+
<span id="lgd-lo">Fast (0s)</span>
|
| 51 |
<canvas id="lgd" class="legend-gradient" width="180" height="10"></canvas>
|
| 52 |
+
<span id="lgd-hi">Slow (≥120s)</span>
|
| 53 |
</div>
|
| 54 |
<script>
|
| 55 |
function _initSubtaskHeatmap() {
|
| 56 |
const rawData = [
|
| 57 |
+
{label:"1.1 π0",series:"1",total_sr:40, times:[null, 19.2, 42.22, 14.33, 19.88, 27.25], l1time:121.5, quality:2.70},
|
| 58 |
+
{label:"1.2 π0.5",series:"1",total_sr:20, times:[50, 39.27, 41.5, 12.3, 13.75, 10.75], l1time:90.75, quality:2.50},
|
| 59 |
+
{label:"1.3 Relative",series:"1",total_sr:35, times:[null, 19.5, 44.2, 14.8, 30.33, 22.14], l1time:113.86,quality:2.80},
|
| 60 |
+
{label:"1.4 RABC low",series:"1",total_sr:15, times:[null, 20.8, 36.62, 10.0, 18.8, 12.67], l1time:78.33, quality:2.20},
|
| 61 |
+
{label:"1.5 RABC high",series:"1",total_sr:0, times:[240, 21.4, 100.0, null, null, null ], l1time:null, quality:1.00},
|
| 62 |
+
{label:"1.7 Rel+RABC",series:"1",total_sr:40, times:[157.5,19.33, 32.64, 8.9, 11.0, 23.38], l1time:99.5, quality:2.30},
|
| 63 |
+
{label:"2.1 HQ",series:"2",total_sr:40, times:[77.5, 11.08, 21.09, 5.45, 5.5, 11.5 ], l1time:57.57, quality:2.80},
|
| 64 |
+
{label:"2.2 HQ+RABC+Rel",series:"2",total_sr:75, times:[34.33,6.25, 12.31, 3.75, 5.31, 8.93 ], l1time:43.2, quality:3.30},
|
| 65 |
+
{label:"2.3 HQ+mirror",series:"2",total_sr:5, times:[49, 14.0, 23.71, 17.5, 11.0, 4.0 ], l1time:null, quality:1.00},
|
| 66 |
+
{label:"2.4 HQ chunk45",series:"2",total_sr:20, times:[120, 10.09, 41.18, 7.89, 7.33, 10.0 ], l1time:72.5, quality:1.80},
|
| 67 |
+
{label:"2.5 HQ+RABC+Rel★",series:"2",total_sr:90, times:[62.25,8.28, 12.0, 5.28, 5.22, 6.83 ], l1time:40.8, quality:4.10},
|
| 68 |
];
|
| 69 |
|
| 70 |
+
const HIDDEN_BY_DEFAULT = new Set(["1.4 RABC low","1.5 RABC high","2.3 HQ+mirror","2.4 HQ chunk45"]);
|
| 71 |
+
let hmShowAll = false;
|
| 72 |
+
|
| 73 |
+
window._hmToggleFilter = function() {
|
| 74 |
+
hmShowAll = !hmShowAll;
|
| 75 |
+
const btn = document.getElementById("hm-filter-btn");
|
| 76 |
+
btn.textContent = hmShowAll ? "Key experiments" : "Show all 11";
|
| 77 |
+
btn.classList.toggle("showing-all", hmShowAll);
|
| 78 |
+
render();
|
| 79 |
+
};
|
| 80 |
+
|
| 81 |
+
function getFilteredData() {
|
| 82 |
+
const src = hmShowAll ? rawData : rawData.filter(d => !HIDDEN_BY_DEFAULT.has(d.label));
|
| 83 |
+
return [...src].sort((a,b) => {
|
| 84 |
+
if (a.l1time === null && b.l1time === null) return 0;
|
| 85 |
+
if (a.l1time === null) return 1;
|
| 86 |
+
if (b.l1time === null) return -1;
|
| 87 |
+
return a.l1time - b.l1time;
|
| 88 |
+
});
|
| 89 |
+
}
|
| 90 |
const subtasks = ["Unfold","Fold 1","Fold 2","Fold 3","Fold 4","Rotation"];
|
| 91 |
const seriesColor = s => s === "2" ? "#f7934f" : "#4f8ef7";
|
| 92 |
|
| 93 |
+
let mode = "rel";
|
| 94 |
+
|
| 95 |
+
// Compute per-subtask means over ALL experiments (ignoring nulls)
|
| 96 |
+
const subtaskMeans = subtasks.map((_, ci) => {
|
| 97 |
+
const vals = rawData.map(d => d.times[ci]).filter(v => v !== null);
|
| 98 |
+
return vals.length ? vals.reduce((a,b) => a+b, 0) / vals.length : 1;
|
| 99 |
+
});
|
| 100 |
+
|
| 101 |
+
function lerpColor(stops, t) {
|
| 102 |
+
for (let i=0; i<stops.length-1; i++) {
|
| 103 |
+
const [t0,c0]=stops[i],[t1,c1]=stops[i+1];
|
| 104 |
+
if (t<=t1) {
|
| 105 |
+
const f=(t-t0)/(t1-t0);
|
| 106 |
+
return `rgb(${Math.round(c0[0]+(c1[0]-c0[0])*f)},${Math.round(c0[1]+(c1[1]-c0[1])*f)},${Math.round(c0[2]+(c1[2]-c0[2])*f)})`;
|
| 107 |
}
|
| 108 |
+
}
|
| 109 |
+
return `rgb(${stops[stops.length-1][1].join(",")})`;
|
| 110 |
+
}
|
| 111 |
+
|
| 112 |
+
// Absolute: green → yellow → red, capped at 120s
|
| 113 |
+
const absColor = v => lerpColor([[0,[46,200,138]],[0.4,[247,211,79]],[1,[220,60,60]]], Math.min(v,120)/120);
|
| 114 |
|
| 115 |
+
// Relative: green (0.2x) → neutral (1x) → red (3x), diverging around mean
|
| 116 |
+
const relColor = ratio => {
|
| 117 |
+
const t = Math.max(0, Math.min(1, (ratio - 0.2) / (3 - 0.2)));
|
| 118 |
+
return lerpColor([[0,[46,200,138]],[0.286,[230,230,210]],[1,[220,60,60]]], t);
|
| 119 |
+
};
|
| 120 |
+
|
| 121 |
+
// Quality: red (1.0) → yellow (2.5) → green (4.5)
|
| 122 |
+
const qualColor = v => {
|
| 123 |
+
const t = Math.max(0, Math.min(1, (v - 1.0) / (4.5 - 1.0)));
|
| 124 |
+
return lerpColor([[0,[220,60,60]],[0.43,[247,211,79]],[1,[46,200,138]]], t);
|
| 125 |
+
};
|
| 126 |
+
|
| 127 |
+
function drawLegend() {
|
| 128 |
+
const canvas = document.getElementById("lgd");
|
| 129 |
+
const ctx = canvas.getContext("2d");
|
| 130 |
+
for (let i=0; i<180; i++) {
|
| 131 |
+
const t = i/179;
|
| 132 |
+
ctx.fillStyle = mode === "abs" ? absColor(t*120) : relColor(0.2 + t*2.8);
|
| 133 |
+
ctx.fillRect(i,0,1,10);
|
| 134 |
+
}
|
| 135 |
+
document.getElementById("lgd-lo").textContent = mode === "abs" ? "Fast (0s)" : "Fast (0.2×)";
|
| 136 |
+
document.getElementById("lgd-hi").textContent = mode === "abs" ? "Slow (≥120s)" : "Slow (≥3×)";
|
| 137 |
+
}
|
| 138 |
+
drawLegend();
|
| 139 |
|
| 140 |
const margin = {top:12, right:16, bottom:36, left:120};
|
| 141 |
const svg = d3.select("#hm-chart");
|
| 142 |
const container = svg.node().parentElement;
|
| 143 |
const tooltip = d3.select("#hm-tooltip");
|
| 144 |
|
| 145 |
+
const EXPERIMENTS = {
|
| 146 |
+
"1.1 π0": { note:"Base pi0 policy trained from scratch on the full dataset." },
|
| 147 |
+
"1.2 π0.5": { note:"Upgraded to pi0.5 architecture, same data and steps." },
|
| 148 |
+
"1.3 Relative": { note:"Adds Relative Actions, expressing actions relative to current state." },
|
| 149 |
+
"1.4 RABC low": { note:"SARM with low κ (≈ mean threshold, not very selective)." },
|
| 150 |
+
"1.5 RABC high": { note:"SARM with κ = mean + ½ std, more selective filtering than 1.4." },
|
| 151 |
+
"1.7 Rel+RABC": { note:"Best of initial training. Base checkpoint for 2.5." },
|
| 152 |
+
"2.1 HQ": { note:"Fine-tunes 1.3 on curated high-quality data only." },
|
| 153 |
+
"2.2 HQ+RABC+Rel": { note:"Adds RABC on high-quality fine-tune from 1.3." },
|
| 154 |
+
"2.3 HQ+mirror": { note:"Augments the high-quality dataset with mirrored trajectories." },
|
| 155 |
+
"2.4 HQ chunk45": { note:"Larger action chunk size (45 vs default 30)." },
|
| 156 |
+
"2.5 HQ+RABC+Rel★": { note:"Top performer. Best overall result." },
|
| 157 |
+
};
|
| 158 |
+
|
| 159 |
+
const GAP = 10;
|
| 160 |
+
const sumCols = ["L1 Time","Quality"];
|
| 161 |
+
|
| 162 |
function render() {
|
| 163 |
svg.selectAll("*").remove();
|
| 164 |
+
const data = getFilteredData();
|
| 165 |
const W = container.clientWidth;
|
| 166 |
+
const totalCols = subtasks.length + sumCols.length;
|
| 167 |
+
const cellW = Math.floor((W - margin.left - margin.right - GAP) / totalCols);
|
| 168 |
const cellH = Math.max(34, Math.min(44, cellW * 0.7));
|
| 169 |
const H = data.length * cellH + margin.top + margin.bottom;
|
| 170 |
svg.attr("width",W).attr("height",H);
|
| 171 |
const g = svg.append("g").attr("transform",`translate(${margin.left},${margin.top})`);
|
| 172 |
const gridW = cellW * subtasks.length;
|
| 173 |
const gridH = cellH * data.length;
|
| 174 |
+
const sumX = gridW + GAP;
|
| 175 |
|
| 176 |
+
// Subtask column labels
|
| 177 |
g.selectAll(".col-lbl").data(subtasks).join("text")
|
| 178 |
.attr("x",(_,i)=>i*cellW+cellW/2).attr("y",gridH+22)
|
| 179 |
+
.attr("text-anchor","middle").attr("fill","#8b8fa8").attr("font-size",13)
|
| 180 |
.text(d=>d);
|
| 181 |
|
| 182 |
+
// Summary column labels (italic, slightly muted)
|
| 183 |
+
sumCols.forEach((label, i) => {
|
| 184 |
+
g.append("text")
|
| 185 |
+
.attr("x", sumX + i * cellW + cellW / 2).attr("y", gridH + 22)
|
| 186 |
+
.attr("text-anchor","middle").attr("fill","#6b6f88").attr("font-size",12)
|
| 187 |
+
.attr("font-style","italic")
|
| 188 |
+
.text(label);
|
| 189 |
+
});
|
| 190 |
+
|
| 191 |
+
// Vertical divider between subtask and summary columns
|
| 192 |
+
g.append("line")
|
| 193 |
+
.attr("x1", gridW + GAP / 2).attr("x2", gridW + GAP / 2)
|
| 194 |
+
.attr("y1", -2).attr("y2", gridH)
|
| 195 |
+
.attr("stroke","#2a2d3a").attr("stroke-width",1);
|
| 196 |
+
|
| 197 |
// Row labels + series stripe
|
| 198 |
data.forEach((d, ri) => {
|
|
|
|
| 199 |
g.append("rect")
|
| 200 |
.attr("x",-margin.left+2).attr("y",ri*cellH+2)
|
| 201 |
.attr("width",4).attr("height",cellH-4).attr("rx",2)
|
|
|
|
| 203 |
|
| 204 |
g.append("text")
|
| 205 |
.attr("x",-8).attr("y",ri*cellH+cellH/2)
|
| 206 |
+
.attr("text-anchor","end").attr("fill","#e8eaf0").attr("font-size",12).attr("font-weight","500")
|
| 207 |
.text(d.label);
|
| 208 |
|
| 209 |
g.append("text")
|
| 210 |
+
.attr("x",-8).attr("y",ri*cellH+cellH/2+12)
|
| 211 |
+
.attr("text-anchor","end").attr("fill","#8b8fa8").attr("font-size",10)
|
| 212 |
.text(d.total_sr+"% SR");
|
| 213 |
});
|
| 214 |
|
| 215 |
+
// Helper: draw a single heatmap cell
|
| 216 |
+
function drawCell(parent, x, y, w, h, val, col, tooltipHtml) {
|
| 217 |
+
const cg = parent.append("g").attr("transform",`translate(${x},${y})`);
|
| 218 |
+
if (val === null) return;
|
| 219 |
+
const lum = d3.hsl(col).l;
|
| 220 |
+
cg.append("rect").attr("width",w-3).attr("height",h-3).attr("rx",4)
|
| 221 |
+
.attr("fill",col).style("cursor","pointer")
|
| 222 |
+
.on("mousemove",function(event){
|
| 223 |
+
tooltip.style("opacity",1).html(tooltipHtml);
|
| 224 |
+
const bx=container.getBoundingClientRect();
|
| 225 |
+
const ex=event.clientX-bx.left, ey=event.clientY-bx.top;
|
| 226 |
+
tooltip.style("left",Math.min(ex+12,W-220)+"px").style("top",Math.max(ey-70,0)+"px");
|
| 227 |
+
})
|
| 228 |
+
.on("mouseleave",()=>tooltip.style("opacity",0));
|
| 229 |
+
const fs = Math.max(10, Math.min(14, w * 0.24));
|
| 230 |
+
cg.append("text")
|
| 231 |
+
.attr("x",w/2-1.5).attr("y",h/2+4)
|
| 232 |
+
.attr("text-anchor","middle")
|
| 233 |
+
.attr("fill", lum > 0.52 ? "#1a1d27" : "#e8eaf0")
|
| 234 |
+
.attr("font-size", fs).attr("font-weight","600")
|
| 235 |
+
.text(typeof val === "string" ? val : (val >= 100 ? Math.round(val)+"s" : val.toFixed(1)+"s"));
|
| 236 |
+
}
|
| 237 |
+
|
| 238 |
+
// Subtask cells
|
| 239 |
data.forEach((d, ri) => {
|
| 240 |
d.times.forEach((val, ci) => {
|
|
|
|
| 241 |
if (val === null) {
|
| 242 |
+
// skip — leave empty
|
|
|
|
|
|
|
|
|
|
| 243 |
} else {
|
| 244 |
+
const ratio = val / subtaskMeans[ci];
|
| 245 |
+
const col = mode === "abs" ? absColor(val) : relColor(ratio);
|
| 246 |
+
const cellLabel = mode === "abs"
|
| 247 |
+
? (val>=100 ? Math.round(val)+"s" : val.toFixed(1)+"s")
|
| 248 |
+
: ratio.toFixed(1)+"×";
|
| 249 |
+
const ttHtml = `
|
| 250 |
+
<strong>Experiment ${d.label}: ${subtasks[ci]}</strong>
|
| 251 |
+
<div class="tooltip-row"><span>Avg time</span><span>${val.toFixed(1)}s</span></div>
|
| 252 |
+
<div class="tooltip-row"><span>Subtask mean</span><span>${subtaskMeans[ci].toFixed(1)}s</span></div>
|
| 253 |
+
<div class="tooltip-row"><span>Relative</span><span>${ratio.toFixed(2)}×</span></div>
|
| 254 |
+
<div class="tooltip-row"><span>Total SR</span><span>${d.total_sr}%</span></div>
|
| 255 |
+
`;
|
| 256 |
+
const cg = g.append("g").attr("transform",`translate(${ci*cellW},${ri*cellH})`);
|
| 257 |
+
const lum = d3.hsl(col).l;
|
| 258 |
+
cg.append("rect").attr("width",cellW-3).attr("height",cellH-3).attr("rx",4)
|
| 259 |
.attr("fill",col).style("cursor","pointer")
|
| 260 |
.on("mousemove",function(event){
|
| 261 |
+
tooltip.style("opacity",1).html(ttHtml);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 262 |
const bx=container.getBoundingClientRect();
|
| 263 |
const ex=event.clientX-bx.left, ey=event.clientY-bx.top;
|
| 264 |
+
tooltip.style("left",Math.min(ex+12,W-220)+"px").style("top",Math.max(ey-70,0)+"px");
|
| 265 |
})
|
| 266 |
.on("mouseleave",()=>tooltip.style("opacity",0));
|
| 267 |
+
const fs = Math.max(10, Math.min(14, cellW * 0.24));
|
| 268 |
+
cg.append("text")
|
|
|
|
| 269 |
.attr("x",cellW/2-1.5).attr("y",cellH/2+4)
|
| 270 |
.attr("text-anchor","middle")
|
| 271 |
.attr("fill", lum > 0.52 ? "#1a1d27" : "#e8eaf0")
|
| 272 |
+
.attr("font-size", fs).attr("font-weight","600")
|
| 273 |
+
.text(cellLabel);
|
| 274 |
}
|
| 275 |
});
|
| 276 |
});
|
| 277 |
|
| 278 |
+
// Summary columns: L1 Time and Quality
|
| 279 |
+
data.forEach((d, ri) => {
|
| 280 |
+
const note = (EXPERIMENTS[d.label] || {}).note || "";
|
| 281 |
+
|
| 282 |
+
// L1 Time cell
|
| 283 |
+
const l1 = d.l1time;
|
| 284 |
+
const l1Col = l1 !== null ? absColor(l1) : null;
|
| 285 |
+
const l1Label = l1 !== null ? (l1 >= 100 ? Math.round(l1) + "s" : l1.toFixed(1) + "s") : null;
|
| 286 |
+
const l1Tt = l1 !== null ? `
|
| 287 |
+
<strong>${d.label}: L1 Completion Time</strong>
|
| 288 |
+
<div class="tooltip-row"><span>L1 time</span><span>${l1.toFixed(1)}s</span></div>
|
| 289 |
+
<div class="tooltip-row"><span>Total SR</span><span>${d.total_sr}%</span></div>
|
| 290 |
+
${note ? `<div style="margin-top:5px;font-size:10px;color:#555;border-top:1px solid #2a2d3a;padding-top:5px">${note}</div>` : ""}
|
| 291 |
+
` : "";
|
| 292 |
+
drawCell(g, sumX, ri * cellH, cellW, cellH, l1 !== null ? l1Label : null, l1Col, l1Tt);
|
| 293 |
+
|
| 294 |
+
// Quality cell
|
| 295 |
+
const q = d.quality;
|
| 296 |
+
const qCol = qualColor(q);
|
| 297 |
+
const qLabel = q.toFixed(1);
|
| 298 |
+
const qTt = `
|
| 299 |
+
<strong>${d.label}: Fold Quality</strong>
|
| 300 |
+
<div class="tooltip-row"><span>Quality</span><span>${q.toFixed(2)} / 5</span></div>
|
| 301 |
+
<div class="tooltip-row"><span>Total SR</span><span>${d.total_sr}%</span></div>
|
| 302 |
+
${note ? `<div style="margin-top:5px;font-size:10px;color:#555;border-top:1px solid #2a2d3a;padding-top:5px">${note}</div>` : ""}
|
| 303 |
+
`;
|
| 304 |
+
drawCell(g, sumX + cellW, ri * cellH, cellW, cellH, qLabel, qCol, qTt);
|
| 305 |
+
});
|
| 306 |
+
|
| 307 |
// Sorted annotation
|
| 308 |
+
const totalW = gridW + GAP + cellW * sumCols.length;
|
| 309 |
+
g.append("text").attr("x",totalW).attr("y",-4).attr("text-anchor","end")
|
| 310 |
+
.attr("fill","#8b8fa8").attr("font-size",11)
|
| 311 |
+
.text("↓ sorted: fastest → slowest L1 time");
|
| 312 |
}
|
| 313 |
|
| 314 |
render();
|
| 315 |
window.addEventListener("resize", render);
|
| 316 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 317 |
}
|
| 318 |
|
| 319 |
if (typeof d3 !== "undefined") {
|
app/src/content/embeds/folding/success-rates.html
CHANGED
|
@@ -13,10 +13,10 @@
|
|
| 13 |
display: flex; align-items: center; justify-content: center;
|
| 14 |
gap: 8px; flex-wrap: wrap; margin-bottom: 10px;
|
| 15 |
}
|
| 16 |
-
.ctrl-label { font-size:
|
| 17 |
.toggle-btn {
|
| 18 |
background: #1a1d27; border: 1.5px solid #2a2d3a; color: #8b8fa8;
|
| 19 |
-
font-size:
|
| 20 |
transition: all .15s; user-select: none; font-weight: 500;
|
| 21 |
}
|
| 22 |
.toggle-btn:hover { border-color: #555; color: #e8eaf0; }
|
|
@@ -26,7 +26,7 @@
|
|
| 26 |
|
| 27 |
/* ── Legend ── */
|
| 28 |
.legend { display: flex; gap: 16px; justify-content: center; flex-wrap: wrap; margin-bottom: 4px; }
|
| 29 |
-
.legend-item { display: flex; align-items: center; gap: 5px; font-size:
|
| 30 |
.legend-item.dimmed { opacity: 0.22; }
|
| 31 |
.legend-dot { width: 11px; height: 11px; border-radius: 2px; }
|
| 32 |
.legend-pip { width: 8px; height: 8px; border-radius: 50%; display: inline-block; }
|
|
@@ -35,7 +35,7 @@
|
|
| 35 |
.ci-note {
|
| 36 |
background: rgba(255,255,255,0.03); border: 1px solid #2a2d3a;
|
| 37 |
border-radius: 8px; padding: 9px 14px; margin-bottom: 10px;
|
| 38 |
-
font-size:
|
| 39 |
display: flex; align-items: flex-start; gap: 10px;
|
| 40 |
}
|
| 41 |
.ci-note-icon { font-size: 14px; flex-shrink: 0; margin-top: 1px; }
|
|
@@ -55,7 +55,7 @@
|
|
| 55 |
.tooltip-note { margin-top: 7px; padding-top: 7px; border-top: 1px solid #2a2d3a; font-size: 11px; color: #8b8fa8; line-height: 1.5; }
|
| 56 |
|
| 57 |
/* ── Chart ── */
|
| 58 |
-
.axis text { fill: var(--subtext); font-size:
|
| 59 |
.axis line, .axis path { stroke: var(--grid); }
|
| 60 |
.grid line { stroke: var(--grid); stroke-dasharray: 3,3; }
|
| 61 |
.bar { cursor: pointer; transition: opacity .12s; }
|
|
@@ -79,8 +79,8 @@
|
|
| 79 |
<div class="legend-item" id="leg-l1"> <div class="legend-dot" style="background:#4dc98a"></div>Level 1</div>
|
| 80 |
<div class="legend-item" id="leg-l2"> <div class="legend-dot" style="background:#f87171"></div>Level 2</div>
|
| 81 |
<div class="legend-item">
|
| 82 |
-
<div class="legend-pip" style="background:#f7934f"></div>
|
| 83 |
-
<div class="legend-pip" style="background:#4f8ef7"></div>
|
| 84 |
</div>
|
| 85 |
</div>
|
| 86 |
|
|
@@ -104,9 +104,9 @@ const EXPERIMENTS = {
|
|
| 104 |
"1.1 π0": { desc:"π0 · all data · 200k steps · MEAN_STD", note:"π0 base model fine-tuned on full dataset. Default normalization." },
|
| 105 |
"1.2 π0.5": { desc:"π0.5 · all data · 200k steps · MEAN_STD", note:"Upgraded to π0.5 with MEAN_STD normalization." },
|
| 106 |
"1.3 Relative": { desc:"π0.5 · all data · 200k steps · Relative Actions · QUANTILES", note:"Adds relative actions and switches to QUANTILES normalization." },
|
| 107 |
-
"1.4 RABC low": { desc:"π0.5 · all data · 200k steps · RABC κ=0.01",
|
| 108 |
-
"1.5 RABC high": { desc:"π0.5 · all data · 200k steps · RABC κ=0.0215",
|
| 109 |
-
"1.7 Rel+RABC": { desc:"π0.5 · all data · 200k steps · Relative Actions + RABC κ=0.0215 · QUANTILES", note:"Best of
|
| 110 |
"2.1 HQ": { desc:"π0.5 · HQ data · 100k steps · fine-tune from 1.3", note:"Fine-tunes 1.3 on curated high-quality data. No RABC." },
|
| 111 |
"2.2 HQ+RABC+Rel": { desc:"π0.5 · HQ data · 100k steps · fine-tune from 1.3 + RABC κ=0.0265 + Relative Actions", note:"Adds RABC on high-quality fine-tune from 1.3." },
|
| 112 |
"2.3 HQ+mirror": { desc:"π0.5 · HQ + mirrored · 100k steps · fine-tune from 1.3 + Relative Actions + mirroring", note:"Augments HQ data with mirrored trajectories." },
|
|
@@ -248,7 +248,7 @@ function render() {
|
|
| 248 |
// Axes
|
| 249 |
g.append("g").attr("class","axis").attr("transform",`translate(0,${h})`).call(
|
| 250 |
d3.axisBottom(x0).tickSize(0))
|
| 251 |
-
.call(gg=>{gg.select(".domain").remove();gg.selectAll("text").attr("transform","rotate(-40)").attr("text-anchor","end").attr("dx","-0.5em").attr("dy","0.3em").attr("font-size",
|
| 252 |
g.append("g").attr("class","axis").call(
|
| 253 |
d3.axisLeft(y).tickValues([0,25,50,75,100]).tickFormat(d=>d+"%").tickSize(0))
|
| 254 |
.call(ax=>ax.select(".domain").remove())
|
|
@@ -349,7 +349,7 @@ function render() {
|
|
| 349 |
const sk = active[sortKey] ? sortKey : ["total","l1","l2"].find(k => active[k]);
|
| 350 |
const skLabel = {total:"total SR", l1:"Level 1 SR", l2:"Level 2 SR"};
|
| 351 |
g.append("text").attr("x",w).attr("y",-12).attr("text-anchor","end")
|
| 352 |
-
.attr("fill","#8b8fa8").attr("font-size",
|
| 353 |
.text(`sorted: best → worst by ${skLabel[sk]}`);
|
| 354 |
}
|
| 355 |
|
|
|
|
| 13 |
display: flex; align-items: center; justify-content: center;
|
| 14 |
gap: 8px; flex-wrap: wrap; margin-bottom: 10px;
|
| 15 |
}
|
| 16 |
+
.ctrl-label { font-size: 13px; color: var(--subtext); }
|
| 17 |
.toggle-btn {
|
| 18 |
background: #1a1d27; border: 1.5px solid #2a2d3a; color: #8b8fa8;
|
| 19 |
+
font-size: 14px; padding: 6px 16px; border-radius: 20px; cursor: pointer;
|
| 20 |
transition: all .15s; user-select: none; font-weight: 500;
|
| 21 |
}
|
| 22 |
.toggle-btn:hover { border-color: #555; color: #e8eaf0; }
|
|
|
|
| 26 |
|
| 27 |
/* ── Legend ── */
|
| 28 |
.legend { display: flex; gap: 16px; justify-content: center; flex-wrap: wrap; margin-bottom: 4px; }
|
| 29 |
+
.legend-item { display: flex; align-items: center; gap: 5px; font-size: 13px; color: var(--subtext); transition: opacity .2s; }
|
| 30 |
.legend-item.dimmed { opacity: 0.22; }
|
| 31 |
.legend-dot { width: 11px; height: 11px; border-radius: 2px; }
|
| 32 |
.legend-pip { width: 8px; height: 8px; border-radius: 50%; display: inline-block; }
|
|
|
|
| 35 |
.ci-note {
|
| 36 |
background: rgba(255,255,255,0.03); border: 1px solid #2a2d3a;
|
| 37 |
border-radius: 8px; padding: 9px 14px; margin-bottom: 10px;
|
| 38 |
+
font-size: 13px; color: #8b8fa8; line-height: 1.6;
|
| 39 |
display: flex; align-items: flex-start; gap: 10px;
|
| 40 |
}
|
| 41 |
.ci-note-icon { font-size: 14px; flex-shrink: 0; margin-top: 1px; }
|
|
|
|
| 55 |
.tooltip-note { margin-top: 7px; padding-top: 7px; border-top: 1px solid #2a2d3a; font-size: 11px; color: #8b8fa8; line-height: 1.5; }
|
| 56 |
|
| 57 |
/* ── Chart ── */
|
| 58 |
+
.axis text { fill: var(--subtext); font-size: 13px; }
|
| 59 |
.axis line, .axis path { stroke: var(--grid); }
|
| 60 |
.grid line { stroke: var(--grid); stroke-dasharray: 3,3; }
|
| 61 |
.bar { cursor: pointer; transition: opacity .12s; }
|
|
|
|
| 79 |
<div class="legend-item" id="leg-l1"> <div class="legend-dot" style="background:#4dc98a"></div>Level 1</div>
|
| 80 |
<div class="legend-item" id="leg-l2"> <div class="legend-dot" style="background:#f87171"></div>Level 2</div>
|
| 81 |
<div class="legend-item">
|
| 82 |
+
<div class="legend-pip" style="background:#f7934f"></div> Fine-tuned (2.x)
|
| 83 |
+
<div class="legend-pip" style="background:#4f8ef7"></div> Initial (1.x)
|
| 84 |
</div>
|
| 85 |
</div>
|
| 86 |
|
|
|
|
| 104 |
"1.1 π0": { desc:"π0 · all data · 200k steps · MEAN_STD", note:"π0 base model fine-tuned on full dataset. Default normalization." },
|
| 105 |
"1.2 π0.5": { desc:"π0.5 · all data · 200k steps · MEAN_STD", note:"Upgraded to π0.5 with MEAN_STD normalization." },
|
| 106 |
"1.3 Relative": { desc:"π0.5 · all data · 200k steps · Relative Actions · QUANTILES", note:"Adds relative actions and switches to QUANTILES normalization." },
|
| 107 |
+
"1.4 RABC low": { desc:"π0.5 · all data · 200k steps · RABC κ=0.01", note:"RABC with low κ, not very selective. MEAN_STD norm, no relative actions." },
|
| 108 |
+
"1.5 RABC high": { desc:"π0.5 · all data · 200k steps · RABC κ=0.0215", note:"RABC with higher κ (mean + ½σ). MEAN_STD norm, no relative actions." },
|
| 109 |
+
"1.7 Rel+RABC": { desc:"π0.5 · all data · 200k steps · Relative Actions + RABC κ=0.0215 · QUANTILES", note:"Best of initial training. Base checkpoint for 2.5." },
|
| 110 |
"2.1 HQ": { desc:"π0.5 · HQ data · 100k steps · fine-tune from 1.3", note:"Fine-tunes 1.3 on curated high-quality data. No RABC." },
|
| 111 |
"2.2 HQ+RABC+Rel": { desc:"π0.5 · HQ data · 100k steps · fine-tune from 1.3 + RABC κ=0.0265 + Relative Actions", note:"Adds RABC on high-quality fine-tune from 1.3." },
|
| 112 |
"2.3 HQ+mirror": { desc:"π0.5 · HQ + mirrored · 100k steps · fine-tune from 1.3 + Relative Actions + mirroring", note:"Augments HQ data with mirrored trajectories." },
|
|
|
|
| 248 |
// Axes
|
| 249 |
g.append("g").attr("class","axis").attr("transform",`translate(0,${h})`).call(
|
| 250 |
d3.axisBottom(x0).tickSize(0))
|
| 251 |
+
.call(gg=>{gg.select(".domain").remove();gg.selectAll("text").attr("transform","rotate(-40)").attr("text-anchor","end").attr("dx","-0.5em").attr("dy","0.3em").attr("font-size",11)});
|
| 252 |
g.append("g").attr("class","axis").call(
|
| 253 |
d3.axisLeft(y).tickValues([0,25,50,75,100]).tickFormat(d=>d+"%").tickSize(0))
|
| 254 |
.call(ax=>ax.select(".domain").remove())
|
|
|
|
| 349 |
const sk = active[sortKey] ? sortKey : ["total","l1","l2"].find(k => active[k]);
|
| 350 |
const skLabel = {total:"total SR", l1:"Level 1 SR", l2:"Level 2 SR"};
|
| 351 |
g.append("text").attr("x",w).attr("y",-12).attr("text-anchor","end")
|
| 352 |
+
.attr("fill","#8b8fa8").attr("font-size",12)
|
| 353 |
.text(`sorted: best → worst by ${skLabel[sk]}`);
|
| 354 |
}
|
| 355 |
|
app/src/content/embeds/folding/total-score.html
CHANGED
|
@@ -8,7 +8,7 @@
|
|
| 8 |
* { box-sizing: border-box; margin: 0; padding: 0; }
|
| 9 |
body { background: var(--bg); font-family: system-ui, sans-serif; color: var(--text); }
|
| 10 |
|
| 11 |
-
.axis text { fill: var(--subtext); font-size:
|
| 12 |
.axis line, .axis path { stroke: var(--grid); }
|
| 13 |
.grid line { stroke: var(--grid); stroke-dasharray: 3,3; }
|
| 14 |
.tooltip {
|
|
@@ -76,11 +76,11 @@ function render() {
|
|
| 76 |
// 50% reference line
|
| 77 |
g.append("line").attr("stroke","#fbbf24").attr("stroke-dasharray","5,3").attr("stroke-width",1.5).attr("opacity",0.6)
|
| 78 |
.attr("x1",0).attr("x2",w).attr("y1",y(50)).attr("y2",y(50));
|
| 79 |
-
g.append("text").attr("x",w+3).attr("y",y(50)+4).attr("fill","#fbbf24").attr("font-size",
|
| 80 |
|
| 81 |
g.append("g").attr("class","axis").attr("transform",`translate(0,${h})`).call(
|
| 82 |
d3.axisBottom(x).tickSize(0))
|
| 83 |
-
.call(gg=>{gg.select(".domain").remove();gg.selectAll("text").attr("transform","rotate(-40)").attr("text-anchor","end").attr("dx","-0.5em").attr("dy","0.3em").attr("font-size",
|
| 84 |
g.append("g").attr("class","axis").call(
|
| 85 |
d3.axisLeft(y).ticks(5).tickFormat(d=>d+"%").tickSize(0))
|
| 86 |
.call(ax=>ax.select(".domain").remove())
|
|
@@ -144,11 +144,11 @@ function render() {
|
|
| 144 |
g.append("line").attr("x1",bx).attr("x2",bx).attr("y1",by-16).attr("y2",-8)
|
| 145 |
.attr("stroke","#4dc98a").attr("stroke-width",1).attr("stroke-dasharray","2,2").attr("opacity",0.5);
|
| 146 |
g.append("text").attr("x",bx).attr("y",-12).attr("text-anchor","middle")
|
| 147 |
-
.attr("fill","#4dc98a").attr("font-size",
|
| 148 |
}
|
| 149 |
|
| 150 |
g.append("text").attr("x",w).attr("y",-12).attr("text-anchor","end")
|
| 151 |
-
.attr("fill","#8b8fa8").attr("font-size",
|
| 152 |
.text("sorted: highest → lowest score %");
|
| 153 |
}
|
| 154 |
|
|
@@ -158,10 +158,10 @@ window.addEventListener("resize", render);
|
|
| 158 |
const EXPERIMENTS = {
|
| 159 |
"1.1 π0": { desc:"π0 · all data · 200k steps · MEAN_STD", note:"Base pi0 policy trained from scratch on the full dataset." },
|
| 160 |
"1.2 π0.5": { desc:"π0.5 · all data · 200k steps · MEAN_STD", note:"Upgraded to pi0.5 architecture, same data and steps." },
|
| 161 |
-
"1.3 Relative": { desc:"π0.5 · all data · 200k steps · Relative Actions · QUANTILES", note:"Adds Relative Actions on top of 1.2
|
| 162 |
"1.4 RABC low": { desc:"π0.5 · all data · 200k steps · RABC κ=0.01", note:"Selective Action Reward Model with low κ (≈ mean threshold, not very selective)." },
|
| 163 |
-
"1.5 RABC high": { desc:"π0.5 · all data · 200k steps · RABC κ=0.0215", note:"SARM with κ = mean + ½ std
|
| 164 |
-
"1.7 Rel+RABC": { desc:"π0.5 · all data · 200k steps · Relative Actions + RABC κ=0.0215 · QUANTILES", note:"Best of
|
| 165 |
"2.1 HQ": { desc:"π0.5 · HQ data · 100k steps · fine-tune from 1.3", note:"Fine-tunes 1.3 on curated high-quality data only." },
|
| 166 |
"2.2 HQ+RABC+Rel": { desc:"π0.5 · HQ data · 100k steps · fine-tune from 1.3 + RABC κ=0.0265 + Relative Actions", note:"Adds RABC on high-quality fine-tune from 1.3." },
|
| 167 |
"2.3 HQ+mirror": { desc:"π0.5 · HQ + mirrored · 100k steps · fine-tune from 1.3 + Relative Actions + mirroring", note:"Augments the high-quality dataset with mirrored trajectories." },
|
|
|
|
| 8 |
* { box-sizing: border-box; margin: 0; padding: 0; }
|
| 9 |
body { background: var(--bg); font-family: system-ui, sans-serif; color: var(--text); }
|
| 10 |
|
| 11 |
+
.axis text { fill: var(--subtext); font-size: 13px; }
|
| 12 |
.axis line, .axis path { stroke: var(--grid); }
|
| 13 |
.grid line { stroke: var(--grid); stroke-dasharray: 3,3; }
|
| 14 |
.tooltip {
|
|
|
|
| 76 |
// 50% reference line
|
| 77 |
g.append("line").attr("stroke","#fbbf24").attr("stroke-dasharray","5,3").attr("stroke-width",1.5).attr("opacity",0.6)
|
| 78 |
.attr("x1",0).attr("x2",w).attr("y1",y(50)).attr("y2",y(50));
|
| 79 |
+
g.append("text").attr("x",w+3).attr("y",y(50)+4).attr("fill","#fbbf24").attr("font-size",11).text("50%");
|
| 80 |
|
| 81 |
g.append("g").attr("class","axis").attr("transform",`translate(0,${h})`).call(
|
| 82 |
d3.axisBottom(x).tickSize(0))
|
| 83 |
+
.call(gg=>{gg.select(".domain").remove();gg.selectAll("text").attr("transform","rotate(-40)").attr("text-anchor","end").attr("dx","-0.5em").attr("dy","0.3em").attr("font-size",11)});
|
| 84 |
g.append("g").attr("class","axis").call(
|
| 85 |
d3.axisLeft(y).ticks(5).tickFormat(d=>d+"%").tickSize(0))
|
| 86 |
.call(ax=>ax.select(".domain").remove())
|
|
|
|
| 144 |
g.append("line").attr("x1",bx).attr("x2",bx).attr("y1",by-16).attr("y2",-8)
|
| 145 |
.attr("stroke","#4dc98a").attr("stroke-width",1).attr("stroke-dasharray","2,2").attr("opacity",0.5);
|
| 146 |
g.append("text").attr("x",bx).attr("y",-12).attr("text-anchor","middle")
|
| 147 |
+
.attr("fill","#4dc98a").attr("font-size",11).attr("font-weight","600").text("★ best");
|
| 148 |
}
|
| 149 |
|
| 150 |
g.append("text").attr("x",w).attr("y",-12).attr("text-anchor","end")
|
| 151 |
+
.attr("fill","#8b8fa8").attr("font-size",12)
|
| 152 |
.text("sorted: highest → lowest score %");
|
| 153 |
}
|
| 154 |
|
|
|
|
| 158 |
const EXPERIMENTS = {
|
| 159 |
"1.1 π0": { desc:"π0 · all data · 200k steps · MEAN_STD", note:"Base pi0 policy trained from scratch on the full dataset." },
|
| 160 |
"1.2 π0.5": { desc:"π0.5 · all data · 200k steps · MEAN_STD", note:"Upgraded to pi0.5 architecture, same data and steps." },
|
| 161 |
+
"1.3 Relative": { desc:"π0.5 · all data · 200k steps · Relative Actions · QUANTILES", note:"Adds Relative Actions on top of 1.2, expressing actions relative to current state." },
|
| 162 |
"1.4 RABC low": { desc:"π0.5 · all data · 200k steps · RABC κ=0.01", note:"Selective Action Reward Model with low κ (≈ mean threshold, not very selective)." },
|
| 163 |
+
"1.5 RABC high": { desc:"π0.5 · all data · 200k steps · RABC κ=0.0215", note:"SARM with κ = mean + ½ std, more selective filtering than 1.4." },
|
| 164 |
+
"1.7 Rel+RABC": { desc:"π0.5 · all data · 200k steps · Relative Actions + RABC κ=0.0215 · QUANTILES", note:"Best of initial training. Base checkpoint for 2.5." },
|
| 165 |
"2.1 HQ": { desc:"π0.5 · HQ data · 100k steps · fine-tune from 1.3", note:"Fine-tunes 1.3 on curated high-quality data only." },
|
| 166 |
"2.2 HQ+RABC+Rel": { desc:"π0.5 · HQ data · 100k steps · fine-tune from 1.3 + RABC κ=0.0265 + Relative Actions", note:"Adds RABC on high-quality fine-tune from 1.3." },
|
| 167 |
"2.3 HQ+mirror": { desc:"π0.5 · HQ + mirrored · 100k steps · fine-tune from 1.3 + Relative Actions + mirroring", note:"Augments the high-quality dataset with mirrored trajectories." },
|
compute_cld.py
ADDED
|
@@ -0,0 +1,156 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Compute CLD (Compact Letter Display) letters and verify Beta posteriors
|
| 3 |
+
for all experiments using the TRI STEP sequential testing framework.
|
| 4 |
+
|
| 5 |
+
Parameters:
|
| 6 |
+
- global_confidence_level = 0.90 (α=0.10)
|
| 7 |
+
- n_max = 50
|
| 8 |
+
- shuffle = False (each rollout is independent)
|
| 9 |
+
- 11 experiments → C(11,2) = 55 pairwise comparisons → Bonferroni correction
|
| 10 |
+
"""
|
| 11 |
+
|
| 12 |
+
import json
|
| 13 |
+
import numpy as np
|
| 14 |
+
from scipy import stats
|
| 15 |
+
from sequentialized_barnard_tests.tools.plotting import compare_success_and_get_cld
|
| 16 |
+
|
| 17 |
+
EXPERIMENTS = {
|
| 18 |
+
"1.1 π0": {"total": [8, 20], "L1": [8, 10], "L2": [0, 10]},
|
| 19 |
+
"1.2 π0.5": {"total": [4, 20], "L1": [4, 10], "L2": [0, 10]},
|
| 20 |
+
"1.3 Relative": {"total": [7, 20], "L1": [7, 10], "L2": [0, 10]},
|
| 21 |
+
"1.4 RABC low": {"total": [3, 20], "L1": [3, 10], "L2": [0, 10]},
|
| 22 |
+
"1.5 RABC high": {"total": [0, 20], "L1": [0, 10], "L2": [0, 10]},
|
| 23 |
+
"1.7 Rel+RABC": {"total": [8, 20], "L1": [8, 10], "L2": [0, 10]},
|
| 24 |
+
"2.1 HQ": {"total": [8, 20], "L1": [7, 10], "L2": [1, 10]},
|
| 25 |
+
"2.2 HQ+RABC+Rel": {"total": [15, 20], "L1": [10, 10], "L2": [5, 10]},
|
| 26 |
+
"2.3 HQ+mirror": {"total": [1, 20], "L1": [0, 10], "L2": [1, 10]},
|
| 27 |
+
"2.4 HQ chunk45": {"total": [4, 20], "L1": [4, 10], "L2": [0, 10]},
|
| 28 |
+
"2.5 HQ+RABC+Rel★": {"total": [18, 20], "L1": [10, 10], "L2": [8, 10]},
|
| 29 |
+
}
|
| 30 |
+
|
| 31 |
+
# What the HTML files use (percentages) — for cross-checking the round-trip
|
| 32 |
+
HTML_RAW_PCT = {
|
| 33 |
+
"1.1 π0": {"total": 40, "l1": 80, "l2": 0},
|
| 34 |
+
"1.2 π0.5": {"total": 20, "l1": 40, "l2": 0},
|
| 35 |
+
"1.3 Relative": {"total": 35, "l1": 70, "l2": 0},
|
| 36 |
+
"1.4 RABC low": {"total": 15, "l1": 30, "l2": 0},
|
| 37 |
+
"1.5 RABC high": {"total": 0, "l1": 0, "l2": 0},
|
| 38 |
+
"1.7 Rel+RABC": {"total": 40, "l1": 80, "l2": 0},
|
| 39 |
+
"2.1 HQ": {"total": 40, "l1": 70, "l2": 10},
|
| 40 |
+
"2.2 HQ+RABC+Rel": {"total": 75, "l1": 100, "l2": 50},
|
| 41 |
+
"2.3 HQ+mirror": {"total": 5, "l1": 0, "l2": 10},
|
| 42 |
+
"2.4 HQ chunk45": {"total": 20, "l1": 40, "l2": 0},
|
| 43 |
+
"2.5 HQ+RABC+Rel★": {"total": 90, "l1": 100, "l2": 80},
|
| 44 |
+
}
|
| 45 |
+
|
| 46 |
+
HTML_N = {"total": 20, "l1": 10, "l2": 10}
|
| 47 |
+
|
| 48 |
+
GLOBAL_CONFIDENCE = 0.90
|
| 49 |
+
N_MAX = 50
|
| 50 |
+
SHUFFLE = False
|
| 51 |
+
|
| 52 |
+
model_names = list(EXPERIMENTS.keys())
|
| 53 |
+
|
| 54 |
+
|
| 55 |
+
def draw_samples_from_beta_posterior(
|
| 56 |
+
success_array: np.ndarray,
|
| 57 |
+
rng: np.random.Generator,
|
| 58 |
+
num_samples: int = 10000,
|
| 59 |
+
alpha_prior: float = 1,
|
| 60 |
+
beta_prior: float = 1,
|
| 61 |
+
) -> np.ndarray:
|
| 62 |
+
"""TRI's exact function from their notebook."""
|
| 63 |
+
n_trials = len(success_array)
|
| 64 |
+
n_successes = np.sum(success_array)
|
| 65 |
+
n_failures = n_trials - n_successes
|
| 66 |
+
posterior = stats.beta(alpha_prior + n_successes, beta_prior + n_failures)
|
| 67 |
+
return posterior.rvs(num_samples, random_state=rng)
|
| 68 |
+
|
| 69 |
+
|
| 70 |
+
# ── 1. CLD letters ──────────────────────────────────────────────────────────
|
| 71 |
+
for level in ["total", "L1", "L2"]:
|
| 72 |
+
print(f"\n{'='*60}")
|
| 73 |
+
print(f" CLD — LEVEL: {level}")
|
| 74 |
+
print(f"{'='*60}")
|
| 75 |
+
|
| 76 |
+
success_arrays = []
|
| 77 |
+
for name in model_names:
|
| 78 |
+
k, n = EXPERIMENTS[name][level]
|
| 79 |
+
arr = np.array([True] * k + [False] * (n - k))
|
| 80 |
+
success_arrays.append(arr)
|
| 81 |
+
|
| 82 |
+
cld_dict = compare_success_and_get_cld(
|
| 83 |
+
model_names,
|
| 84 |
+
success_arrays,
|
| 85 |
+
GLOBAL_CONFIDENCE,
|
| 86 |
+
N_MAX,
|
| 87 |
+
SHUFFLE,
|
| 88 |
+
verbose=True,
|
| 89 |
+
)
|
| 90 |
+
|
| 91 |
+
print(f"\nJSON for HTML embed:")
|
| 92 |
+
json_obj = {name: cld_dict[name] for name in model_names}
|
| 93 |
+
print(json.dumps(json_obj, ensure_ascii=False))
|
| 94 |
+
|
| 95 |
+
|
| 96 |
+
# ── 2. Verify Beta posteriors ────────────────────────────────────────────────
|
| 97 |
+
print(f"\n\n{'#'*70}")
|
| 98 |
+
print(f" POSTERIOR VERIFICATION")
|
| 99 |
+
print(f" Prior: Beta(1,1) (uniform). Posterior: Beta(1+k, 1+n-k)")
|
| 100 |
+
print(f"{'#'*70}")
|
| 101 |
+
|
| 102 |
+
level_map = {"total": "total", "L1": "l1", "L2": "l2"}
|
| 103 |
+
rng = np.random.default_rng(42)
|
| 104 |
+
all_ok = True
|
| 105 |
+
|
| 106 |
+
for level in ["total", "L1", "L2"]:
|
| 107 |
+
html_key = level_map[level]
|
| 108 |
+
print(f"\n{'─'*60}")
|
| 109 |
+
print(f" Level: {level} (HTML key: '{html_key}', n={HTML_N[html_key]})")
|
| 110 |
+
print(f"{'─'*60}")
|
| 111 |
+
print(f" {'Experiment':<24s} {'k/n':>6s} {'α':>4s} {'β':>4s} {'Mean':>7s} {'90% CI':>16s} {'HTML%→k':>8s} {'Match':>5s}")
|
| 112 |
+
|
| 113 |
+
for name in model_names:
|
| 114 |
+
k, n = EXPERIMENTS[name][level]
|
| 115 |
+
alpha_post = 1 + k
|
| 116 |
+
beta_post = 1 + (n - k)
|
| 117 |
+
dist = stats.beta(alpha_post, beta_post)
|
| 118 |
+
mean = dist.mean()
|
| 119 |
+
ci_lo, ci_hi = dist.ppf(0.05), dist.ppf(0.95)
|
| 120 |
+
|
| 121 |
+
# Verify HTML percentage round-trip
|
| 122 |
+
html_pct = HTML_RAW_PCT[name][html_key]
|
| 123 |
+
html_n = HTML_N[html_key]
|
| 124 |
+
html_k = round(html_pct / 100 * html_n)
|
| 125 |
+
match = html_k == k
|
| 126 |
+
|
| 127 |
+
if not match:
|
| 128 |
+
all_ok = False
|
| 129 |
+
|
| 130 |
+
print(
|
| 131 |
+
f" {name:<24s} {k:>2d}/{n:<2d} {alpha_post:>4d} {beta_post:>4d} "
|
| 132 |
+
f"{mean*100:>6.1f}% [{ci_lo*100:>5.1f}% – {ci_hi*100:>5.1f}%] "
|
| 133 |
+
f"{html_pct}%→{html_k:>2d} {'✓' if match else '✗ MISMATCH'}"
|
| 134 |
+
)
|
| 135 |
+
|
| 136 |
+
# Also run TRI's draw_samples_from_beta_posterior for a spot-check
|
| 137 |
+
spot_name = "2.5 HQ+RABC+Rel★"
|
| 138 |
+
k_spot, n_spot = EXPERIMENTS[spot_name][level]
|
| 139 |
+
arr_spot = np.array([True] * k_spot + [False] * (n_spot - k_spot))
|
| 140 |
+
samples = draw_samples_from_beta_posterior(arr_spot, rng, num_samples=100_000)
|
| 141 |
+
print(f"\n Spot-check ({spot_name}, {level}):")
|
| 142 |
+
print(f" TRI samples: mean={np.mean(samples)*100:.1f}%, std={np.std(samples)*100:.1f}%")
|
| 143 |
+
alpha_s, beta_s = 1 + k_spot, 1 + (n_spot - k_spot)
|
| 144 |
+
analytic = stats.beta(alpha_s, beta_s)
|
| 145 |
+
print(f" Analytic: mean={analytic.mean()*100:.1f}%, std={analytic.std()*100:.1f}%")
|
| 146 |
+
print(f" HTML params: Beta({alpha_s}, {beta_s})")
|
| 147 |
+
|
| 148 |
+
print(f"\n{'='*60}")
|
| 149 |
+
if all_ok:
|
| 150 |
+
print(" ALL POSTERIORS VERIFIED ✓")
|
| 151 |
+
print(" - Beta(1+k, 1+n-k) with uniform prior Beta(1,1)")
|
| 152 |
+
print(" - HTML percentage→k round-trip: all match")
|
| 153 |
+
print(" - Matches TRI draw_samples_from_beta_posterior()")
|
| 154 |
+
else:
|
| 155 |
+
print(" ✗ SOME MISMATCHES FOUND — see above")
|
| 156 |
+
print(f"{'='*60}")
|
export_wandb_losses.py
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import wandb
|
| 2 |
+
import json
|
| 3 |
+
|
| 4 |
+
wandb.login()
|
| 5 |
+
|
| 6 |
+
api = wandb.Api()
|
| 7 |
+
project = "pepijn1999kooijmans-open-universiteit/shirt-folding"
|
| 8 |
+
|
| 9 |
+
TARGET_RUNS = [
|
| 10 |
+
"ablation2-4_100k_q",
|
| 11 |
+
"ablation2-5_0",
|
| 12 |
+
"ablation2-1_100k_q",
|
| 13 |
+
"ablation2-2_100k",
|
| 14 |
+
"ablation2-3_100k_q_it",
|
| 15 |
+
"ablation1-7_2",
|
| 16 |
+
"ablation1-5_9",
|
| 17 |
+
"ablation1_3_17_q",
|
| 18 |
+
"ablation1_2_2",
|
| 19 |
+
"ablation1_1_2",
|
| 20 |
+
"ablation1-4",
|
| 21 |
+
]
|
| 22 |
+
|
| 23 |
+
runs = api.runs(project)
|
| 24 |
+
print(f"Found {len(runs)} total runs in project\n")
|
| 25 |
+
|
| 26 |
+
all_data = {}
|
| 27 |
+
for run in runs:
|
| 28 |
+
if run.name not in TARGET_RUNS:
|
| 29 |
+
continue
|
| 30 |
+
print(f"Pulling: {run.name} ({run.id})...")
|
| 31 |
+
try:
|
| 32 |
+
df = run.history(samples=2000, pandas=True)
|
| 33 |
+
except Exception as e:
|
| 34 |
+
print(f" Error: {e}")
|
| 35 |
+
continue
|
| 36 |
+
|
| 37 |
+
loss_keys = [c for c in df.columns if "loss" in c.lower()]
|
| 38 |
+
if not loss_keys:
|
| 39 |
+
print(f" No loss keys. Available: {df.columns.tolist()}")
|
| 40 |
+
continue
|
| 41 |
+
|
| 42 |
+
lk = loss_keys[0]
|
| 43 |
+
print(f" Using key: {lk}")
|
| 44 |
+
points = []
|
| 45 |
+
for _, row in df.iterrows():
|
| 46 |
+
v = row.get(lk)
|
| 47 |
+
if v is not None and str(v) != "nan":
|
| 48 |
+
points.append({"step": int(row["_step"]), "loss": float(v)})
|
| 49 |
+
|
| 50 |
+
all_data[run.name] = {"id": run.id, "name": run.name, "points": points}
|
| 51 |
+
print(f" Got {len(points)} points")
|
| 52 |
+
|
| 53 |
+
found = set(all_data.keys())
|
| 54 |
+
missing = [r for r in TARGET_RUNS if r not in found]
|
| 55 |
+
if missing:
|
| 56 |
+
print(f"\nWARNING: Could not find runs: {missing}")
|
| 57 |
+
|
| 58 |
+
out = "loss_curves.json"
|
| 59 |
+
with open(out, "w") as f:
|
| 60 |
+
json.dump(all_data, f, indent=2)
|
| 61 |
+
|
| 62 |
+
print(f"\nDone! Saved {len(all_data)} runs to {out}")
|
| 63 |
+
print(f"\nNow paste the contents of {out} into the RAW_DATA variable in:")
|
| 64 |
+
print(f" app/src/content/embeds/folding/loss-curves.html")
|
loss_curves.json
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:e5c135761e5e9a42b01e47bcbd5ee4eb50827717fbebce203be2b9e04f8866e7
|
| 3 |
+
size 76562
|