Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
84de711
feat: add ROSCon 2026 workshop courses
skalade Jul 30, 2026
e802ae3
fix: enable MolmoAct2 training
skalade Aug 3, 2026
1ddd60d
add latest local inference updates from Joao
skalade Aug 3, 2026
680795a
update catalog names
skalade Aug 3, 2026
af94805
Add in-notebook manipulation demo and harden headless runtime
joaoluiz-godoy Aug 7, 2026
efa9c53
Explain the RAI stack in the robot agents notebook
joaoluiz-godoy Aug 11, 2026
e5ccbff
Merge pull request #1 from skalade/lemonade_and_rai_notebooks
skalade Aug 11, 2026
8342392
Finetuning interactive sim (#3)
ClockWorkKid Aug 13, 2026
a67360a
harcode assets path, simplify readme
skalade Aug 13, 2026
31785a9
Merge upstream/develop into roscon26
skalade Aug 13, 2026
1b2ce76
add placeholders for Mehdi
skalade Aug 13, 2026
2ef797c
Fastwam inference notebook (#4)
ClockWorkKid Aug 15, 2026
1077d8b
add helpers for bulk copy to igpu
skalade Aug 17, 2026
b2e7792
Add the CaP-X code-as-policies notebook
joaoluiz-godoy Aug 17, 2026
beb1d5a
Removed part that suggested users to open new port for Lemonade's int…
joaoluiz-godoy Aug 17, 2026
7d5b5a5
Merge origin/lemonade_and_rai_notebooks into roscon26
skalade Aug 18, 2026
d04b95a
Add PandaPickCube inference demo to RLLearning.
Aug 18, 2026
0eee025
Use Dockerfile-only env setup for RLLearning demo.
Aug 18, 2026
79323fb
improve wording and explainations
Aug 18, 2026
363268c
Merge pull request #5 from noamonti748/roscon26
skalade Aug 18, 2026
8b75ffd
fix: install RL Learning deps as root with python3
Aug 18, 2026
d4cfcb5
Merge pull request #6 from noamonti748/roscon26
skalade Aug 18, 2026
0240f9f
Add bounded RHO workshop follow-up
skalade Aug 20, 2026
c4af5d7
fix(rl-learning): bake menagerie and quiet demo warnings
Aug 20, 2026
bd08e1b
Merge pull request #7 from noamonti748/roscon26
skalade Aug 20, 2026
288b488
Pin gpt-oss GGUF and quiet the CaP-X notebook
joaoluiz-godoy Aug 20, 2026
bc486d2
Fix HELIX progress rendering in notebooks
skalade Aug 21, 2026
792e675
Merge remote-tracking branch 'origin/lemonade_and_rai_notebooks' into…
skalade Aug 21, 2026
dcdf8e0
Add the 20260807 PandaPickCube checkpoint next to the existing 150103…
Aug 21, 2026
d60d429
Polish RLLearning demo notebook for ROSCon checkpoint progression.
Aug 21, 2026
1a3e42e
Optimize the LocalInference RHO workshop
skalade Aug 23, 2026
89d4223
Polish the RHO workshop for faster guided runs
skalade Aug 23, 2026
06f5ac5
Document Gemma task sweep results
skalade Aug 23, 2026
4a799bd
Expand the multi-task RHO workshop
skalade Aug 24, 2026
d49a136
Merge branch 'rho-workshop-follow-up' into roscon26
skalade Aug 24, 2026
77668a6
Merge pull request #8 from noamonti748/roscon26
skalade Aug 24, 2026
79cc955
Consolidate LocalInference scripts and shrink the course image
skalade Aug 25, 2026
ae77b94
Reduce the multi-task RHO fixtures to cube stack and spill wipe
skalade Aug 25, 2026
1d7162d
Merge remote-tracking branch 'origin/roscon26' into roscon26
skalade Aug 25, 2026
2a397cb
Lemonade notebook feedback implemented
joaoluiz-godoy Aug 25, 2026
130e1d0
Added graphic to Lemonade notebook
joaoluiz-godoy Aug 25, 2026
6398b92
Implemented feedback for Rai notebook
joaoluiz-godoy Aug 26, 2026
9a7d7cb
Minimal changes to lemonade notebook
joaoluiz-godoy Aug 28, 2026
6105554
Applied revision to four subsections of the RAI notebook
joaoluiz-godoy Aug 28, 2026
0462791
Fix to ros2 action command, removed --no-daemon --spin-time 3
joaoluiz-godoy Sep 2, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/build-config.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,5 @@
{"name": "gfx120x", "pytorch_whl": "gfx120X-all", "rocm_sdk": "gfx120x"}
],
"default_gpu_target": "gfx1151",
"courses": ["CV", "DL", "LLM", "PhySim"]
"courses": ["CV", "DL", "LLM", "PhySim", "Finetuning", "LocalInference"]
}
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -388,6 +388,7 @@ vite.config.ts.timestamp-*
# Project specific
dockerfiles/Courses/DL/data/FashionMNIST/raw/
*.vsix
projects/LocalInference/sweep_results/

