Skip to content

Fix and rescope the L40S runner stability qualification - #6863

Closed
Neil4561 wants to merge 2 commits into
isaac-sim:perf-smoke/develop-stagingfrom
NVIDIA-Omniverse:neilm/perf-smoke-stability-newton
Closed

Fix and rescope the L40S runner stability qualification#6863
Neil4561 wants to merge 2 commits into
isaac-sim:perf-smoke/develop-stagingfrom
NVIDIA-Omniverse:neilm/perf-smoke-stability-newton

Conversation

@Neil4561

@Neil4561 Neil4561 commented Aug 3, 2026

Copy link
Copy Markdown

Summary

The previous qualification run collected 57 of 135 samples and never reached a verdict. This fixes the causes and narrows the re-run to the buckets that actually failed.

Why it fell short

Two unrelated faults, both confirmed from the run logs:

  1. All five Newton-touching buckets crashed on from newton.solvers import SolverNotifyFlags. The staging source was far behind develop while the image's Newton was newer, and that symbol had already been replaced by ModelFlags. This accounts for 75 of the 78 missing samples.
  2. One allocation hit a PermissionError on /tmp/jit-cache/warp/.... All five parallel allocations shared a single Warp JIT cache directory and raced on creating it. This accounts for the remaining 3.

What changed

  • Isolated the JIT cache per allocation, keyed on run id, attempt, and run label.
  • Scoped the run to the Newton buckets, 75 samples instead of 135. The four PhysX-only buckets already produced clean evidence, so re-measuring them would double the pool time for no new information. The seeder and the qualifier take the same backend allowlist, so the expected scope matches what is collected. A test keeps the two lists in sync, and an unknown backend key is rejected rather than silently narrowing the scope.
  • Benchmark execution health is always enforced. A crash could previously exit zero while FPS regressions were advisory.
  • Added a stale-image guard so that hardening cannot fail PRs for reasons outside their control. When a crash log shows the image missing a symbol the source pins, the gate reports a stale CI image and stays advisory for those tasks. Only packages the image installs (newton, warp, isaacsim, ...) qualify; a missing Isaac Lab symbol is still a real defect and still fails.
  • Qualification no longer writes baselines or chains a second gate run. It measures the pool and reports a verdict.

Depends on a staging catch-up

This PR deliberately contains no merge from develop, so the diff stays reviewable. It does not on its own fix cause 1: at the current staging tip, newton_manager.py still imports SolverNotifyFlags, so the Newton buckets would crash again and collect nothing.

Staging must be caught up to develop before this qualification is worth running. On current develop, newton_manager.py uses ModelFlags and imports only SolverBase, SolverKamino, which matches what the image provides. With this PR's guard in place, the crash would at least be reported as a stale image and stay advisory rather than reading as a performance failure.

Scope note

The verdict will cover the five Newton buckets. The four PhysX buckets are not re-qualified at this commit, since their July 28 samples carry the old staging SHA and cannot be mixed with new ones. Runner-to-runner spread is largely a hardware property and the Newton buckets sample the same five runners, so this still answers the pool-stability question.

The gate remains in advisory mode.

Test plan

  • Land the staging catch-up to develop first.
  • Merge triggers Performance Smoke - L40S Runner Stability, which waits for this commit's Performance Smoke Test to pass and for the L40 pool to go quiet.
  • All five allocations complete with no PermissionError in the JIT cache.
  • 75 of 75 samples collected (5 allocations x 3 samples x 5 Newton buckets).
  • The qualification report reaches a verdict instead of INCONCLUSIVE from missing evidence.
  • 104 unit tests pass and pre-commit is clean (verified locally).

Horde added 2 commits August 3, 2026 17:04
The 2026-07-28 qualification run collected only 57 of 135 samples and
produced no verdict. Two independent faults caused the shortfall, and
both are fixed here.

Most Newton tasks crashed on `from newton.solvers import
SolverNotifyFlags`. Staging source was 145 commits behind develop while
the CI image was built from recent develop, where that symbol had been
replaced by ModelFlags. Merging develop into staging removes the
mismatch.

One allocation died partway through because five parallel allocations
shared a single Warp JIT cache directory and raced on creating it. Each
allocation now derives its own cache root from the run id, attempt, and
run label.

Separately, a crashed benchmark could exit zero while FPS regressions
were advisory, so execution health is now always enforced. To keep that
from failing PRs for reasons outside their control, a crash whose log
shows the image missing a symbol the source pins is reported as a stale
CI image and stays advisory. Only packages the image installs qualify;
a missing Isaac Lab symbol is still a real defect.

The qualification workflow no longer promotes samples to the rolling
baseline or chains a second gate run. It measures the pool and reports
a verdict.
The 2026-07-28 run collected clean evidence for the four PhysX-only
buckets and none for the five that load Newton, which all crashed on
`from newton.solvers import SolverNotifyFlags`. Re-running the full
matrix would spend half the pool time re-measuring buckets that already
worked.

Restrict both the sampling and the qualification to the Newton-touching
backends, cutting the run from 135 to 75 samples. The seeder already
accepted a backend allowlist; runner_stability now takes the same list
so the expected scope matches what was collected, instead of reporting
the PhysX buckets as missing evidence and failing closed.

An unknown backend key is rejected rather than silently dropped, since
a typo would otherwise narrow the scope and yield a weaker verdict that
still reads as qualified.
@greptile-apps

greptile-apps Bot commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

The PR rescopes L40S stability qualification to Newton-backed buckets, isolates per-allocation caches, and makes ordinary benchmark crashes fail even in advisory mode. It also introduces stale-image diagnosis, but that diagnosis currently suppresses genuine source-caused third-party API failures.

  • Waits for the pinned-image performance gate and a quiet L40 pool before launching five allocations.
  • Applies a shared Newton backend allowlist to sampling and qualification.
  • Separates allocation JIT/Kit cache paths and removes baseline publication from qualification.
  • Adds stale dependency-image reporting and always enforces unexplained benchmark execution failures.

Confidence Score: 4/5

The stale-image exception must be narrowed before merging because a genuine PR-caused third-party API crash can currently leave the performance gate successful.

The new classifier infers image skew solely from a missing-name string and package allowlist, then suppresses the hard-failure exit without proving the source pins a compatible API or that the image caused the crash.

Files Needing Attention: tools/perf_smoke_test/aggregate.py, tools/perf_smoke_test/environment_skew.py

Important Files Changed

Filename Overview
.github/workflows/perf-smoke-runner-stability.yaml Rescopes collection and qualification to five Newton buckets, waits for the initial gate, and removes baseline publication and the chained gate.
tools/perf_smoke_test/aggregate.py Enforces ordinary benchmark hard failures in advisory mode, but suppresses failures whenever the new unverified log classifier labels them stale-image skew.
tools/perf_smoke_test/environment_skew.py Adds regex-based missing-symbol detection for image-provided packages without validating source pins, image versions, or causal association with the hard failure.
tools/perf_smoke_test/runner_stability.py Adds validated backend allowlist filtering before constructing the expected qualification scope.
tools/perf_smoke_test/seed_baselines.py Includes the caller's run label in disposable JIT and Kit cache paths to isolate parallel stability allocations.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
  A[Staging workflow change] --> B[Wait for initial gate and quiet L40 pool]
  B --> C[Five runner allocations]
  C --> D[Seed Newton backend samples]
  D --> E[Per-allocation JIT and Kit caches]
  D --> F[Download sample records]
  F --> G[Qualify expected Newton buckets]
  G --> H[Report stability verdict]
Loading

Reviews (1): Last reviewed commit: "Scope stability qualification to the New..." | Re-trigger Greptile

Comment on lines +455 to +460
# reported loudly but never fails the PR.
skew = detect_dependency_skew(bench_result.stdout_tail)
if skew is None:
has_hard_failure = True
else:
print(f"[aggregate] {task_id}/{backend}: stale CI image; {skew.describe()}")

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 Stale-image classifier hides source crashes

When PR source imports or accesses a nonexistent symbol from an allowlisted package such as newton or warp, this branch classifies the resulting hard failure as a stale image using only the log message and package prefix. It then leaves has_hard_failure unset, causing the aggregate command to return success even though the change under test crashed the benchmark.

@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 qualification rescoping and cache-isolation changes are coherent, but the new stale-image exemption is overly broad and can allow genuine PR defects to pass. Aggregate reporting also contradicts the new hard-failure exit behavior and can hide a blocking regression behind a stale-image headline.

  • Design and architecture: Separating dependency-skew detection into a reusable module is reasonable, and the backend allowlist fails closed on unknown keys. However, skew classification relies only on the missing symbol’s top-level package being image-provided; it does not verify that the source-pinned dependency actually contains the symbol. Consequently, PR-authored typos or unsupported API usage in Newton, Warp, or Isaac Sim can be misclassified as infrastructure skew and exempted from failure.
  • API: The new --backends option remains backward compatible by defaulting to all configured buckets. The material behavior change is that genuine hard failures now return exit code 2 even in non-blocking mode, but the generated advisory-mode text still says results do not fail the PR. The reporting contract must accurately distinguish advisory performance verdicts from always-enforced execution health.
  • Implementation: The aggregate verdict path has two reporting defects: non-blocking summaries incorrectly claim that no result can fail the check, and stale-image failures take headline precedence over BLOCK results even when a blocking regression determines the exit status. Tighten skew verification and order or phrase the summary so its overall result matches the effective gate outcome.

Significant concerns. Posted 3 actionable findings inline.

Automated review; human maintainers own approval decisions.

# Benchmark execution health is never advisory. A crash, missing result, or
# invalid benchmark must fail even while FPS regressions are being rolled out
# in advisory mode.
if has_hard_failure:

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 · Implementation — Advisory wording contradicts new failing exit code

Hard failures now return 2 regardless of blocking, but _build_summary_markdown still prints "Advisory: results are reported for review but do not fail the PR" when the gate is non-blocking. Reviewers of an advisory run see a comment claiming the check cannot fail while the job exits 2. Update the advisory mode string to state that benchmark execution failures always fail the check.

continue
module = match.group("module")
package = module.split(".", 1)[0]
if package not in IMAGE_PROVIDED_PACKAGES:

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 — Skew guard also excuses PR-side API misuse

Classification depends only on the top-level package name of the missing symbol, with no check that the source-pinned version actually provides it. A PR that mistypes or misuses a Newton/Warp/Isaac Sim symbol produces the same crash signature and is silently excused, so aggregate.main() exits 0 on a real defect. Narrow this by matching only verified skew signatures, or by comparing the installed package version against the pin before exempting.

elif counts[OracleVerdict.HARD_FAILURE]:
elif unexplained_failures:
overall = "❌ One or more benchmarks failed before producing usable performance data"
elif skewed:

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.

🔵 Suggestion · Implementation — Stale-image headline outranks blocking regression

The new elif skewed: branch is evaluated before the BLOCK branch. A run with one excused stale-image crash plus one blocking regression reports "⚠️ The CI image is stale…" as the overall result while main() still returns 1 in blocking mode. Evaluate BLOCK first and keep the stale-image section as an additional note.

@Neil4561

Neil4561 commented Aug 3, 2026

Copy link
Copy Markdown
Author

Superseded by #6864, which lands the gate directly on develop.

The fixes here are all carried over. Targeting develop also removes the SolverNotifyFlags crash at its root: that failure came from benchmarking three-week-old staging source inside a container built from current develop, and on develop the source and image move together.

@Neil4561 Neil4561 closed this Aug 3, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant