Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
6456905
Run ARM CI tests through the x86 run-package-tests action
hujc7 Oct 1, 2026
a018260
Drop ARM CI's duplicate Cartpole training smoke
hujc7 Oct 1, 2026
4489717
Re-enable the shadow-hand rendering and Isaac Sim PhysX Cartpole test…
hujc7 Oct 1, 2026
adccfae
Run the operational-space controller tests on ARM
hujc7 Oct 1, 2026
8938bf9
Stop running the kuka visual-material kit-less rendering test on ARM
hujc7 Oct 1, 2026
3c22994
Build Docker images for the runner's architecture by default
hujc7 Oct 1, 2026
7850851
Drop run-package-tests' image-source input
hujc7 Oct 1, 2026
a7e156c
Read the default build platform from the Docker daemon
hujc7 Oct 1, 2026
bd5f11f
Keep docker-build's platform default unchanged
hujc7 Oct 1, 2026
de9960c
Pass the target platform through run-package-tests
hujc7 Oct 1, 2026
097d6b0
Merge develop into ARM CI alignment branch
hujc7 Oct 6, 2026
f6b0e2d
Preserve ARM camera smoke coverage with shared caches
hujc7 Oct 6, 2026
8fa6f50
Re-enable ARM PhysX training in the uv workspace tests
hujc7 Oct 6, 2026
735bef8
Merge remote-tracking branch 'upstream/develop' into jichuanh/arm-ci-…
hujc7 Oct 6, 2026
7b73daf
Preserve the ARM system preload in installation probes
hujc7 Oct 6, 2026
1bfed29
Merge remote-tracking branch 'upstream/develop' into jichuanh/arm-ci-…
hujc7 Oct 12, 2026
211ff55
Merge remote-tracking branch 'upstream/develop' into jichuanh/arm-ci-…
hujc7 Oct 12, 2026
a29615a
Build ARM CI images in the runner daemon and run test files in parallel
hujc7 Oct 12, 2026
3392b80
TEMP: Save ARM caches from PR runs for the warm benchmark
hujc7 Oct 12, 2026
3db7d91
Redirect the Newton RTX visualizer shader cache
hujc7 Oct 12, 2026
dd17bc1
Serialize rendering test files that start OVRTX through rendering_tes…
hujc7 Oct 12, 2026
998d850
Export daemon-built CI image layers with zstd
hujc7 Oct 12, 2026
2954457
Revert "TEMP: Save ARM caches from PR runs for the warm benchmark"
hujc7 Oct 12, 2026
62226c7
Revert "Redirect the Newton RTX visualizer shader cache"
hujc7 Oct 12, 2026
5a940d4
Run ARM Cartpole smoke tests outside the shared test action
hujc7 Oct 12, 2026
d02aedd
State which callers build in the runner daemon
hujc7 Oct 12, 2026
01a3ff4
Merge remote-tracking branch 'upstream/develop' into jichuanh/arm-ci-…
hujc7 Oct 12, 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
25 changes: 20 additions & 5 deletions .github/actions/docker-build/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -168,13 +168,28 @@ runs:
BUILD_ARGS+=( --cache-to "${{ inputs.cache-to }}" )
fi
BUILDER_NAME="docker-build-${{ github.run_id }}-${{ github.job }}"
docker buildx create --use --driver docker-container --name "${BUILDER_NAME}" \
|| docker buildx use "${BUILDER_NAME}"
trap 'docker buildx rm "${BUILDER_NAME}" || true' EXIT
# Registry cache export needs a docker-container builder, whose image must
# then be exported and loaded into the daemon. Callers without a registry
# cache (ARM, which has no ECR) build in the daemon itself instead, so the
# image, layer cache and uv cache mount stay on the runner. A containerd
# image store still compresses new layers; zstd level 1 is several times
# faster than the default gzip for the multi-GB venv layer.
if [ -n "${{ inputs.cache-from }}${{ inputs.cache-to }}" ]; then

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.

🟡 Warning · Design Architecture — Builder choice inferred from cache inputs

Empty cache inputs also occur on ecr-build-push-pull's documented no-ECR fallback, so those builds now export via --output type=image on the default builder, which fails without a containerd image store. Gate on an explicit input.

BUILDER_NAME="docker-build-${{ github.run_id }}-${{ github.job }}"
docker buildx create --driver docker-container --name "${BUILDER_NAME}" \
|| true
trap 'docker buildx rm "${BUILDER_NAME}" || true' EXIT
BUILD_ARGS+=( --builder "${BUILDER_NAME}" --load )
else
BUILD_ARGS+=(
--builder default
--provenance=false
--output "type=image,name=${{ inputs.image-tag }},compression=zstd,compression-level=1"
)
fi
echo "🔵 Building ${{ inputs.image-tag }}..."
docker buildx build --load "${BUILD_ARGS[@]}" "${{ inputs.context-path }}"
docker buildx build "${BUILD_ARGS[@]}" "${{ inputs.context-path }}"
echo "was-built=true" >> "$GITHUB_OUTPUT"
##### 6: Layer the CI pytest harness onto the built image #####
Expand Down
6 changes: 6 additions & 0 deletions .github/actions/ecr-build-push-pull/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@ inputs:
description: 'Path to Dockerfile, relative to the repository root'
default: 'docker/Dockerfile.base'
required: false
platform:
description: 'Target platform for the build and its dependency-cache identity'
default: 'linux/amd64'
required: false
ecr-url:
description: >
ECR repository URL (e.g. "123456789.dkr.ecr.us-west-2.amazonaws.com/my-repo").
Expand Down Expand Up @@ -208,6 +212,7 @@ runs:
dockerfile-path: ${{ inputs.dockerfile-path }}
isaacsim-base-image: ${{ inputs.isaacsim-base-image }}
isaacsim-version: ${{ inputs.isaacsim-version }}
platform: ${{ inputs.platform }}

- name: Check deps cache
id: deps-cache
Expand Down Expand Up @@ -250,6 +255,7 @@ runs:
isaacsim-base-image: ${{ inputs.isaacsim-base-image }}
isaacsim-version: ${{ inputs.isaacsim-version }}
dockerfile-path: ${{ inputs.dockerfile-path }}
platform: ${{ inputs.platform }}
cache-from: ${{ steps.resolve-ecr.outputs.available == 'true' && format('type=registry,ref={0}', env.CACHE_IMAGE) || '' }}
cache-to: ${{ steps.resolve-ecr.outputs.available == 'true' && format('type=registry,ref={0},mode=max', env.CACHE_IMAGE) || '' }}
deps-hash: ${{ steps.deps-hash.outputs.hash }}
Expand Down
22 changes: 20 additions & 2 deletions .github/actions/run-package-tests/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,14 @@ inputs:
description: 'ECR cache tag'
default: 'cache-base'
required: false
ci-marker:
description: 'Optional pytest -m marker expression applied to every test file (e.g. arm_ci)'
default: ''
required: false
platform:
description: 'Target platform if the image has to be built'
default: 'linux/amd64'
required: false
filter-pattern:
description: >-
Pattern to filter test files (e.g., isaaclab_tasks); test files whose path
Expand Down Expand Up @@ -111,14 +119,15 @@ inputs:
newest compatible snapshot and never writes; every test job uses this, so
a pull request can never publish kernels built from unmerged code. 'save'
restores the same way and writes the result back as a new snapshot, making
post-merge a cumulative writeback; only warm-warp-cache uses it.
post-merge a cumulative writeback; only trusted post-merge cache producers
use it.
default: ''
required: false
ovrtx-shader-cache:
description: >-
OVRTX shader cache mode. Empty disables the cache. 'restore' reads the
newest compatible snapshot and never writes. 'save' restores and writes
back a new snapshot; only warm-ovrtx-cache uses it.
back a new snapshot; only trusted post-merge cache producers use it.
default: ''
required: false
ovrtx-shader-cache-trees:
Expand Down Expand Up @@ -146,6 +155,13 @@ inputs:
container-name:
description: 'Docker container name prefix (run-id is appended automatically)'
required: true
outputs:
warp-cache-host-dir:
description: 'Host directory mounted as the Warp kernel cache; empty when the cache is disabled'
value: ${{ steps.warp-cache-key.outputs.host-dir }}
ovrtx-shader-cache-host-dir:
description: 'Host directory mounted as the OVRTX shader cache; empty when the cache is disabled'
value: ${{ steps.ovrtx-cache.outputs.host-dir }}
runs:
using: composite
steps:
Expand Down Expand Up @@ -197,6 +213,7 @@ runs:
isaacsim-version: ${{ inputs.isaacsim-version }}
dockerfile-path: ${{ inputs.dockerfile-path }}
cache-tag: ${{ inputs.cache-tag }}
platform: ${{ inputs.platform }}

- name: Report pull duration
if: always()
Expand Down Expand Up @@ -348,6 +365,7 @@ runs:
include-files: ${{ inputs.include-files }}
test-node-ids-file: ${{ inputs.test-node-ids-file }}
test-node-ids-key: ${{ inputs.test-node-ids-key }}
ci-marker: ${{ inputs.ci-marker }}
volume-mount-source: ${{ github.workspace }}
extra-pip-packages: ${{ inputs.extra-pip-packages }}
extra-uv-packages: ${{ inputs.extra-uv-packages }}
Expand Down
59 changes: 36 additions & 23 deletions .github/workflows/arm-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -174,40 +174,53 @@ jobs:
uses: ./.github/actions/resolve-ov-pins
id: ov_pins

# Same action as the x86 test jobs, with four file slots; rendering files still
# run one at a time. With no ECR on the Spark, it picks up the image built above from the local
# store. Pushes to protected branches publish the arm64 Warp and OVRTX caches
# that pull requests restore, the role x86's cache-warm jobs play.
- name: Run arm_ci marker tests
uses: ./.github/actions/run-tests
id: tests
uses: ./.github/actions/run-package-tests
with:
test-path: tools
result-file: arm-ci-report.xml
container-name: isaac-lab-arm-ci-${{ github.run_id }}-${{ github.run_attempt }}
image-tag: ${{ needs.config.outputs.ci_image_tag }}-arm64
extra-pip-packages: "${{ steps.ov_pins.outputs.ovrtx }} ${{ steps.ov_pins.outputs.ovphysx }}"
# ovstage rendering cases are post-merge only on x86 as well.
test-k-expr: not ovphysx and not ovstage
platform: linux/arm64
isaacsim-base-image: ${{ needs.config.outputs.isaacsim_image_name }}
isaacsim-version: ${{ needs.config.outputs.isaacsim_image_tag }}
ci-marker: arm_ci
volume-mount-source: ${{ github.workspace }}

- name: Run arm_ci OVPhysX marker tests
uses: ./.github/actions/run-tests
with:
test-path: tools
result-file: arm-ci-ovphysx-report.xml
container-name: isaac-lab-arm-ci-ovphysx-${{ github.run_id }}-${{ github.run_attempt }}
image-tag: ${{ needs.config.outputs.ci_image_tag }}-arm64
# ovstage rendering cases are post-merge only on x86 as well.
test-k-expr: not ovstage
test-jobs: "4"
extra-pip-packages: "${{ steps.ov_pins.outputs.ovrtx }} ${{ steps.ov_pins.outputs.ovphysx }}"
test-k-expr: ovphysx and not ovstage
ci-marker: arm_ci
volume-mount-source: ${{ github.workspace }}

warp-cache: ${{ github.event_name == 'push' && 'save' || 'restore' }}
ovrtx-shader-cache: ${{ github.event_name == 'push' && 'save' || 'restore' }}
ovrtx-shader-cache-trees: kitless
result-file: arm-ci-report.xml
container-name: isaac-lab-arm-ci
omni-github-test-type: arm-ci

- name: Run shared Cartpole smoke
# The test orchestrator skips install_ci/, so the Cartpole state/camera probes
# run here in the same image, reusing the restored Warp and OVRTX caches.
- name: Run Cartpole smoke tests
uses: ./.github/actions/run-tests
Comment on lines +203 to 204

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.

P2 Smoke caches miss publication

On protected-branch pushes, run-package-tests saves both caches before this Cartpole step runs. Any cache files added by the state or camera probes miss the published snapshot, so later PRs may compile them again. Run these probes before the action saves the caches, as the PR description intends.

with:
test-path: source/isaaclab/test/install_ci/misc/cartpole_training_smoke.py
result-file: arm-ci-cartpole-smoke-report.xml
container-name: isaac-lab-arm-ci-cartpole-${{ github.run_id }}-${{ github.run_attempt }}
result-file: arm-ci-smoke-report.xml
container-name: isaac-lab-arm-ci-smoke-${{ github.run_id }}-${{ github.run_attempt }}
image-tag: ${{ needs.config.outputs.ci_image_tag }}-arm64
volume-mount-source: ${{ github.workspace }}
warp-cache-host-dir: ${{ steps.tests.outputs.warp-cache-host-dir }}
ovrtx-shader-cache-host-dir: ${{ steps.tests.outputs.ovrtx-shader-cache-host-dir }}
omni-github-test-type: arm-ci

# run-tests records failures in the report rather than its exit code.
- name: Check Cartpole smoke results
shell: bash
run: |
report=reports/arm-ci-smoke-report.xml
if [ ! -f "$report" ] || grep -qE '(failures|errors)="[1-9][0-9]*"' "$report"; then
echo "::error::Cartpole smoke tests failed or produced no report: $report"
exit 1
fi
- name: Upload test reports
if: always()
Expand Down
Empty file.
4 changes: 1 addition & 3 deletions source/isaaclab/test/controllers/test_operational_space.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
import pytest
import torch

