Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ Positronic supports state-of-the-art foundation models with first-class workflow
| Model | Capability | Training | Inference | Best For |
|-------|-----------|----------|-----------|----------|
| **[OpenPI (π₀.₅)](positronic/vendors/openpi/README.md)** | Most capable, generalist | Capable GPU (~78GB, LoRA) | Capable GPU (~62GB) | Complex multi-task manipulation |
| **[GR00T](positronic/vendors/gr00t/README.md)** | Generalist robot policy | Capable GPU (~50GB) | Smaller GPU (~7.5GB) | Logistics and industry applications |
| **[GR00T N1.7 DROID](positronic/vendors/gr00t/README.md)** | DROID robot policy | CUDA GPU | CUDA GPU | Joint control with 2 or 3 camera views |
| **[LeRobot SmolVLA](positronic/vendors/lerobot/README.md)** | VLM-based, multi-task | Consumer GPU | Consumer GPU | Multi-task manipulation with language |
| **[LeRobot ACT](positronic/vendors/lerobot_0_3_3/README.md)** | Single-task, efficient | Consumer GPU | Consumer GPU | Specific manipulation tasks |

Expand Down
14 changes: 9 additions & 5 deletions docker/Dockerfile.groot
Original file line number Diff line number Diff line change
@@ -1,14 +1,18 @@
ARG BASE_IMAGE=positro/gr00t-base:latest
ARG BASE_IMAGE
FROM ${BASE_IMAGE}

# Set working directory
COPY --from=ghcr.io/astral-sh/uv:latest /uv /uvx /usr/local/bin/

WORKDIR /positronic

# Copy the positronic repository
RUN apt-get update && apt-get install -y --no-install-recommends libturbojpeg && rm -rf /var/lib/apt/lists/*

COPY . /positronic

# Set Python path to include source root and gr00t
ENV PYTHONPATH=/positronic:/gr00t

# Default command
# Keep Positronic's dependencies separate from the checkpoint runtime.
ENV UV_PROJECT_ENVIRONMENT=/positronic/.venv VIRTUAL_ENV=/positronic/.venv
RUN --mount=type=cache,target=/root/.cache/uv uv sync --locked --python 3.12 --no-dev

CMD ["bash"]
14 changes: 13 additions & 1 deletion docker/Makefile
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
.PHONY: all build tag push clean prune help build-training tag-training push-training build-openpi tag-openpi push-openpi build-groot tag-groot push-groot build-dreamzero-base push-dreamzero-base build-dreamzero tag-dreamzero push-dreamzero build-robolab tag-robolab push-robolab nebius-login push-robolab-cr

.DEFAULT_GOAL := help

# Image configuration
IMAGE_NAME_TRAINING := positro/positronic
IMAGE_NAME_OPENPI := positro/openpi
Expand Down Expand Up @@ -41,6 +43,16 @@ TAG_DREAMZERO_BASE_LATEST := $(IMAGE_NAME_DREAMZERO_BASE):latest
# OpenPI base image (pulled from registry)
TAG_OPENPI_BASE := $(IMAGE_NAME_OPENPI_BASE):latest

GROOT_REF := be79d6244dda302ace1ff7a2cd55239aad2ad109
ifndef GROOT_BASE_IMAGE
GROOT_BASE_IMAGE := positro/gr00t-base:$(GROOT_REF)
build-groot: build-groot-base
endif

.PHONY: build-groot-base
build-groot-base:
docker build --platform linux/amd64 -f docker/Dockerfile -t $(GROOT_BASE_IMAGE) https://github.com/Positronic-Robotics/gr00t.git\#$(GROOT_REF)

help:
@echo "Positronic Docker Build System - Makefile"
@echo ""
Expand Down Expand Up @@ -81,7 +93,7 @@ build-openpi:

build-groot: build-training
@echo "Building $(IMAGE_NAME_GROOT)..."
docker build --pull --platform linux/amd64 --build-arg BASE_IMAGE=positro/gr00t-base:latest -f Dockerfile.groot -t $(IMAGE_NAME_GROOT):local ..
docker build --platform linux/amd64 --build-arg BASE_IMAGE=$(GROOT_BASE_IMAGE) -f Dockerfile.groot -t $(IMAGE_NAME_GROOT):local ..

build-dreamzero-base:
@echo "Building $(IMAGE_NAME_DREAMZERO_BASE)..."
Expand Down
22 changes: 19 additions & 3 deletions docker/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,23 @@ Now you are ready to build our Docker.
docker/build.sh
```

## GR00T containers: uv mount caveat
## GR00T N1.7 containers

If you customize `docker-compose.yml` volumes, **do not bind-mount** your host `~/.local/share/uv` into `/root/.local/share/uv` for `positro/gr00t` images.
GR00T's `/.venv/bin/python` can be a symlink into the image's own uv-managed CPython under `/root/.local/share/uv/python/...`, and the bind mount can hide that target and cause `/.venv/bin/python` to fail with `ENOENT`.
`make build-groot` builds the GR00T fork revision pinned in `Makefile`. The fork image contains
CUDA 12.8 and the upstream Python 3.12 environment at `/opt/gr00t-venv`.
Positronic installs its own locked environment at `/positronic/.venv`.
Both training and serving launch GR00T in its separate environment.

Build both images from Positronic:

```bash
make -C docker build-groot
IMAGE_TAG=local docker compose -f docker/docker-compose.yml run --rm --service-ports groot-server droid
```

Pass `GROOT_BASE_IMAGE=<image:tag>` to use an existing base and skip its build.
For local fork development, run `make -C docker build` in the fork,
then `make -C docker build-groot GROOT_BASE_IMAGE=positro/gr00t-base:local` in Positronic.

Do not mount host uv interpreter directories over the image's interpreter directories.
See [GR00T](../positronic/vendors/gr00t/README.md) for conversion, fine-tuning and inference.
10 changes: 4 additions & 6 deletions docker/docker-compose.phail.desktop.yml
Original file line number Diff line number Diff line change
@@ -1,13 +1,11 @@
# GR00T on desktop (RTX 3060 12GB — GR00T uses ~4.6GB)
# GR00T N1.6 on desktop (RTX 3060 12GB — GR00T uses ~4.6GB)
#
# Set GR00T_N16_IMAGE to a pinned N1.6 image.
# docker --context desktop compose -f docker-compose.phail.desktop.yml up
services:
phail-groot-server:
extends:
file: docker-compose.yml
service: groot-server
container_name: phail-groot-server
pull_policy: always
command: ["phail"]
file: docker-compose.phail.yml
service: phail-groot-server
Comment thread
vertix marked this conversation as resolved.
ports: !override
- "8000:8000"
3 changes: 3 additions & 0 deletions docker/docker-compose.phail.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ services:
extends:
file: docker-compose.yml
service: groot-server
# These experiment checkpoints use the N1.6 action schema.
image: ${GR00T_N16_IMAGE:?Set GR00T_N16_IMAGE to a pinned GR00T N1.6 image}
entrypoint: ["uv", "run", "--python", "3.13", "python", "-m", "positronic.vendors.gr00t.server"]
container_name: phail-groot-server
pull_policy: always
command: ["phail"]
Expand Down
13 changes: 11 additions & 2 deletions docker/docker-compose.spoons-ablation.yml
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
# GR00T spoons data ablation servers (~4.6GB each, 2 fit on desktop, 1 on notebook)
#
# Desktop (2 servers):
# IMAGE_TAG=latest CACHE_ROOT=/home/<user> docker --context desktop compose -f docker-compose.spoons-ablation.yml up spoons-100 spoons-50
# GR00T_N16_IMAGE=positro/gr00t:<N1.6-tag> CACHE_ROOT=/home/<user> docker --context desktop compose -f docker-compose.spoons-ablation.yml up spoons-100 spoons-50
# Notebook (1 server):
# IMAGE_TAG=latest docker --context notebook compose -f docker-compose.spoons-ablation.yml up spoons-25
# GR00T_N16_IMAGE=positro/gr00t:<N1.6-tag> docker --context notebook compose -f docker-compose.spoons-ablation.yml up spoons-25
services:
spoons-100:
extends:
file: docker-compose.yml
service: groot-server
# These experiment checkpoints use the N1.6 action schema.
image: ${GR00T_N16_IMAGE:?Set GR00T_N16_IMAGE to a pinned GR00T N1.6 image}
entrypoint: ["uv", "run", "--python", "3.13", "python", "-m", "positronic.vendors.gr00t.server"]
container_name: spoons-100
pull_policy: always
command:
Expand All @@ -21,6 +24,9 @@ services:
extends:
file: docker-compose.yml
service: groot-server
# These experiment checkpoints use the N1.6 action schema.
image: ${GR00T_N16_IMAGE:?Set GR00T_N16_IMAGE to a pinned GR00T N1.6 image}
entrypoint: ["uv", "run", "--python", "3.13", "python", "-m", "positronic.vendors.gr00t.server"]
container_name: spoons-50
pull_policy: always
command:
Expand All @@ -33,6 +39,9 @@ services:
extends:
file: docker-compose.yml
service: groot-server
# These experiment checkpoints use the N1.6 action schema.
image: ${GR00T_N16_IMAGE:?Set GR00T_N16_IMAGE to a pinned GR00T N1.6 image}
entrypoint: ["uv", "run", "--python", "3.13", "python", "-m", "positronic.vendors.gr00t.server"]
container_name: spoons-25
pull_policy: always
command:
Expand Down
9 changes: 3 additions & 6 deletions docker/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -181,10 +181,7 @@ services:
volumes:
- ${CACHE_ROOT:-${HOME}}/.cache:/root/.cache
- ${CACHE_ROOT:-${HOME}}/.aws:/root/.aws:ro
# NOTE: Do NOT mount `/root/.local/share/uv` for gr00t images.
# gr00t's `/.venv/bin/python` is a symlink into the image's own uv-managed CPython under
# `/root/.local/share/uv/python/...`. Bind-mounting the host uv dir can hide that target and
# make `/.venv/bin/python` fail with ENOENT.
# Keep image-owned uv interpreters visible; do not mount /root/.local/share/uv here.

# Set HOME inside container to match the mount expectations
environment:
Expand All @@ -201,12 +198,12 @@ services:
container_name: groot-train
shm_size: 8g
ipc: host # Enable shared memory access for training
entrypoint: ["uv", "run", "--python", "3.13", "python", "-m", "positronic.vendors.gr00t.train"]
entrypoint: ["uv", "run", "--no-sync", "--python", "3.12", "python", "-m", "positronic.vendors.gr00t.train"]

groot-server: &groot-server-common
<<: *groot-common
container_name: groot-server
entrypoint: ["uv", "run", "--python", "3.13", "python", "-m", "positronic.vendors.gr00t.server"]
entrypoint: ["uv", "run", "--no-sync", "--python", "3.12", "python", "-m", "positronic.vendors.gr00t.server"]
ports:
- "8000:8000"

Expand Down
38 changes: 12 additions & 26 deletions docs/codecs.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ It is declared in two places, for the two things it does:
- **Training** — `compose(ee_frame=DROID_EE_FRAME)` re-expresses the dataset in that frame, which is what makes the resulting checkpoint speak it. It defaults to unset, which trains in `default`.
- **Serving** — the OpenPI pipeline's `ee_frame=` puts the codec left of the `remote` marker, so the rig converts and the server stays frame-agnostic. It has no default: every deployment states its frame — `None` for a checkpoint trained in `default`, or one that speaks joints, which are unambiguous. Nothing checks a stated frame against how the checkpoint was trained, so it is set beside the checkpoint path it belongs to.

Both take the transform itself — `models.DROID_EE_FRAME` is the one we ship — so a checkpoint declares its own frame and no robot model is consulted to serve it. The other vendor servers take no `ee_frame`: every checkpoint they serve was trained in the rig's `default`, so none has a transform to declare.
Both take the transform itself — `models.DROID_EE_FRAME` is the one we ship — so a checkpoint declares its own frame and no robot model is consulted to serve it. GR00T's DROID codec uses `DROID_EE_FRAME` for both dataset conversion and serving. Its pipeline exposes this through `codec.ee_frame`.

A `CartesianDelta` is the one command this cannot convert on its own: a delta has no anchor pose, so it carries `frame` and the driver composes it where the measured pose lives.

Expand All @@ -70,7 +70,9 @@ Two wrappers in [`positronic/cfg/codecs.py`](../positronic/cfg/codecs.py) apply
| Wrapper | Expands to | Used by |
|---------|-----------|---------|
| `droid_execution(action)` | `SetControlMode(DROID_IMPEDANCE) \| action` ([the DROID gains](../positronic/cfg/hardware/roboarm/__init__.py)) | the `droid` pipelines of OpenPI, DreamZero and MolmoAct2, and OpenPI's `droid_jointpos` |
| `phail_v1_execution(action)` | `SetControlMode(PositionControl()) \| action` | the `phail_v1` pipelines of LeRobot, GR00T, OpenPI and DreamZero |
| `phail_v1_execution(action)` | `SetControlMode(PositionControl()) \| action` | the `phail_v1` pipelines of LeRobot, OpenPI and DreamZero |

GR00T's DROID codec sets `DROID_IMPEDANCE` directly on its joint-position commands.

## Writing custom codecs

Expand Down Expand Up @@ -118,31 +120,15 @@ cd docker && docker compose run --rm lerobot-convert convert \

See [`positronic/vendors/gr00t/codecs.py`](../positronic/vendors/gr00t/codecs.py).

| Codec | Observation | Action | Modality Configs |
|-------|-------------|--------|------------------|
| `ee_quat` | EE pose (quat) + grip + images (224x224) | Absolute EE position (quat) + grip | `ee`, `ee_rel` |
| `ee_rot6d` | EE pose (rot6d) + grip + images | Absolute EE position (rot6d) + grip | `ee_rot6d`, `ee_rot6d_rel` |
| `ee_quat_joints` | EE pose + joints + grip + images | Absolute EE position + grip | `ee_q`, `ee_q_rel` |
| `ee_rot6d_joints` | EE pose (rot6d) + joints + grip + images | Absolute EE position (rot6d) + grip | `ee_rot6d_q`, `ee_rot6d_q_rel` |
| `ee_quat_traj` | EE pose (quat) + grip + images | Absolute EE trajectory (quat) + grip (binarized) | `ee`, `ee_rel` |
| `ee_rot6d_traj` | EE pose (rot6d) + grip + images | Absolute EE trajectory (rot6d) + grip (binarized) | `ee_rot6d`, `ee_rot6d_rel` |
| `ee_quat_joints_traj` | EE pose + joints + grip + images | Absolute EE trajectory + grip (binarized) | `ee_q`, `ee_q_rel` |
| `ee_rot6d_joints_traj` | EE pose (rot6d) + joints + grip + images | Absolute EE trajectory (rot6d) + grip (binarized) | `ee_rot6d_q`, `ee_rot6d_q_rel` |
| `joints_traj` | Joints + grip + images (no EE pose) | Absolute joint trajectory + grip (binarized) | — |

The codec must match the modality config during training.

```bash
# Convert with codec
cd docker && docker compose run --rm lerobot-0_3_3-convert convert \
--dataset.codec=@positronic.vendors.gr00t.codecs.ee_rot6d_joints \
--output_dir=~/datasets/groot/my_task
| Codec | Cameras | State and training actions | Inference actions |
|-------|---------|----------------------------|-------------------|
| `droid` | Exterior + wrist | Absolute EEF pose (XYZ + row-based rot6d), gripper, 7 joints | Absolute joint targets + binary gripper |
| `droid_three_cameras` | Two exteriors + wrist | Same as `droid` | Same as `droid` |

# Train with matching modality
cd docker && docker compose run --rm groot-train \
--modality_config=ee_rot6d_q \
--input_path=~/datasets/groot/my_task
```
Images use the upstream DROID client's 320×180 padded resize, then the checkpoint's native
preprocessing. GR00T owns pose-relative and joint-relative conversion. Training labels are
recorded state trajectories. Use the same camera layout for conversion and inference;
the published DROID checkpoint uses two cameras. See the [Docker workflow](../positronic/vendors/gr00t/README.md).

### OpenPI

Expand Down
6 changes: 3 additions & 3 deletions docs/inference.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ Deploy trained policies for evaluation and production use. Positronic supports l

Positronic's unified WebSocket protocol connects any hardware to any model (LeRobot, GR00T, OpenPI). The key benefit is running heavy models on powerful GPU hardware (OpenPI needs ~62GB, GR00T ~8GB) separate from the robot/simulator machine.

Each server carries a full **policy pipeline** — one chain naming the rig-side stack, the `remote` split marker, the server-side codec, and the model source that loads checkpoints (see `positronic.policy.spec`). The server runs the half right of the marker and declares the half left of it in its handshake; the client builds the declared stack automatically. Vendors ship their pipelines by name, and every name is a server subcommand — `groot-server ee_rot6d_joints` launches that one. The available names are listed in each vendor's README.
Each server carries a full **policy pipeline** — one chain naming the rig-side stack, the `remote` split marker, the server-side codec, and the model source that loads checkpoints (see `positronic.policy.spec`). The server runs the half right of the marker and declares the half left of it in its handshake; the client builds the declared stack automatically. Vendors ship their pipelines by name, and every name is a server subcommand — `groot-server droid` launches that one. The available names are listed in each vendor's README.

**Start inference server:**
```bash
Expand All @@ -20,8 +20,8 @@ cd docker && docker compose run --rm --service-ports lerobot-0_3_3-server ee \
--pipeline.source.checkpoints_dir=~/checkpoints/lerobot/experiment_v1/

# GR00T
cd docker && docker compose run --rm --service-ports groot-server ee_rot6d_joints \
--pipeline.source.checkpoints_dir=~/checkpoints/groot/experiment_v1/
cd docker && docker compose run --rm --service-ports -v "$PWD/groot-data:/data" groot-server droid \
--pipeline.source.checkpoints_dir=/data/checkpoints/experiment_v1/

# OpenPI (--pipeline.ee_frame states the EE frame the checkpoint speaks; None means the rig's `default`)
cd docker && docker compose run --rm --service-ports openpi-server ee \
Expand Down
22 changes: 8 additions & 14 deletions docs/model-selection.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,9 @@ Positronic supports three foundation models with different capabilities and reso
| Aspect | OpenPI (π₀.₅) | GR00T | SmolVLA | LeRobot ACT |
|--------|---------------|-------|---------|-------------|
| **Capability** | Most capable, generalist | Generalist | Vision-language-action | Single-task specialist |
| **Training Hardware** | capable cloud GPU (~78GB, LoRA) | capable cloud GPU (~50GB) | Consumer GPU (RTX 3090, 4090) | Consumer GPU (RTX 3090, 4090) |
| **Training Hardware** | capable cloud GPU (~78GB, LoRA) | CUDA GPU (measure for batch/layout) | Consumer GPU (RTX 3090, 4090) | Consumer GPU (RTX 3090, 4090) |
| **Training Time** | Multiple days | 0.5-2 days | Several hours | Several hours |
| **Inference Hardware** | GPU (~62GB, likely cloud) | GPU (~7.5GB, can run on robot) | Consumer GPU (4GB+) | Consumer GPU (4GB+) |
| **Inference Hardware** | GPU (~62GB, likely cloud) | CUDA GPU (measure for camera layout) | Consumer GPU (4GB+) | Consumer GPU (4GB+) |
| **Inference Speed** | Moderate | Moderate | Moderate | Fast |
| **Best For** | Complex multi-task manipulation, generalization | General robotics tasks | Language-conditioned manipulation | Specific manipulation tasks, fast iteration |
| **When to Use** | Need generalization, multi-task scenarios, leveraging foundation models | Prefer NVIDIA stack | Language instructions, VLM backbone | Single task, resource constraints, rapid experimentation |
Expand All @@ -36,20 +36,14 @@ Positronic supports three foundation models with different capabilities and reso

→ [OpenPI Documentation](../positronic/vendors/openpi/README.md)

### GR00T — NVIDIA's Generalist Robot Policy
### GR00T N1.7 DROID

**What it is:** NVIDIA's foundation model for generalist robot control.

**Strengths:**
- Generalist capabilities
- Can run on smaller GPU (~7.5GB inference, can run closer to robot)
- Requires ~50GB for training (less than OpenPI)
- Training takes 1-2 days (faster than OpenPI)

**Limitations:**
- Requires capable GPU for training
- Slower than single-task models
NVIDIA's robot policy, using the published DROID checkpoint. Supports wrist plus one exterior
camera by default, and wrist plus two exteriors through fine-tuning. It predicts joint-position
actions and preserves the checkpoint's image and relative-action processing.

Training and inference require a CUDA GPU. Measure memory requirements with the intended batch
size and camera layout.

→ [GR00T Documentation](../positronic/vendors/gr00t/README.md)

Expand Down
Loading