Skip to content

[None][fix] Bound GEN log sentinel wait - #17140

Open
chienchunhung wants to merge 2 commits into
NVIDIA:mainfrom
chienchunhung:codex/gen-worker-teardown-sentinel
Open

[None][fix] Bound GEN log sentinel wait#17140
chienchunhung wants to merge 2 commits into
NVIDIA:mainfrom
chienchunhung:codex/gen-worker-teardown-sentinel

Conversation

@chienchunhung

@chienchunhung chienchunhung commented Jul 31, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • skip GEN log completion-sentinel synchronization for e2e/ctx-only modes, which do not emit the GEN device-step-time metric
  • give the gen-only sentinel wait a dedicated 120-second teardown budget instead of reusing the whole pytest timeout
  • fall back to the existing current-log parse when the enclosing multi-node GEN srun does not reap

Problem

#16717 waits for gen_server_<n>.done before parsing GEN worker logs. The launcher creates that file only after the enclosing GEN srun returns. Recent targeted and main post-merge runs completed the workload and wrote benchmark_status=Done, but the GEN srun did not finish reaping, so the benchmark consumed the pytest timeout waiting for a non-critical log-flush sentinel. The benchmark step then exited nonzero and Slurm's --kill-on-bad-exit=1 cancelled the remaining job.

The sentinel remains the preferred path. This change prevents it from turning a post-workload teardown delay into a one-hour pytest/Slurm cancellation.

Stack

Validation

Dev Engineer Review

  • jenkins/scripts/perf/submit.py adds pytest-split-aware test selection.
  • The implementation loads duration data and validates split and group settings.
  • The selection path requires exactly one test per group.
  • parse_test_case_name remains backward-compatible through the optional selected_line argument.
  • tests/integration/defs/perf/test_perf_sanity.py limits GEN sentinel waits to gen_only runs.
  • GEN_LOG_SENTINEL_TIMEOUT provides a dedicated 120-second bound.
  • The sentinel remains the preferred synchronization path, with current-log parsing as fallback.
  • The changes avoid applying GEN log handling to e2e and ctx-only modes.
  • No configuration or test-list files changed.
  • Review verdict: sufficient, subject to normal CI validation.

QA Engineer Review

  • Modified test functions:
    • Added CI shard-selection coverage using stored duration data.
    • Added validation coverage for mismatched split groups.
  • The tests are unit tests in tests/unittest/scripts/test_perf_submit.py.
  • No tests/integration/test_lists/, test-db/, or qa/ entries changed.
  • The targeted GB300 multi-node disaggregated perf-sanity run provides integration coverage for GEN sentinel handling.
  • Verdict: sufficient.

