Skip to content

ci(cuda): overlap AOTI .pt2 compiles with GPU unit tests#5882

Open
wanghan-iapcm wants to merge 4 commits into
deepmodeling:masterfrom
wanghan-iapcm:overlap-aoti-gpu-cuda-ci
Open

ci(cuda): overlap AOTI .pt2 compiles with GPU unit tests#5882
wanghan-iapcm wants to merge 4 commits into
deepmodeling:masterfrom
wanghan-iapcm:overlap-aoti-gpu-cuda-ci

Conversation

@wanghan-iapcm

@wanghan-iapcm wanghan-iapcm commented Jul 20, 2026

Copy link
Copy Markdown
Collaborator

What

The CUDA job runs source/tests as one serial pytest process. The tests that freeze a .pt2 (torch.export + AOTInductor) are CPU-bound: inductor / g++ / ptxas generate code for minutes while the GPU sits idle. On a Tesla T4 the GPU measured ~98% idle (peak <250 MiB) for the entire duration of that test group, so those minutes are pure serialization in front of the GPU-bound tests.

This splits the pytest step into two groups that run concurrently on the one GPU:

  • -m aoti_compile — the CPU-bound .pt2-freezing tests, niced so the GPU group keeps CPU priority
  • -m "not aoti_compile" — the GPU-bound remainder

so the compile CPU-time overlaps the GPU unit tests. The compile group's GPU footprint is negligible (<250 MiB), so both groups coexist on one device without contention. Wall-clock becomes max(compile, gpu) instead of their sum.

How the partition stays correct

The aoti_compile marker is auto-applied in source/tests/conftest.py to any test whose module references a .pt2 freeze entry point (deserialize_to_file / _trace_and_export / aoti_compile_and_package). No hand-maintained file list — new compiling tests are partitioned automatically. The two selections are complementary and exhaustive (verified locally: A + B = total, disjoint).

Notes / limitations

  • The actual speedup (max(T_A, T_B) vs the sum) depends on the runner and is measurable on the CUDA CI; overlap quality scales with the runner's core count (nice is a no-op unless the CPU saturates).
  • Whole-file granularity: a compiling module's few non-compiling tests also run in the compile group (harmless).
  • The compile group tolerates pytest exit 5 ("no tests collected") so the split is safe on branches without .pt2-freezing tests.
  • Generic CI orchestration, independent of any feature branch.

Summary by CodeRabbit

  • Tests / CI
    • Improved CUDA CI by running AOT compilation tests and non-AOT GPU tests concurrently, producing separate JUnit reports with independent pass/fail handling.
    • Persisted the PyTorch compilation cache across CI runs to reduce repeated compilation time.
    • Introduced a dedicated AOT compilation test marker and updated test configuration to consistently classify tests via an explicit module allowlist.
    • Added a CI safety check that fails the run if compilation tests occur without the expected marker, with guidance on how to fix it.

The CUDA job ran source/tests as one serial process, so the .pt2-freezing
tests (torch.export + AOTInductor) held the step for minutes while
inductor/g++/ptxas generated code on the CPU -- the GPU measured ~98% idle
(<250 MiB) during those compiles. Split the pytest step into two groups that
run concurrently on the one GPU: a CPU-bound compile group (-m aoti_compile,
niced so the GPU group keeps CPU priority) and the GPU-bound remainder
(-m "not aoti_compile"), so the compile CPU-time overlaps the GPU unit tests.
The compile group's GPU footprint is negligible, so the two coexist on one
device without contention; wall-clock becomes max(compile, gpu) rather than
their sum.

The aoti_compile marker is auto-applied in source/tests/conftest.py to any
test whose module references a .pt2 freeze entry point, so the partition
stays correct as tests are added without a hand-maintained file list.
@wanghan-iapcm
wanghan-iapcm requested a review from njzjz July 20, 2026 00:15
@wanghan-iapcm wanghan-iapcm added the Test CUDA Trigger test CUDA workflow label Jul 20, 2026
@coderabbitai

coderabbitai Bot commented Jul 20, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: fdef6f5c-e497-4f2c-9d70-1a9b38e182c4

📥 Commits

Reviewing files that changed from the base of the PR and between b4a7996 and 6f8d76b.

📒 Files selected for processing (2)
  • .github/workflows/test_cuda.yml
  • source/tests/conftest.py
🚧 Files skipped from review as they are similar to previous changes (2)
  • .github/workflows/test_cuda.yml
  • source/tests/conftest.py

📝 Walkthrough

Walkthrough

