Skip to content

feat(deployment): add Xavier GPU offload for robot inference - #1556

Merged
David White (dwhiteddsoft) merged 42 commits into
microsoft:mainfrom
dwhiteddsoft:xavier-integration
Sep 21, 2026
Merged

David White (dwhiteddsoft) merged 42 commits into
microsoft:mainfrom
dwhiteddsoft:xavier-integration

Conversation

@dwhiteddsoft

@dwhiteddsoft David White (dwhiteddsoft) commented Sep 15, 2026

Copy link
Copy Markdown
Contributor

Pull Request

Description

Adds a gpu-offload/ domain that transparently offloads robot policy inference to a nearby on-premise GPU, based on the Xavier remote-execution runtime.

  • Runtime (gpu-offload/runtime): remoter library for opted-in functions and classes over TCP/UDP/Unix sockets, using a restricted MessagePack codec (no pickle) with size and type limits
  • Admission controller (gpu-offload/controller): mutating webhook that injects server/client configuration into workloads that explicitly opt in
  • Helm chart (gpu-offload/helm): controller deployment with TLS, RBAC, and workload opt-in; node agent template retained but disabled
  • Specifications: offload opt-in contract and remote.yaml schema
  • Examples: first-run GPU validation, SO-101 LeRobot real-hardware rollout, UR10e single-arm
  • Local setup scripts: k3s, NVIDIA container toolkit, and preflight checks (T0-compatible, Kubernetes optional)
  • Docs: README and tier docs updated for efficient inference offload; frontmatter added to touched docs that lacked it

Draft while #1555 is in triage.

Closes #1555

Type of Change

  • 🐛 Bug fix (non-breaking change fixing an issue)
  • ✨ New feature (non-breaking change adding functionality)
  • 💥 Breaking change (fix or feature causing existing functionality to change)
  • 📚 Documentation update
  • 🏗️ Infrastructure change (Terraform/IaC)
  • ♻️ Refactoring (no functional changes)

Component(s) Affected

  • infrastructure/terraform/prerequisites/ - Azure subscription setup
  • infrastructure/terraform/ - Terraform infrastructure
  • infrastructure/setup/ - OSMO control plane / Helm
  • workflows/ - Training and evaluation workflows
  • training/ - Training pipelines and scripts
  • docs/ - Documentation
  • gpu-offload/ - New GPU offload domain (runtime, controller, Helm chart, examples, scripts)

Testing Performed

  • Terraform plan reviewed (no unexpected changes)
  • Terraform apply tested in dev environment
  • Training scripts tested locally with Isaac Sim
  • OSMO workflow submitted successfully
  • Smoke tests passed (smoke_test_azure.py)

No Terraform, Azure, or OSMO changes; the checks above do not apply.

Validation performed on the final branch (local, macOS):

  • ruff check and ruff format --check (ruff 0.15.21, CI-pinned): pass
  • taplo fmt --check, uv lock --check, npm run lint:hfpins: pass
  • cspell, markdownlint-cli2, markdown-table-formatter --check, npm run lint:links: pass on changed files
  • Frontmatter validation and ms.date freshness (-ChangedFilesOnly): 0 errors, 0 stale files
  • py_compile on all Python files modified during lint cleanup: pass
  • Ruff safe fixes reviewed: only unused imports, f-strings without placeholders, import sorting, and __all__ ordering; no removed import is referenced elsewhere

Environment used: local workstation only; no Azure resources deployed for this sync.

Cost incurred: none.

Known limitations:

  • Frontmatter validation reports 8 non-blocking warnings (mostly missing Copilot footer)

Update (2026-09-18): gpu-offload/runtime and gpu-offload/controller pytest suites now run
locally (all passing) and in CI via the new path-gated Pytest GPU Offload job
(.github/workflows/pytest-gpu-offload.yml), which also caught and fixed a Hatch
packaging bug in the controller's pyproject.toml. shellcheck and actionlint
(the tool behind the "YAML Lint" check) were also run locally and pass; both already
ran in CI prior to this update.

Documentation Impact

  • No documentation changes needed
  • Documentation updated in this PR
  • Documentation issue filed

Bug Fix Checklist

Not applicable: feature PR.

Checklist

  • My code follows the project conventions
  • Commit messages follow conventional commit format
  • I have performed a self-review
  • Documentation impact assessed above
  • No new linting warnings introduced (verified locally and in CI: shellcheck, actionlint, pytest)

🤖 Generated with Claude Code

David White (dwhiteddsoft) and others added 15 commits July 24, 2026 09:44
- add offload contract + remote.yaml schema specs
- add parameterized Helm scaffolding and SO-101 example
- vendor LeRobot ROS 2 bridge (lint-only reference)
- add provenance, audit, and open-items docs

