Skip to content
Closed
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
54 changes: 54 additions & 0 deletions .github/actions/renderer-cache-key/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
# Copyright (c) 2022-2026, The Isaac Lab Project Developers (https://github.com/isaac-sim/IsaacLab/blob/main/CONTRIBUTORS.md).
# All rights reserved.
#
# SPDX-License-Identifier: BSD-3-Clause

name: 'Renderer Cache Key'
description: 'Computes a hardware- and Isaac-Sim-specific renderer cache key and host directory'

inputs:
isaacsim-base-image:
description: 'Isaac Sim base image repository'
required: true
isaacsim-version:
description: 'Isaac Sim base image version'
required: true

outputs:
collection:
description: 'Collection prefix shared by mutually compatible renderer caches'
value: ${{ steps.compute.outputs.collection }}
key:
description: 'Immutable cache key for this workflow run'
value: ${{ steps.compute.outputs.key }}
restore-keys:
description: 'Collection prefix used to restore the newest compatible snapshot'
value: ${{ steps.compute.outputs.restore-keys }}
host-dir:
description: 'Host directory bind-mounted over renderer cache paths'
value: ${{ steps.compute.outputs.host-dir }}

runs:
using: composite
steps:
- name: Compute renderer cache key
id: compute
shell: bash
env:
ISAACSIM_BASE_IMAGE: ${{ inputs.isaacsim-base-image }}
ISAACSIM_VERSION: ${{ inputs.isaacsim-version }}
run: |
set -euo pipefail

gpu_inventory="$(nvidia-smi --query-gpu=name,driver_version --format=csv,noheader | sort -u)"
compatibility="$ISAACSIM_BASE_IMAGE:$ISAACSIM_VERSION|$RUNNER_OS|$RUNNER_ARCH|$gpu_inventory"
compatibility_hash="$(printf '%s' "$compatibility" | sha256sum | cut -c1-20)"
collection="renderer-v1-${RUNNER_OS}-${RUNNER_ARCH}-${compatibility_hash}"

echo "collection=${collection}" >> "$GITHUB_OUTPUT"
echo "key=${collection}-${GITHUB_SHA}-${GITHUB_RUN_ID}-${GITHUB_RUN_ATTEMPT}" >> "$GITHUB_OUTPUT"
echo "restore-keys=${collection}-" >> "$GITHUB_OUTPUT"
echo "host-dir=${RUNNER_TEMP}/isaaclab-renderer-cache" >> "$GITHUB_OUTPUT"

echo "Renderer cache collection: ${collection}"
echo "Renderer cache compatibility: ${compatibility}"
109 changes: 109 additions & 0 deletions .github/actions/run-package-tests/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,13 @@ inputs:
post-merge a cumulative writeback; only warm-warp-cache uses it.
default: ''
required: false
renderer-cache:
description: >-
Kit, RTX, and NVIDIA compute cache mode. Empty disables the cache,
'restore' reads the newest compatible snapshot, and 'save' additionally
publishes a cumulative snapshot. PR jobs must use restore only.
default: ''
required: false
standalone-script-scope:
description: 'Enable standalone script smoke tests for this scripts/ subdirectory'
default: ''
Expand Down Expand Up @@ -192,6 +199,14 @@ runs:
id: warp-cache-key
uses: ./.github/actions/warp-cache-key

- name: Resolve renderer cache key
if: inputs.renderer-cache != ''
id: renderer-cache-key
uses: ./.github/actions/renderer-cache-key
with:
isaacsim-base-image: ${{ inputs.isaacsim-base-image }}
isaacsim-version: ${{ inputs.isaacsim-version }}

# Both modes restore. 'save' additionally writes the result back, so a
# post-merge run gets the same speedup as a pull request and the collection
# accumulates whatever was missing instead of restarting from empty.
Expand Down Expand Up @@ -237,6 +252,45 @@ runs:
echo "WARP_CACHE_FINGERPRINT_BEFORE=$fingerprint" >> "$GITHUB_ENV"
fi

- name: Restore renderer cache
if: inputs.renderer-cache != ''
id: renderer-cache-restore
uses: actions/cache/restore@v4
with:
path: ${{ steps.renderer-cache-key.outputs.host-dir }}
key: ${{ steps.renderer-cache-key.outputs.key }}
restore-keys: ${{ steps.renderer-cache-key.outputs.restore-keys }}