Signed-off-by: Chien-Chun Hung <2679986+chienchunhung@users.noreply.github.com>
Signed-off-by: Chien-Chun Hung <2679986+chienchunhung@users.noreply.github.com>
@chienchunhung
chienchunhung force-pushed the codex/gen-worker-teardown-sentinel branch from 40e3ff1 to 9259db9 Compare July 31, 2026 19:53
@chienchunhung chienchunhung changed the title [https://nvbugs/6487038][fix] Bound GEN log sentinel wait [None][fix] Bound GEN log sentinel wait Jul 31, 2026
@chienchunhung

Copy link
Copy Markdown
Collaborator Author

/bot run --disable-fail-fast

@chienchunhung
chienchunhung marked this pull request as ready for review July 31, 2026 23:19
@chienchunhung
chienchunhung requested review from a team as code owners July 31, 2026 23:19
@coderabbitai

coderabbitai Bot commented Jul 31, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Walkthrough

The PR adds pytest-split duration-balanced test selection to the CI performance submit flow. It also bounds generation-log sentinel waits and limits device-step-time collection to gen_only runs.

Changes

Pytest split selection

Layer / File(s) Summary
Duration-balanced test selection
jenkins/scripts/perf/submit.py, tests/unittest/scripts/test_perf_submit.py
The submit flow parses pytest options, loads duration data, reproduces least-duration grouping, validates split settings, and passes the selected test line to test-name parsing. Tests cover balanced selection and mismatched group rejection.

Generation-only timing

Layer / File(s) Summary
Bounded gen_only log timing
tests/integration/defs/perf/test_perf_sanity.py
Generation-log sentinel synchronization uses a dedicated monotonic timeout. Device-step-time log snapshots and deferred parsing run only for gen_only configurations.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant CI as CI submit flow
  participant Submit as submit.py
  participant TestList as Test list
  participant Durations as Duration data
  CI->>Submit: Provide pytestCommand and split group
  Submit->>TestList: Read test cases
  Submit->>Durations: Load test durations
  Submit->>Submit: Assign tests to least-duration groups
  Submit-->>CI: Return selected test line and test name
Loading

Possibly related PRs

Suggested reviewers: bowenfu

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly identifies the primary fix: limiting the GEN log sentinel wait.
Description check ✅ Passed The description clearly explains the problem, solution, affected modes, fallback behavior, stacking context, and validation steps.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai 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.

Actionable comments posted: 1

🧹 Nitpick comments (5)
jenkins/scripts/perf/submit.py (1)

645-667: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Drop the redundant split_group argument.

select_test_case_line always returns a line. parse_test_case_name therefore always takes the selected_line branch, and args.split_group is never used in that call. parse_test_case_name also raises when the line has no bracket, so the "[" in selected_test_line guard at line 666 is always true.

♻️ Proposed simplification
     config_yaml, server_name, benchmark_mode, runtime_mode = parse_test_case_name(
         args.test_list,
         args.llm_src,
-        args.split_group,
         selected_line=selected_test_line,
     )
 
     with open(config_yaml, "r") as f:
         config = yaml.safe_load(f)
 
-    test_case_name = (
-        selected_test_line.split("[")[-1].split("]")[0] if "[" in selected_test_line else ""
-    )
+    test_case_name = selected_test_line.split("[")[-1].split("]")[0]
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@jenkins/scripts/perf/submit.py` around lines 645 - 667, Update the
parse_test_case_name call to remove the redundant args.split_group argument and
rely on selected_line. Simplify test_case_name extraction by removing the
unnecessary bracket-presence guard, since select_test_case_line always returns a
valid bracketed line and parse_test_case_name enforces that contract.
tests/unittest/scripts/test_perf_submit.py (3)

113-145: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add coverage for the tie-breaking and error paths.

I traced the assertion and it is correct: group 3 of 4 resolves to test_lines[1]. However all four durations are distinct, so the name pre-sort in _select_least_duration_group is never exercised. That pre-sort is the part most likely to diverge from pytest-split. Ties occur whenever tests are missing from .test_durations, because each such test receives the same average_duration.

Add cases for:

  1. Two or more tests with equal or unknown durations, to pin the tie order.
  2. A missing durations file.
  3. A --splitting-algorithm value other than least_duration, which must raise ValueError.
  4. A group that selects more than one test, which must raise ValueError.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/unittest/scripts/test_perf_submit.py` around lines 113 - 145, Extend
test_ci_submit_selects_same_least_duration_shard_as_pytest_split or add focused
tests covering _select_least_duration_group: verify deterministic name-based
ordering for equal durations and missing-duration entries, assert missing
.test_durations handling, assert ValueError for a non-least_duration
--splitting-algorithm, and assert ValueError when the selected group contains
multiple tests.

113-164: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Test coverage summary.

  1. Changed test functions:
    • Added test_ci_submit_selects_same_least_duration_shard_as_pytest_split.
    • Added test_ci_submit_rejects_split_group_disagreement.
    • Added the ci_submit_module fixture.
    • No test functions were modified or removed.
  2. Test list registration: both tests live under tests/unittest/scripts/. Unit tests in this directory are collected by the unit-test stage and are not registered per-function in tests/integration/test_lists/test-db/ or tests/integration/test_lists/qa/. No list file change is required. Confirm that tests/unittest/scripts/ is already inside the collected unit-test scope.
  3. Coverage verdict: insufficient.

The happy path and one validation error are covered. The following behaviours in select_test_case_line and _select_least_duration_group have no test: equal or unknown durations (the tie order that must match pytest-split), a missing .test_durations file, a non-least_duration algorithm, the positional fallback when --splits or --group is absent, and a group that selects more than one test. The tie case is the highest priority, because it determines whether the launcher and pytest agree on the selected test.

As per path instructions "Always produce a test coverage summary, even if no issues are found."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/unittest/scripts/test_perf_submit.py` around lines 113 - 164, Add
coverage for select_test_case_line and _select_least_duration_group beyond the
existing happy path and group validation. Prioritize equal-duration ties and
unknown durations, asserting selection order matches pytest-split; also cover
missing .test_durations, non-least_duration algorithms, positional fallback
without --splits or --group, and groups selecting multiple tests. Keep tests
under the existing tests/unittest/scripts/ unit-test scope without modifying
integration test-list registrations.

Source: Path instructions


66-70: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Select the CI submit module by path, not by list index.

SUBMIT_PATHS[0] depends on list order. Select the path that identifies the CI implementation so reordering cannot load the local implementation.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/unittest/scripts/test_perf_submit.py` around lines 66 - 70, Update the
ci_submit_module fixture to select the CI implementation from SUBMIT_PATHS by
its identifying path or predicate instead of relying on index 0, ensuring list
reordering cannot load the local implementation.
tests/integration/defs/perf/test_perf_sanity.py (1)

1355-1359: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Document the new timeout arguments.

Add Google-style Args entries for timeout and poll_interval, and a Returns entry for the boolean result.

As per coding guidelines, public function arguments must be documented in Google-style docstrings.

Suggested docstring update
         """Block until every gen worker signals that its log is fully written.
 
+        Args:
+            timeout: Maximum time to wait for all sentinel files.
+            poll_interval: Delay between sentinel checks.
+
+        Returns:
+            True when all sentinels exist; otherwise False after timeout.
+
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/integration/defs/perf/test_perf_sanity.py` around lines 1355 - 1359,
Add a Google-style docstring to the public method wait_for_gen_log_sentinels
documenting the timeout and poll_interval arguments under Args, and the boolean
return value under Returns. Keep the existing behavior and default values
unchanged.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@tests/integration/defs/perf/test_perf_sanity.py`:
- Around line 1368-1391: Update run_cmd and the corresponding path around
parse_gen_worker_device_step_time to check the result of
wait_for_gen_log_sentinels before parsing or appending the device-step metric.
When the sentinel wait returns False, skip the metric or mark it unavailable
unless an independent completion check confirms the log is complete; never
report a parsed value as a normal measurement from an unverified log.

---

Nitpick comments:
In `@jenkins/scripts/perf/submit.py`:
- Around line 645-667: Update the parse_test_case_name call to remove the
redundant args.split_group argument and rely on selected_line. Simplify
test_case_name extraction by removing the unnecessary bracket-presence guard,
since select_test_case_line always returns a valid bracketed line and
parse_test_case_name enforces that contract.

In `@tests/integration/defs/perf/test_perf_sanity.py`:
- Around line 1355-1359: Add a Google-style docstring to the public method
wait_for_gen_log_sentinels documenting the timeout and poll_interval arguments
under Args, and the boolean return value under Returns. Keep the existing
behavior and default values unchanged.

In `@tests/unittest/scripts/test_perf_submit.py`:
- Around line 113-145: Extend
test_ci_submit_selects_same_least_duration_shard_as_pytest_split or add focused
tests covering _select_least_duration_group: verify deterministic name-based
ordering for equal durations and missing-duration entries, assert missing
.test_durations handling, assert ValueError for a non-least_duration
--splitting-algorithm, and assert ValueError when the selected group contains
multiple tests.
- Around line 113-164: Add coverage for select_test_case_line and
_select_least_duration_group beyond the existing happy path and group
validation. Prioritize equal-duration ties and unknown durations, asserting
selection order matches pytest-split; also cover missing .test_durations,
non-least_duration algorithms, positional fallback without --splits or --group,
and groups selecting multiple tests. Keep tests under the existing
tests/unittest/scripts/ unit-test scope without modifying integration test-list
registrations.
- Around line 66-70: Update the ci_submit_module fixture to select the CI
implementation from SUBMIT_PATHS by its identifying path or predicate instead of
relying on index 0, ensuring list reordering cannot load the local
implementation.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 1716d257-3da3-4026-b114-4a40031784fe

📥 Commits

Reviewing files that changed from the base of the PR and between 376d219 and 9259db9.

📒 Files selected for processing (3)
  • jenkins/scripts/perf/submit.py
  • tests/integration/defs/perf/test_perf_sanity.py
  • tests/unittest/scripts/test_perf_submit.py

Comment on lines +1368 to 1391
Returns True once all sentinels exist, or False if the dedicated
sentinel timeout is reached first. On False the caller still parses
whatever is on disk: the sentinel is a correctness optimization
against reading a mid-flush log, not a reason to consume the whole-test
timeout and trigger Slurm's kill-on-bad-exit cascade (nvbugs 6487036 /
6487040 / 6487038).
"""
sentinels = [
os.path.join(self.test_output_dir, f"gen_server_{i}.done")
for i in range(self.num_gen_servers)
]
start_time = time.time()
start_time = time.monotonic()
while True:
missing = [p for p in sentinels if not os.path.exists(p)]
if not missing:
print_info("All gen worker log sentinels present; log flush complete.")
return True
elapsed_time = time.time() - start_time
if elapsed_time > self.timeout:
elapsed_time = time.monotonic() - start_time
if elapsed_time > timeout:
print_info(
f"Timeout ({self.timeout}s) waiting for gen worker log "
f"Timeout ({timeout}s) waiting for gen worker log "
f"sentinels {missing}; parsing current log contents."
)
return False

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.

🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift

Do not report a device-step metric from an unverified log.

When wait_for_gen_log_sentinels() returns False, run_cmd() still calls parse_gen_worker_device_step_time() and appends a normal summary. The parser relies on completed sentinel files and explicitly avoids partially written or truncated logs.

If the timeout expires, skip or mark the metric unavailable unless the parser has an independent completion check. Do not present a potentially truncated value as a valid measurement.

Also applies to: 1620-1623

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/integration/defs/perf/test_perf_sanity.py` around lines 1368 - 1391,
Update run_cmd and the corresponding path around
parse_gen_worker_device_step_time to check the result of
wait_for_gen_log_sentinels before parsing or appending the device-step metric.
When the sentinel wait returns False, skip the metric or mark it unavailable
unless an independent completion check confirms the log is complete; never
report a parsed value as a normal measurement from an unverified log.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant