Skip to content

[None][feat] Enable PyTorch profiler traces for VisualGen - #16814

Merged
chang-l merged 14 commits into
NVIDIA:mainfrom
chang-l:agent/visual-gen-torch-profiler
Aug 1, 2026
Merged

[None][feat] Enable PyTorch profiler traces for VisualGen#16814
chang-l merged 14 commits into
NVIDIA:mainfrom
chang-l:agent/visual-gen-torch-profiler

Conversation

@chang-l

@chang-l chang-l commented Jul 23, 2026

Copy link
Copy Markdown
Collaborator

++ BrianLi23 for viz

Description

VisualGen already used TLLM_PROFILE_VISUAL_GEN_START_STOP to gate Nsight Systems capture, but it could not emit PyTorch/Kineto traces the way the LLM executor does. This adds that, and fixes the coverage gaps found while wiring it up.

PyTorch traces on the existing VisualGen ranges

When TLLM_TORCH_PROFILE_TRACE is set, a torch.profiler.profile session opens and closes on the same windows the CUDA/Nsight gate already uses:

  • all — the complete request, text encoding through VAE decode
  • predenoise — request start through denoise-loop setup
  • postdenoise — end of a denoise loop through request completion
  • A-B, A-B,C-D, A,B — per-denoise-loop step ranges

Warmup is excluded. Each rank writes its own file (-rank-N), and each repeated window within a rank gets a -window-N suffix, so nothing is overwritten. There are no public API or dependency changes.

cudaProfilerStart()/cudaProfilerStop() remain capture gates for an attached Nsight collector — they do not start Nsight on their own. Run Nsight and torch traces in separate invocations, since both use CUPTI.

Profiler ownership

VisualGenProfiler (_torch/visual_gen/profiler.py) owns every window decision. BasePipeline holds one instance and exposes two thin seams:

  • run_inference() wraps a request in request_scope(), which opens the all/predenoise window and guarantees every window closes on the way out, including after an exception. DiffusionExecutor calls this instead of infer().
  • _profile_denoise_steps() replaces a denoise loop's enumerate() and drives every boundary that loop owns — closing the pre-denoise window before the first step, toggling step windows on the configured indices, and arming the post-denoise window after the last step.

Folding all boundaries into the iterator means a loop cannot be half-instrumented. Each denoise loop differs from a plain enumerate() by exactly one call.

Denoise loops that bypass BasePipeline.denoise()

Five loops exist in visual_gen; four do not go through the base denoise() and were invisible to the profiler. All are now instrumented:

loop
BasePipeline.denoise() base path
pipeline_qwen_image.py::forward custom true-CFG loop
pipeline_ltx2_two_stages.py::_refinement_denoise stage 2 — a numeric range previously captured stage 1 only and silently dropped stage 2
pipeline_qwen_image_edit.py::forward landed on main during this PR
pipeline_qwen_image_layered.py::forward landed on main during this PR

test_every_denoise_loop_is_instrumented walks the visual_gen AST, finds every loop that steps a transformer, and asserts each one uses _profile_denoise_steps(). It caught the two Qwen-Image loops above when main was merged in, which is what it was written for.

Window lifecycle fixes

  • Close on an idle device. torch.cuda.synchronize() runs before cudaProfilerStop(). A collector may stop collecting — or end the process, as nsys --capture-range-end=stop-shutdown does — the moment the range closes, so no async work may still be in flight. Mirrors PyExecutor.profile_step().
  • A numeric window never outlives its loop. Previously only the exact stop index closed it, so a range extending past the last step (0-4 against LTX-2 stage 2's three steps) stayed open through VAE decode and, on a multi-stage pipeline, into the following stage. It now closes when the iterator exhausts.
  • Cleanup survives a failing sync. A sticky CUDA error surfacing at synchronize() previously skipped cudaProfilerStop(), left _active set, and left Kineto recording — wedging the profiler for the rest of the process. The shutdown now runs from a finally; the error still propagates.

Known limitation

postdenoise is single-shot and arms after the first denoise loop. On a multi-stage pipeline its window is therefore a superset of VAE decode — on LTX-2 two-stage it also holds the spatial upsample and all of stage 2. Isolating decode there would require the profiler to know which loop is last, which BasePipeline.denoise() cannot tell it. Documented in parse_profile_range and the perf-analysis guide, and pinned by test_postdenoise_arms_after_the_first_loop_on_multi_stage so it cannot drift silently.

Test Coverage

tests/unittest/_torch/visual_gen/test_profiler.py (registered in l0_a10.yml) covers:

  • profiler setup, rank-suffixed trace paths, and the missing-range warning
  • window start/stop driving both collectors, and a fresh trace file per window
  • sync ordering before cudaProfilerStop(), and collector shutdown when the sync raises
  • CUDA gate closing when trace export fails
  • all / predenoise / postdenoise boundary ordering, single-shot phases not re-arming, and window closure when inference raises
  • numeric ranges, including a window that must not outlive a stage shorter than the range
  • warmup exclusion
  • the structural guard that every denoise loop is instrumented

Also updated: test_qwen_image_pipeline.py (numeric-range coverage through the real forward), test_visual_gen_params.py and test_executor_shared_tensor_ipc.py (run_inference entry point).

Verification:

  • Focused suite green: 116 passed across test_profiler, test_qwen_image_pipeline, test_visual_gen_params, test_flux_infer, test_qwen_image_infer, test_executor_shared_tensor_ipc. Run in the nightly staging release container against the merged tree. test_ltx2_pipeline.py was not run — it requires LLM_MODELS_ROOT.
  • Pre-commit passes on all changed files.
  • Earlier manual validation on B200: Wan 2.1 T2V 1.3B completed a 480x832 17-frame request with TLLM_PROFILE_VISUAL_GEN_START_STOP=all, producing a valid 47.7 MB / 128,681-event trace containing text-encoder ops, 15,250 GPU kernels, and VAE-decode convolutions. A separate five-step Nsight run exited 0 and observed the same capture gates.

PR Checklist

  • PR description clearly explains what and why.

  • PR follows the TRT-LLM coding guidelines.

  • Test cases are provided for the new code paths and are routed in the test database.

  • No public API changes or new dependencies.

  • Documentation is updated.

  • No CODEOWNERS or architecture-diagram change is required.

  • Please check this after reviewing the above items as appropriate for this PR.

GitHub Bot Help

To see a list of available CI bot commands, comment /bot help.

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

coderabbitai Bot commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

VisualGen profiling now supports environment-controlled PyTorch traces coordinated with CUDA profiler ranges, denoising-phase hooks, rank- and window-specific trace output, executor integration through run_inference, expanded tests, and updated profiling guidance.

Changes

VisualGen profiling and inference integration

Layer / File(s) Summary
Profiling configuration and initialization
tensorrt_llm/_torch/visual_gen/profiler.py
Adds profiling range parsing, optional torch.profiler setup, and rank- and window-specific trace paths.
Profiler lifecycle and denoising integration
tensorrt_llm/_torch/visual_gen/profiler.py, tensorrt_llm/_torch/visual_gen/pipeline.py, tensorrt_llm/_torch/visual_gen/models/...
Coordinates request, denoise-step, cleanup, Qwen Image, and LTX-2 Stage 2 profiling windows.
Inference entrypoint and profiling boundaries
tensorrt_llm/_torch/visual_gen/executor.py, tests/unittest/visual_gen/test_executor_shared_tensor_ipc.py, tests/unittest/_torch/visual_gen/test_visual_gen_params.py
Routes executor requests through run_inference and updates stubs and failure assertions.
Profiling validation and usage guidance
tests/unittest/_torch/visual_gen/test_profiler.py, tests/unittest/_torch/visual_gen/test_qwen_image_pipeline.py, tests/integration/test_lists/test-db/l0_a10.yml, docs/source/developer-guide/perf-analysis.md
Validates profiler setup, lifecycle ordering, cleanup, step ranges, warmup behavior, loop instrumentation, Qwen Image integration, test-list wiring, and documented usage.

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

Sequence Diagram(s)

sequenceDiagram
  participant DiffusionExecutor
  participant BasePipeline
  participant VisualGenProfiler
  participant CUDAProfiler
  participant TorchProfiler
  DiffusionExecutor->>BasePipeline: Call run_inference(req)
  BasePipeline->>VisualGenProfiler: Enter request_scope()
  VisualGenProfiler->>CUDAProfiler: Start selected window
  VisualGenProfiler->>TorchProfiler: Start tracing
  BasePipeline->>VisualGenProfiler: Iterate denoise steps
  VisualGenProfiler->>CUDAProfiler: Stop profiling window
  VisualGenProfiler->>TorchProfiler: Export Chrome trace
  BasePipeline-->>DiffusionExecutor: Return PipelineOutput
Loading

Possibly related PRs

Suggested labels: api-compatible

Suggested reviewers: qijune

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 55.93% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
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.
Title check ✅ Passed The title matches the main change and follows the required [None][feat] format.
Description check ✅ Passed The description covers the change, tests, and checklist, and is mostly complete.
✨ 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: 2

🧹 Nitpick comments (1)
tensorrt_llm/_torch/visual_gen/pipeline.py (1)

191-212: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Annotate profiler lifecycle methods.

Add -> None to both methods.

-    def _cuda_profiler_start(self):
+    def _cuda_profiler_start(self) -> None:
...
-    def _cuda_profiler_stop(self):
+    def _cuda_profiler_stop(self) -> None:

As per coding guidelines, “Annotate every function, use None for non-returning functions.”

🤖 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 `@tensorrt_llm/_torch/visual_gen/pipeline.py` around lines 191 - 212, Add the
`-> None` return annotation to both `_cuda_profiler_start` and
`_cuda_profiler_stop`, preserving their existing profiler lifecycle behavior.

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 `@tensorrt_llm/_torch/visual_gen/pipeline.py`:
- Around line 178-179: Update the trace path construction around
self._torch_profile_trace_path so each profiling window receives a unique output
filename instead of reusing the same rank-based path. Add and maintain a window
index across repeated A-B,C-D profiling stops, incorporating it into the
exported trace path while preserving the existing rank and extension components.

In `@tests/unittest/_torch/visual_gen/test_profiler.py`:
- Around line 17-88: The profiler tests cover only a single start/stop window;
add a test for multiple configured ranges such as 0-1 and 3-4, asserting the
corresponding profiling behavior across both windows. Also register
tests/unittest/_torch/visual_gen/test_profiler.py in the relevant test-db and QA
test-list files, preserving their existing list format.

---

Nitpick comments:
In `@tensorrt_llm/_torch/visual_gen/pipeline.py`:
- Around line 191-212: Add the `-> None` return annotation to both
`_cuda_profiler_start` and `_cuda_profiler_stop`, preserving their existing
profiler lifecycle behavior.
🪄 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: ce070dc7-3ebc-4306-babd-3cbb37cc95e9

📥 Commits

Reviewing files that changed from the base of the PR and between 80b4eb3 and d787ad9.

📒 Files selected for processing (3)
  • docs/source/developer-guide/perf-analysis.md
  • tensorrt_llm/_torch/visual_gen/pipeline.py
  • tests/unittest/_torch/visual_gen/test_profiler.py

Comment thread tensorrt_llm/_torch/visual_gen/pipeline.py Outdated
Comment thread tests/unittest/_torch/visual_gen/test_profiler.py Outdated

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
tensorrt_llm/_torch/visual_gen/pipeline.py (2)

181-190: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Select the profiler activity at runtime. torch.profiler.profile() should not be given both CUDA and XPU unconditionally; on builds that only support one accelerator backend, profiler setup can fail. Build the list from the supported activities or branch on the active device so the trace requests only CPU + the matching accelerator.

🤖 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 `@tensorrt_llm/_torch/visual_gen/pipeline.py` around lines 181 - 190, Update
the profiler setup around self._torch_profiler and activities to select only the
accelerator activity supported by the active device or build, always retaining
CPU while excluding the incompatible CUDA/XPU activity. Pass the resulting
CPU-plus-matching-accelerator list to torch.profiler.profile.

189-189: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

with_modules=True won’t record module hierarchy here — VisualGen uses eager nn.Modules, so the profiler trace won’t include module info unless this path is scripted/traced or you add explicit instrumentation.

🤖 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 `@tensorrt_llm/_torch/visual_gen/pipeline.py` at line 189, The profiler
configuration using with_modules=True does not capture hierarchy for VisualGen’s
eager nn.Module execution. Remove this ineffective option or replace it with
explicit module instrumentation, ensuring module hierarchy is recorded only
through a supported mechanism.
🧹 Nitpick comments (1)
tests/unittest/_torch/visual_gen/test_qwen_image_pipeline.py (1)

226-253: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Expand profiler lifecycle coverage

test_forward_honors_profile_step_range only covers the 0-1 path. Add focused cases for predenoise, all, postdenoise, warmup gating, and the final inactive state; the postdenoise case should assert that tracing stops and exports.

Coverage summary: added test_forward_honors_profile_step_range; listed in tests/integration/test_lists/test-db/l0_a10.yml; no matching entry in tests/integration/test_lists/qa/. Coverage verdict: needs follow-up.

🤖 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/_torch/visual_gen/test_qwen_image_pipeline.py` around lines
226 - 253, Expand test_forward_honors_profile_step_range with focused profiler
lifecycle cases covering predenoise, all, postdenoise, warmup gating, and the
final inactive state. Configure each profile range and assert the corresponding
CUDA and torch profiler calls, especially that postdenoise stops tracing and
exports the configured trace. Keep the tests isolated with the existing pipeline
test doubles and mocks.

Source: Path instructions

🤖 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 `@tensorrt_llm/_torch/visual_gen/models/qwen_image/pipeline_qwen_image.py`:
- Around line 528-530: Update the forward path around
_start_postdenoise_profile() so post-denoise profiling is stopped and exported
after _decode_latents() completes. Use a finally block to call the corresponding
stop operation even when decoding raises, ensuring profiler state is not left
active and the Chrome trace is exported.
- Around line 488-494: Move the `_start_predenoise_profile()` call earlier in
the request flow, before timestep creation and scheduler preparation, so
pre-loop scheduler refresh work is included in profiling. Keep
`_start_denoise_profile()` immediately before the denoising loop.

---

Outside diff comments:
In `@tensorrt_llm/_torch/visual_gen/pipeline.py`:
- Around line 181-190: Update the profiler setup around self._torch_profiler and
activities to select only the accelerator activity supported by the active
device or build, always retaining CPU while excluding the incompatible CUDA/XPU
activity. Pass the resulting CPU-plus-matching-accelerator list to
torch.profiler.profile.
- Line 189: The profiler configuration using with_modules=True does not capture
hierarchy for VisualGen’s eager nn.Module execution. Remove this ineffective
option or replace it with explicit module instrumentation, ensuring module
hierarchy is recorded only through a supported mechanism.

---

Nitpick comments:
In `@tests/unittest/_torch/visual_gen/test_qwen_image_pipeline.py`:
- Around line 226-253: Expand test_forward_honors_profile_step_range with
focused profiler lifecycle cases covering predenoise, all, postdenoise, warmup
gating, and the final inactive state. Configure each profile range and assert
the corresponding CUDA and torch profiler calls, especially that postdenoise
stops tracing and exports the configured trace. Keep the tests isolated with the
existing pipeline test doubles and mocks.
🪄 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: dbb8a4b9-421d-4cb2-bc91-1fad5661b0e9

📥 Commits

Reviewing files that changed from the base of the PR and between d787ad9 and f930d1a.

📒 Files selected for processing (4)
  • docs/source/developer-guide/perf-analysis.md
  • tensorrt_llm/_torch/visual_gen/models/qwen_image/pipeline_qwen_image.py
  • tensorrt_llm/_torch/visual_gen/pipeline.py
  • tests/unittest/_torch/visual_gen/test_qwen_image_pipeline.py
🚧 Files skipped from review as they are similar to previous changes (1)
  • docs/source/developer-guide/perf-analysis.md

Comment thread tensorrt_llm/_torch/visual_gen/models/qwen_image/pipeline_qwen_image.py Outdated
Comment thread tensorrt_llm/_torch/visual_gen/models/qwen_image/pipeline_qwen_image.py Outdated
@chang-l
chang-l requested review from a team as code owners July 24, 2026 06:35
@chang-l
chang-l marked this pull request as draft July 24, 2026 16:38
chang-l added 8 commits July 27, 2026 11:09
Signed-off-by: Chang Liu <9713593+chang-l@users.noreply.github.com>
Signed-off-by: Chang Liu <9713593+chang-l@users.noreply.github.com>
Signed-off-by: Chang Liu <9713593+chang-l@users.noreply.github.com>
Signed-off-by: Chang Liu <9713593+chang-l@users.noreply.github.com>
Profiling state and lifecycle lived directly on BasePipeline: five
attributes and six methods on a class that is already 1300+ lines. Every
test double had to reproduce that state (the Qwen-Image doubles carried
eight profiler attributes just to call forward()), and the profiler tests
could only reach the logic by rebinding unbound methods onto
SimpleNamespace fakes.

Move it all to VisualGenProfiler in a new profiler.py. BasePipeline keeps
one attribute and thin hooks that gate on warmup and delegate. Three
changes ride along:

* Close each capture window on an idle device. A collector may stop
  collecting -- or end the process, as nsys --capture-range-end=stop-shutdown
  does -- the moment the range closes, so torch.cuda.synchronize() must run
  first. PyExecutor.profile_step() already does this; VisualGen did not.

* Name the hooks for what they do. _profile_denoise_start() stopped the
  profiler and _profile_denoise_end() started it, which reads backwards at
  the call site. They are now _close_predenoise_window() and
  _open_postdenoise_window(), matching _open_step_window() /
  _close_step_window().

* Test the profiler directly. No SimpleNamespace fakes, no MethodType
  rebinding; new coverage for the pre-stop sync, the no-op close, request
  scopes that unwind on an exception, and single-shot phases not re-arming.

No behavior change beyond the added synchronize().

Signed-off-by: Chang Liu <9713593+chang-l@users.noreply.github.com>
visual_gen has three denoise loops: BasePipeline.denoise(), Qwen-Image's
true-CFG loop, and LTX-2 two-stage's _refinement_denoise(). Only the first
two were instrumented, so on LTX2TwoStagesPipeline a numeric
TLLM_PROFILE_VISUAL_GEN_START_STOP range captured stage 1 and silently
dropped stage 2. That is worse than the Qwen-Image gap it mirrors: Qwen
produced no trace at all, which is obvious, whereas this produces a
plausible trace missing half the denoising.

Add the same hooks to the stage 2 loop, and add a source-level test that
finds every loop in visual_gen stepping a transformer and asserts it calls
them -- the next hand-written loop fails the test instead of quietly
producing a short trace.

Also document what multi-stage pipelines mean for the per-loop modes:
numeric ranges now emit one trace file per stage, and postdenoise still
opens at the end of stage 1, so its window spans the later stages too.

Signed-off-by: Chang Liu <9713593+chang-l@users.noreply.github.com>
A pipeline with its own denoise loop had to place four calls in the right
four spots -- close_predenoise before the loop, open/close_step_window
around each step, open_postdenoise after. Placing three of four still
compiles and still produces a trace, just a wrong one, which is the failure
mode that hid the Qwen-Image and LTX-2 stage 2 gaps.

Fold all four into the iterator the loop already needs:

    for i, t in self._profile_denoise_steps(timesteps):
        ...

The generator closes the pre-denoise window before yielding the first step,
toggles step windows on the indices a numeric range names, and opens the
post-denoise window after the last one. Partial instrumentation is no
longer expressible, and the loop bodies keep their indentation. Breaking
out early or raising skips the post-denoise arm, as before -- request_scope
still closes any window left open.

All three denoise loops now differ from a plain enumerate() by one call,
and the structural test asserts exactly that.

Signed-off-by: Chang Liu <9713593+chang-l@users.noreply.github.com>
steps() only closed a numeric window on the exact stop index, so a range
extending past a loop's last step never closed inside the loop --
request_scope() closed it at request exit instead. LTX-2 stage 2 runs three
steps, so 0-4 opened on its step 0 and stayed open through VAE decode:

    stage2 step0 -> open -> step1 -> step2 -> [still open] -> decode -> close

The stage-2 trace therefore contained decode, contradicting the documented
one-trace-per-stage behavior, and on a pipeline with a third stage the
window would have swallowed that too.

Numeric ranges select denoise steps, so close any window still open when
the iterator exhausts. Regression test drives two stages with the second
shorter than the selected range. Keyword modes are untouched: the new close
is guarded on the range being numeric, so predenoise/postdenoise/all keep
their existing boundaries.

Signed-off-by: Chang Liu <9713593+chang-l@users.noreply.github.com>
chang-l added 3 commits July 29, 2026 16:39
The parse_profile_range docstring claimed postdenoise ran "from the end of
the last denoise loop", but it is single-shot and arms at the end of the
*first* one. On LTX-2 two-stage that window is a superset of VAE decode --
it also holds the spatial upsample and all of stage 2 -- which contradicted
the docstring while the perf-analysis guide described it correctly.

Keep the behavior and state the limitation in both places. It is a superset,
not a gap: decode is always in the trace, just not alone. No mode isolates
decode on that pipeline; doing so needs the profiler to know which loop is
last, which the base denoise() cannot tell it.

Add a test pinning the window contents so the behavior cannot drift without
the docs being revisited.

Signed-off-by: Chang Liu <9713593+chang-l@users.noreply.github.com>
…rch-profiler

Signed-off-by: Chang Liu <9713593+chang-l@users.noreply.github.com>

# Conflicts:
#	tensorrt_llm/_torch/visual_gen/pipeline.py
Merging main brought in Qwen-Image-Edit (NVIDIA#16095) and Qwen-Image-Layered,
each with its own denoise loop that bypasses BasePipeline.denoise(). Both
were invisible to TLLM_PROFILE_VISUAL_GEN_START_STOP, the same gap this
branch fixed for Qwen-Image and LTX-2 stage 2.

test_every_denoise_loop_is_instrumented caught them on the merge, which is
what it was written for. Route both through _profile_denoise_steps().

Also relax that test's exact loop count to a lower bound: a hard count fails
on every newly added pipeline rather than on the thing that matters, which is
whether each detected loop is instrumented.

Signed-off-by: Chang Liu <9713593+chang-l@users.noreply.github.com>
@chang-l
chang-l requested a review from a team as a code owner July 29, 2026 23:52
@chang-l
chang-l requested a review from jieli-matrix July 29, 2026 23:52

@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

♻️ Duplicate comments (1)
tensorrt_llm/_torch/visual_gen/profiler.py (1)

214-220: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Guard CUDA runtime calls on non-CUDA builds.

XPU tracing is explicitly supported in _create_torch_profiler(), but these unconditional torch.cuda.cudart() calls fail before startup or during cleanup on XPU-only builds. Gate both start/stop calls with torch.cuda.is_available() while still starting/stopping the torch profiler.

Also applies to: 260-263

🤖 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 `@tensorrt_llm/_torch/visual_gen/profiler.py` around lines 214 - 220, Update
the profiler start and cleanup logic around the CUDA runtime calls to check
torch.cuda.is_available() before invoking torch.cuda.cudart(),
cudaProfilerStart(), or cudaProfilerStop(). Keep _torch_profiler.start() and its
corresponding stop behavior active regardless of CUDA availability so XPU-only
tracing continues to work.
🧹 Nitpick comments (1)
tensorrt_llm/_torch/visual_gen/profiler.py (1)

27-38: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Use parameterized Python 3.10+ annotations.

ProfileRange leaves its endpoint types unknown and uses legacy Union/Tuple/Optional forms. Use str | tuple[frozenset[int], frozenset[int]] | None and str | None.

Also applies to: 133-133

🤖 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 `@tensorrt_llm/_torch/visual_gen/profiler.py` around lines 27 - 38, Update the
ProfileRange alias to use Python 3.10 union and built-in generic syntax,
explicitly typing both range endpoints as frozenset[int] and allowing None. Also
replace the separately referenced Optional annotation at the indicated usage
with str | None, removing legacy Union, Tuple, and Optional imports if no longer
needed.

Sources: Coding guidelines, Learnings

🤖 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 `@tensorrt_llm/_torch/visual_gen/profiler.py`:
- Around line 49-59: Replace every en dash character in the profiler mode
docstring entries with Ruff-compliant punctuation, such as a hyphen or
equivalent ASCII separator, while preserving the documented meanings and
formatting of the A-B, range, predenoise, postdenoise, all, and unset options.

---

Duplicate comments:
In `@tensorrt_llm/_torch/visual_gen/profiler.py`:
- Around line 214-220: Update the profiler start and cleanup logic around the
CUDA runtime calls to check torch.cuda.is_available() before invoking
torch.cuda.cudart(), cudaProfilerStart(), or cudaProfilerStop(). Keep
_torch_profiler.start() and its corresponding stop behavior active regardless of
CUDA availability so XPU-only tracing continues to work.

---

Nitpick comments:
In `@tensorrt_llm/_torch/visual_gen/profiler.py`:
- Around line 27-38: Update the ProfileRange alias to use Python 3.10 union and
built-in generic syntax, explicitly typing both range endpoints as
frozenset[int] and allowing None. Also replace the separately referenced
Optional annotation at the indicated usage with str | None, removing legacy
Union, Tuple, and Optional imports if no longer needed.
🪄 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: da9a2e31-3c51-4289-972c-59c6e8e9c49d

📥 Commits

Reviewing files that changed from the base of the PR and between 62f7864 and cd2081d.

📒 Files selected for processing (7)
  • docs/source/developer-guide/perf-analysis.md
  • tensorrt_llm/_torch/visual_gen/models/ltx2/pipeline_ltx2_two_stages.py
  • tensorrt_llm/_torch/visual_gen/models/qwen_image/pipeline_qwen_image_edit.py
  • tensorrt_llm/_torch/visual_gen/models/qwen_image_layered/pipeline_qwen_image_layered.py
  • tensorrt_llm/_torch/visual_gen/pipeline.py
  • tensorrt_llm/_torch/visual_gen/profiler.py
  • tests/integration/test_lists/test-db/l0_a10.yml
🚧 Files skipped from review as they are similar to previous changes (1)
  • docs/source/developer-guide/perf-analysis.md

Comment thread tensorrt_llm/_torch/visual_gen/profiler.py

@fredricz-20070104 fredricz-20070104 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review summary - Approve

Reviewed the full diff; no blocking or major issues found.

Minor, non-blocking notes:

  • tensorrt_llm/_torch/visual_gen/profiler.py: CUDA runtime gate calls are unconditional while torch profiler supports XPU
  • tensorrt_llm/_torch/visual_gen/profiler.py: Legacy typing forms / missing parameterization

Automated review by NVCortex Lite, run by @fredricz-20070104.

@chang-l

chang-l commented Jul 30, 2026

Copy link
Copy Markdown
Collaborator Author

/bot run --disable-fail-fast

@tensorrt-cicd

Copy link
Copy Markdown
Collaborator

PR_Github #62839 [ run ] triggered by Bot. Commit: cd2081d Link to invocation

@tensorrt-cicd

Copy link
Copy Markdown
Collaborator

PR_Github #62839 [ run ] completed with state SUCCESS. Commit: cd2081d
/LLM/main/L0_MergeRequest_PR pipeline #50963 completed with status: 'SUCCESS'

CI Report

Link to invocation

…rch-profiler

Signed-off-by: Chang Liu <9713593+chang-l@users.noreply.github.com>

# Conflicts:
#	tensorrt_llm/_torch/visual_gen/models/qwen_image/pipeline_qwen_image.py
#	tests/unittest/_torch/visual_gen/test_qwen_image_pipeline.py
@chang-l

chang-l commented Jul 30, 2026

Copy link
Copy Markdown
Collaborator Author

/bot run --disable-fail-fast

@tensorrt-cicd

Copy link
Copy Markdown
Collaborator

PR_Github #62882 [ run ] triggered by Bot. Commit: dd59c84 Link to invocation

@tensorrt-cicd

Copy link
Copy Markdown
Collaborator

PR_Github #62882 [ run ] completed with state SUCCESS. Commit: dd59c84
/LLM/main/L0_MergeRequest_PR pipeline #51005 completed with status: 'SUCCESS'

CI Report

Link to invocation

…rch-profiler

Signed-off-by: Chang Liu <9713593+chang-l@users.noreply.github.com>

# Conflicts:
#	tensorrt_llm/_torch/visual_gen/executor.py
@chang-l

chang-l commented Jul 31, 2026

Copy link
Copy Markdown
Collaborator Author

/bot run --disable-fail-fast

@tensorrt-cicd

Copy link
Copy Markdown
Collaborator

PR_Github #63074 [ run ] triggered by Bot. Commit: 5da5d78 Link to invocation

@chang-l
chang-l enabled auto-merge (squash) July 31, 2026 17:12
@tensorrt-cicd

Copy link
Copy Markdown
Collaborator

PR_Github #63074 [ run ] completed with state FAILURE. Commit: 5da5d78
/LLM/main/L0_MergeRequest_PR pipeline #51168 completed with status: 'FAILURE'

CI Report

⚠️ Action Required:

  • Please check the failed tests and fix your PR
  • If you cannot view the failures, ask the CI triggerer to share details
  • Once fixed, request an NVIDIA team member to trigger CI again

CI Agent Failure Analysis

Link to invocation

@chang-l

chang-l commented Jul 31, 2026

Copy link
Copy Markdown
Collaborator Author

/bot run

@tensorrt-cicd

Copy link
Copy Markdown
Collaborator

PR_Github #63129 [ run ] triggered by Bot. Commit: 5da5d78 Link to invocation

@tensorrt-cicd

Copy link
Copy Markdown
Collaborator

PR_Github #63129 [ run ] completed with state SUCCESS. Commit: 5da5d78
/LLM/main/L0_MergeRequest_PR pipeline #51217 completed with status: 'SUCCESS'

CI Report

Link to invocation

@chang-l
chang-l merged commit 7e6692a into NVIDIA:main Aug 1, 2026
8 checks passed
yuanjingx87 pushed a commit that referenced this pull request Aug 1, 2026
Signed-off-by: Chang Liu <9713593+chang-l@users.noreply.github.com>
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.

8 participants