# Local config overrides
runtime/values.local.yaml
Expand Down
51 changes: 49 additions & 2 deletions auplc_installer/catalog.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,27 @@ class Course:
Course("Course-DL", "auplc-dl", True, "dl", "Deep Learning Course"),
Course("Course-LLM", "auplc-llm", True, "llm", "Large Language Model Course"),
Course("Course-PhySim", "auplc-physim", True, "physim", "Physical Simulation Course"),
Course(
"Course-Finetuning",
"auplc-finetuning",
True,
"finetuning",
"Fine-tuning on GPUs: from cloud to robot",
),
Course(
"Course-LocalInference",
"auplc-localinference",
True,
"local-inference",
"Local inference of embodied AI",
),
Course(
"Course-RLLearning",
"auplc-rl-learning",
True,
"rl-learning",
"Reinforcement learning for robotics",
),
)

COURSE_KEYS_ALL: tuple[str, ...] = tuple(c.key for c in COURSE_CATALOG)
Expand Down Expand Up @@ -81,7 +102,16 @@ class Course:

BASE_TEAM_MAPPING: dict[str, list[str]] = {
"cpu": ["cpu", "code-cpu"],
"gpu": ["code-gpu", "Course-CV", "Course-DL", "Course-LLM", "Course-PhySim"],
"gpu": [
"code-gpu",
"Course-CV",
"Course-DL",
"Course-LLM",
"Course-PhySim",
"Course-Finetuning",
"Course-LocalInference",
"Course-RLLearning",
],
"official": [
"cpu",
"gpu",
Expand All @@ -91,15 +121,29 @@ class Course:
"Course-DL",
"Course-LLM",
"Course-PhySim",
"Course-Finetuning",
"Course-LocalInference",
"Course-RLLearning",
],
"AUP": [
"Course-CV",
"Course-DL",
"Course-LLM",
"Course-PhySim",
"Course-Finetuning",
"Course-LocalInference",
"Course-RLLearning",
],
"AUP": ["Course-CV", "Course-DL", "Course-LLM", "Course-PhySim"],
"native-users": [
"code-cpu",
"code-gpu",
"Course-CV",
"Course-DL",
"Course-LLM",
"Course-PhySim",
"Course-Finetuning",
"Course-LocalInference",
"Course-RLLearning",
"cpu",
"gpu",
],
Expand All @@ -112,6 +156,9 @@ class Course:
"Course-DL",
"Course-LLM",
"Course-PhySim",
"Course-Finetuning",
"Course-LocalInference",
"Course-RLLearning",
],
}

Expand Down
3 changes: 3 additions & 0 deletions auplc_installer/overlay.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,9 @@
"Course-DL": "auplc-dl",
"Course-LLM": "auplc-llm",
"Course-PhySim": "auplc-physim",
"Course-Finetuning": "auplc-finetuning",
"Course-LocalInference": "auplc-localinference",
"Course-RLLearning": "auplc-rl-learning",
}


Expand Down
281 changes: 281 additions & 0 deletions dockerfiles/Courses/Finetuning/Dockerfile

Large diffs are not rendered by default.

43 changes: 43 additions & 0 deletions dockerfiles/Courses/Finetuning/build.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
#!/usr/bin/env bash
# Copyright (C) 2026 Advanced Micro Devices, Inc. All rights reserved.
# SPDX-License-Identifier: MIT

set -euo pipefail

# Hardcoded workshop asset bundle location: drop mm2_workshop_assets.zip into projects/Finetuning/
# (next to the notebooks) and it gets baked into the image. Nothing to configure.
PROJECT_DIR="../../../projects/Finetuning"
ASSETS_ZIP="${PROJECT_DIR}/mm2_workshop_assets.zip"

WORK_ASSETS=""
cleanup() {
rm -rf course_data
[ -n "${WORK_ASSETS:-}" ] && rm -rf "${WORK_ASSETS}"
return 0
}
trap cleanup EXIT

# Stage the course notebooks + helper scripts into the build context, EXCLUDING the large asset
# bundle so it is never baked into /ryzers/notebooks.
rm -rf course_data
mkdir -p course_data
cp -r "$PROJECT_DIR"/. course_data/
rm -rf course_data/mm2_workshop_assets course_data/mm2_workshop_assets.zip

# Bake the workshop assets if the bundle is present; otherwise build a code-only image (CI/registry).
BUILD_EXTRA=()
if [ -f "${ASSETS_ZIP}" ]; then
echo "unpacking mm2_workshop_assets.zip (one-time; the bundle is large, this can take a while)..."
# Unpack OUTSIDE the docker build context (extracting here would bloat the context sent to the
# daemon). /var/tmp is disk-backed and typically large enough for the ~16 GB bundle.
WORK_ASSETS="$(mktemp -d "${TMPDIR:-/var/tmp}/auplc-ft-assets.XXXXXX")"
unzip -q "${ASSETS_ZIP}" -d "${WORK_ASSETS}"
echo "baking workshop assets into the image from: ${WORK_ASSETS}/mm2_workshop_assets"
BUILD_EXTRA+=(--build-context "assets=${WORK_ASSETS}/mm2_workshop_assets" --build-arg "FINAL=with-assets")
else
echo "no mm2_workshop_assets.zip in projects/Finetuning/ -> building code-only image (assets NOT baked in)."
fi

DOCKER_BUILDKIT=1 docker build ${BASE_IMAGE:+--build-arg BASE_IMAGE="$BASE_IMAGE"} \
${BUILD_EXTRA[@]+"${BUILD_EXTRA[@]}"} \
-t ghcr.io/amdresearch/auplc-finetuning:latest .
123 changes: 123 additions & 0 deletions dockerfiles/Courses/Finetuning/fastwam/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
### FastWAM

This package runs [FastWAM](https://github.com/yuantianyuan01/FastWAM) — a Wan2.2-TI2V-5B
world-action model (T5 text encoder + Wan VAE + video/action DiT) — on AMD Ryzen AI Max+
395 (Strix Halo, `gfx1151`) under ROCm 7.2.2. Direct PyTorch port: upstream code runs on
the base image's ROCm torch; only the CUDA torch pins are stripped.

It is a **slim policy/model layer that ships no simulator**, and the reference consumer of
the simulator packages' `Policy` seam. It composes on:

- the plain ROCm base → non-sim demos (smoke / latency / open-loop / videogen);
- the `simulation/libero` base → closed-loop + interactive LIBERO;
- the `simulation/robotwin` base → closed-loop + interactive RoboTwin 2.0.

The same policy layer composes on all three because the FastWAM install is pinned to the
base image's torch + numpy (so a plain base's numpy 2.x and a sim base's numpy 1.26.4 both
work). Weights/datasets are fetched by the scripts below; sim assets come from the sim base.

### Build

```sh
# Standalone (non-sim demos + model sign-of-life):
ryzers build fastwam --name fastwam
ryzers run --name fastwam # test.py: ROCm torch + GPU + deps sign-of-life

# Chain on a simulator base for closed-loop / interactive rollouts:
ryzers build libero fastwam --name fastwam-libero
ryzers build robotwin fastwam --name fastwam-robotwin
```

Artifacts are written to `workspace/*/outputs`. For faster/gated HF downloads set
`HF_TOKEN`. The ~12 GB Wan2.2 base is fetched automatically on the first model run.

```sh
ryzers run --name fastwam /ryzers/scripts/download_checkpoints.sh # LIBERO + RoboTwin ckpts
ryzers run --name fastwam /ryzers/scripts/download_datasets.sh # open-loop / video data
```

The chain drives a sim base's model-agnostic `Policy` seam via a runtime adapter
(`adapters/fastwam_{libero,robotwin}_policy.py`, selected by `POLICY_FACTORY`); these
adapters double as the worked reference for wiring any VLA/WAM into the sim bases (see each
`simulation/*` README). The RoboTwin closed-loop instead runs RoboTwin's own
`script/eval_policy.py` against `experiments/robotwin/fastwam_policy`
(`EVALUATION.robotwin_root=/opt/RoboTwin`).

### Demos

| Demo | Base | What it does |
|---|---|---|
| `demos/demo_smoke.sh` | plain | Load checkpoint, one `infer_action`; cold/steady latency + VRAM. |
| `demos/demo_latency.sh` | plain | Per-part latency (T5 / VAE / world prefill / plan) + SDPA backends. |
| `demos/demo_openloop.sh` | plain | Replay GT observations, overlay predicted vs GT action chunks + MAE. |
| `demos/demo_videogen.sh` | plain | Imagine future frames from the first observation; GT-vs-imagined clips. |
| `demos/demo_closedloop_libero.sh` | `libero` | Closed-loop LIBERO rollouts (MuJoCo/EGL) + success rate. |
| `demos/demo_interactive_libero.sh` / `_rt.sh` | `libero` | Interactive LIBERO over HTTP/MJPEG. |
| `demos/demo_closedloop_robotwin.sh` | `robotwin` | Closed-loop RoboTwin 2.0 rollouts (SAPIEN Vulkan RT) + success rate. |
| `demos/demo_interactive_robotwin.sh` / `_rt.sh` | `robotwin` | Interactive RoboTwin over HTTP/MJPEG. |

```sh
ryzers run --name fastwam /ryzers/demos/demo_smoke.sh
ryzers run --name fastwam-libero /ryzers/demos/demo_closedloop_libero.sh
TASKS="click_bell lift_pot" NUM_EPISODES=10 \
ryzers run --name fastwam-robotwin /ryzers/demos/demo_closedloop_robotwin.sh
ryzers run --name fastwam-robotwin /ryzers/demos/demo_interactive_robotwin.sh # http://localhost:8082
```

### Open-loop replay

Predicted action chunks track ground truth over 100 episodes: mean normalized MAE
**0.0222** (LIBERO) / **0.0208** (RoboTwin), action inference ~1.5 s.

<p align="center">
<img src="assets/d1_libero_per_dim_mae.png" alt="open-loop per-dim MAE, LIBERO" width="700">
<br><em>Per-dimension normalized MAE (LIBERO).</em>
</p>
<p align="center">
<img src="assets/d1_libero_ep00.png" alt="open-loop GT-vs-pred overlay, LIBERO episode 0" width="700">
<br><em>GT (solid) vs predicted (dashed) action chunks, LIBERO episode 0.</em>
</p>

### Video imagination

Joint path imagines the future video + actions (GT left, imagined right). Steady-state
joint latency ~18.6 s (LIBERO) / ~21.9 s (RoboTwin) for a 33-frame clip at 20 denoise
steps (the first call pays a one-time ROCm warmup).

<p align="center">
<img src="assets/d2_libero.gif" alt="GT vs imagined, LIBERO" width="600">
<br><em>Ground truth vs imagined future (LIBERO).</em>
</p>
<p align="center">
<img src="assets/d2_robotwin.gif" alt="GT vs imagined, RoboTwin" width="600">
<br><em>Ground truth vs imagined future (RoboTwin).</em>
</p>

### Closed-loop LIBERO

`libero_object` suite, 10 tasks × 20 trials: **199/200 (99.5%)** success, rendered headless
via EGL. With `VISUALIZE_FUTURE=true` the slow path also renders the model's imagined future
alongside the real rollout (GT left, imagined right; PSNR ~27.3 dB).

<p align="center">
<img src="assets/d3_libero.gif" alt="closed-loop LIBERO rollout" width="400">
<img src="assets/d4_slow.gif" alt="closed-loop slow path, GT vs imagined" width="400">
<br><em>Closed-loop rollout (left) and slow-path GT-vs-imagined (right).</em>
</p>

### Useful knobs

- Non-sim: `DATASET=libero|robotwin` (open-loop/videogen/latency), `NUM_STEPS`, `SEED`.
- Closed-loop LIBERO: `SUITE`, `NUM_TASKS`, `NUM_TRIALS`, `VISUALIZE_FUTURE`.
- Closed-loop RoboTwin: `TASKS`, `TASK_CONFIG`, `NUM_EPISODES`.
- Interactive: `PORT`, `CKPT`, `DATASET_STATS`, `REPLAN_STEPS`, `NUM_INFERENCE_STEPS`.
- `HF_TOKEN` for faster/gated downloads.

### References

- Upstream: https://github.com/yuantianyuan01/FastWAM (pinned in `docs/UPSTREAM_PIN.commit.txt`)
- Model: https://huggingface.co/yuanty/fastwam
- Datasets: https://huggingface.co/datasets/yuanty/LIBERO-fastwam · https://huggingface.co/datasets/yuanty/robotwin2.0-fastwam

Copyright (C) 2026 Advanced Micro Devices, Inc. All rights reserved.
SPDX-License-Identifier: MIT
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
# Copyright(C) 2026 Advanced Micro Devices, Inc. All rights reserved.
# SPDX-License-Identifier: MIT
"""FastWAM LIBERO policy adapter for the simulation/libero harness.

Implements the model-agnostic `sim_libero.Policy` seam by wrapping the FastWAM world-action
model. Reuses the *validated* eval machinery from experiments/libero/eval_libero_single.py
verbatim (config compose, model instantiate + checkpoint load, processor/normalizer, and
`_predict_action_chunk`) so interactive/closed-loop rollouts match the shipped numbers.

Selected at runtime by the sim harness via
POLICY_FACTORY=fastwam_libero_policy:build_policy

Env: CKPT, DATASET_STATS, MIXED_PRECISION (bf16), SUITE, NUM_INFERENCE_STEPS,
REPLAN_STEPS, NUM_STEPS_WAIT, FASTWAM_REPO (/repos/fastwam).
Requires /repos/fastwam and its experiments/libero dir on PYTHONPATH (the demo sets this).
"""
import os

import numpy as np
from hydra import compose, initialize_config_dir
from hydra.core.global_hydra import GlobalHydra
from hydra.utils import instantiate

import experiments.libero.eval_libero_single as E
from fastwam.datasets.lerobot.utils.normalizer import load_dataset_stats_from_json
from sim_libero.policy import Policy

FASTWAM_REPO = os.environ.get("FASTWAM_REPO", "/repos/fastwam")
CONFIG_DIR = os.path.join(FASTWAM_REPO, "configs")
DEFAULT_CKPT = "/models/fastwam_release/libero_uncond_2cam224.pt"
DEFAULT_STATS = "/models/fastwam_release/libero_uncond_2cam224_dataset_stats.json"


class FastwamLiberoPolicy(Policy):
name = "fastwam"

def __init__(self, model, processor, cfg, action_horizon, input_w, input_h, device):
self.model = model
self.processor = processor
self.cfg = cfg
self.action_horizon = action_horizon
self.input_w = input_w
self.input_h = input_h
self.device = device
self.replan_steps = int(cfg.EVALUATION.get("replan_steps", 5))
self.num_steps_wait = int(cfg.EVALUATION.get("num_steps_wait", 5))

def predict_action_chunk(self, obs, instruction):
action, _imgs, _pred = E._predict_action_chunk(
obs=obs,
task_description=instruction,
model=self.model,
processor=self.processor,
cfg=self.cfg,
action_horizon=self.action_horizon,
input_w=self.input_w,
input_h=self.input_h,
model_device=self.device,
)
return np.asarray(action, dtype=np.float32)


def build_policy():
# ryzers passes optional knobs as empty strings; treat "" as unset.
ckpt = os.environ.get("CKPT") or DEFAULT_CKPT
stats = os.environ.get("DATASET_STATS") or DEFAULT_STATS
mixed = os.environ.get("MIXED_PRECISION") or "bf16"
suite = os.environ.get("SUITE") or "libero_object"

overrides = [
f"ckpt={ckpt}",
"gpu_id=0",
f"mixed_precision={mixed}",
f"EVALUATION.task_suite_name={suite}",
"EVALUATION.task_id=0",
"EVALUATION.num_trials=1",
f"EVALUATION.dataset_stats_path={stats}",
"EVALUATION.output_dir=/tmp/fastwam_interactive",
]
if os.environ.get("NUM_INFERENCE_STEPS"):
overrides.append(f"EVALUATION.num_inference_steps={os.environ['NUM_INFERENCE_STEPS']}")
if os.environ.get("REPLAN_STEPS"):
overrides.append(f"EVALUATION.replan_steps={os.environ['REPLAN_STEPS']}")
if os.environ.get("NUM_STEPS_WAIT"):
overrides.append(f"EVALUATION.num_steps_wait={os.environ['NUM_STEPS_WAIT']}")

if GlobalHydra.instance().is_initialized():
GlobalHydra.instance().clear()
with initialize_config_dir(config_dir=CONFIG_DIR, version_base="1.3"):
cfg = compose(config_name="sim_libero", overrides=overrides)

device = E._resolve_eval_device(cfg)
dtype = E._mixed_precision_to_model_dtype(mixed)
model = instantiate(cfg.model, model_dtype=dtype, device=device)
E._load_model_checkpoint(model, str(cfg.ckpt))
model = model.to(device).eval()

stats_path = E._resolve_dataset_stats_path(cfg)
dataset_stats = load_dataset_stats_from_json(str(stats_path))
processor = instantiate(cfg.data.train.processor).eval()
processor.set_normalizer_from_stats(dataset_stats)

action_horizon_cfg = cfg.EVALUATION.get("action_horizon", None)
if action_horizon_cfg is None:
action_horizon = int(cfg.data.train.num_frames) - 1
else:
action_horizon = int(action_horizon_cfg)

video_size = cfg.data.train.get("video_size", [224, 224])
input_h = int(video_size[0])
input_w = int(video_size[1])

print(f"[fastwam_libero_policy] model ready (ckpt={ckpt}, horizon={action_horizon}, "
f"input={input_w}x{input_h}, replan={cfg.EVALUATION.get('replan_steps', 5)})", flush=True)
return FastwamLiberoPolicy(model, processor, cfg, action_horizon, input_w, input_h, device)
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
yuantianyuan01/FastWAM
45d8e1458921d83f8ad6cf9ce993d371208dabd0
Loading