Skip to content

[Backport] PR #6905 to release/3.0.0 - #7557

Merged
kellyguo11 merged 2 commits into
release/3.0.0from
backport/release/3.0.0/pr-6905
Sep 4, 2026
Merged

[Backport] PR #6905 to release/3.0.0#7557
kellyguo11 merged 2 commits into
release/3.0.0from
backport/release/3.0.0/pr-6905

Conversation

@isaaclab-bot

@isaaclab-bot isaaclab-bot Bot commented Sep 4, 2026

Copy link
Copy Markdown
Contributor

Backports #6905 to release/3.0.0.

The original automatic cherry-pick conflicted in ovrtx_renderer.py. Its inferred resolution incorrectly replaced the release branch's newer renderer with the older develop-side file, producing a +444/-1130 renderer diff and removing ovstage behavior.

Follow-up commit 64ae8edb1 corrects that resolution:

  • The other 13 changed files remain exact patch replays of Add OVRTX shader cache for CI rendering tests #6905.
  • ovrtx_renderer.py retains the complete release implementation.
  • The renderer receives only the original shader-cache integration: the redirect_shader_cache import and the call immediately before Renderer(config).
  • The net renderer diff is now seven additions and no deletions.
  • The net PR diff is 866 additions and 7 deletions.
Field Commit
Original merged change 9847b71e3324cb46d4f5882d226ac666bbc22f4d
Release base used 2af02510cc7cac6171fa25bf912f1c4dc83c3279
Initial automatic resolution 88ef9ed54831eb33ea9a93fdc0c860089488f71e
Corrected backport 64ae8edb1

Validation

  • uv run --no-project --with pytest --with lazy-loader python -m pytest source/isaaclab_ov/test/test_ovrtx_shader_cache_redirect.py -q - 6 passed.
  • Shell syntax checks passed for the shader-cache and test-runner scripts.
  • All changed YAML files parsed successfully.
  • Applicable file-scoped pre-commit hooks passed.
  • The branch-wide changelog hook was skipped locally because it compares historical release differences against develop; the existing Add OVRTX shader cache for CI rendering tests #6905 isaaclab_ov changelog fragment remains included.
  • The canonical uv run isaaclab -f command cannot resolve the release branch's Linux/Windows-only lockfile on macOS; equivalent file-scoped hooks were run directly.

Each CI run starts with an empty `nv_shadercache` directory, so the
NVIDIA driver recompiles every pipeline state object (PSO) before the
first render — roughly 600 s, which is what `COLD_CACHE_BUFFER = 700` in
`tools/conftest.py` exists to absorb. The cost is paid on every
rendering job, twice per run (kit-based and kitless).

This persists the driver's PSO blobs across runs with GitHub Actions
cache, so warm runs skip the compile entirely.

Two independent cache trees live under
`${RUNNER_TEMP}/isaaclab-ovrtx-shader-cache/`, because the two render
paths are compiled by different runtimes and a bump to one must not
invalidate the other:

| Tree | Produced by | Reaches the container as |
|---|---|---|
| `kit/` | RTX renderer inside the Isaac Sim image | nested bind mount
over `/isaac-sim/kit/cache/nv_shadercache`, which the enclosing
`kit/cache` tmpdir mount would otherwise present as empty |
| `kitless/` | pip-installed `ovrtx` wheel | `OVRTX_SHADER_CACHE_PATH`,
applied by `OVRTXRenderer` as a carb setting |

One action owns the whole lifecycle — key computation, restore,
reporting and writeback — so callers only decide *when* each phase runs.
A composite action cannot span the caller's test step, so the phases are
separate invocations selected by `mode`:

- `restore` — reads the newest compatible snapshot of each tree, never
writes
- `report` — summarises how far the run compiled beyond what was
restored
- `save` — reports, then writes each **populated** tree back as a new
snapshot; only the warmer uses it

Every mode recomputes keys from the same `key.sh`, so the collection a
job reads and the one it writes cannot drift.

Cache key composition:

| Component | Source | Why |
|---|---|---|
| `RUNNER_OS`, `RUNNER_ARCH` | GHA env | platform |
| `sm<cc>` | `nvidia-smi --query-gpu=compute_cap` | PSO blobs are
GPU-architecture specific |
| `drv<version>` | `nvidia-smi --query-gpu=driver_version` | PSO blobs
are driver specific |
| `isaacsim<tag>` (kit only) | `isaacsim-version` input | identifies the
Kit RTX build that compiled `kit/` |
| `ovrtx<version>` (kitless only) | `uv.lock` pin | keeps the key a
function of the commit, so an upstream wheel release cannot re-key open
PRs onto a cold collection |

`verify.sh` fails the warmer when either tree came out empty, which
would otherwise publish a silent half-warm snapshot that every consumer
prefers over the last good one.

- `run-package-tests`: new `ovrtx-shader-cache` input (`''` / `restore`
/ `save`); restores before the tests, reports or publishes after.
Publish is gated on `!cancelled()` rather than `success()` — compiled
PSO blobs stay valid when a golden-image assertion fails.
- `run-tests` / `run_tests.sh`: new `ovrtx-shader-cache-host-dir` input;
owns the canonical mount layout and runs the pre-test mount check.
- `build.yaml`: `ovrtx-shader-cache: restore` on
`rendering-correctness`, `rendering-correctness-kitless-legacy` and
`rendering-correctness-kitless-ovstage`; new post-merge
`warm-ovrtx-cache` job (`continue-on-error`, publishes only on `main` /
`develop` / `release/*`) that renders one Kit and one kitless cartpole
case to fill both trees.

- `source/isaaclab_ov/isaaclab_ov/renderers/ovrtx_shader_cache.py`
(new): reads `OVRTX_SHADER_CACHE_PATH` and points both
`/rtx/shaderDb/driverShaderCachePath` and
`/rtx/shaderDb/driverAppShaderCachePath` at it via the ovrtx settings
extension. No-ops when the variable is unset; warns when the runtime has
no settings extension; raises when a setting is rejected, since a
partial redirect still compiles cold. Kept out of `ovrtx_renderer` so
the policy imports without the ovrtx runtime.
- `ovrtx_renderer.py`: calls `redirect_shader_cache(OVRTX_CONFIG)`
before constructing `Renderer()` — the settings are read at
renderer-creation time and cannot be changed on a live instance.
- `tools/verify_ovrtx_shader_cache.py` (new): in-container check that
the mounted trees are present and writable, run before tests start. A
shadowed `kit/` mount is otherwise invisible — the restore step still
reports a hit and the only symptom is a slower job.
- `tools/conftest.py`: `COLD_CACHE_BUFFER` docstring condensed to its
functional contract; the value is unchanged.

`source/isaaclab_ov/test/test_ovrtx_shader_cache_redirect.py` covers the
redirect boundary with a fake settings applier — no GPU, no ovrtx
runtime, no renderer: both settings redirected, rejection raises and is
not logged as success, unset env var queries nothing, the renderer
config reaches the applier (it is what initializes the library), and a
runtime without the extension degrades to a warning.

- New feature (non-breaking change which adds functionality)

- [x] <!-- backport-active-release --> Backport this pull request to the
active release branch after it merges into `develop`

- [ ] `warm-ovrtx-cache` runs post-merge and publishes both entries —
step summary shows a non-zero file count for `kit/` and `kitless/`
- [ ] Subsequent `rendering-correctness` and
`rendering-correctness-kitless-*` runs restore the cache and report
`fully covered`
- [ ] The `COLD_CACHE_BUFFER` allowance is no longer consumed on warm
runs — the first camera test completes within its normal budget
- [ ] Cold run (ovrtx bump or driver bump) still succeeds: cache miss →
compile → warmer publishes into the new collection

- [x] I have read and understood the contribution guidelines
- [x] I have run the `pre-commit` checks with `./isaaclab.sh --format`
- [x] My changes generate no new warnings
- [x] I have added a changelog fragment under
`source/isaaclab_ov/changelog.d/`
- [x] My name already exists in `CONTRIBUTORS.md`

(cherry picked from commit 9847b71)
@github-actions github-actions Bot added isaac-lab Related to Isaac Lab team infrastructure labels Sep 4, 2026
@kellyguo11
kellyguo11 marked this pull request as ready for review September 4, 2026 02:28
@kellyguo11
kellyguo11 requested a review from a team September 4, 2026 02:28

@isaaclab-review-bot isaaclab-review-bot Bot 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.

Isaac Lab Review Bot

The shader-cache plumbing is coherent, but the conflict resolution rewrites most of ovrtx_renderer.py and introduces concrete renderer regressions: simple-shading outputs are no longer populated, the ovstage backend is largely stubbed out, ovstage camera updates fail, and the configured combined-stage diagnostic export is removed.

  • Design and architecture: The cache action and dedicated shader-cache redirection module are cleanly separated. However, the renderer rewrite is far broader than the cache redirect requires, violates the smallest-change and behavior-preservation criteria, and replaces the functional ovstage implementation with no-op methods while dropping clone transforms.
  • API: The new shader-cache environment contract is documented and tested, but existing renderer contracts regress. Three advertised simple-shading output types are left unwritten, temp_usd_dir no longer emits the combined renderer stage, and public method annotations and docstrings were broadly removed.
  • Implementation: The CI restore, mount, report, verification, and warmer save paths are connected end to end. In the renderer, the ovstage binding and update methods are now pass; clone root positions are no longer written; and _update_camera_ovstage calls the legacy implementation, which accesses an attribute not initialized in ovstage mode and therefore raises AttributeError. The deleted renderer behavior should be restored while limiting the backport to the shader-cache integration.

Needs rework. Posted 5 actionable findings inline.

Automated review; human maintainers own approval decisions.

def _process_render_frame(self, data, frame, outputs):
data.renderer_info.clear()
for key, output in (
(_LDR_COLOR_VAR, "rgba"),

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.

🔴 Critical · Api — Simple-shading outputs never populated

_process_render_frame now routes LdrColor only to rgba; the previous selection that fell back to an _RTX_MINIMAL_MODES key was deleted. supported_output_types still advertises SIMPLE_SHADING_CONSTANT_DIFFUSE, SIMPLE_SHADING_DIFFUSE_MDL and SIMPLE_SHADING_FULL_MDL, so a camera requesting only those data types receives an untouched zero buffer. Restore the minimal-mode buffer-key selection.

if self._object_xform_query is None or self._object_newton_indices is None or self._object_scales is None:
return
from isaaclab_newton.physics import NewtonManager
def _setup_xform_bindings_ovstage(self):

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.

🔴 Critical · Design Architecture — ovstage backend reduced to no-op stubs

_setup_xform_bindings_ovstage, _setup_deformable_bindings_ovstage, _setup_particle_bindings_ovstage, _setup_cable_bindings_ovstage, _update_transforms_ovstage, _update_geometries_ovstage and _update_scene_partitions_after_clone_ovstage now only pass, and _clone_sources_ovstage no longer writes env-root positions. With ISAAC_LAB_OVRTX_USE_OVSTAGE=1 nothing is bound or updated, so clones stack at the origin and geometry stays frozen. Restore the deleted implementations.

pass

def _update_camera_ovstage(self, data, positions, orientations, intrinsics):
self._update_camera_legacy(data, positions, orientations, intrinsics)

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.

🔴 Critical · Implementation — ovstage camera update raises AttributeError

_update_camera_ovstage delegates to _update_camera_legacy, which reads self._camera_xform_binding (line 549). _init_fields_ovstage defines _camera_xform_query and never that attribute, so the first camera update in ovstage mode raises AttributeError and the ovstage camera query is never written. Restore the ovstage write_attribute path for the camera transform.


- **ovrtx_usd.py**: USD helpers for OVRTX: render var config, camera injection, etc.
"""
"""OVRTX Renderer implementation."""

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 — Backport rewrites renderer beyond shader-cache scope

The shader-cache change needs only the redirect_shader_cache import and call, yet this file loses ~1130 lines: module/class/method docstrings, public parameter and return annotations, and validation and cleanup code. That conflicts with the repository rules on smallest change, preserved behavior, Google-style docstrings for public APIs, and concrete public types. Reduce the diff to the redirect import and call.

)
self._render_product_paths.append(render_product_path)
combined_usd_string = self._exported_usd_string + "\n\n" + render_product_string
combined = self._exported_usd_string + "\n\n" + product

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 — temp_usd_dir no longer writes combined stage

Both _initialize_from_spec_legacy and _initialize_from_spec_ovstage build combined but dropped the _write_file(..., "ovrtx_renderer_stage.usda", ...) call guarded by cfg.temp_usd_dir. Only the pre-renderer stage is still written, so the configured diagnostic artifact silently disappears while the config field remains. Restore the write before opening the combined stage.

@greptile-apps

greptile-apps Bot commented Sep 4, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This backport adds an OVRTX shader-cache redirect and CI cache lifecycle, but its conflict resolution also rewrites the renderer and removes the ovstage backend’s synchronization logic.

  • Adds cache keying, restoration, reporting, verification, and publication for Kit and kitless rendering.
  • Redirects standalone OVRTX driver shader caches through OVRTX_SHADER_CACHE_PATH.
  • Adds cache-mount verification and a post-merge cache-warmer job.
  • Replaces operational ovstage setup and update methods with no-ops, breaking that renderer variant.

Confidence Score: 4/5

This PR is not safe to merge until the ovstage renderer’s removed setup and synchronization implementations are restored.

Enabled ovstage rendering now invokes no-op scene, binding, transform, and geometry methods, while camera updates target a legacy binding that the ovstage path never initializes, so affected rendering jobs can produce stale or incorrect frames.

Files Needing Attention: source/isaaclab_ov/isaaclab_ov/renderers/ovrtx_renderer.py

Important Files Changed

Filename Overview
source/isaaclab_ov/isaaclab_ov/renderers/ovrtx_renderer.py Integrates cache redirection but also strips the ovstage backend’s scene, binding, transform, and geometry implementations, breaking enabled ovstage rendering.
source/isaaclab_ov/isaaclab_ov/renderers/ovrtx_shader_cache.py Adds fail-fast redirection of both OVRTX driver shader-cache settings before renderer construction.
.github/actions/ovrtx-shader-cache/action.yml Adds separate restore, report, and save phases with per-tree change and population gates.
.github/actions/run-tests/run_tests.sh Bind-mounts the two shader-cache trees and verifies their availability before running tests.
.github/workflows/build.yaml Enables cache restoration for rendering jobs and adds a serialized post-merge cache warmer.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart LR
  W[Rendering workflow] --> R[Restore compatible shader cache]
  R --> M[Bind-mount Kit and kitless trees]
  M --> T[Run rendering tests]
  T --> G[Measure cache growth]
  G --> S[Publish changed snapshots]
  T --> O[OVRTX ovstage renderer]
  O --> N[No-op scene and transform updates]
  N --> F[Stale or incorrect frames]
Loading

Reviews (1): Last reviewed commit: "Add OVRTX shader cache for CI rendering ..." | Re-trigger Greptile

Comment on lines +853 to +857
def _update_scene_partitions_after_clone_ovstage(self, count):
pass

def _setup_particle_bindings_ovstage(self) -> None:
try:
from isaaclab_newton.physics import NewtonManager
except ImportError:
return
records = NewtonManager._particle_visual_prims
if not records:
return
paths = list(records)
self._particle_visual_offsets = [record.offset for record in records.values()]
self._particle_visual_counts = [record.count for record in records.values()]
self._particle_paths_list = self._stage_paths.create_path_list_from_strings(paths)
self._particle_points_query = self._stage.query_from_path_list(self._particle_paths_list)
self._write_identity_xforms(self._particle_points_query, len(paths))

def _setup_cable_bindings_ovstage(self) -> None:
discovered = self._discover_cable_segment_bindings()
if discovered is None:
return
paths, ids, offsets, counts = discovered
self._cable_paths_list = self._stage_paths.create_path_list_from_strings(paths)
self._cable_points_query = self._stage.query_from_path_list(self._cable_paths_list)
self._write_identity_xforms(self._cable_points_query, len(paths))
self._allocate_cable_device_buffers(ids, offsets, counts)
self._cable_point_slices = [
self._cable_points[offset + curve : offset + curve + count + 1]
for curve, (offset, count) in enumerate(zip(offsets, counts, strict=True))
]
self._cable_point_tensors = [points_tensor_from_warp(points) for points in self._cable_point_slices]

def _write_identity_xforms(self, query, count: int) -> None:
self._stage.write_attribute(
query,
"omni:resetXformStack",
ordinal=self._current_ordinal,
tensors=np.full(count, True, dtype=np.bool_),
is_array=False,
).wait()
self._stage.write_attribute(
query,
"omni:xform",
ordinal=self._current_ordinal,
tensors=xform_tensor_from_numpy(np.tile(np.eye(4, dtype=np.float64), (count, 1, 1))),
is_array=False,
semantic=ovstage.AttributeSemantic.MATRIX,
).wait()

def _update_transforms_ovstage(self) -> None:
if self._object_xform_query is None or self._object_newton_indices is None or self._object_scales is None:
return
from isaaclab_newton.physics import NewtonManager
def _setup_xform_bindings_ovstage(self):
pass

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.

P1 Ovstage synchronization is disabled

When ISAAC_LAB_OVRTX_USE_OVSTAGE=1, initialization and frame updates invoke methods that now do nothing, while camera updates use a legacy binding that the ovstage path never creates. Scene partitions, cameras, object transforms, and dynamic geometry therefore remain stale, causing incorrect frames in the ovstage rendering jobs.

@kellyguo11

Copy link
Copy Markdown
Contributor

run-ci

@isaaclab-bot isaaclab-bot Bot added ci:run-docker Trigger the on-demand Docker and GPU CI workflow and removed ci:run-docker Trigger the on-demand Docker and GPU CI workflow labels Sep 4, 2026
@kellyguo11
kellyguo11 merged commit a20a9c5 into release/3.0.0 Sep 4, 2026
51 of 53 checks passed
@kellyguo11
kellyguo11 deleted the backport/release/3.0.0/pr-6905 branch September 4, 2026 05:18
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

infrastructure isaac-lab Related to Isaac Lab team

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants