Skip to content

Commit a936bf3

Browse files
authored
Merge branch 'develop' into shauryad/bug_fixes
2 parents 771ca54 + ab34e8c commit a936bf3

305 files changed

Lines changed: 6429 additions & 3666 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/actions/ecr-build-push-pull/README.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,19 @@ ECR is also used as the BuildKit layer cache.
1616
ecr-url: (optional, complete url for ECR storage)
1717
```
1818
19+
## Verifying a freshly built image
20+
21+
Pass `verify-test-path` to assert against the image before it is published:
22+
23+
```yaml
24+
verify-test-path: docker/test/test_image_invariants.py
25+
```
26+
27+
The tests run only on a full build, with `IMAGE_TAG` set, so the caller's job needs `uv`
28+
(`astral-sh/setup-uv`). A failure fails the action with nothing pushed, so the next run
29+
rebuilds rather than serving the bad image from the deps cache. Exact-tag and deps-cache hits skip
30+
them: that image passed when it was built.
31+
1932
## ECR URL resolution order
2033

2134
1. `ecr-url` input

.github/actions/ecr-build-push-pull/action.yml

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,16 @@ inputs:
3737
description: Tag used for the ECR layer cache image (e.g. "cache-base", "cache-curobo").
3838
required: false
3939
default: 'cache'
40+
verify-test-path:
41+
description: >
42+
Path to a test file or directory asserted against a freshly built image, before it is
43+
tagged or pushed. Tests run with IMAGE_TAG set; a failure fails the action with nothing
44+
published, so the next run rebuilds instead of inheriting the bad image from the cache.
45+
46+
Not run on an exact-tag or deps-cache hit: those serve an image that already passed when it
47+
was built.
48+
required: false
49+
default: ''
4050
pull-on-deps-hit:
4151
description: >
4252
Pull the image locally after a deps-cache hit. Needed by jobs that run
@@ -242,6 +252,23 @@ runs:
242252
cache-to: ${{ steps.resolve-ecr.outputs.available == 'true' && format('type=registry,ref={0},mode=max', env.CACHE_IMAGE) || '' }}
243253
deps-hash: ${{ steps.deps-hash.outputs.hash }}
244254

255+
# Assert against the image while it is only local: the push steps below publish under both
256+
# the commit tag and the deps tag, and a deps-cache hit later serves that image without
257+
# rebuilding it, so anything published unverified stays unverified.
258+
- name: Verify freshly built image
259+
if: >
260+
inputs.verify-test-path != '' &&
261+
steps.pull-exact.outputs.hit != 'true' &&
262+
steps.deps-cache.outputs.deps-cache-hit != 'true'
263+
shell: bash
264+
env:
265+
IMAGE_TAG: ${{ inputs.image-tag }}
266+
TEST_PATH: ${{ inputs.verify-test-path }}
267+
run: |
268+
set -euo pipefail
269+
uv run --no-project --with pytest \
270+
python -m pytest -q "${TEST_PATH}"
271+
245272
- name: Tag built image with ECR-prefixed name
246273
if: >
247274
steps.resolve-ecr.outputs.available == 'true' &&
Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
# Copyright (c) 2022-2026, The Isaac Lab Project Developers (https://github.com/isaac-sim/IsaacLab/blob/main/CONTRIBUTORS.md).
2+
# All rights reserved.
3+
#
4+
# SPDX-License-Identifier: BSD-3-Clause
5+
6+
name: 'OVRTX Shader Cache'
7+
description: >
8+
Restores, reports on and publishes the NVIDIA driver PSO blobs (nv_shadercache)
9+
that Isaac Lab rendering tests would otherwise recompile from scratch on every
10+
run. Requires a prior checkout.
11+
12+
A composite action cannot span the caller's test step, so the phases are
13+
separate invocations selected by 'mode': 'restore' before the tests, then
14+
either 'report' or 'save' after them. Every mode recomputes the keys from the
15+
same key.sh, so the collection a job reads and the one it writes cannot drift.
16+
17+
kit/ is compiled by the RTX renderer inside the Isaac Sim image and kitless/ by
18+
the pip-installed ovrtx wheel, so each tree is a separate entry keyed by its own
19+
producer. The mount layout that fills them lives in
20+
.github/actions/run-tests/run_tests.sh.
21+
22+
inputs:
23+
mode:
24+
description: >-
25+
'restore' reads the newest compatible snapshot of each tree and never
26+
writes. 'report' summarises how far the run compiled beyond what was
27+
restored. 'save' reports the same way and writes each tree the run
28+
actually added to back as a new snapshot; only the cache warmer uses it.
29+
required: true
30+
isaacsim-version:
31+
description: 'Isaac Sim image tag; identifies the exact Kit RTX build that compiles the kit/ blobs'
32+
required: true
33+
publishes:
34+
description: >-
35+
'true' when the same job later invokes this action in 'save' mode. The
36+
restore pass then fingerprints what it restored, so the save pass can tell
37+
a run that compiled new blobs from one that only read back the snapshot it
38+
started with. Other modes ignore it; leaving it false only costs a
39+
duplicate snapshot, never a lost one.
40+
required: false
41+
default: 'false'
42+
trees:
43+
description: >-
44+
Which cache tree(s) this job needs: 'kit', 'kitless', or 'both'. A job that
45+
only exercises one render path should request just that tree, so it does
46+
not pay the restore/save cost of the tree it never populates.
47+
default: 'both'
48+
required: false
49+
50+
outputs:
51+
host-dir:
52+
description: 'Parent directory holding both cache trees; bind-mounted into the test container'
53+
value: ${{ steps.compute.outputs.host-dir }}
54+
55+
runs:
56+
using: composite
57+
steps:
58+
# always() here and on every post-test step below: the caller already gates
59+
# this invocation on job status, and without it the composite inherits a
60+
# failed job context and skips work that stays valid after a test failure.
61+
# This step in particular has to run in every mode, since every later step
62+
# reads its outputs.
63+
- name: Compute OVRTX shader cache keys
64+
id: compute
65+
if: always()
66+
shell: bash
67+
env:
68+
ISAACSIM_VERSION: ${{ inputs.isaacsim-version }}
69+
run: bash "$GITHUB_ACTION_PATH/key.sh"
70+
71+
- name: Restore OVRTX kit shader cache
72+
if: inputs.mode == 'restore' && inputs.trees != 'kitless'
73+
id: restore-kit
74+
uses: actions/cache/restore@v4
75+
with:
76+
path: ${{ steps.compute.outputs.kit-dir }}
77+
key: ${{ steps.compute.outputs.kit-key }}
78+
restore-keys: ${{ steps.compute.outputs.kit-restore-keys }}
79+
80+
- name: Restore OVRTX kitless shader cache
81+
if: inputs.mode == 'restore' && inputs.trees != 'kit'
82+
id: restore-kitless
83+
uses: actions/cache/restore@v4
84+
with:
85+
path: ${{ steps.compute.outputs.kitless-dir }}
86+
key: ${{ steps.compute.outputs.kitless-key }}
87+
restore-keys: ${{ steps.compute.outputs.kitless-restore-keys }}
88+
89+
- name: Report restored OVRTX shader cache
90+
if: inputs.mode == 'restore'
91+
shell: bash
92+
env:
93+
HOST_DIR: ${{ steps.compute.outputs.host-dir }}
94+
TREES: ${{ inputs.trees }}
95+
KIT_MATCHED_KEY: ${{ steps.restore-kit.outputs.cache-matched-key }}
96+
KITLESS_MATCHED_KEY: ${{ steps.restore-kitless.outputs.cache-matched-key }}
97+
KIT_COLLECTION: ${{ steps.compute.outputs.kit-collection }}
98+
KITLESS_COLLECTION: ${{ steps.compute.outputs.kitless-collection }}
99+
PUBLISHES: ${{ inputs.publishes }}
100+
run: bash "$GITHUB_ACTION_PATH/report.sh" restore
101+
102+
- name: Report OVRTX shader cache growth
103+
if: always() && inputs.mode != 'restore'
104+
id: growth
105+
shell: bash
106+
env:
107+
HOST_DIR: ${{ steps.compute.outputs.host-dir }}
108+
TREES: ${{ inputs.trees }}
109+
run: bash "$GITHUB_ACTION_PATH/report.sh" growth
110+
111+
# Each tree is gated on its own file count so a populated kit/ never carries
112+
# an empty kitless/ into a published snapshot. The count is empty rather than
113+
# '0' when the growth step aborted before counting; both mean "not measured"
114+
# and both must block the save, since the snapshot would publish under a key
115+
# every consumer prefers over the last good one.
116+
#
117+
# The changed gate is a quota guard: every save writes a new immutable entry,
118+
# so a warm run that only read back what it restored would spend ~270 MB of
119+
# the repository's cache allowance on a duplicate of the snapshot it started
120+
# from. It is 'true' whenever the growth step could not rule that out.
121+
- name: Save OVRTX kit shader cache
122+
if: >-
123+
always() && inputs.mode == 'save' && inputs.trees != 'kitless'
124+
&& steps.growth.outputs.kit-files != ''
125+
&& steps.growth.outputs.kit-files != '0'
126+
&& steps.growth.outputs.kit-changed == 'true'
127+
uses: actions/cache/save@v4
128+
with:
129+
path: ${{ steps.compute.outputs.kit-dir }}
130+
key: ${{ steps.compute.outputs.kit-key }}
131+
132+
- name: Save OVRTX kitless shader cache
133+
if: >-
134+
always() && inputs.mode == 'save' && inputs.trees != 'kit'
135+
&& steps.growth.outputs.kitless-files != ''
136+
&& steps.growth.outputs.kitless-files != '0'
137+
&& steps.growth.outputs.kitless-changed == 'true'
138+
uses: actions/cache/save@v4
139+
with:
140+
path: ${{ steps.compute.outputs.kitless-dir }}
141+
key: ${{ steps.compute.outputs.kitless-key }}
142+
143+
- name: Verify requested OVRTX shader cache tree(s) were populated
144+
if: always() && inputs.mode == 'save'
145+
shell: bash
146+
env:
147+
TREES: ${{ inputs.trees }}
148+
KIT_FILES: ${{ steps.growth.outputs.kit-files }}
149+
KITLESS_FILES: ${{ steps.growth.outputs.kitless-files }}
150+
run: bash "$GITHUB_ACTION_PATH/verify.sh"
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
#!/usr/bin/env bash
2+
# Copyright (c) 2022-2026, The Isaac Lab Project Developers (https://github.com/isaac-sim/IsaacLab/blob/main/CONTRIBUTORS.md).
3+
# All rights reserved.
4+
#
5+
# SPDX-License-Identifier: BSD-3-Clause
6+
7+
# Computes the collection prefix, per-run write key and host directory for each
8+
# of the two OVRTX shader cache trees, and appends them to $GITHUB_OUTPUT.
9+
#
10+
# Every mode of the action runs this, so the collection a job restores from and
11+
# the one it saves to cannot drift. Only values that stay constant for the
12+
# lifetime of a job may be keyed on.
13+
#
14+
# Reads: ISAACSIM_VERSION, GITHUB_WORKSPACE (for pyproject.toml), RUNNER_OS,
15+
# RUNNER_ARCH, RUNNER_TEMP, GITHUB_SHA, GITHUB_RUN_ID, GITHUB_RUN_ATTEMPT.
16+
17+
set -euo pipefail
18+
19+
: "${ISAACSIM_VERSION:?isaacsim-version input is required}"
20+
21+
# nv_shadercache holds NVIDIA Vulkan driver PSO blobs, which are only valid for
22+
# the GPU architecture and driver version that compiled them, so both gate every
23+
# entry. Missing values fail rather than default: head and tr succeed on empty
24+
# input, so the captured values have to be tested directly.
25+
driver_ver="$(nvidia-smi --query-gpu=driver_version --format=csv,noheader 2>/dev/null | head -1 || true)"
26+
gpu_arch="$(nvidia-smi --query-gpu=compute_cap --format=csv,noheader 2>/dev/null | head -1 | tr -d '.' || true)"
27+
if [ -z "${driver_ver}" ] || [ -z "${gpu_arch}" ]; then
28+
echo "::error::nvidia-smi reported no driver version or compute capability; cannot key the OVRTX shader cache"
29+
exit 1
30+
fi
31+
32+
# [tool.isaaclab.versions].ovrtx in pyproject.toml, the single source of truth
33+
# resolve-ov-pins reads to build the pip specifier CI actually installs from
34+
# (a special internal release, not the public wheel uv.lock would resolve), so
35+
# the key stays a function of the commit and an upstream release cannot re-key
36+
# every open PR onto a cold collection.
37+
ovrtx_ver="$(awk -F'"' '
38+
/^\[tool\.isaaclab\.versions\]/ { in_section = 1; next }
39+
/^\[/ { in_section = 0 }
40+
in_section && /^ovrtx[[:space:]]*=/ { print $2; exit }
41+
' "${GITHUB_WORKSPACE}/pyproject.toml" || true)"
42+
if [ -z "${ovrtx_ver}" ]; then
43+
echo "::error::pyproject.toml [tool.isaaclab.versions] has no entry for ovrtx"
44+
exit 1
45+
fi
46+
47+
# Cache keys may not contain commas, and the isaacsim tag is only conventionally
48+
# bare.
49+
sanitize() { printf '%s' "$1" | tr -c 'A-Za-z0-9._-' '_'; }
50+
51+
base="v1-${RUNNER_OS}-${RUNNER_ARCH}-sm${gpu_arch}-drv$(sanitize "$driver_ver")"
52+
kit_collection="ovrtx-kit-${base}-isaacsim$(sanitize "$ISAACSIM_VERSION")"
53+
kitless_collection="ovrtx-kitless-${base}-ovrtx$(sanitize "$ovrtx_ver")"
54+
host_dir="${RUNNER_TEMP}/isaaclab-ovrtx-shader-cache"
55+
56+
# Write key is unique per run so each cumulative snapshot is its own immutable
57+
# entry; restore matches the collection prefix, newest first.
58+
emit() {
59+
echo "$1-collection=$2" >> "$GITHUB_OUTPUT"
60+
echo "$1-key=$2-${GITHUB_SHA}-${GITHUB_RUN_ID}-${GITHUB_RUN_ATTEMPT}" >> "$GITHUB_OUTPUT"
61+
echo "$1-restore-keys=$2-" >> "$GITHUB_OUTPUT"
62+
echo "$1-dir=${host_dir}/$1" >> "$GITHUB_OUTPUT"
63+
}
64+
emit kit "$kit_collection"
65+
emit kitless "$kitless_collection"
66+
echo "host-dir=${host_dir}" >> "$GITHUB_OUTPUT"
67+
68+
echo "OVRTX shader cache collections (driver ${driver_ver}, sm${gpu_arch}):"
69+
echo " kit/ ${kit_collection}"
70+
echo " kitless/ ${kitless_collection}"

0 commit comments

Comments
 (0)