🚀 - Generated by Copilot
- move audit, carry-decision, and open-items notes into internal .tasks/
- strip provenance/porting narrative and attribution footers from docs
- generalize SPDX attribution headers in source files
- reframe scope and validation notes for end users

📝 - Generated by Copilot
🔄 - Generated by Copilot

# Conflicts:
#	.cspell.json
- add safe MessagePack runtime and admission reconciliation
- harden Helm deployment, TLS, RBAC, and workload opt-in
- document upstream deviations and compatibility

🔒 - Generated by Copilot
* test

* fix issue related to non-writeable location for config, fix issue related to dehydration in certain cases with multiple stages of servers, remove syncwithremote functionality since it does not work with messagepack

* dockerfile/containerfile for source to include in workload

* move

* always pull to get latest image

* Fix server deployment volume mounts

Copy only mounts backed by volume definitions allowed on generated remote server deployments.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* revert back to defaults so repository and tag for image file can be specified

* change created server deployment's pull policy to match workload's

* class2dict for cases where class not being converted to metaremoteduuid

* allow hostPath to go to server deployment - otherwise model files are missing

* special serialization for torch tensors

* benchmark pickle vs messagepack

* improve serialization of tensors

* perf: optimize large remoter payloads

- frame large byte values outside MessagePack metadata
- preserve legacy packet decoding and codec limits

🚀 - Generated by Copilot

* fix one potential race condition

* reenable syncwithremote as class2dict offers serialization method

* fix: prevent remoter serialization deadlocks

- support UUID and arbitrary-precision integer payloads
- return encoding failures and restore state without constructors

🔧 - Generated by Copilot

* fix: preserve remote attribute fallback

- reconstruct remote AttributeError as the native built-in
- retain wrapped errors for other remote exception types

🔧 - Generated by Copilot

* dockerignore to only copy source

* remove importlib for safety

* feat: restrict remote server hostPath volumes

- allow configured host paths and deny unlisted hostPath volumes
- inject the Helm allowlist and set the default mutator image repository

🔒 - Generated by Copilot

* chore(settings): ignore gpu-offload uv lockfiles

🧹 - Generated by Copilot

* chore(settings): restore gpu-offload uv lock tracking

- revert the scoped ignore rule so project locks remain visible

🧹 - Generated by Copilot

* fix: fall back for unavailable tensor devices

- restore tensors on the source device with CPU fallback
- cache unavailable advisory devices

🔧 - Generated by Copilot

* security: bound tensor allocations during decode

- enforce codec byte limits before tensor allocation
- validate payload length and scalar tensor shapes
- cover allocation limits with codec tests

🔒 - Generated by Copilot

* security: harden codec error handling

- validate call and result wire envelopes
- preserve remote errors through serialization failures
- add malformed payload regression coverage

🔒 - Generated by Copilot

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* fix(deployment): support LeRobot GPU offload runtime

Inherit runtime classes, avoid duplicate servers, and preserve enum, path, and scalar types across RPC.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: ef603d1e-d629-46cb-b210-38658d1a140e

* fix(gpu-offload): resolve generated pod server labels

Defer per-client label resolution when admission receives only generateName, allowing the runtime and generated server to use the final Job pod name.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: ef603d1e-d629-46cb-b210-38658d1a140e

---------

Co-authored-by: Thor <xavier@thor.corp.microsoft.com>
Copilot-Session: ef603d1e-d629-46cb-b210-38658d1a140e
* running on baremetal

wsl support
validation
mise helpers

* fix(deployment): support LeRobot GPU offload runtime

Inherit runtime classes, avoid duplicate servers, and preserve enum, path, and scalar types across RPC.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: ef603d1e-d629-46cb-b210-38658d1a140e

* fix(gpu-offload): resolve generated pod server labels

Defer per-client label resolution when admission receives only generateName, allowing the runtime and generated server to use the final Job pod name.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: ef603d1e-d629-46cb-b210-38658d1a140e

* init

* docs(examples): clarify first-run demo must be uninstalled before pi05 GPU claim

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* build(deployment): add missing uv.lock for gpu-offload controller and runtime

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* update todo

* refactor(infrastructure): extract gpu-offload mise task scripts

- move inline heredoc task bodies to scripts/ and examples/*/scripts/
- rename tasks with a-/b-/c-/d-/e-/f- prefixes so mise tasks sorts
  into the clone -> setup -> verify -> teardown workflow order
- convert setup/verify/teardown orchestration to mise depends
- update mise run references in docs and pi05 README

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* add ur10e policy example

* feat(deployment): run ur10e-single Pi0.5 policy headless on the GPU offload stage

- add headless mode driving the real UR10e with no display, homing on entry and exit
- stop memoizing get_action: singleinstance returned the first action forever
- add self-contained host registry with per-upstream pull-through caches
- pass USB through to the control pod so RealSense cameras enumerate in cluster
- wait for client deletion before replacing the stage to avoid a load() deadlock

🤖 - Generated by Copilot

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* feat(deployment): add one-command ur10e-single policy demo task

- add g-ur10e-52-demo: deploy headless with USB, then follow the run to homing
- bound the demo to 80 steps so the arm homes instead of cycling indefinitely

🤖 - Generated by Copilot

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* docs(deployment): surface the ur10e-single demo task in the gpu-offload docs

- link the single-command hardware demo from the gpu-offload quick start
- list demo.sh in the example file table and normalize table formatting

📝 - Generated by Copilot

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* fix docs

---------

Co-authored-by: Thor <xavier@thor.corp.microsoft.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: ef603d1e-d629-46cb-b210-38658d1a140e
⚡ - Generated by Copilot

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Shawn Cicoria <cicorias@users.noreply.github.com>
* feat(deployment): integrate SO-101 LeRobot example

- replace the legacy example with the validated LeRobot workflows
- source the offload runtime from the local BuildKit context
- exclude benchmark and result artifacts

🤖 - Generated by Copilot

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: ef603d1e-d629-46cb-b210-38658d1a140e

* feat(deployment): add SO-101 rollout timing

- Add opt-in aggregated control-loop stage timing

- Configure remoter log thresholds through Helm

🤖 - Generated by Copilot

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: ef603d1e-d629-46cb-b210-38658d1a140e

* perf(deployment): reduce SO-101 offload payload

- Move image preparation and policy processing to the GPU server
- Document float32 and uint8 real-hardware measurements

🤖 - Generated by Copilot

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: ef603d1e-d629-46cb-b210-38658d1a140e

* docs(deployment): clarify SO-101 offload configuration

- Explain transparent and optimized inference modes
- Replace host-specific values with a reusable example

📚 - Generated by Copilot

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: ef603d1e-d629-46cb-b210-38658d1a140e

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: ef603d1e-d629-46cb-b210-38658d1a140e
Added details about efficient inference and offloaded inference capabilities to the Physical AI Toolchain section.
…yering pattern, and prune the docs (#9)

* chore(deployment): remove obsolete pi05 GPU offload example

- delete examples/pi05 chart, control loop, scripts, and container image
- drop the e-pi05-* mise tasks and PI05_* entries from .env.example
- remove the Pi05 UR10e motion section and stale cspell words from docs/TODO.md
- repoint the ur10e-single teardown note at an existing task

🧹 - Generated by Copilot

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* refactor(deployment): layer the remoter SDK into the first-run image

- build from the pyremote payload image with a sitecustomize hook
- drop the SDK import and autoremote.start call from client.py
- fold Containerfile.local and Containerfile.gpu into two build targets
- add the missing example README and a reference remote.yaml

🔌 - Generated by Copilot

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* docs(deployment): prune and refresh the gpu-offload doc set

- delete PR3-merge-issues.md; every recommendation is implemented
- rewrite the docs index for all three platforms and link the diagrams
- check off the completed mise task extraction in TODO.md
- correct T0 plan rows for the replaced so101 example and torch codec

📚 - Generated by Copilot

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
- apply ruff format, safe fixes, and taplo formatting
- move module docstrings above __future__ imports
- drop unreachable logger call in msgqueue
- ignore vendored remoter style rules; add cspell terms
- fix mise-tasks instructions frontmatter and headings

🎨

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
- add title and description frontmatter required by validation
- refresh stale ms.date on docs changed in this branch

📝

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@codecov-commenter

Codecov Comments Bot (codecov-commenter) commented Sep 15, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 88.13%. Comparing base (48da998) to head (1c5be88).

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #1556      +/-   ##
==========================================
+ Coverage   88.10%   88.13%   +0.02%     
==========================================
  Files         279      279              
  Lines       23203    23245      +42     
  Branches     3199     3199              
==========================================
+ Hits        20444    20486      +42     
  Misses       2071     2071              
  Partials      688      688              
Flag Coverage Δ *Carryforward flag
go 100.00% <ø> (ø)
pester 86.75% <ø> (+0.10%) ⬆️
pytest-data-pipeline 100.00% <ø> (ø) Carriedforward from 637f707
pytest-dataviewer 89.66% <ø> (ø) Carriedforward from 637f707
pytest-dm-tools 100.00% <ø> (ø) Carriedforward from 637f707
pytest-evaluation 95.40% <ø> (ø)
pytest-fuzz 3.99% <ø> (ø)
pytest-inference 100.00% <ø> (ø) Carriedforward from 637f707
pytest-shared-ci 100.00% <ø> (ø) Carriedforward from 637f707
pytest-training 92.60% <ø> (ø)
vitest 85.66% <ø> (ø) Carriedforward from 637f707
vitest-app 85.66% <ø> (ø) Carriedforward from 637f707
vitest-components 85.66% <ø> (ø) Carriedforward from 637f707
vitest-features 85.66% <ø> (ø) Carriedforward from 637f707
vitest-lib 85.66% <ø> (ø) Carriedforward from 637f707
vitest-state 85.66% <ø> (ø) Carriedforward from 637f707

*This pull request uses carry forward flags. Click here to find out more.

Files with missing lines Coverage Δ
scripts/security/Test-DependencyPinning.ps1 90.46% <ø> (+0.47%) ⬆️
🚀 New features to boost your workflow:
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@github-advanced-security github-advanced-security AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CodeQL found more than 20 potential problems in the proposed changes. Check the Files changed tab for more details.

- raise kubernetes pin in gpu-offload controller and runtime
- relock urllib3 2.3.0 -> 2.7.0 (GHSA-gm62, 2xpw, 38jv, qccp, pq67, 48p4)
- drop google-auth/pyasn1 deps no longer required

🔒

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
- split readonly declaration from command substitution (SC2155)
- document GPU_OFFLOAD_DIR consumption by sourcing scripts (SC2034)

🐚

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
- reject unsupported socket families instead of unbound ep
- return generic errors from config server, log details
- pass required callback in rmtconfig self-test

🔒

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
- fix GHSA-6w46-j5rx-g56g (CVE-2025-71176) tmpdir handling
- align with repo-wide pytest==9.1.1 pin

🔒

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
- fix GHSA-r6ph, 537c, g6cj, jwv3, p423, m2h6
- align with repo-wide cryptography==50.0.1 pin

🔒

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
- msgpack 1.2.2 (GHSA-6v7p-g79w-8964)
- filelock 3.20.3 (GHSA-w853-jp5j-5j7f, GHSA-qmgc-5h2g-mvrw)
- requests 2.34.2 (GHSA-gc5v-m9x4-r6x2)
- flask 3.1.3 (GHSA-68rp-wp8r-4726)

🔒

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@dwhiteddsoft
David White (dwhiteddsoft) marked this pull request as ready for review September 18, 2026 01:08
@dwhiteddsoft
David White (dwhiteddsoft) requested a review from a team as a code owner September 18, 2026 01:08
…ength, DoS, registry/build tooling)

controller/mutate.py:
- Server Deployments no longer copy Secret volumes, or projected volumes that
  combine a Secret source, from the client container -- a client's mounted
  credentials were being exposed on a separately-specified server image/node.
- The client container's environment is no longer copied onto the server by
  default. Only offload-protocol variables the controller itself injects and
  names explicitly listed in a new remoteableenv config field transfer; this
  closes a secret-exposure path via both literal env values and valueFrom
  references (secretKeyRef, configMapKeyRef).
- _deployment_name now truncates and hashes names that would exceed
  Kubernetes' 63-character label-value limit (it's used as both the
  Deployment's own name and a label value), instead of letting a
  moderately-long client workload name fail Deployment/label validation.

runtime/remoter/msgudp.py:
- Reject a UDP chunk header whose totalchunks is zero/oversized or whose
  chunkindex is out of range, and reject a chunk whose totalchunks disagrees
  with an already-tracked message, before allocating [None] * totalchunks or
  indexing into it. A peer could previously force large allocations or an
  out-of-range index purely via header values, ahead of any codec-level size
  check.

Registry/build tooling:
- .env.example and the first-local-offload doc no longer claim
  GPU_OFFLOAD_REGISTRY_HOST accepts an Azure Container Registry endpoint --
  the registry scripts only ever treat it as a local host:port; ACR caching
  already has its own, correct mechanism (GPU_OFFLOAD_ACR_NAME).
- controller/Containerfile and build-controller-image.sh now pass
  PIP_INDEX_URL as a BuildKit secret (matching Containerfile.local), not a
  build arg, so a credential embedded in it can't land in image history.
- install-controller.sh always installs the controller image as
  localhost/xavier-mutate:local with pullPolicy Never. Nothing in the
  documented flow (build-controller-image.sh, load-images.sh) ever pushes or
  tags that image under the host-local registry's host:port, on k3s or kind,
  so preferring the registry reference there always pointed at an image that
  didn't exist under that name.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@algattik

Copy link
Copy Markdown
Collaborator

The current CI does not exercise the new gpu-offload domain. In particular, it does not run gpu-offload/runtime/tests or gpu-offload/controller/tests, install or build either Python package, lint/render the Helm charts, build the new container images, or perform a CPU-only client/server and admission-mutation smoke test.

Most of this coverage does not require Xavier hardware or a GPU. Please add a path-gated gpu-offload CI job that runs both pytest suites, builds both packages and relevant images, validates the Helm charts, and performs a hardware-independent smoke test. GPU allocation, CUDA execution, performance, and real-robot validation can remain separate hardware-backed checks.

The PR description currently says these test suites are “relying on CI”, but no workflow references gpu-offload; please correct that statement until the missing coverage is added.

@algattik

Copy link
Copy Markdown
Collaborator

Could you please add dedicated CI coverage for the new gpu-offload domain? Most of the required building blocks already exist, so it may be possible to extend the repository’s current controls and compose the validation assets introduced by this PR.

1. Existing unit tests

The PR adds 44 tests under:

  • gpu-offload/controller/tests
  • gpu-offload/runtime/tests

Please consider adding a path-gated reusable workflow based on the existing pytest-inference.yml or pytest-dm-tools.yml patterns. Since each subproject has its own manifest and lock, it would be preferable to run them in separate frozen environments and publish separate coverage artifacts or Codecov flags.

A frozen controller test invocation currently fails because Hatch cannot infer the wheel contents. Could you clarify the intended packaging contract and either configure the build target or mark the project as script-only and test it with --no-install-project?

The new job should also be included in the PR and main validation summaries.

2. CPU smoke coverage

Please consider extending shared/ci/smoke-import.sh and the matrix in smoke-cpu.yml with GPU-offload probes. These could install from the committed locks and verify the runtime and controller import surfaces without requiring GPU hardware.

For container validation, could the existing path-gated runtime-image smoke pattern be reused to build and probe the controller, runtime payload and first-run CPU images?

3. CPU-only Kubernetes E2E

The PR already contains most of a suitable E2E scenario:

  • kind cluster creation;
  • controller and runtime image-build scripts;
  • Helm installation;
  • the first-run chart and deployment script;
  • check-execution.sh, which verifies an actual remote call.

Please consider composing these assets into one path-gated workflow on a standard GitHub runner. Keeping the scenario logic in the existing scripts would avoid duplicating it in workflow YAML.

This CPU path could verify chart installation, webhook TLS/RBAC, admission mutation, generated server reconciliation, packaging, networking and remote execution. GPU allocation, CUDA execution, latency measurements and robot motion could remain separate hardware-backed qualification checks.

4. Containerfile discovery

container-scan.yml uses scripts/security/discover-base-images.sh, which currently discovers only *Dockerfile*. Several new images use Containerfile, so their bases appear to be outside this control.

Could you please extend the existing discovery and change-trigger logic to include Containerfile*? If final-image scanning is desired, please consider reusing the current Trivy tooling against images produced by the CPU image-smoke job.

5. Helm and remote.yaml validation

There does not appear to be a shared Helm-validation harness today. Please consider adding a small reusable script or job that runs helm lint --strict and renders the main, first-run, SO-101 and UR10e charts with representative CPU and GPU values.

For remote.yaml, could the production validate_xavier_config() implementation be reused in a parameterized test over every committed example? Cross-reference checks such as unique stage names and valid remoteloc targets could be added there without introducing a second schema implementation.

The existing YAML job runs actionlint against GitHub workflows only, so it does not cover Helm values or remote.yaml.

6. SO-101 submodule integrity

The standard checkout does not initialize gpu-offload/examples/so101-real-hardware/upstream.

Please consider adding a targeted check that confirms .lerobot-version agrees with the committed gitlink, while initializing the submodule only for the SO-101 build job. update_upstream.sh could remain the single update mechanism.

A practical delivery order might be:

  1. Unit tests and controller packaging.
  2. Path gating and validation-summary wiring.
  3. Containerfile discovery.
  4. CPU import and image smoke.
  5. Helm and remote.yaml validation.
  6. CPU kind E2E.
  7. SO-101 submodule validation.

Lock consistency, Ruff, ShellCheck, documentation checks, CodeQL and dependency controls already apply and can remain unchanged.

Comment thread .gitmodules Outdated
…ndency

Address PR review feedback that the vendored LeRobot submodule costs ~403 MiB
to initialize (git history + worktree) while the Docker build never patches
the source. Since lerobot==0.6.1 is published on PyPI with all required
entry points, depend on it directly via pyproject.toml/uv.lock instead of
vendoring the repository.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…oject.toml

- gpu-offload/examples/so101-real-hardware/pyproject.toml: reformat with
  taplo (4-space indent) to satisfy TOML Format Check.
- .github/workflows/dependency-review.yml: allow GHSA-xrqw-3rrv-vx5w
  (transformers path traversal via save_pretrained chat_template keys,
  CVE-2026-9856). lerobot==0.6.1 constrains transformers<5.6.0 and the fix
  landed in 5.10.0, so no compatible patched version exists yet; the SO-101
  example never calls save_pretrained() on a Hub-downloaded tokenizer or
  processor, only load()s its own pinned checkpoint.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Address PR review feedback that the 44 tests added under
gpu-offload/{controller,runtime}/tests never run in CI.

- gpu-offload/controller/pyproject.toml: drop the unused [build-system]
  (Hatchling) section and mark the project virtual (tool.uv.package = false).
  mutate.py is a flat script, never installed as a wheel or built as an
  editable install anywhere; Hatchling's wheel builder failed with "Unable
  to determine which files to ship" because there is no gpu_offload_controller/
  package directory. Re-locked uv.lock accordingly (source: editable -> virtual).
- .github/workflows/pytest-gpu-offload.yml: new reusable workflow, matrixed
  over controller and runtime, each synced from its own uv.lock in an
  isolated environment and run with pytest.
- pr-validation.yml: path-gate the new job on gpu-offload/{controller,runtime}/
  changes and add it to the validation summary.
- main.yml: run the new job unconditionally and add it to the release gate.
- CONTRIBUTING.md: document the new component suite and its local-run command.

Coverage/Codecov integration is intentionally left out of scope; both
subprojects currently test without coverage tooling.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@dwhiteddsoft

Copy link
Copy Markdown
Contributor Author

Thanks for the detailed breakdown — this is a lot of good infra to add. To keep this PR reviewable I've scoped it to the highest-value, lowest-risk pieces from your list and will treat the rest as follow-up work:

Done in dce4e6b / 2b8b0b0:

  • [Task]: Enforce strict compiler warning equivalents #6 SO-101 submodule integrity: moot now — the submodule is gone entirely (see the .gitmodules thread), replaced with a pinned lerobot==0.6.1 PyPI dependency + uv.lock.
  • chore: track HVE-Core workflow and script migration #1 Unit tests / controller packaging: confirmed and fixed the Hatch packaging bug you flagged — gpu-offload/controller had no package directory matching its project name, so uv sync failed trying to build it as an editable wheel. Dropped the unused [build-system]/Hatchling section and marked it [tool.uv] package = false (it's a flat script, never installed as a wheel anywhere). Added .github/workflows/pytest-gpu-offload.yml, a reusable workflow matrixed over gpu-offload/controller and gpu-offload/runtime, each synced from its own lock in an isolated environment, path-gated in pr-validation.yml and unconditional in main.yml, wired into both validation summaries.

Left for follow-up (would meaningfully grow this PR and each involves its own design decisions worth reviewing separately):

Coverage/Codecov integration for the new pytest job is also intentionally out of scope for now — neither subproject has coverage tooling wired up yet.

Happy to open a tracking issue for the remaining items if that's useful.

@dwhiteddsoft

Copy link
Copy Markdown
Contributor Author

Replying here since this predates my more detailed reply on your follow-up comment (#issuecomment-5726472922):

  • Added a path-gated Pytest GPU Offload CI job (.github/workflows/pytest-gpu-offload.yml) that runs both gpu-offload/controller/tests and gpu-offload/runtime/tests in their own locked environments, wired into both the PR and main validation summaries. It's green on this PR now.
  • While wiring it up, found and fixed a real bug you'd have hit: gpu-offload/controller's pyproject.toml declared a Hatchling [build-system] but had no package directory matching its project name, so uv sync failed trying to build it as an editable wheel. Fixed by marking it [tool.uv] package = false (it's a flat script, never installed as a wheel anywhere).
  • Corrected the PR description — it no longer claims the pytest suites are "relying on CI" without being run anywhere; both now run and pass locally and in CI.

Building/scanning the images, Helm lint/render, and a CPU-only smoke test are still open (tracked in my reply on the other comment) — those are more involved and I'd like to scope them as follow-up work rather than grow this PR further.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added inline review comments for the current head.

Comment thread .github/workflows/pr-validation.yml
Comment thread gpu-offload/controller/mutate.py
Comment thread gpu-offload/controller/mutate.py
Comment thread gpu-offload/runtime/remoter/remoter.py
Comment thread gpu-offload/helm/gpu-offload/templates/mutating-webhook.yaml
Comment thread .github/workflows/pytest-gpu-offload.yml
Comment thread gpu-offload/scripts/preflight-check.sh
Comment thread gpu-offload/runtime/pyproject.toml Outdated
Comment thread gpu-offload/README.md
Comment thread gpu-offload/docs/TODO.md
@katriendg

Copy link
Copy Markdown
Collaborator

Thanks for the substantial GPU-offload integration and for addressing earlier review rounds by Alexandre. Also adding some inline comments. The added pytest workflow and submodule removal are useful completed responses. The proposed follow-up split should be narrowed: hardware-only GPU qualification can be tracked separately, but CPU smoke, chart/config validation, image build discovery, Codecov wiring, and a validated fail-closed rollback path belong in this PR because they validate or govern the newly introduced merge surface. TODO.md should not remain the sole durable owner of known implementation defects; unresolved release-relevant items need explicit issue ownership and blocking/non-blocking disposition. Open question is even if a file named TODO.md should be there? Leaving one critical and some High findings, I believe those are key to address before merge? And then for Medium findings letting you decide which ones to tackle or add to backlog issues to track.

The customer-facing documentation should also establish the value before exposing the implementation machinery. State plainly that this is an optional deployment profile for running policies that exceed a robot's onboard compute, power, thermal, or battery budget on a nearby site GPU while robot I/O and control remain local. Add a decision guide covering when to use onboard inference, this GPU-offload profile, a conventional inference service, or cloud inference, together with explicit non-goals for disconnected and hard real-time operation. Clarify the intended audience and prerequisites so users without Kubernetes operations experience do not mistake this specialized capability for the default inference path.

The naming also needs an explicit decision. The PR title and documentation use “Xavier,” but the implementation does not appear to require NVIDIA Jetson Xavier hardware; the term instead refers to another project), wonder if we keep that and if this will become clear for users. If “Xavier” remains in the title or user-facing terminology, confirm that this ambiguity is intentional and add a prominent clarification that it is not an NVIDIA Jetson Xavier requirement. Otherwise, prefer “GPU Offload” in the title and customer-facing navigation, and keep Xavier in provenance or implementation-history material.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added three follow-up inline comments on dependency update coverage and pinning enforcement.

Comment thread gpu-offload/controller/pyproject.toml
Comment thread .github/workflows/dependency-review.yml
Comment thread gpu-offload/controller/Containerfile Outdated
pr-validation.yml only gated the gpu-offload controller/runtime unit
tests, so changes confined to the Helm chart, host scripts, local
registry, or examples (including committed remote.yaml manifests and
Containerfiles) could merge with zero GPU-offload validation.

Add a new gpu_offload_e2e path filter covering those paths, and wire
it to a new reusable workflow that runs the existing first-run example
end to end against a CPU-only kind cluster: real admission (the
webhook mutates the client and generates the server Deployment) and a
real remote call (the function executes on the generated server pod,
not the client). Every GPU-only step in the pipeline already self-skips
on a GPU-less runner, so this reuses the existing mise tasks as-is.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Extend the root Ruff config instead of redefining it, per PR review
feedback. Fixes the small set of originally-suppressed E712/E731/F841
instances directly, and scopes the rest of the vendored remoter/
package's findings to narrowly-justified per-line noqa comments
instead of a blanket per-file-ignore.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
GitHub Actions in gpu-offload were already SHA-pinned, but the container
images referenced by its Dockerfiles/Containerfiles, shell-generated
manifests, and Helm values were not. The required pinning scanner missed
this because it explicitly excluded Dockerfile FROM lines (deferring to
OpenSSF Scorecard, which never actually runs on pull_request), only scanned
docker image YAML under workflow/infrastructure paths, and discovered base
images via a *Dockerfile* glob that never matches a file named Containerfile.

- Digest-pin every tag-only image under gpu-offload/: the controller,
  first-run, ur10e-single, and so101 Python base images, and the CUDA base
  image in cluster-gpu-check.sh. configure-k3s-nvidia.sh's device-plugin
  image gets a paired --plugin-digest flag (alongside --plugin-version) and
  a guard against silently pinning an overridden version to the old digest.
- Add Get-DockerfileFromViolations (new 'dockerfile-base-image' type) to
  Test-DependencyPinning.ps1, handling multi-stage aliases, scratch, and
  bare ${VAR} bases; add '**/*.sh' to the existing docker type so shell-
  embedded image: manifests are covered too. Both wired into the actual
  CI default type list.
- Fix discover-base-images.sh's glob to also match Containerfile, and its
  digest-extraction regex to not truncate an ARG-templated FROM line into
  a garbage partial match.
- Add dependabot docker ecosystem entries for the gpu-offload directories
  so the new pins get kept up to date.

Verified: 266/266 Pester tests pass, zero pinning violations under the new
type or under gpu-offload, and all digests independently re-resolved
against their registries.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
# Conflicts:
#	docs/README.md
#	docs/contributing/ROADMAP.md
#	docs/getting-started/README.md
#	docs/infrastructure/cleanup.md
#	docs/infrastructure/cluster-setup-advanced.md
#	docs/training/isaac-lab-training.md
@github-advanced-security

Copy link
Copy Markdown
Contributor

You are seeing this message because GitHub Code Scanning has recently been set up for this repository, or this pull request contains the workflow file for the Code Scanning tool.

What Enabling Code Scanning Means:

  • The 'Security' tab will display more code scanning analysis results (e.g., for the default branch).
  • Depending on your configuration and choice of analysis tool, future pull requests will be annotated with code scanning analysis results.
  • You will be able to see the analysis results for the pull request's branch on this overview once the scans have completed and the checks have passed.

For more information about GitHub Code Scanning, check out the documentation.

Splitting into two sentences keeps each line under the 500-char
MD013 limit after combining upstream's Node.js/uv correction with
this branch's optional-local-Kubernetes framing during the main merge.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
f-10-setup listed ten tasks in `depends`, but mise treats sibling
depends entries as independent and runs them in parallel unless a task
declares its own dependency on another. With no such edges, cluster-
smoke-check.sh raced ahead of cluster-create.sh and failed immediately
with "context was not found for specified context: kind-gpu-offload"
(this workflow's first-ever CI run, since Actions required manual
approval on every prior push to this fork).

Chain each task to its true predecessor, matching the order the task
IDs already encode: a-detect -> b-host-10-preflight -> c-cluster-20-
create -> c-cluster-21-check -> c-cluster-30-gpu-enable -> c-cluster-
31-gpu-check -> d-offload-40-build-images -> d-offload-41-load-images
-> d-offload-43-install-controller -> d-offload-50-deploy. Also chain
c-cluster-90-delete after d-offload-90-teardown so teardown-all removes
workloads before deleting the cluster they ran on.

Verified with `mise tasks deps f-10-setup` (now a single linear chain,
was ten unordered siblings) and `mise run f-10-setup` locally (tasks
now execute strictly in order; it stops at the first missing local
tool rather than racing ahead).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…Containerfile

RUN --mount=type=secret,id=PIP_INDEX_URL,env=PIP_INDEX_URL fails under
Podman/buildah with "secret should have syntax id=id[,target=path,
required=bool,mode=uint,uid=uint,gid=uint]" - env is not a recognized
attribute on the RUN --mount=type=secret directive itself in this
buildah version (it belongs on the outer `podman build --secret
id=...,env=...` flag, which build-controller-image.sh already sets
correctly). Read the secret from its mounted file instead, which works
both with and without the secret supplied.

Reproduced and verified the fix locally with the same podman version
(5.8.5) the CI runner uses: the original RUN line fails identically
locally; the fixed line proceeds past the mount step in both the
secret-provided and no-secret cases.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Required by the pr-validation-summary gate (the sole required check on
main), which fails if msdate-freshness fails. Cross-checked every claim
in the doc against the actual release workflow (main.yml) rather than
blindly bumping the date: the sigstore==4.3.0 pin, the gitsign
cert-identity string, the attest-build-provenance step, and all four
SBOM files (SPDX/CycloneDX, source/dependencies) match exactly what's
described. No content changes needed.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
The mutating webhook has failurePolicy: Fail, so any admission call
that can't reach it blocks every offload-labeled pod/deployment/job.
install-controller.sh only waited for `kubectl rollout status` (pod
Ready), not for the Service's Endpoints to actually populate, leaving
a race where the very next resource creation (deploy.sh) hit
"connection refused" against the webhook Service before routing had
propagated - reproduced in CI on commit 0faa279, ~2s after rollout
success.

Add a `kubectl wait --for=jsonpath=...` on the Endpoints object after
rollout status. Verified against a real local kind cluster (same
kubectl version CI uses): the wait resolves immediately once an
Endpoints subset has a real address, and correctly times out (does not
false-succeed) against an Endpoints object with none.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…s state

The Endpoints-populated wait from the previous commit was insufficient:
CI showed "endpoints/gpu-offload-mutate condition met" immediately
followed (~250ms later) by the same "connection refused" on the very
next admission call. The Endpoints API object reflects the pod's
address before kube-proxy has actually programmed the ClusterIP's
netfilter rule on the node - a control-plane/data-plane propagation
gap that no API object's state can promise against.

Replace it with a genuine reachability probe: retry
`kubectl apply --dry-run=server` against a minimal disposable Pod
carrying the webhook's trigger label. dry-run requests still invoke
mutating webhooks that declare sideEffects: None (this one does), so
this exercises the real webhook call without persisting anything.

Verified the full mechanism locally against a real kind cluster with a
throwaway webhook standing in for the real one: with no backing
listener it fails identically to CI ("connect: connection refused");
once a listener exists it fails differently ("connection reset by
peer" - proving reachability, not webhook logic, is what changes the
outcome). Also traced controller/mutate.py's DoMutate to confirm a
labeled-but-unannotated probe pod is legitimately allowed (`xaviercfg
is None -> return False -> allowed: true`), not rejected for missing
config, so a real, ready webhook will make this probe succeed.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@dwhiteddsoft

Copy link
Copy Markdown
Contributor Author

Katrien De Graeve (@katriendg) and Alexandre Gattiker (@algattik) , thank you for the feedback and the assistance in making this better. I have done my best to address all the issues raised. Some wil have to be put off for a review with the MS Research team. I will merge this and address the remainder of the issues in follow on work denoted here

@dwhiteddsoft
David White (dwhiteddsoft) merged commit ccd7603 into microsoft:main Sep 21, 2026
72 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat(deploy): add Xavier GPU offload for robot inference

10 participants