- name: Report restored renderer cache
if: inputs.renderer-cache != ''
shell: bash
env:
HOST_DIR: ${{ steps.renderer-cache-key.outputs.host-dir }}
MATCHED_KEY: ${{ steps.renderer-cache-restore.outputs.cache-matched-key }}
COLLECTION: ${{ steps.renderer-cache-key.outputs.collection }}
CACHE_MODE: ${{ inputs.renderer-cache }}
run: |
set -euo pipefail
mkdir -p "$HOST_DIR"
if [ ! -w "$HOST_DIR" ]; then
echo "::error::Renderer cache directory is not writable: $HOST_DIR"
exit 1
fi

if [ -z "$MATCHED_KEY" ]; then
echo "::warning::Renderer cache miss - no entry in collection ${COLLECTION}"
echo "RENDERER_CACHE_HIT=miss" >> "$GITHUB_ENV"
else
echo "Renderer cache hit: ${MATCHED_KEY}"
echo "RENDERER_CACHE_HIT=${MATCHED_KEY}" >> "$GITHUB_ENV"
fi
echo "RENDERER_CACHE_BYTES_BEFORE=$(du -sb "$HOST_DIR" | cut -f1)" >> "$GITHUB_ENV"
python3 .github/actions/run-package-tests/renderer_cache_inventory.py "$HOST_DIR" "Renderer cache restored"
if [ "$CACHE_MODE" = "save" ]; then
fingerprint="$(python3 .github/actions/run-package-tests/renderer_cache_inventory.py "$HOST_DIR" --fingerprint)"
echo "RENDERER_CACHE_FINGERPRINT_BEFORE=$fingerprint" >> "$GITHUB_ENV"
fi

- name: Setup wheelhouse registry authentication
if: inputs.wheelhouse-image != ''
uses: ./.github/actions/_lib/setup-docker-config
Expand Down Expand Up @@ -318,6 +372,7 @@ runs:
wheelhouse-packages: ${{ inputs.wheelhouse-packages }}
omni-github-test-type: ${{ inputs.omni-github-test-type }}
warp-cache-host-dir: ${{ steps.warp-cache-key.outputs.host-dir }}
renderer-cache-host-dir: ${{ steps.renderer-cache-key.outputs.host-dir }}
standalone-script-scope: ${{ inputs.standalone-script-scope }}
standalone-script-visualizer: ${{ inputs.standalone-script-visualizer }}
standalone-script-runtime-group: ${{ inputs.standalone-script-runtime-group }}
Expand Down Expand Up @@ -407,6 +462,60 @@ runs:
path: ${{ steps.warp-cache-key.outputs.host-dir }}
key: ${{ steps.warp-cache-key.outputs.key }}

- name: Report renderer cache growth
if: always() && inputs.renderer-cache != ''
id: renderer-cache-growth
shell: bash
env:
HOST_DIR: ${{ steps.renderer-cache-key.outputs.host-dir }}
CACHE_MODE: ${{ inputs.renderer-cache }}
run: |
set -euo pipefail

if [ ! -d "$HOST_DIR" ]; then
echo "Renderer cache directory is missing; nothing to report"
echo "empty=true" >> "$GITHUB_OUTPUT"
exit 0
fi

before="${RENDERER_CACHE_BYTES_BEFORE:-0}"
after="$(du -sb "$HOST_DIR" | cut -f1)"
grew=$(( after - before ))
echo "Renderer cache: restored $(( before / 1000000 )) MB, ended at $(( after / 1000000 )) MB (+$(( grew / 1000000 )) MB)"
python3 .github/actions/run-package-tests/renderer_cache_inventory.py "$HOST_DIR" "Renderer cache final"

if [ -z "$(find "$HOST_DIR" -type f -print -quit)" ]; then
echo "::warning::Renderer cache contains no files; skipping publish"
echo "empty=true" >> "$GITHUB_OUTPUT"
echo "changed=false" >> "$GITHUB_OUTPUT"
elif [ "$after" -gt 8000000000 ]; then
echo "::warning::Renderer cache exceeds 8 GB; skipping publish"
echo "empty=false" >> "$GITHUB_OUTPUT"
echo "too-large=true" >> "$GITHUB_OUTPUT"
echo "changed=false" >> "$GITHUB_OUTPUT"
else
echo "empty=false" >> "$GITHUB_OUTPUT"
echo "too-large=false" >> "$GITHUB_OUTPUT"
if [ "$CACHE_MODE" = "save" ] && [ "${RENDERER_CACHE_HIT:-miss}" != "miss" ]; then
fingerprint="$(python3 .github/actions/run-package-tests/renderer_cache_inventory.py "$HOST_DIR" --fingerprint)"
if [ "$fingerprint" = "${RENDERER_CACHE_FINGERPRINT_BEFORE:-}" ]; then
echo "Renderer cache contents are unchanged; skipping duplicate snapshot"
echo "changed=false" >> "$GITHUB_OUTPUT"
else
echo "changed=true" >> "$GITHUB_OUTPUT"
fi
else
echo "changed=true" >> "$GITHUB_OUTPUT"
fi
fi

- name: Save renderer cache
if: '!cancelled() && inputs.renderer-cache == ''save'' && steps.renderer-cache-growth.outputs.empty != ''true'' && steps.renderer-cache-growth.outputs.too-large != ''true'' && steps.renderer-cache-growth.outputs.changed == ''true'''
uses: actions/cache/save@v4
with:
path: ${{ steps.renderer-cache-key.outputs.host-dir }}
key: ${{ steps.renderer-cache-key.outputs.key }}

- name: Cleanup wheelhouse artifacts
if: always() && steps.extract-wheelhouse.outputs.wheelhouse_host_dir != ''
shell: bash
Expand Down
71 changes: 71 additions & 0 deletions .github/actions/run-package-tests/renderer_cache_inventory.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
# Copyright (c) 2022-2026, The Isaac Lab Project Developers (https://github.com/isaac-sim/IsaacLab/blob/main/CONTRIBUTORS.md).
# All rights reserved.
#
# SPDX-License-Identifier: BSD-3-Clause

"""Report the size and stable metadata fingerprint of a renderer cache tree."""

import hashlib
import os
import pathlib
import sys


def fingerprint(root: pathlib.Path) -> str:
"""Return a digest of cache file paths, sizes, and modification times."""
digest = hashlib.sha256()
for path in sorted(path for path in root.rglob("*") if path.is_file()):
try:
stat = path.stat()
except OSError:
continue
digest.update(path.relative_to(root).as_posix().encode())
digest.update(f"\0{stat.st_size}\0{stat.st_mtime_ns}\0".encode())
return digest.hexdigest()


def inventory(root: pathlib.Path) -> tuple[int, int, dict[str, int]]:
"""Return total bytes, file count, and bytes grouped by top-level directory."""
total_bytes = 0
file_count = 0
groups: dict[str, int] = {}
for dirpath, _, filenames in os.walk(root):
for name in filenames:
path = pathlib.Path(dirpath, name)
try:
size = path.stat().st_size
except OSError:
continue
relative = path.relative_to(root)
group = relative.parts[0] if relative.parts else "."
groups[group] = groups.get(group, 0) + size
total_bytes += size
file_count += 1
return total_bytes, file_count, groups


def main() -> int:
"""Print a fingerprint or human-readable renderer cache inventory."""
root = pathlib.Path(sys.argv[1])
if len(sys.argv) > 2 and sys.argv[2] == "--fingerprint":
print(fingerprint(root))
return 0

label = sys.argv[2] if len(sys.argv) > 2 else "Renderer cache"
if not root.is_dir():
print(f"{label}: directory is missing")
return 0

total_bytes, file_count, groups = inventory(root)
breakdown = ", ".join(f"{name}={size / 1e6:.0f} MB" for name, size in sorted(groups.items())) or "empty"
print(f"{label}: {total_bytes / 1e6:.0f} MB across {file_count} files ({breakdown})")

summary = os.environ.get("GITHUB_STEP_SUMMARY")
if summary:
with open(summary, "a", encoding="utf-8") as handle:
handle.write(f"🔵 {label}: {total_bytes / 1e6:.0f} MB across {file_count} files ({breakdown})\n")
return 0


if __name__ == "__main__":
raise SystemExit(main())
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
# Copyright (c) 2022-2026, The Isaac Lab Project Developers (https://github.com/isaac-sim/IsaacLab/blob/main/CONTRIBUTORS.md).
# All rights reserved.
#
# SPDX-License-Identifier: BSD-3-Clause

"""Tests for renderer cache inventory and change detection."""

import importlib.util
from pathlib import Path


def _load_inventory_module():
module_path = Path(__file__).with_name("renderer_cache_inventory.py")
spec = importlib.util.spec_from_file_location("renderer_cache_inventory", module_path)
assert spec is not None
assert spec.loader is not None
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module)
return module


def test_inventory_groups_cache_files_by_top_level_directory(tmp_path: Path) -> None:
"""Inventory should report every cache file without reading its contents."""
inventory_module = _load_inventory_module()
(tmp_path / "home").mkdir()
(tmp_path / "isaac-sim").mkdir()
(tmp_path / "home" / "shader.bin").write_bytes(b"123")
(tmp_path / "isaac-sim" / "kit.bin").write_bytes(b"12345")

total_bytes, file_count, groups = inventory_module.inventory(tmp_path)

assert total_bytes == 8
assert file_count == 2
assert groups == {"home": 3, "isaac-sim": 5}


def test_fingerprint_changes_when_cache_file_changes(tmp_path: Path) -> None:
"""A writer must publish a new snapshot when a cache artifact changes."""
inventory_module = _load_inventory_module()
artifact = tmp_path / "shader.bin"
artifact.write_bytes(b"before")
before = inventory_module.fingerprint(tmp_path)

artifact.write_bytes(b"after-content")

assert inventory_module.fingerprint(tmp_path) != before
6 changes: 5 additions & 1 deletion .github/actions/run-tests/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,10 @@ inputs:
description: 'Host Warp kernel cache directory bind-mounted into the container as WARP_CACHE_PATH'
default: ''
required: false
renderer-cache-host-dir:
description: 'Host directory containing persistent Kit, RTX, and NVIDIA compute caches'
default: ''
required: false
ci-marker:
description: 'CI_MARKER value forwarded to the container (read by tools/conftest.py to select test files by pytest marker)'
default: ''
Expand Down Expand Up @@ -134,7 +138,7 @@ runs:
TEST_K_EXPR_INPUT: ${{ inputs.test-k-expr }}
CI_MARKER_INPUT: ${{ inputs.ci-marker }}
run: |
bash .github/actions/run-tests/run_tests.sh "${{ inputs.test-path }}" "${{ inputs.result-file }}" "${{ inputs.container-name }}" "${{ inputs.image-tag }}" "${{ inputs.reports-dir }}" "$PYTEST_OPTIONS" "${{ inputs.filter-pattern }}" "${{ inputs.exclude-pattern }}" "${{ inputs.curobo-only }}" "${{ inputs.include-files }}" "${{ inputs.quarantined-only }}" "${{ inputs.shard-index }}" "${{ inputs.shard-count }}" "${{ inputs.volume-mount-source }}" "${{ inputs.extra-pip-packages }}" "${{ inputs.test-node-ids-file }}" "${{ inputs.test-node-ids-key }}" "${{ inputs.wheelhouse-host-dir }}" "${{ inputs.wheelhouse-packages }}" "$TEST_K_EXPR_INPUT" "$CI_MARKER_INPUT" "${{ inputs.standalone-script-scope }}" "${{ inputs.standalone-script-visualizer }}" "${{ inputs.standalone-script-runtime-group }}" "${{ inputs.warp-cache-host-dir }}" "${{ inputs.extra-uv-packages }}"
bash .github/actions/run-tests/run_tests.sh "${{ inputs.test-path }}" "${{ inputs.result-file }}" "${{ inputs.container-name }}" "${{ inputs.image-tag }}" "${{ inputs.reports-dir }}" "$PYTEST_OPTIONS" "${{ inputs.filter-pattern }}" "${{ inputs.exclude-pattern }}" "${{ inputs.curobo-only }}" "${{ inputs.include-files }}" "${{ inputs.quarantined-only }}" "${{ inputs.shard-index }}" "${{ inputs.shard-count }}" "${{ inputs.volume-mount-source }}" "${{ inputs.extra-pip-packages }}" "${{ inputs.test-node-ids-file }}" "${{ inputs.test-node-ids-key }}" "${{ inputs.wheelhouse-host-dir }}" "${{ inputs.wheelhouse-packages }}" "$TEST_K_EXPR_INPUT" "$CI_MARKER_INPUT" "${{ inputs.standalone-script-scope }}" "${{ inputs.standalone-script-visualizer }}" "${{ inputs.standalone-script-runtime-group }}" "${{ inputs.warp-cache-host-dir }}" "${{ inputs.extra-uv-packages }}" "${{ inputs.renderer-cache-host-dir }}"
- name: Kill container on cancellation
if: cancelled()
shell: bash
Expand Down
33 changes: 30 additions & 3 deletions .github/actions/run-tests/run_tests.sh
Original file line number Diff line number Diff line change
Expand Up @@ -37,10 +37,15 @@ run_tests() {
local standalone_script_runtime_group="${24}"
local warp_cache_host_dir="${25}"
local extra_uv_packages="${26}"
local renderer_cache_host_dir="${27}"
local logs_pid=""
local wait_pid=""
local docker_wait_file="/tmp/.docker_exit_${container_name}"
local docker_runtime_dir=""
local home_cache_args=""
local isaacsim_cache_dir=""
local isaacsim_compute_cache_dir=""
local kit_cache_dir=""

# Kill the container immediately if the runner is cancelled.
# The GitHub Actions runner can deliver HUP, INT, or TERM on cancellation
Expand Down Expand Up @@ -240,14 +245,36 @@ run_tests() {
"${docker_runtime_dir}/isaac-sim/data" \
"${docker_runtime_dir}/isaac-sim/logs" \
"${docker_runtime_dir}/isaac-sim/pkg"

kit_cache_dir="${docker_runtime_dir}/isaac-sim/kit/cache"
isaacsim_cache_dir="${docker_runtime_dir}/isaac-sim/cache"
isaacsim_compute_cache_dir="${docker_runtime_dir}/isaac-sim/computecache"
home_cache_args=""
if [ -n "$renderer_cache_host_dir" ]; 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 · Api — Renderer cache silently requires unrelated source mount

renderer-cache-host-dir is declared as an independent optional input, but its mount setup is nested inside the if [ -n "$volume_mount_source" ] block, while the analogous warp-cache-host-dir is handled outside it. A caller that supplies only the renderer cache directory gets no mounts and no diagnostic, so the cache silently does nothing. Handle the renderer mounts outside that branch, or validate and document the dependency.

mkdir -p \
"${renderer_cache_host_dir}/home/cache" \
"${renderer_cache_host_dir}/home/computecache" \
"${renderer_cache_host_dir}/isaac-sim/kit-cache" \
"${renderer_cache_host_dir}/isaac-sim/cache" \
"${renderer_cache_host_dir}/isaac-sim/computecache"
kit_cache_dir="${renderer_cache_host_dir}/isaac-sim/kit-cache"
isaacsim_cache_dir="${renderer_cache_host_dir}/isaac-sim/cache"
isaacsim_compute_cache_dir="${renderer_cache_host_dir}/isaac-sim/computecache"
home_cache_args="\
-v ${renderer_cache_host_dir}/home/cache:/tmp/isaaclab-ci-home/.cache:rw \

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 — Cache mount covers entire XDG cache home

/tmp/isaaclab-ci-home/.cache is both HOME/.cache and XDG_CACHE_HOME, so this mount persists and publishes every tool cache written there (including pip/uv caches from extra-pip-packages/extra-uv-packages), not just Kit/RTX artifacts as the input documents. Those entries are keyed only on GPU, driver and Isaac Sim image and consume the 8 GB publish budget. Mount only the renderer subdirectories instead.

-v ${renderer_cache_host_dir}/home/computecache:/tmp/isaaclab-ci-home/.nv/ComputeCache:rw"
echo "🔵 Mounting persistent renderer caches from ${renderer_cache_host_dir}"
fi

docker_volume_args="\
-v ${volume_mount_source}:/workspace/isaaclab:rw \
-v ${docker_runtime_dir}/home:/tmp/isaaclab-ci-home:rw \
-v ${docker_runtime_dir}/isaac-sim/kit/cache:/isaac-sim/kit/cache:rw \
${home_cache_args} \
-v ${kit_cache_dir}:/isaac-sim/kit/cache:rw \
-v ${docker_runtime_dir}/isaac-sim/kit/data:/isaac-sim/kit/data:rw \
-v ${docker_runtime_dir}/isaac-sim/kit/logs:/isaac-sim/kit/logs:rw \
-v ${docker_runtime_dir}/isaac-sim/cache:/isaac-sim/.cache:rw \
-v ${docker_runtime_dir}/isaac-sim/computecache:/isaac-sim/.nv/ComputeCache:rw \
-v ${isaacsim_cache_dir}:/isaac-sim/.cache:rw \
-v ${isaacsim_compute_cache_dir}:/isaac-sim/.nv/ComputeCache:rw \
-v ${docker_runtime_dir}/isaac-sim/config:/isaac-sim/.nvidia-omniverse/config:rw \
-v ${docker_runtime_dir}/isaac-sim/data:/isaac-sim/.local/share/ov/data:rw \
-v ${docker_runtime_dir}/isaac-sim/logs:/isaac-sim/.nvidia-omniverse/logs:rw \
Expand Down
Loading
Loading