CUDA pytest collection now assigns an explicit aoti_compile marker, detects unmarked AOTInductor compilations, and enables concurrent AOTI/non-AOTI CUDA test lanes with shared caching and separate JUnit artifacts.

Changes

AOTI CUDA test partitioning

Layer / File(s) Summary
Classify AOTI compilation tests
source/tests/conftest.py, pyproject.toml
An explicit module allowlist is normalized against collected paths, and matching tests receive the registered aoti_compile marker.
Detect marker drift
source/tests/conftest.py
Compilation entry points are wrapped to associate unmarked .pt2 compilations with test items, fail otherwise-successful sessions, and report offending nodeids.
Run partitioned CUDA tests
.github/workflows/test_cuda.yml
CUDA CI caches the Inductor directory, runs AOTI and non-AOTI pytest groups concurrently, handles an empty AOTI group, and uploads separate JUnit reports.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Suggested labels: build

Suggested reviewers: njzjz

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly matches the main change: overlapping AOTI .pt2 compilation tests with GPU unit tests in CUDA CI.
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

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

🤖 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 `@source/tests/conftest.py`:
- Around line 36-39: Update the file-reading exception handler in the conftest
collection logic to catch UnicodeDecodeError alongside OSError, preserving the
existing fallback of assigning an empty string to src for unreadable or
non-UTF-8 files.
🪄 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: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: abaf51b2-a9cb-488f-879f-939eaa65a1a4

📥 Commits

Reviewing files that changed from the base of the PR and between 20c7b75 and a9dab3a.

📒 Files selected for processing (3)
  • .github/workflows/test_cuda.yml
  • pyproject.toml
  • source/tests/conftest.py

Comment thread source/tests/conftest.py Outdated
Comment on lines +36 to +39
try:
src = Path(path).read_text(encoding="utf-8")
except OSError:
src = ""

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.

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Catch UnicodeDecodeError when reading file contents.

If pytest collects a non-text file or a text file with a different encoding (e.g., a compiled library or binary data file), Path.read_text(encoding="utf-8") will raise a UnicodeDecodeError. Since UnicodeDecodeError inherits from ValueError and not OSError, it will bypass the current except block and crash the test collection process.

Catch UnicodeDecodeError alongside OSError to safely fall back to an empty string for non-UTF-8 files.

🛠️ Proposed fix
-        try:
-            src = Path(path).read_text(encoding="utf-8")
-        except OSError:
+        try:
+            src = Path(path).read_text(encoding="utf-8")
+        except (OSError, UnicodeDecodeError):
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
try:
src = Path(path).read_text(encoding="utf-8")
except OSError:
src = ""
try:
src = Path(path).read_text(encoding="utf-8")
except (OSError, UnicodeDecodeError):
src = ""
🤖 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 `@source/tests/conftest.py` around lines 36 - 39, Update the file-reading
exception handler in the conftest collection logic to catch UnicodeDecodeError
alongside OSError, preserving the existing fallback of assigning an empty string
to src for unreadable or non-UTF-8 files.

@njzjz-bot

Copy link
Copy Markdown
Contributor

The overlap direction is worth exploring, but I don't think the current automatic aoti_compile marker is reliable enough to use as the scheduling boundary.

The marker is inferred by grepping source text, which is not equivalent to “this test actually triggers a .pt2 compile”:

  • It appears to miss real freeze(...)-driven .pt2 cases such as test_dp_freeze.py, test_change_bias.py, and test_finetune.py (the first two even document ~82 s per .pt2), because they do not contain the scanned strings directly.
  • It can over-classify whole files: some hits only produce .pth, mock aoti_compile_and_package, or test .pte; moving a large module such as test_deep_eval.py also moves many cases that are not necessarily CPU-only compilation work.
  • The premise that this lane is CPU-only is not fully established: the .pt2 path in serialization.py can use a CUDA target, run autotuning on that device, and move the exported program to CUDA before AOTInductor. The resulting tests also run GPU inference.

Could we instead mark the concrete .pt2-producing tests/fixtures/classes explicitly (including the freeze(...) cases), then benchmark both lanes on a CUDA runner? I would want a few repeated runs with --durations, GPU utilization/VRAM, and end-to-end wall time before relying on concurrency. If the overlap remains beneficial, adding a CPU/compiler-thread constraint to the compile lane would also help reduce GPU contention and timeout variance.

Keeping separate JUnit reports is a good part of the implementation.

Authored by OpenClaw (model: custom-chat-jinzhezeng-group/gpt-5.6-terra)

@codecov

codecov Bot commented Jul 20, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 78.56%. Comparing base (20c7b75) to head (6f8d76b).