pytestmark = pytest.mark.arm_ci
pytestmark = [pytest.mark.integration, pytest.mark.arm_ci]

import isaaclab.envs.mdp as mdp
import isaaclab.sim as sim_utils
Expand Down Expand Up @@ -52,8 +52,6 @@

from isaaclab_assets import FRANKA_PANDA_LEGACY_CFG, G1_29DOF_CFG # isort:skip

pytestmark = pytest.mark.integration


@pytest.fixture
def sim():
Expand Down
7 changes: 6 additions & 1 deletion source/isaaclab/test/install_ci/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,10 +58,15 @@ def run() -> Callable[..., str]:
clean_env["LD_PRELOAD"] = "/lib/aarch64-linux-gnu/libgomp.so.1"

def execute(*args: str, cwd: Path, env: dict[str, str] | None = None, timeout: int = 900) -> str:
command_env = {**clean_env, **(env or {})}
# Keep platform-required libraries when a probe adds another preload.
preloads = [clean_env.get("LD_PRELOAD", ""), (env or {}).get("LD_PRELOAD", "")]
if any(preloads):
command_env["LD_PRELOAD"] = ":".join(preload for preload in preloads if preload)
result = subprocess.run(
args,
cwd=cwd,
env={**clean_env, **(env or {})},
env=command_env,
text=True,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,8 @@

"""Shared state-training, camera-rendering, and camera-training Cartpole smoke probes.
The caller owns environment preparation. Installation CI executes this file
inside the environment it just installed, while architecture CI executes the
same probes inside its prepared image.
The caller owns environment preparation. Architecture CI executes all probes
inside its prepared image, while kit-less image validation runs the state-training probe.
"""

from __future__ import annotations
Expand Down
2 changes: 0 additions & 2 deletions source/isaaclab/test/install_ci/test_workspace.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,6 @@ def test_workspace_install(
def test_workspace_trains(workspace: tuple[str, Path, dict[str, str]], checkout: Path, run: Callable[..., str]) -> None:
runtime, directory, env = workspace
if runtime == "isaacsim":
if platform.machine().lower() in ("aarch64", "arm64"):
pytest.skip("Isaac Sim PhysX training is not functional on aarch64 yet")
if platform.system() == "Linux":
shim = directory / "venv/lib/python3.12/site-packages/isaacsim/kit/kernel/plugins/libcarb.env.shim.so"
assert shim.is_file()
Expand Down
Empty file.
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@
)

pytestmark = [
pytest.mark.arm_ci,
pytest.mark.integration,
pytest.mark.rendering,
pytest.mark.kitless,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@
rendering_test_shadow_hand,
)

# no arm_ci marker: intermittently stalls the arm64 runner mid-file (renderer hang after a few
# passing cases, killed at the per-file timeout); x86 kitless coverage remains in place
pytestmark = pytest.mark.arm_ci

_RENDERING_PARAMS = group_rendering_params(
make_kitless_rendering_params(MINIMAL_KITLESS_PHYSICS_RENDERER_AOV_COMBINATIONS)
)
Expand Down
11 changes: 9 additions & 2 deletions tools/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -1402,8 +1402,15 @@ def _run_one_pass(


def _starts_renderer(test_content: str) -> bool:
"""Return whether a test file's source starts an RTX renderer: Kit's, by enabling cameras, or OVRTX."""
return "enable_cameras=True" in test_content or "ovrtx" in test_content.lower()
"""Return whether a test file's source starts an RTX renderer: Kit's, by enabling cameras, or OVRTX.

Files built on ``rendering_test_utils`` pick their renderer through its helpers, so they name neither.
"""
return (
"enable_cameras=True" in test_content
or "ovrtx" in test_content.lower()
or "rendering_test_utils" in test_content

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.

P2 Helper tests occupy rendering lane

Importing rendering_test_utils does not mean a test starts a renderer. This now marks test_maybe_save_stage_golden.py as a rendering file, although it writes USD files and mocks isaaclab.sim.save_stage. The scheduler unnecessarily blocks it from running beside real rendering files and gives it the cold-shader timeout allowance. Use a narrower check and cover this helper-only test in the classifier tests.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

)


def _test_file_job(test_file: str, max_slots: int) -> TestFileJob:
Expand Down
Loading