Additional details and impacted files
@@            Coverage Diff             @@
##           master    #5882      +/-   ##
==========================================
- Coverage   78.83%   78.56%   -0.27%     
==========================================
  Files        1054     1054              
  Lines      121758   121758              
  Branches     4410     4411       +1     
==========================================
- Hits        95983    95663     -320     
- Misses      24215    24528     +313     
- Partials     1560     1567       +7     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Han Wang added 2 commits July 20, 2026 14:41
Replace the source-grep aoti_compile auto-marker with an explicit curated list
of the modules that actually freeze a .pt2, plus an always-on runtime guard.
The grep both missed freeze(...)-driven compiles (test_dp_freeze, test_finetune,
test_multitask, test_dp_test, test_change_bias, test_models -- none name the
low-level entry points) and over-tagged files that only mock the compiler or
emit .pth/.pte. The producer list was derived by running the suite under a
detector that wraps the real compile entry points (aoti_compile_and_package,
_trace_and_export).

The guard wraps those same entry points every run and fails the session, with
the offending nodeids, if any test freezes a .pt2 without the aoti_compile
marker -- so the list cannot silently drift, and a compile can never escape into
the GPU lane unnoticed. It runs in the CPU test_python CI too, so drift is
caught cheaply rather than only in the expensive CUDA job.
- echo each lane's wall-clock ([lane aoti_compile] / [lane gpu]) so T_A and T_B
  (vs the ~3h46m serial baseline) appear directly in the log
- cap the compile lane at TORCHINDUCTOR_COMPILE_THREADS=nproc/2 (plus nice) so
  the GPU lane keeps CPU for its host-side dispatch
- upload junit-aoti.xml / junit-gpu.xml as artifacts for post-run inspection
- share one AOTInductor compile cache across all steps via a job-level
  TORCHINDUCTOR_CACHE_DIR, so the C++ fixture builds (gen_*.py in
  test_cc_local.sh) reuse the kernels the Python lane compiled, and persist it
  across runs with actions/cache (10 GB repo-cache limit noted inline)

@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: 3

🤖 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 @.github/workflows/test_cuda.yml:
- Line 36: Update the actions/checkout step in the CUDA test workflow to
explicitly disable credential persistence by setting persist-credentials to
false, while leaving the existing checkout behavior unchanged.
- Around line 97-108: Update both pytest lane subshells in the parallel test
execution block to initialize rc=0 before running pytest and append || rc=$? to
each pytest command. Preserve the existing wall-time echo and exit $rc reporting
so failures are captured without set -e bypassing the reporting.

In `@source/tests/conftest.py`:
- Around line 118-123: Update the pytest_runtest_protocol hookwrapper so the
yield executes inside a try...finally block, and move the _current_item["item"]
reset into the finally clause to guarantee cleanup when the wrapped hook raises.
🪄 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: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 2d10c758-b81a-495e-9f64-75a7057c1315

📥 Commits

Reviewing files that changed from the base of the PR and between a9dab3a and b4a7996.

📒 Files selected for processing (2)
  • .github/workflows/test_cuda.yml
  • source/tests/conftest.py

# options: --gpus all
if: github.repository_owner == 'deepmodeling' && (github.event_name == 'pull_request' && github.event.label && github.event.label.name == 'Test CUDA' || github.event_name == 'workflow_dispatch' || github.event_name == 'merge_group')
steps:
- uses: actions/checkout@v7

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.

🔒 Security & Privacy | 🟡 Minor | ⚡ Quick win

Disable credential persistence to improve workflow security.

The actions/checkout action persists the GITHUB_TOKEN in the local git config by default. If untrusted code or tests are executed within this environment, the token could be exposed. It is a security best practice to explicitly set persist-credentials: false unless the workflow requires authenticating subsequent git commands (like pushing a commit).

🛡️ Proposed fix
-      - uses: actions/checkout@v7
+      - uses: actions/checkout@v7
+        with:
+          persist-credentials: false
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
- uses: actions/checkout@v7
- uses: actions/checkout@v7
with:
persist-credentials: false
🧰 Tools
🪛 zizmor (1.26.1)

[warning] 36-36: credential persistence through GitHub Actions artifacts (artipacked): does not set persist-credentials: false

(artipacked)

🤖 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 @.github/workflows/test_cuda.yml at line 36, Update the actions/checkout step
in the CUDA test workflow to explicitly disable credential persistence by
setting persist-credentials to false, while leaving the existing checkout
behavior unchanged.

Source: Linters/SAST tools

Comment thread .github/workflows/test_cuda.yml Outdated
Comment thread source/tests/conftest.py
@wanghan-iapcm

Copy link
Copy Markdown
Collaborator Author

Thanks — you're right that a source grep is the wrong basis for the scheduling boundary, on both counts. Replaced it (f660993, b4a7996).

The aoti_compile set is now an explicit curated list of the modules that actually freeze a .pt2, derived by running the suite under a detector that wraps the real compile entry points (aoti_compile_and_package, _trace_and_export) instead of scanning text:

  • Missed freeze(...) casestest_dp_freeze, test_finetune, test_multitask, test_dp_test, test_change_bias, infer/test_models are now in the compile lane (none name the low-level entry points, so the grep skipped them all).
  • Over-tagging — the detector confirmed 8 grep-candidate files (test_dpa1, test_dpa4, test_dpa1_triton, test_get_model_dpa4, test_dpa2_graph_lower, test_sezm_model, test_entrypoint, test_descriptor_dpa1_triton) never actually compile (mock / .pth / .pte), so they stay in the GPU lane.

An always-on guard wraps the same entry points every run and fails the session (with the offending nodeids) if any test freezes a .pt2 without the marker, so the list can't silently drift; it runs in the CPU test_python CI too, so a missed producer is caught cheaply rather than only in the CUDA job.

Other points: compile-thread constraint added (TORCHINDUCTOR_COMPILE_THREADS=nproc/2 + nice); each lane now echoes its wall-clock and uploads its own JUnit report to compare T_A/T_B against the ~3h46m serial baseline. The current labelled CUDA run is on the pre-fix commit (grep split, so a lower bound); I'll re-trigger on the fixed markers and post the lane timings + GPU util/VRAM before relying on the concurrency beyond what those numbers support. Agreed the lane isn't categorically CPU-only — the ~98% idle / <250 MiB is empirical and the benchmark will confirm on the fixed split.

Also made the inductor compile cache explicit (shared TORCHINDUCTOR_CACHE_DIR + actions/cache) so the C++ fixture builds reuse the Python lane's kernels. (The UnicodeDecodeError note is moot now — the conftest no longer reads test source.)

- CUDA lanes: init rc=0 and `|| rc=$?` so the step's default `set -e` cannot
  skip the per-lane wall-clock echo when a lane fails (that timing is exactly
  what we want on a failure).
- conftest guard: wrap the hookwrapper yield in try/finally so _current_item is
  reset even if the wrapped hook raises, avoiding misattributed compiles in
  teardown hooks.
@wanghan-iapcm wanghan-iapcm added Test CUDA Trigger test CUDA workflow and removed Test CUDA Trigger test CUDA workflow labels Jul 20, 2026
@github-actions github-actions Bot removed the Test CUDA Trigger test CUDA workflow label Jul 20, 2026

@njzjz-bot njzjz-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.

Thanks for replacing the source-grep classifier with an explicit producer list and a runtime drift guard. That fixes the main issue in the first revision. I still think this needs changes before merge.

  1. The scheduling boundary remains module-level, while the safety/performance premise is test-level. Every test in each _AOTI_COMPILE_MODULES file is sent to the aoti_compile process, including tests that do GPU inference or other GPU-heavy work but do not freeze a .pt2. Both pytest processes are pinned to CUDA_VISIBLE_DEVICES=0, so the implementation can still create real GPU/VRAM contention between two independent CUDA processes. The marker/guard establishes that .pt2 producers do not escape the compile lane; it does not establish that the compile lane has negligible GPU use.

    Please either partition at concrete test/fixture/class granularity, or provide repeated CUDA-run evidence for the current explicit partition: per-lane --durations, GPU-utilization/VRAM traces, end-to-end wall time, and a few clean repeat runs. I would not merge based only on the earlier aggregate 98%-idle observation.

  2. Make the persistent Inductor cache environment-safe. restore-keys: inductor-cuda- can restore artifacts created under a different Torch/Inductor, CUDA/toolchain, or GPU architecture. Please key the cache at least on the dependency/workflow lock inputs and the relevant Torch/CUDA environment (and GPU capability if runners may vary), rather than restoring from every historical CUDA cache. A miss is fine; cache correctness and reproducibility matter more than a hit.

  3. Minor security hygiene: set persist-credentials: false on actions/checkout, since this workflow executes PR code/tests and does not need to push.

The separate JUnit reports and the per-lane exit-code/timing handling look good.

Authored by OpenClaw (model: custom-chat-jinzhezeng-group/gpt-5.6-terra)

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

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants